https://github.com/dap-cool/protocol
Decentralized protocol for token-gating files
https://github.com/dap-cool/protocol
Last synced: 2 days ago
JSON representation
Decentralized protocol for token-gating files
- Host: GitHub
- URL: https://github.com/dap-cool/protocol
- Owner: dap-cool
- License: gpl-3.0
- Created: 2022-10-11T01:17:08.000Z (over 2 years ago)
- Default Branch: develop
- Last Pushed: 2023-01-31T17:37:05.000Z (about 2 years ago)
- Last Synced: 2025-04-08T12:47:24.727Z (8 days ago)
- Language: TypeScript
- Homepage:
- Size: 310 KB
- Stars: 5
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome - DAP
README
# Dap Cool 🆒 (Digital Asset Protocol)
Decentralized Protocol for token-gating files
* [Developers / SDK](#developers--sdk)
* [End-to-end example](#end-to-end-example)
* [Principles](#principles-%EF%B8%8F)
* [Example Use Case](#example-use-case-)
* [How It Works](#how-it-works-%EF%B8%8F)# Install via npm
```shell
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](https://litprotocol.com/)
* Upload/download encrypted files to decentralized immutable storage
via [Shadow-Drive](https://docs.genesysgo.com/shadow/)
* Track state (who downloaded / how many times) via [Solana Program](./programs/dap-protocol/src/lib.rs)# Developers / SDK
* The [Solana Program](./programs/dap-protocol/src/lib.rs) provides methods for deterministically finding uploaded files
via [program-derived-addresses](https://docs.solana.com/developing/programming-model/calling-between-programs#hash-based-generated-program-addresses)* The [TypeScript SDK](./sdk/src/index.js) provides methods for
* Encrypting your files with an NFT mint address
* typically [FileList](https://developer.mozilla.org/en-US/docs/Web/API/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](./tests/integration/src/index.js) to see all the imports, how we're bundling, etc.
```javascript
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);
}
```* [Anchor Macros](https://docs.rs/anchor-lang/latest/anchor_lang/derive.Accounts.html)