The Shadow Book
Search…
⌃K

The SDK

Bake Storage directly into your Web App

It's a common design. We need to store data as files and put those files somewhere. Or we want to allow the end user to upload files.
The CLI is great and all, but we want to bake this functionality to use the Shadow Drive directly into our app. We got you covered with SDKs in both JavaScript/Typescript as well as Rust.

Getting Started

Let's scaffold a React app and add our dependencies
npx create-react-app shdwapp
cd shdwapp/
yarn add @shadow-drive/sdk @project-serum/anchor \
@solana/wallet-adapter-base \
@solana/wallet-adapter-react \
@solana/wallet-adapter-react-ui \
@solana/wallet-adapter-wallets \
@solana/web3.js \
@solana-mobile/wallet-adapter-mobile

Instantiate the Wallet and Connection the ol fashion way

Use the Solana docs and examples here if you need help. We're going to focus on Shadow Drive SDK in these docs, so if you need a primer on how to build a React site with Solana, we can refer you to other resources.
Solana example code:
Sample Code provided by Solana

Now you can focus on building components for various Shadow Drive operations

Let's start by instantiating the Shadow Drive connection class object. This will have all the methods to do all the things, and it implements the signing wallet within the class for all the transactions.
At the simplest level, it is recommend for a React app to immediately try to load a connection to a user's Shadow Drives upon wallet connection. This can be done with the useEffect React hook.
import React, { useEffect } from "react";
import * as anchor from "@project-serum/anchor";
import {ShdwDrive} from "@shadow-drive/sdk";
import { AnchorWallet, useAnchorWallet, useConnection } from "@solana/wallet-adapter-react";
export default function Drive() {
const { connection } = useConnection();
const wallet = useAnchorWallet();
useEffect(() => {
(async () => {
if (wallet?.publicKey) {
const drive = await new ShdwDrive(connection, wallet).init();
}
})();
}, [wallet?.publicKey])
return (
<div></div>
Also this can be done in a simple script using Node; however, you will want to use TypeScript for your Node scripts which you will realize when we get to the uploadFile method.
const anchor = require("@project-serum/anchor")
const {
Connection,
clusterApiUrl,
Keypair,
} = require("@solana/web3.js")
const {ShdwDrive} = require("@shadow-drive/sdk");
const key = require('./shdwkey.json')
async function main(){
let secretKey = Uint8Array.from(key)
let keypair=Keypair.fromSecretKey(secretKey);
const connection = new Connection(clusterApiUrl("mainnet-beta"),"confirmed")
const wallet = new anchor.Wallet(keypair);
const drive = await new ShdwDrive(connection, wallet).init();
}
main()
NOTE: the wallet object WILL need to be an AnchorWallet object in either instance.

Create a Storage Account

This implementation is effectively the same for both Web and Node implementations. There are three params that are required to create a storage account:
  • name: a friendly name for your storage account
  • size: The size of your storage accounts with a human readable ending containing "KB", "MB", or "GB"
  • version: can be either "v1" or "v2" but you should really stick to v2 if you are creating new accounts
//create account
const newAcct = await drive.createStorageAccount("myDemoBucket","10MB", "v2")
console.log(newAcct)

Get a list of Owned Storage Accounts

This implementation is effectively the same for both Web and Node implementations. The only parameter required is either "v1" or "v2" for the version of storage account you created in the previous step.
const accts = await drive.getStorageAccounts("v2")
// handle printing pubKey of first storage acct
let acctPubKey = new anchor.web3.PublicKey(accts[0].publicKey)
console.log(acctPubKey.toBase58())
Full Response:
Output

Get a Specific Storage Account

This implementation is effectively the same for both Web and Node implementations. The only parameter required is either a PublicKey object or a base-58 string of the public key.
const acct = await drive.getStorageAccount("EY8ZktbRmecPLfopBxJfNBGUPT1LMqZmDFVcWeMTGPcN")
console.log(acct)
Full Response:
Output

Upload a File

This is where things start to get hairy. Let's take a close look at why:
The uploadFile method requires two parameters:
  • key: A PublicKey object representing the public key of the Shadow Storage Account
  • data: A file - and here's the kicker - of either the File object type or ShadowFile object type
Check the intellisense popup below when hovering over the method
So why is this so tricky? Because File objects are implemented in web browsers, and ShadowFile is a custom type created in TypeScript. So either you are using File in the web, or you are scripting in TS. There is not currently an implementation for uploading files written in JS using only Node.
Here is an example of how to pull it off as a React component:
React Upload component
And a TypeScript implementation would look like:
TS UploadFile

Upload Multiple Files

This is a nearly identical implementation to uploadFile, except that it requires a FileList or array of ShadowFiles. But one of the interesting things about this is the optional concurrency parameter.
Recall that the default setting is to attempt to upload 3 files concurrently. Here you can override this and specify how many files you want to try to upload based on the cores and bandwith of your infrastructure. Don't worry - Shadow Drive can handle the heat. Can you?

Delete a File

The implementation of deleteFile is the same between web and Node. There are three required parameters to delete a file:
  • key: the storage account's public key
  • url: the current URL of the file to be deleted
  • version: can be either "v1" or "v2"
const url = 'https://shdw-drive.genesysgo.net/4HUkENqjnTAZaUR4QLwff1BvQPCiYkNmu5PPSKGoKf9G/fape.png'
const acctPubKey = new anchor.web3.PublicKey("EY8ZktbRmecPLfopBxJfNBGUPT1LMqZmDFVcWeMTGPcN")
const delFile = await drive.deleteFile(acctPubKey,url,"v2")
console.log(delFile)

Edit a File (aka Replace a file)

\
The editFile method is a combo of uploadFile and deleteFile. Let's look at the params:
  • key: the Public Key of the storage account
  • url: the URL of the file that is being replaced
  • data: the file that is replacing the current file. It must have the exact same filename and extension, and it must be a File or ShadowFile object
  • version: either "v1" or "v2" (prolly v2)
const fileToUpload: ShadowFile = {
name: 'mytext.txt',
file: fileBuff
}
const url = 'https://shdw-drive.genesysgo.net/4HUkENqjnTAZaUR4QLwff1BvQPCiYkNmu5PPSKGoKf9G/fape.png'
const acctPubKey = new anchor.web3.PublicKey("EY8ZktbRmecPLfopBxJfNBGUPT1LMqZmDFVcWeMTGPcN")
const editFile = await drive.editFile(acctPubKey,url,"v2", fileToUpload)

List Storage Account Files (aka List Objects)

This is a simple implementation that only requires a public key to get the file names of a storage account.
const acctPubKey = new anchor.web3.PublicKey("EY8ZktbRmecPLfopBxJfNBGUPT1LMqZmDFVcWeMTGPcN")
const listItems = await drive.listObjects(acctPubKey)
console.log(listItems)
And the response payload:
{ keys: [ 'index.html' ] }

Increase Storage Account Size

This is a method that simply increase the storage limit of a storage account. It requires three params:
  • key: storage account public key
  • size: amount to increase by, must end with "KB", "MB", or "GB"
  • version: storage account version, must be "v1" or "v2"
const accts = await drive.getStorageAccounts("v2")
let acctPubKey = new anchor.web3.PublicKey(accts[1].publicKey)
const addStgResp = await drive.addStorage(acctPubKey,"10MB","v2")

Reduce Storage Account Size

So you didn't use all of your storage? Shrink it down so that you can get some SHDW back. This implementation only requires three params - the storage account key, the amount to reduce it by, and the version.
const acctPubKey = new anchor.web3.PublicKey("EY8ZktbRmecPLfopBxJfNBGUPT1LMqZmDFVcWeMTGPcN")
const shrinkAcct = await drive.reduceStorage(acctPubKey,"10MB", "v2")

Next you'll want to claim your unused SHDW

Here you can claim your SHDW that is no longer being used. This method only requires a storage account public key and a version.
const acctPubKey = new anchor.web3.PublicKey("EY8ZktbRmecPLfopBxJfNBGUPT1LMqZmDFVcWeMTGPcN")
const claimStake = await drive.claimStake(acctPubKey,"v2")

Delete a Storage Account

As the name implies, you can delete a storage account and all of its files. The storage account can still be recovered until the current epoch ends, but after that, it's toast. This implementation only requires two params - a storage account key and a version.
const acctPubKey = new anchor.web3.PublicKey("EY8ZktbRmecPLfopBxJfNBGUPT1LMqZmDFVcWeMTGPcN")
const delAcct = await drive.deleteStorageAccount(acctPubKey,"v2")

Undelete the Deleted Storage Account

You can still get your storage account back if the current epoch hasn't elapsed. Same drill - just need an account public key and a version.
const acctPubKey = new anchor.web3.PublicKey("EY8ZktbRmecPLfopBxJfNBGUPT1LMqZmDFVcWeMTGPcN")
const cancelDelStg = await drive.cancelDeleteStorageAccount(acctPubKey,"v2")

Last modified 1mo ago