Five minutes from npm install to a verifiable blob on testnet Walrus.
Walks through every credential you need, plus a hands-on recovery from
the most common first-time failure (out of SUI for gas).
What you'll need
01
A testnet Sui keypair
Funded with at least 0.01 SUI. Generate via Sui Wallet or
sui client new-address ed25519.
02
A Waldrop subscription id
Subscribe once via the dapp at
waldrop.xyz. The
Subscription object id will appear in your Sui owned-objects list.
If you already did bun add @waldrop/sdk @mysten/sui, you have the deps.
Otherwise:
Or click the faucet button inside Sui Wallet → testnet. One drop is 1 SUI,
which is enough for ~125 register-blob transactions.
Step 2 — Find your Subscription id
The fastest way: visit https://waldrop.xyz in your wallet, subscribe to a
plan (Free is fine — no payment). The Subscription object's id appears in
the dapp's account screen, or in your Sui Wallet owned objects.
Or query directly:
import { WaldropClient } from "@waldrop/sdk";const client = new WaldropClient({ network: "testnet" });const sub = await client.subscription.get({ owner: "0xYOUR_ADDRESS" });console.log(sub?.subscriptionId);
null here means you haven't subscribed yet — go through the dapp once.
Step 3 — Write the script
Save this as upload.ts:
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";import { decodeSuiPrivateKey } from "@mysten/sui/cryptography";import { SuiGrpcClient } from "@mysten/sui/grpc";import type { Transaction } from "@mysten/sui/transactions";import { WaldropClient, InsufficientGasError, RegistrationError, type TransactionSigner,} from "@waldrop/sdk";const { secretKey } = decodeSuiPrivateKey(process.env.WALDROP_PRIVATE_KEY!);const keypair = Ed25519Keypair.fromSecretKey(secretKey);const sender = keypair.toSuiAddress();const suiClient = new SuiGrpcClient({ network: "testnet", baseUrl: "https://fullnode.testnet.sui.io:443",});const signer: TransactionSigner = { async signAndExecuteTransaction({ transaction }) { const r = await (suiClient as any).signAndExecuteTransaction({ transaction: transaction as Transaction, signer: keypair, include: { effects: true }, }); const digest = r?.digest ?? r?.effects?.transactionDigest ?? ""; if (digest) await (suiClient as any).waitForTransaction?.({ digest }); return { digest, effects: r?.effects }; },};const client = new WaldropClient({ network: "testnet", suiClient });try { const result = await client.blob.upload({ data: new TextEncoder().encode(`hello waldrop · ${new Date().toISOString()}`), fileName: "hello.txt", contentType: "text/plain", epochs: 2, senderAddress: sender, subscriptionId: process.env.WALDROP_SUBSCRIPTION_ID!, signer, onProgress: (e) => console.log(` [${e.stage}] ${e.percent}%`), }); console.log(`✓ blob landed: ${result.blobId}`); console.log(` tx: https://testnet.suivision.xyz/txblock/${result.transactionDigest}`);} catch (err) { if (err instanceof InsufficientGasError) { console.error(`✗ wallet needs ${err.requiredMist} MIST — top up via faucet.`); } else if (err instanceof RegistrationError) { console.error("✗ tx failed; bytes are safe on Walrus.", err.message); console.error(" blobId:", err.checkpoint.blobId); } else { throw err; }}
Step 4 — Run it
export WALDROP_PRIVATE_KEY=suiprivkey1... # from your wallet exportexport WALDROP_SUBSCRIPTION_ID=0x... # from step 2bun upload.ts
Re-run — you'll see your original message printed back. Round trip in 6
seconds.
That's it
You now have a working Waldrop pipeline. The same client.blob.upload
call handles encryption, sharing, multi-file bundles, and progress
callbacks. See the Recipes section for each variation.
If it failed
The single most common first-run failure is insufficient gas:
✗ wallet needs 7694984 MIST — top up via faucet.
This is actually a successful publisher PUT — your bytes already landed
on Walrus. Top up SUI and call client.blob.registerOnly({ checkpoint, … })
with the checkpoint from the error. See the
Resume guide for the recovery pattern.