Skip to content

dap-cool/protocol

Repository files navigation

Dap Cool 🆒 (Digital Asset Protocol)

Decentralized Protocol for token-gating files

Install via npm

npm i @dap-cool/sdk

Principles ⚖️

  • NFTs should provide ownership of encrypted digital assets (metadata)
  • Metadata should not be limited to .jpeg profile pictures
    • can be any file(s) or byte-array that you can imagine
  • Metadata should be hosted on an immutable decentralized network
  • Metadata should be encrypted & can only be decrypted via NFT ownership

Example Use Case 💿

  • Exclusive Music
    • Your favorite indie musician already has a catalog on Spotify & other streaming platforms
    • They release an exclusive project for their biggest fans who are willing to purchase music
      • this release is not made available on streaming platforms or elsewhere
    • 500 copies of this music are printed as an NFT
    • You buy one of the NFTs & can now decrypt the music & even download it
    • The release sold out & there's more fans that missed out on the primary sale
    • After downloading your copy you list your NFT for sale
    • Someone buys from you & now they can decrypt/download

How it works 🛠️

  • Encrypt/decrypt files with an NFT or FT via LIT Protocol
  • Upload/download encrypted files to decentralized immutable storage via Shadow-Drive
  • Track state (who downloaded / how many times) via Solana Program

Developers / SDK

  • The Solana Program provides methods for deterministically finding uploaded files via program-derived-addresses

  • The TypeScript SDK provides methods for

    • Encrypting your files with an NFT mint address
      • typically FileList objects from your HTML input
        • zips your file-list before encrypting
      • can be used to encrypt any arbitrary file object
    • Uploading your encrypted files to shadow-drive
    • Deterministically finding the upload URL via solana on-chain state
    • Fetching your encrypted files from shadow-drive
    • Decrypting your encrypted files with the NFT

End-to-end example

  • check out our integration test to see all the imports, how we're bundling, etc.
async function uploadMutable() {
    // select files
    const files = document.getElementById("gg-sd-zip").files;
    // build encryption args
    // // this is specifying which mint-address you want to "gate" with
    const litArgs = defaultLitArgs(mint.toString());
    // encrypt
    const encrypted = await encrypt(files, litArgs);
    // provision storage on shdw drive
    // // this takes about 15seconds to provision decentralized storage
    // // you'll want to notify your app what is happening
    // // these methods are intentionally seperated to provide opportunity to notify progress
    const provisioned = await provision(connection, provider.wallet, encrypted.file.size);
    // build metadata
    const metadata = {
        key: encrypted.key,
        lit: litArgs,
        title: "e2e-demo",
        zip: {
            count: files.length,
            types: Array.from(files).map(file => file.type)
        },
        timestamp: Date.now()
    }
    const encodedMetadata = encodeMetadata(metadata);
    // uploaded encrypted file & metadata
    // // this is super fast thanks to shadow-drive throughput
    // // comparable to an AWS S3 upload
    await uploadMultipleFiles(
        [encrypted.file, encodedMetadata],
        provisioned.drive,
        provisioned.account
    );
    // publish url to solana
    // // this is encoding the shadow-drive public key on-chain via solana program-derived-address
    // // which means we can deterministically find it & don't need a centralized index
    // // typically very fast (as fast as any other rpc transaction)
    await increment(program, provider, mint, provisioned.account);
}

async function editMetadata() {
    // edit metadata before marking as immutable
    // // create new shadow client
    const drive = await buildClient(connection, provider.wallet);
    // // deterministically find latest upload
    const incrementPda = await deriveIncrementPda(program, mint, provider.wallet.publicKey);
    const fetchedIncrement = await getIncrementPda(program, incrementPda);
    const datumPda = await getDatumPda(program, mint, provider.wallet.publicKey, fetchedIncrement.increment);
    // // fetch existing metadata from latest uploader
    // // has stuff for decryption that we need to copy to new metadata
    const oldMetadata = await getMetaData(datumPda.shadow.url);
    // // this is super fast because shadow-drive is already provisioned
    // // throughput is comparable to an AWS S3 upload
    await editMetaData(drive, datumPda.shadow.account, oldMetadata, "new-title");
    // mark as immutable
    // // this takes about 15seconds again to mark the storage as immutable
    // // technically this optional but we highly recommend it to promote web3 ethos
    await markAsImmutable(drive, datumPda.shadow.account);
}

async function getMany() {
    // derive dummy public-key with no uploads for testing
    const dummy = web3.Keypair.generate();
    // // deterministically find all uploads with duplicate uploader
    const incrementArray = await getManyIncrementPdaByUploader(
        program,
        mint,
        [provider.wallet.publicKey,
            provider.wallet.publicKey, // intentionally duplicated
            dummy.publicKey
        ]
    );
    console.log("increment array -->", incrementArray);
    const datumArray = await getManyDatumPda(program, incrementArray);
    console.log("datum array -->", datumArray);
}

async function download() {
    // get increment pda
    // // deterministically find how many times this user has uploaded behind this mint
    const incrementPda = await deriveIncrementPda(program, mint, provider.wallet.publicKey);
    const fetchedIncrement = await getIncrementPda(program, incrementPda);
    // get datum (of latest upload)
    // // deterministically find the URL of the latest upload
    let datumPda = await getDatumPda(program, mint, provider.wallet.publicKey, fetchedIncrement.increment);
    // fetch metadata (of latest upload) from shadow-drive
    const metadata = await getMetaData(datumPda.shadow.url);
    // fetch & decrypt files
    // // super fast thanks to shadow-drive, LIT, and a bunch of WASM
    // // does not charge any gas but does require a message signature (to prove ownership of the mint)
    const decryptedZip = await decrypt(datumPda.shadow.url, metadata);
    // download zip
    downloadZip(decryptedZip);
    // mark upload as filtered
    // // this provides a method for UIs to not render certain uploads
    // // if an uploader chooses without actually removing the upload from the blockchain
    // // so we guarantee legacy users of the upload can still access the files
    await filter(program, provider, mint, fetchedIncrement.increment);
    datumPda = await getDatumPda(program, mint, provider.wallet.publicKey, fetchedIncrement.increment);
    console.log(datumPda);
}

About

Decentralized protocol for token-gating files

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published