Waldrop SDK · Integration
Next.js + dapp-kit
Browser dapp setup. Reuse the dapp-kit SuiGrpcClient for both wallet
ops and Waldrop reads — one connection, one wallet picker, one signer.
This is the path the Waldrop dapp itself uses.
Install
npm install @waldrop/sdk @mysten/sui @mysten/dapp-kit-react
# Optional — only if you'll encrypt / decrypt:
npm install @mysten/seal
Provider setup
Wrap your app in DAppKitProvider. The SDK will pick up the same client
your wallets do.
import { createDAppKit } from " @mysten/dapp-kit-react " ;
import { SuiGrpcClient } from " @mysten/sui/grpc " ;
export const dAppKit = createDAppKit ({
networks : [ " testnet " , " mainnet " ],
createClient : ( network ) =>
new SuiGrpcClient ({
network ,
baseUrl :
network === " testnet "
? " https://fullnode.testnet.sui.io:443 "
: " https://fullnode.mainnet.sui.io:443 " ,
}),
});
declare module " @mysten/dapp-kit-react " {
interface Register {
dAppKit : typeof dAppKit ;
}
}
" use client " ;
import { DAppKitProvider } from " @mysten/dapp-kit-react " ;
import { dAppKit } from " @/config/dapp-kit " ;
export function Providers ({ children }: { children : React . ReactNode }) {
return < DAppKitProvider dAppKit ={ dAppKit }>{ children }</ DAppKitProvider >;
}
import { Providers } from " ./providers " ;
export default function RootLayout ({ children }) {
return (
< html >
< body >
< Providers >{ children }</ Providers >
</ body >
</ html >
);
}
useWaldrop hook
One memoized client per dapp-kit client change:
" use client " ;
import { useMemo } from " react " ;
import { useCurrentClient } from " @mysten/dapp-kit-react " ;
import { WaldropClient } from " @waldrop/sdk " ;
export function useWaldrop () {
const client = useCurrentClient ();
return useMemo (
() => new WaldropClient ({ network : " testnet " , suiClient : client }),
[ client ],
);
}
useCurrentClient() returns the active network's SuiGrpcClient — when the
user switches networks, the WaldropClient is recreated automatically.
Connect + upload component
" use client " ;
import { useState } from " react " ;
import {
ConnectButton ,
useCurrentAccount ,
useDAppKit ,
} from " @mysten/dapp-kit-react " ;
import {
RegistrationError ,
InsufficientGasError ,
type UploadProgressEvent ,
} from " @waldrop/sdk " ;
import { useWaldrop } from " @/hooks/useWaldrop " ;
export function Upload ({
subscriptionId ,
blobStoreId ,
}: {
subscriptionId : string ;
blobStoreId ? : string ;
}) {
const account = useCurrentAccount ();
const dAppKit = useDAppKit ();
const waldrop = useWaldrop ();
const [ stage , setStage ] = useState < UploadProgressEvent | null >( null );
const [ blobId , setBlobId ] = useState < string | null >( null );
const [ error , setError ] = useState < string | null >( null );
if ( ! account ) {
return < ConnectButton />;
}
async function handleFile ( file : File ) {
setError ( null );
setBlobId ( null );
try {
const result = await waldrop . blob . upload ({
data : new Uint8Array ( await file . arrayBuffer ()),
fileName : file . name ,
contentType : file . type || " application/octet-stream " ,
epochs : 26 ,
senderAddress : account ! . address ,
subscriptionId ,
blobStoreId ,
signer : dAppKit , // ← dAppKit is a TransactionSigner
onProgress : setStage ,
});
setBlobId ( result . blobId );
} catch ( err ) {
if ( err instanceof InsufficientGasError ) {
setError ( ` Wallet needs ~ ${ ( err . requiredMist ! / 1e9 ). toFixed ( 4 ) } SUI for gas. ` );
} else if ( err instanceof RegistrationError ) {
// bytes are safe on Walrus — persist checkpoint for retry
localStorage . setItem (
" waldrop:checkpoint " ,
JSON . stringify ({
... err . checkpoint ,
contentHash : Array . from ( err . checkpoint . contentHash ),
}),
);
setError ( " Registration failed — checkpoint saved for retry. " );
} else {
setError ( err instanceof Error ? err . message : " Upload failed " );
}
}
}
return (
< div >
< input
type = " file "
onChange ={( e ) => e . target . files ?.[ 0 ] && handleFile ( e . target . files [ 0 ])}
/>
{ stage && < p > [ { stage . stage } ] { stage . percent } % </ p >}
{ blobId && < p > ✓ uploaded as { blobId }</ p >}
{ error && < p style ={{ color : " var(--red-tx) " }}>{ error }</ p >}
</ div >
);
}
The key line: signer: dAppKit. dapp-kit's useDAppKit() returns an object
that already matches the SDK's TransactionSigner interface — no adapter
needed.
Reading without a connected wallet
Read operations don't need an account — drop them in any component:
" use client " ;
import { useEffect , useState } from " react " ;
import { useWaldrop } from " @/hooks/useWaldrop " ;
import type { BlobRef } from " @waldrop/sdk " ;
export function BlobList ({ owner }: { owner : string }) {
const waldrop = useWaldrop ();
const [ blobs , setBlobs ] = useState < BlobRef [] | null >( null );
useEffect (() => {
waldrop . blob . list ({ owner }). then ( setBlobs );
}, [ waldrop , owner ]);
if ( ! blobs ) return < p > Loading… </ p >;
return (
< ul >
{ blobs . map (( b ) => (
< li key ={ b . blobId }>
{ b . originalName } · { b . sizeDisplay } { b . encrypted && " 🔒 " }
</ li >
))}
</ ul >
);
}
Decrypt with the user's wallet
The signer for client.crypto.decrypt is whatever @mysten/seal expects
— dapp-kit's signer works as-is:
" use client " ;
import {
useCurrentAccount ,
useCurrentClient ,
useDAppKit ,
} from " @mysten/dapp-kit-react " ;
import { useWaldrop } from " @/hooks/useWaldrop " ;
export function useDecrypt () {
const waldrop = useWaldrop ();
const client = useCurrentClient () as any ; // SealCompatibleClient
return async ( blob : { blobId : string ; sealPolicyId : string }) => {
const { bytes } = await waldrop . blob . fetch ({ blobId : blob . blobId });
return await waldrop . crypto . decrypt ({
bytes ,
blobStoreId : blob . sealPolicyId ,
signer : client , // SEAL needs the client, not dAppKit
});
};
}
The first decrypt prompts a personal-message signature for the session
key. Subsequent decrypts within ~10 minutes reuse the cached session.
Server-side rendering caveats
The SDK is client-only for upload + decrypt — those need a wallet. For
read flows in a Server Component, instantiate a standalone client:
import { WaldropClient } from " @waldrop/sdk " ;
export default async function BlobsPage () {
const waldrop = new WaldropClient ({ network : " testnet " });
const blobs = await waldrop . blob . list ({
owner : " 0x9d655392521726d0… " ,
});
return (
< ul >
{ blobs . map (( b ) => (
< li key ={ b . blobId }>{ b . originalName }</ li >
))}
</ ul >
);
}
This is a Server Component — runs at request time on your Next server.
Good for SEO + public profile pages.
Don't mix server and client clients
Don't pass WaldropClient instances across the server/client boundary.
They're not serialisable. Construct them where they're used — once per
Server Component request, once per useWaldrop() call client-side.
Vercel deployment
The SDK has no Node-only APIs in the hot paths (uses fetch, crypto.subtle,
no fs / child_process). Deploys to Vercel Edge or Node runtimes both
work out of the box.
If you're calling SDK methods from a Server Component or Route Handler,
they run server-side on Node — no special config needed.
← Recipes
List + fetch
Integrations →
Node.js / CLI
Waldrop · 2026 cryptokarigar