# Project export: Blockify

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 11.0
- Tagline: Empowering Artists with Blockchain: Seamless Royalty Payments and Ownership Control Through Spotify Integration.
- Devpost: https://devpost.com/software/blockify-x2owt7
- GitHub: https://github.com/ChiragSeth14/CalHacks
- Video: https://www.youtube.com/embed/bKVkiuuv-uM?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Cal Hacks / Berkeley RDI: Berkeley RDI Web3 Prize; Sui: Reimagine the internet with Sui)
- Team: 1 GitHub contributor(s) — ChiragSeth14 (5 commits)

## Devpost submission (written by the team)

### Inspiration

The project was inspired by looking at the challenges that artists face when dealing with traditional record labels and distributors. Artists often have to give up ownership of their music, lose creative control, and receive only a small fraction of the revenue generated from streams. Record labels and intermediaries take the bulk of the earnings, leaving the artists with limited financial security. Being a music producer and a DJ myself, I really wanted to make a product with potential to shake up this entire industry for the better. The music artists spend a lot of time creating high quality music and they deserve to be paid for it much more than they are right now.

### What it does

Blockify lets artists harness the power of smart contracts by attaching them to their music while uploading it and automating the process of royalty payments which is currently a very time consuming process. Our primary goal is to remove the record labels and distributors from the industry since they they take a majority of the revenue which the artists generate from their streams for the hard work which they do. By using a decentralized network to manage royalties and payments, there won't be any disputes regarding missed or delayed payments, and artists will have a clear understanding of how much money they are making from their streams since they will be dealing with the streaming services directly. This would allow artists to have full ownership over their work and receive a fair compensation from streams which is currently far from the reality.

### How we built it

BlockChain: We used the Sui blockchain for its scalability and low transaction costs. Smart contracts were written in Move, the programming language of Sui, to automate royalty distribution. Spotify API: We integrated Spotify's API to track streams in real time and trigger royalty payments. Wallet Integration: Sui wallets were integrated to enable direct payments to artists, with real-time updates on royalties as songs are streamed. Frontend: A user-friendly web interface was built using React to allow artists to connect their wallets, and track their earnings. The frontend interacts with the smart contracts via the Sui SDK.

### Challenges we ran into

The most difficult challenge we faced was the Smart Contract Development using the Move language. Unlike commonly known language like Ethereum, Move is relatively new and specifically designed to handle asset management. Another challenge was trying to connect the smart wallets in the application and transferring money to the artist whenever a song was streamed, but thankfully the mentors from the Sui team were really helpful and guided us in the right path.

### Accomplishments we're proud of

This was our first time working with blockchain, and me and my teammate were really proud of what we were able to achieve over the two days. We worked on creating smart contracts and even though getting started was the hardest part but we were able to complete it and learnt some great stuff along the way. My teammate had previously worked with React but I had zero experience with JavaScript, since I mostly work with other languages, but we did the entire project in Node and React and I was able to learn a lot of the concepts in such a less time which I am very proud of myself for.

### What we learned

We learned a lot about Blockchain technology and how can we use it to apply it to the real-world problems. One of the most significant lessons we learned was how smart contracts can be used to automate complex processes like royalty payments. We saw how blockchain provides an immutable and auditable record of every transaction, ensuring that every stream, payment, and contract interaction is permanently recorded and visible to all parties involved. Learning more and more about this technology everyday just makes me realize how much potential it holds and is certainly one aspect of technology which would rule the future. It is already being used in so many aspects of life and we are still discovering the surface.

### What's next

We plan to add more features, such as NFTs for exclusive content or fan engagement, allowing artists to create new revenue streams beyond streaming. There have been some real-life examples of artists selling NFT's to their fans and earning millions from it, so we would like to tap into that industry as well. Our next step would be to collaborate with other streaming services like Apple Music and eliminate record labels to the best of our abilities.

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 16 recognized source files, 27 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (27 of 27)

```
royalty_manager/.env
royalty_manager/0x61dcdf5c991966fac4c762ebc6c5d9171951245475c0a3ffa87d7e6928522dcd.key
royalty_manager/main.py
royalty_manager/Move.lock
royalty_manager/Move.toml
royalty_manager/package.json
royalty_manager/sources/royalty_manager.move
royalty_manager/spotify_auth.js
royalty_manager/tests/royalty_manager_tests.move
sui-dapp-frontend/.gitignore
sui-dapp-frontend/package.json
sui-dapp-frontend/public/index.html
sui-dapp-frontend/public/manifest.json
sui-dapp-frontend/public/robots.txt
sui-dapp-frontend/README.md
sui-dapp-frontend/src/App.css
sui-dapp-frontend/src/App.js
sui-dapp-frontend/src/App.test.js
sui-dapp-frontend/src/AppRouter.js
sui-dapp-frontend/src/index.css
sui-dapp-frontend/src/index.js
sui-dapp-frontend/src/OwnedObjects.js
sui-dapp-frontend/src/PlayTrackPage.css
sui-dapp-frontend/src/PlayTrackPage.js
sui-dapp-frontend/src/reportWebVitals.js
sui-dapp-frontend/src/setupTests.js
sui-dapp-frontend/src/WalletStatus.js
```

### Dependencies

- royalty_manager/package.json: @mysten/sui.js@^0.54.1, axios@^1.7.7, dotenv@^16.4.5, express@^4.21.1, querystring@^0.2.1
- sui-dapp-frontend/package.json: @mysten/dapp-kit@^0.14.27, @mysten/sui@^1.13.0, @mysten/sui.js@^0.54.1, @tanstack/react-query@^5.59.15, @testing-library/jest-dom@^5.17.0, @testing-library/react@^13.4.0, @testing-library/user-event@^13.5.0, react@^18.3.1, react-dom@^18.3.1, react-router-dom@^6.27.0, react-scripts@5.0.1, web-vitals@^2.1.4

### Recent commits (newest first)

- final commit
- final commit
- frontend done
- smart contract updated
- smart contract working

## Key source files (fetched from GitHub, selected and truncated for size)

### royalty_manager/package.json

```
{
  "name": "royalty_manager",
  "version": "1.0.0",
  "main": "spotify_auth.js",   
  "type": "commonjs",          
  "directories": {
    "test": "tests"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "Spotify API and Sui Blockchain integration",
  "dependencies": {
    "@mysten/sui.js": "^0.54.1",    
    "axios": "^1.7.7",              
    "dotenv": "^16.4.5",            
    "express": "^4.21.1",           
    "querystring": "^0.2.1"         
  }
}

```

### sui-dapp-frontend/package.json

```
{
  "name": "sui-dapp-frontend",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@mysten/dapp-kit": "^0.14.27",
    "@mysten/sui": "^1.13.0",
    "@mysten/sui.js": "^0.54.1",
    "@tanstack/react-query": "^5.59.15",
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-router-dom": "^6.27.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

```

### royalty_manager/main.py

```python
from dotenv import load_dotenv
import os
import base64
from requests import post, get
import json

load_dotenv()

client_id = os.getenv("CLIENT_ID")
client_secret = os.getenv("CLIENT_SECRET")

def get_token():
    auth_string = client_id + ":" + client_secret
    auth_bytes = auth_string.encode("utf-8")
    auth_base64 = str(base64.b64encode(auth_bytes), "utf-8")

    url = "https://accounts.spotify.com/api/token"
    headers = {
        "Authorization": "Basic " + auth_base64,
        "Content-Type": "application/x-www-form-urlencoded"
    }
    data= {"grant_type": "client_credentials"}
    result = post(url, headers=headers, data=data)
    json_result = json.loads(result.content)
    token = json_result["access_token"]
    return token

def get_auth_header(token):
    return {"Authorization": "Bearer " + token}

def search_for_artist(token, artist_name):
    url = "https://api.spotify.com/v1/search"
    headers = get_auth_header(token)
    
    # Fix the query by adding '?' before the parameters
    query = f"?q={artist_name}&type=artist&limit=1"

    query_url = url + query
    result = get(query_url, headers=headers)
    json_result = json.loads(result.content)["artists"]["items"]
    if len(json_result) == 0:
        print("No artist with this name exists...")
        return None
    
    return json_result[0]

token = get_token()
result = search_for_artist(token, "ACDC")
print(result["name"])

```

### sui-dapp-frontend/src/index.js

```javascript
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import AppRouter from './AppRouter'; // Import your router instead of App
import reportWebVitals from './reportWebVitals';
import { createNetworkConfig, SuiClientProvider, WalletProvider } from '@mysten/dapp-kit';
import { getFullnodeUrl } from '@mysten/sui/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import '@mysten/dapp-kit/dist/index.css';
// Config options for the networks you want to connect to
const { networkConfig } = createNetworkConfig({
	devnet: { url: getFullnodeUrl('devnet') },
	mainnet: { url: getFullnodeUrl('mainnet') },
});
const queryClient = new QueryClient();
ReactDOM.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
			<SuiClientProvider networks={networkConfig} defaultNetwork="devnet">
				<WalletProvider autoConnect>
    <AppRouter /> {/* Use AppRouter instead of App */}
    </WalletProvider>
			</SuiClientProvider>
		</QueryClientProvider>
  </React.StrictMode>,
  document.getElementById('root')
);

reportWebVitals();

```

### sui-dapp-frontend/src/App.js

```javascript
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom'; // Updated for react-router-dom v6
import { SuiClient } from '@mysten/sui.js/client';
import './App.css';
import { ConnectModal, useCurrentAccount, useCurrentWallet, ConnectButton, useSignAndExecuteTransaction, useSuiClient } from '@mysten/dapp-kit';
import { Transaction } from '@mysten/sui/transactions';
// Connect to the Sui network (devnet in this case)
const client = new SuiClient({ url: 'https://fullnode.devnet.sui.io:443' });

function App() {

  const { mutate: signAndExecuteTransaction } = useSignAndExecuteTransaction();
  const { currentWallet, connectionStatus } = useCurrentWallet();
  console.log('currentWallet', currentWallet)
  console.log(connectionStatus)
  const account = useCurrentAccount();
  const suiClient = useSuiClient();
  
  const [trackId, setTrackId] = useState('');
  const [owner, setOwner] = useState('');
  const [title, setTitle] = useState('');
  const [artist, setArtist] = useState('');
  const [value, setValue] = useState('');
  const [royalties, setRoyalties] = useState('');

  const navigate = useNavigate(); // Use navigate instead of useHistory

  // This function will handle form submission
  const handleSubmit = async (e) => {
    e.preventDefault();

    try {
      const txb = new Transaction();

      txb.moveCall({
        target: `0x27817c5fabdafbdebbc2ea762c1cf8bf5a0c882233837e62d3a8808eafb478a9::royalty_manager::create_new_track`,
        arguments: [txb.pure.u64(trackId), txb.pure.address(account.address), txb.pure.string(title), txb.pure.string(artist), txb.pure.u64(value), txb.pure.u64(royalties)]
      });

      signAndExecuteTransaction(
        {
          transaction: txb,
          
        },
        {
          onSuccess: (result) => {
            console.log('executed transaction', result);
            // setDigest(result.digest);
            suiClient.waitForTransaction({
              digest: result.digest, options: {
                showObjectChanges: true
              }
            }).then(async (result) => {
              console.log('waited', result)
              const objectId = result.objectChanges.filter((value) => {
                return value.type == "created"
              })[0].objectId
              console.log(objectId)
              navigate(`/play-track#${objectId}`); // Use navigate instead of history.push
            })
          },
        },
      );
      

        /*
        {
    "digest": "CNFPZUCUWyngTwtrGQzXfMF2MeqdUvmhBL1GzFftSWab",
    "objectChanges": [
        {
            "type": "mutated",
            "sender": "0x639e8531c65fc1ac2709a3bef97093f10b9f823f60e2ac7a36c3d42360e71397",
            "owner": {
                "AddressOwner": "0x639e8531c65fc1ac2709a3bef97093f10b9f823f60e2ac7a36c3d42360e71397"
            },
            "objectType": "0x2::coin::Coin<0x2::sui::SUI>",
            "objectId": "0x2d2dd67e5132c7b6793363a04defa4f0ab003ce050bedfd77f2b534f43daac6a",
            "version": "388",
            "previousVersion": "387",
            "digest": "8YMAbY9ugUN87BCdF7pcEVT4VotByKxYu8jQY4AfANxu"
        },
        {
            "type": "created",
            "sender": "0x639e8531c65fc1ac2709a3bef97093f10b9f823f60e2ac7a36c3d42360e71397",
            "owner": {
                "AddressOwner": "0x639e8531c65fc1ac2709a3bef97093f10b9f823f60e2ac7a36c3d42360e71397"
            },
            "objectType": "0xd0e65a8f3222ff3784d800a64552f1a29f3723610712ef29e6f77e27be920d7d::royalty_manager::MusicTrack",
            "objectId": "0x2fab5817aae63f51a929b63dcffabb0d6bbe2d8a66981394b0c5f5d1b2088bfd",
            "version": "388",
            "digest": "FqpPaCide66e1RiQrDAoZP6rzohuYs1WyQwpqmgzLjUc"
        }
    ],
    "timestampMs": "1729439869994",
    "checkpoint": "2488929"
}
*/

      // // After successful creation, navigate to the PlayTrackPage
      

    } catch (error) {
      console.error('Error creating track:', error);
      alert('Failed to create track.');
    }
  };

  return (
    <div className="App">
      <h1>Create New Track</h1>
      <ConnectButton />
      <form onSubmit={handleSubmit}>
        <div>
          <label>Track ID:</label>
          <input
            type="number"
            value={trackId}
            onChange={(e) => setTrackId(e.target.value)}
            required
          />
        </div>
        <div>
          <label>Owner Address:</label>
          <input
            type="text"
            value={owner}
            onChange={(e) => setOwner(e.target.value)}
            required
          />
        </div>
        <div>
          <label>Track Title:</label>
          <input
            type="text"
            value={title}
            onChange={(e) => setTitle(e.target.value)}
            required
          />
        </div>
        <div>
          <label>Artist:</label>
          <input
            type="text"
            value={artist}
            onChange={(e) => setArtist(e.target.value)}
            required
          />
        </div>
        <div>
          <label>Initial Value:</label>
          <input
            type="number"
            value={value}
            onChange={(e) => setValue(e.target.value)}
            required
          />
        </div>
        <div>
          <label>Royalties (%):</label>
          <input
            type="number"
            value={royalties}
            onChange={(e) => setRoyalties(e.target.value)}
            required
          />
        </div>
        <button type="submit">Create Track</button>
      </form>
    </div>
  );
}

export default App;

```

### royalty_manager/spotify_auth.js

```javascript
const express = require('express');
const axios = require('axios');
const querystring = require('querystring');
require('dotenv').config(); // Load environment variables from .env file if needed

const app = express();

// Manually using the provided access and refresh tokens
let accessToken = 'BQByn5-ZijPYRqEpuh7or4JrzFAIO6kt7bBw5j4g7EG0a5sqqMpQxYqd_JOtr6Y3Ps2H3z6_x0_NcxUHXymTx_8q0IX1lu_invi3BEjZwZP88QjdqJypIO0jwCHpijwByF5x-Q1uFHXvo0yqTkCVWykLFmtNanPuV4jawm4595ugOBS5AHCGr7HasZqpLpKfErG2TvBEDaj0qyIlg3z_Pg';
let refreshToken = 'AQDuAqOw_C8bO4TGOP0wxLkhY4TENq9IhBFk6DNuHWukIzrZaC3rC3TwlZdkgY-KaRIvtco_DdCrmM_w6d-KSPbpFr7W1MB4tVW6DZ2VdvREwrnRPs32mvl29nZEoe-nRAA';

const CLIENT_ID_NEW = process.env.CLIENT_ID || 'your_spotify_client_id'; // Replace if using .env
const CLIENT_SECRET_NEW = process.env.CLIENT_SECRET || 'your_spotify_client_secret'; // Replace if using .env

// Function to refresh access token when expired
async function refreshAccessToken() {
    const tokenUrl = 'https://accounts.spotify.com/api/token';

    const data = {
        grant_type: 'refresh_token',
        refresh_token: refreshToken,
        client_id: CLIENT_ID_NEW,
        client_secret: CLIENT_SECRET_NEW
    };

    try {
        const response = await axios.post(tokenUrl, querystring.stringify(data), {
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        });

        accessToken = response.data.access_token; // Update access token
        console.log('New access token:', accessToken);
        return accessToken;
    } catch (error) {
        console.error('Error refreshing access token:', error.response.data);
    }
}

// Function to get currently playing track using access token
async function getCurrentlyPlayingTrack() {
    const url = 'https://api.spotify.com/v1/me/player/currently-playing';

    try {
        const response = await axios.get(url, {
            headers: {
                'Authorization': `Bearer ${accessToken}`
            }
        });

        if (response.data && response.data.item) {
            console.log('Currently playing track:', response.data.item.name);
            return response.data.item;
        } else {
            console.log('No song is currently playing.');
        }
    } catch (error) {
        // Check if the error is due to token expiration
        if (error.response && error.response.status === 401) {
            console.log('Access token expired, refreshing token...');
            await refreshAccessToken(); // Refresh token
            return await getCurrentlyPlayingTrack(); // Retry request
        } else {
            console.error('Error fetching currently playing track:', error.response ? error.response.data : error.message);
        }
    }
}

// Endpoint to display currently playing track
app.get('/currently-playing', async (req, res) => {
    const track = await getCurrentlyPlayingTrack();
    if (track) {
        res.send(`Currently playing: ${track.name} by ${track.artists[0].name}`);
    } else {
        res.send('No song is currently playing.');
    }
});

// Listen on port 3000
app.listen(3000, () => {
    console.log('Server running on http://localhost:3000');
});

```

### sui-dapp-frontend/src/setupTests.js

```javascript
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

```

### sui-dapp-frontend/src/App.test.js

```javascript
import { render, screen } from '@testing-library/react';
import App from './App';

test('renders learn react link', () => {
  render(<App />);
  const linkElement = screen.getByText(/learn react/i);
  expect(linkElement).toBeInTheDocument();
});

```

### sui-dapp-frontend/src/reportWebVitals.js

```javascript
const reportWebVitals = onPerfEntry => {
  if (onPerfEntry && onPerfEntry instanceof Function) {
    import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
      getCLS(onPerfEntry);
      getFID(onPerfEntry);
      getFCP(onPerfEntry);
      getLCP(onPerfEntry);
      getTTFB(onPerfEntry);
    });
  }
};

export default reportWebVitals;

```

### sui-dapp-frontend/src/index.css

```css
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

```

[7 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]