# Project export: soundchain

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: decentralizing music by empowering artists to create & monetize their music independently, with backers that care about even the smallest ideas.
- Devpost: https://devpost.com/software/soundchain
- GitHub: https://github.com/m20arcusk/soundchain
- Result: winner (Sui: Reimagine the internet with Sui)
- Team: 1 GitHub contributor(s) — m20arcusk (3 commits)

## Devpost submission (written by the team)

### Inspiration

Growing up, many of us dreamed of exploring the world of music. However, we soon discovered that pursuing this passion often requires significant financial investment in production, mixing, and additional instruments. 🎛️ Well-meaning adults frequently warned us about the instability and security concerns associated with a music career. The reality is that music can be challenging, and we want to change that narrative. There are barriers to entry at every turn: 87.6% of all artists remain undiscovered. Out of 1.3 million registered artists on Chartmetric over the past year, 710,000 have yet to achieve any career milestones. Only 11% of independent artists can sustain a living through music. Even signing with a record label doesn’t guarantee success: Labels typically take 50-90% of the revenue generated by an artist, often with fine print that includes hidden distributor fees and overhead costs. Remarkably, even with a major label, only 1 in 2,149 artists achieves commercial success. With soundchain, we aim to empower aspiring artists to pursue their musical dreams independently while connecting them with a supportive community that believes in their potential. This platform is also perfect for hobbyists who have a polished piece sitting in their files, just waiting for the right moment to be released. Whether you’re looking to build a career in music or simply share your passion, we’re here to help you take that next step. 🔊

### What it does

soundchain is a decentralized platform that connects aspiring artists with music enthusiasts, enabling fans to discover and support emerging talent through unbiased music playback and NFT purchases in exchange for rights. Artists and "angel music investors" are matched through our music-forward playback system. It's actually quite simple: Artists upload snippets of their creative process (verses, beats, melody lines, anything in between). They set SUI-based crowdfunding goals to produce their song, and issuance terms for partners. Potential supporters browse uploaded projects. They can view goals and decide to support projects based on the provided snippets. They buy-in to the potential of the project. The Sui blockchain records the smart contract and creates an enforceable legal parameter for investors. If the project takes off, initial investors can now resell their rights on secondary web3 markets. 🚀

### How we built it

We utilized the Sui blockchain for its scalability and performance, implementing smart contracts to handle transactions. The application is integrated/connected with google-auth and the Enoki developer portal. The platform was developed with a user-friendly interface mocked on Figma to ensure seamless interactions between artists and listeners. Our front-end is made using Next.js, typescript, and assets from libraries such as material UI.

### Challenges we ran into

This project was our team's first time ever working with any web3 products. We went from not understanding what blockchain even was to maybe understanding a little bit of how blockchain works. We faced challenges in the integration of blockchain elements into our front-end application since we weren't familiar with the technology. Additionally, navigating copyright issues and establishing fair revenue-sharing models for artists required careful consideration.

### Accomplishments we're proud of

We think that we came up with a bangin' 🎸 idea that can really change the way funding in the music industry works. We also ensured that this product was realistic and viable for integration in the current state of the music industry.

### What we learned

SO MUCH !!!! All new ways of designing, methodologies of ideation, new technologies, and first-time experiences. We also learned the importance of community engagement in building a platform that truly serves its users. Understanding the pain points and publication process from both artists and listeners helped us refine our approach and identify key features that enhance the overall experience.

### What's next

Mint NFTs that provide a visual badge/proof of support for supporters. Implement tags for different types of projects including Songwriting, Lyrics, Rap, and Beats. Integrate tracking systems to measure expected revenue shares through traditional music streaming applications, allowing the smart contract to take a more active role in revenue sharing.

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 52 recognized source files, 80 KB.
- CSS (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (63 of 63)

```
app/api/EnokiClient.ts
app/api/execute/route.ts
app/api/sponsor/route.ts
app/auth/page.tsx
app/globals.css
app/layout.tsx
app/page.tsx
app/page1.tsx
app/page2.tsx
app/ProvidersAndLayout.tsx
components.json
components/Artist.tsx
components/Card.tsx
components/CardFlip.tsx
components/Contribute.tsx
components/CountContribute.tsx
components/Counter.tsx
components/CreateArtist.tsx
components/CreateCounter.tsx
components/Flashcard.tsx
components/Loading.tsx
components/Music.tsx
components/ProfilePopover.tsx
components/Progress.tsx
components/Taskbar.tsx
components/ui/avatar.tsx
components/ui/button.tsx
components/ui/card.tsx
components/ui/input.tsx
components/ui/label.tsx
components/ui/popover.tsx
components/ui/sonner.tsx
config/clientConfig.ts
config/networkConfig.ts
config/serverConfig.ts
contexts/Authentication.tsx
contexts/CustomWallet.tsx
helpers/isFollowingUserPropsSchema.ts
hooks/useCreateArtist.ts
hooks/useCreateCounterTransaction.ts
hooks/useFundProject.ts
hooks/useIncrementCounterTransaction.ts
hooks/useResetCounterTransaction.ts
lib/utils.ts
move/counter/Move.toml
move/counter/sources/counter.move
move/soundchain/Move.toml
move/soundchain/sources/soundchain.move
move/soundchain/tests/soundchain_tests.move
next-env.d.ts
next.config.mjs
package.json
postcss.config.mjs
README.md
styles/Music.scss
tailwind.config.ts
tsconfig.json
types/Authentication.ts
types/ChildrenProps.ts
types/GeneralSuiObject.ts
types/NavigationLink.ts
types/Project.ts
types/SponsorTx.ts
```

### Dependencies

- package.json: @emotion/react@^11.13.3, @emotion/styled@^11.13.0, @mui/icons-material@^6.1.4, @mui/material@^6.1.4, @mysten/dapp-kit@^0.14.5, @mysten/enoki@^0.3.5, @mysten/sui@^1.0.5, @radix-ui/react-avatar@^1.0.4, @radix-ui/react-label@^2.0.2, @radix-ui/react-popover@^1.0.7, @radix-ui/react-slot@^1.0.2, @tanstack/react-query@^5.37.1, @types/node@^20, @types/react@^18.3.11, @types/react-dom@^18, @vercel/analytics@^1.3.1, axios@^1.7.7, class-variance-authority@^0.7.0, clsx@^2.1.1, eslint@^8, eslint-config-next@14.2.3, eslint-config-prettier@^9.1.0, framer-motion@^11.11.9, jwt-decode@^4.0.0, lucide-react@^0.379.0, next@14.2.3, next-themes@^0.3.0, postcss@^8, react@^18, react-card-flip@^1.2.3, react-dom@^18, react-h5-audio-player@^3.9.3, react-spinners@^0.13.8, sass@^1.80.3, sonner@^1.4.41, tailwind-merge@^2.3.0, tailwindcss@^3.4.1, tailwindcss-animate@^1.0.7, typescript@^5, yarn@^1.22.22, zod@^3.23.8

### Recent commits (newest first)

- Update README.md
- added files from other repo since
- Initial commit

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

### package.json

```
{
  "name": "enoki-example-app",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@emotion/react": "^11.13.3",
    "@emotion/styled": "^11.13.0",
    "@mui/icons-material": "^6.1.4",
    "@mui/material": "^6.1.4",
    "@mysten/dapp-kit": "^0.14.5",
    "@mysten/enoki": "^0.3.5",
    "@mysten/sui": "^1.0.5",
    "@radix-ui/react-avatar": "^1.0.4",
    "@radix-ui/react-label": "^2.0.2",
    "@radix-ui/react-popover": "^1.0.7",
    "@radix-ui/react-slot": "^1.0.2",
    "@tanstack/react-query": "^5.37.1",
    "@vercel/analytics": "^1.3.1",
    "axios": "^1.7.7",
    "class-variance-authority": "^0.7.0",
    "clsx": "^2.1.1",
    "framer-motion": "^11.11.9",
    "jwt-decode": "^4.0.0",
    "lucide-react": "^0.379.0",
    "next": "14.2.3",
    "next-themes": "^0.3.0",
    "react": "^18",
    "react-card-flip": "^1.2.3",
    "react-dom": "^18",
    "react-h5-audio-player": "^3.9.3",
    "react-spinners": "^0.13.8",
    "sass": "^1.80.3",
    "sonner": "^1.4.41",
    "tailwind-merge": "^2.3.0",
    "tailwindcss-animate": "^1.0.7",
    "yarn": "^1.22.22",
    "zod": "^3.23.8"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18.3.11",
    "@types/react-dom": "^18",
    "eslint": "^8",
    "eslint-config-next": "14.2.3",
    "eslint-config-prettier": "^9.1.0",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}

```

### app/layout.tsx

```typescript
import { Metadata } from "next";
import { ProvidersAndLayout } from "./ProvidersAndLayout";
import "./globals.css";
import "@mysten/dapp-kit/dist/index.css";

export const metadata: Metadata = {
  title: "Enoki Example App",
  description: "Learn how to build a dApp with Enoki",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body>
        <ProvidersAndLayout>{children}</ProvidersAndLayout>
      </body>
    </html>
  );
}

```

### app/page.tsx

```typescript
'use client';

import { useEffect, useState } from "react";
import { useCustomWallet } from "@/contexts/CustomWallet";
import ProfilePopover from "@/components/ProfilePopover";
import { CreateArtist } from "@/components/CreateArtist";
import { isValidSuiObjectId } from "@mysten/sui/utils";
import { GithubIcon } from "lucide-react";
import clientConfig from "@/config/clientConfig";
import Image from "next/image";
import Taskbar from "@/components/Taskbar";
import Flashcards from "@/components/Flashcard";
import { Box } from '@mui/material';
import { Artist } from "@/components/Artist";
import { useCreateArtistTransaction } from "@/hooks/useCreateArtist";
import { object } from "zod";
// import { useState } from 'react';

// const cardData = [
//   { projectTitle: "FEIN", audioLink: "/fein.mp3", completionPercentage: 30 },
//   { projectTitle: "HelloHacks", audioLink: "/fein.mp3", completionPercentage: 40 },
//   { projectTitle: "UX Open", audioLink: "/fein.mp3", completionPercentage: 50 },
// ];

// ** NEED TO ALSO UPDATE smart contract ** 
// upon website load, make 3 create calls to make an 'artist',
// update create() to take in some params, and update handler           
// include audio link field  
// * include the global client id as the first one in the array
// and store the id's returned in an array. pass the array if id's to flashcard
// ** will need to replace the global id with one with the right fields 
// id array would replace cardData
// fetch and access the artist info using cardFlip, and know which one through
// currentIndex


export default function Home() {
  const [waitingForTxn, setWaitingForTxn] = useState(false);
  // card data is a list of strings which represent the id's 
  const [cardData, setCardData] = useState<string[]>([]);
  const { isConnected } = useCustomWallet();
  const [ArtistId, setArtist] = useState<string | null>(null);
  const [currentCardIndex, setCurrentCardIndex] = useState(0);
  const { handleExecute } = useCreateArtistTransaction();

  useEffect(() => {
    const hash = window.location.hash.slice(1); // takes the hash and then 
    if (isValidSuiObjectId(hash)) { // if valid sui object, sets the counter
      setArtist(hash);
    }
    // create three
    if (isConnected) {
      create("shake it down", "/tswift.mp3");
      create("focus on me", "/attention.mp3");
      // create("best song ever", "/hellohacks.wav");
    }

  }, []);

  async function create(name: string, audioLink: string) {
    setWaitingForTxn(true);

    const txn = await handleExecute(name, audioLink);

    console.log("txn", txn);

    const objectId = txn.effects?.created?.[0]?.reference?.objectId; // access the object that is created

    if (objectId) {
      // onCreated(objectId);
      // id to list
      setCardData([...cardData, objectId])
    }

    setWaitingForTxn(false);
  }

  const handleNext = () => {
    setCurrentCardIndex((prevIndex) => (prevIndex + 1) % cardData.length);
  };

  const handlePrev = () => {
    setCurrentCardIndex((prevIndex) =>
      (prevIndex - 1 + cardData.length) % cardData.length
    );
  };

  return (
    <div className="">
      <ProfilePopover />
      <Box
        sx={{
          display: 'flex',
          flexDirection: 'column',
          height: '100vh',
          backgroundImage: 'url("/background.svg")',
          backgroundSize: 'cover',
          backgroundPosition: 'center',
          position: 'relative',
        }}
      >
        <Box
          sx={{
            position: 'absolute',
            top: 0,
            left: 0,
            width: '100%',
            zIndex: 1,
          }}
        >
          <Taskbar />
        </Box>
        <Box
          sx={{
            display: 'flex',
            justifyContent: 'center',
            alignItems: 'center',
            flexGrow: 1,
          }}
        >
          <Image
            src="/leftarrow.png"
            alt="Previous"
            width={70}
            height={70}
            onClick={handlePrev}
            style={{ cursor: 'pointer', marginRight: '10vh' }}
          />

          {/* Pass the currentCardIndex and the handler functions to Flashcards */}
          <Flashcards
            id={clientConfig.GLOBAL_COUNTER_ID}
            currentCardIndex={currentCardIndex}
            cardData={cardData}
            handleNext={handleNext}
            handlePrev={handlePrev}
          />

          <Image
            src="/rightarrow.png"
            alt="Next"
            width={70}
            height={70}
            onClick={handleNext}
            style={{ cursor: 'pointer', marginLeft: '10vh' }}
          />
        </Box>
      </Box>
    </div>
  );
}

```

### app/auth/page.tsx

```typescript
"use client";

import Loading from "@/components/Loading";
import { useAuthCallback } from "@mysten/enoki/react";
import { useEffect } from "react";

export default function Page() {
  const { handled } = useAuthCallback(); // This hook will handle the callback from the authentication provider

  useEffect(() => {}, [handled]);

  return <Loading />;
}

```

### app/api/execute/route.ts

```typescript
import { NextRequest, NextResponse } from "next/server";
import { enokiClient } from "../EnokiClient";
import { ExecuteSponsoredTransactionApiInput } from "@/contexts/CustomWallet";

export const POST = async (request: NextRequest) => {
  const { digest, signature }: ExecuteSponsoredTransactionApiInput =
    await request.json();

  return enokiClient
    .executeSponsoredTransaction({
      digest,
      signature,
    })
    .then(({ digest }) => {
      return NextResponse.json(
        { digest },
        {
          status: 200,
        }
      );
    })
    .catch((error) => {
      console.error(error);
      return NextResponse.json(
        {
          error: "Could not execute sponsored transaction block.",
        },
        {
          status: 500,
        }
      );
    });
};

```

### app/api/sponsor/route.ts

```typescript
import { SponsorTxRequestBody } from "@/types/SponsorTx";
import { NextRequest, NextResponse } from "next/server";
import { enokiClient } from "../EnokiClient";

/*
 - Right now any txBlock whose moveCall targets are whitelisted in the Enoki Portal can be sponsored
 - In mainnet products, we will probably want to add constraints, such as:
  - require a JWT token if the user is signed in with Enoki
  - require a signed message if the user uses wallet-kit
  - require that the number of commands in the txBlock is exactly one, we can check this via `TransactionBlock.from(txBytes)`
  - require that the allowedAddresses only contain the sender's address (in case of an airdrop)
*/

export const POST = async (request: NextRequest) => {
  const { network, txBytes, sender, allowedAddresses }: SponsorTxRequestBody =
    await request.json();

  return enokiClient
    .createSponsoredTransaction({
      network,
      transactionKindBytes: txBytes,
      sender,
      allowedAddresses,
    })
    .then((resp) => {
      return NextResponse.json(resp, {
        status: 200,
      });
    })
    .catch((error) => {
      console.error(error);
      return NextResponse.json(
        {
          error: "Could not create sponsored transaction block.",
        },
        {
          status: 500,
        }
      );
    });
};

```

### next-env.d.ts

```typescript
/// <reference types="next" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

```

### tailwind.config.ts

```typescript
import type { Config } from "tailwindcss"

const config = {
  darkMode: ["class"],
  content: [
    './pages/**/*.{ts,tsx}',
    './components/**/*.{ts,tsx}',
    './app/**/*.{ts,tsx}',
    './src/**/*.{ts,tsx}',
	],
  prefix: "",
  theme: {
    container: {
      center: true,
      padding: "2rem",
      screens: {
        "2xl": "1400px",
      },
    },
    extend: {
      colors: {
        border: "hsl(var(--border))",
        input: "hsl(var(--input))",
        ring: "hsl(var(--ring))",
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
        primary: {
          DEFAULT: "hsl(var(--primary))",
          foreground: "hsl(var(--primary-foreground))",
        },
        secondary: {
          DEFAULT: "hsl(var(--secondary))",
          foreground: "hsl(var(--secondary-foreground))",
        },
        destructive: {
          DEFAULT: "hsl(var(--destructive))",
          foreground: "hsl(var(--destructive-foreground))",
        },
        muted: {
          DEFAULT: "hsl(var(--muted))",
          foreground: "hsl(var(--muted-foreground))",
        },
        accent: {
          DEFAULT: "hsl(var(--accent))",
          foreground: "hsl(var(--accent-foreground))",
        },
        popover: {
          DEFAULT: "hsl(var(--popover))",
          foreground: "hsl(var(--popover-foreground))",
        },
        card: {
          DEFAULT: "hsl(var(--card))",
          foreground: "hsl(var(--card-foreground))",
        },
      },
      borderRadius: {
        lg: "var(--radius)",
        md: "calc(var(--radius) - 2px)",
        sm: "calc(var(--radius) - 4px)",
      },
      keyframes: {
        "accordion-down": {
          from: { height: "0" },
          to: { height: "var(--radix-accordion-content-height)" },
        },
        "accordion-up": {
          from: { height: "var(--radix-accordion-content-height)" },
          to: { height: "0" },
        },
      },
      animation: {
        "accordion-down": "accordion-down 0.2s ease-out",
        "accordion-up": "accordion-up 0.2s ease-out",
      },
    },
  },
  plugins: [require("tailwindcss-animate")],
} satisfies Config

export default config
```

### types/ChildrenProps.ts

```typescript
export interface ChildrenProps {
  children: React.ReactNode | React.ReactNode[];
}

```

### types/NavigationLink.ts

```typescript
import { ReactElement } from "react";

export interface NavigationLink {
  title: string;
  href: string;
  icon?: ReactElement;
}

```

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