Waldrop SDK · Recipe

Resume an upload that failed mid-register

The publisher PUT succeeded — your bytes are on Walrus, storage is paid for — but the on-chain register_blob transaction failed. Don't re-upload. Persist the checkpoint and retry just the tx.

The failure modes

01
InsufficientGasError
Wallet ran out of SUI for gas. Top up via faucet, retry.
02
RegistrationError
Generic tx failure — plan-tier gate, network blip, wallet rejection.

Both carry .checkpoint — that's the only thing you need to keep.

Recipe (catch + persist + retry)

import {
  WaldropClient,
  RegistrationError,
  InsufficientGasError,
  type UploadCheckpoint,
} from "@waldrop/sdk";

const client = new WaldropClient({ network: "testnet", suiClient });

async function uploadWithResume(args: { /* … */ }) {
  // ── Try the full upload first ────────────────────────────────────
  try {
    return await client.blob.upload({ /* … */ });
  } catch (err) {
    if (!(err instanceof RegistrationError)) throw err;

    if (err instanceof InsufficientGasError) {
      console.warn(
        `Wallet ${err.address} needs ${err.requiredMist} MIST — top up SUI.`,
      );
    }

    // ── Persist the checkpoint so it survives a process restart ────
    persistCheckpoint(err.checkpoint);

    // ── Wait for the user to top up / fix the cause ────────────────
    await waitForUserToFixIt();

    // ── Retry register only ────────────────────────────────────────
    const checkpoint = loadCheckpoint();
    if (!checkpoint) throw new Error("Lost checkpoint");

    const result = await client.blob.registerOnly({
      checkpoint,
      epochs: args.epochs,
      senderAddress: args.senderAddress,
      subscriptionId: args.subscriptionId,
      signer: args.signer,
      blobStoreId: args.blobStoreId,
    });

    clearCheckpoint();
    return result;
  }
}

Filesystem persistence (Node)

The checkpoint is plain JSON — except contentHash, which is a Uint8Array. Serialize it as a number array and reconstitute on load:

import { writeFileSync, readFileSync, existsSync, unlinkSync } from "node:fs";
import type { UploadCheckpoint } from "@waldrop/sdk";

const PATH = ".waldrop-checkpoint.json";

function persistCheckpoint(c: UploadCheckpoint) {
  writeFileSync(
    PATH,
    JSON.stringify({ ...c, contentHash: Array.from(c.contentHash) }, null, 2),
  );
}

function loadCheckpoint(): UploadCheckpoint | null {
  if (!existsSync(PATH)) return null;
  const raw = JSON.parse(readFileSync(PATH, "utf8"));
  return { ...raw, contentHash: new Uint8Array(raw.contentHash) };
}

function clearCheckpoint() {
  if (existsSync(PATH)) unlinkSync(PATH);
}

Browser persistence

Swap fs for localStorage:

function persistCheckpoint(c: UploadCheckpoint) {
  localStorage.setItem(
    "waldrop:checkpoint",
    JSON.stringify({ ...c, contentHash: Array.from(c.contentHash) }),
  );
}

function loadCheckpoint(): UploadCheckpoint | null {
  const raw = localStorage.getItem("waldrop:checkpoint");
  if (!raw) return null;
  const parsed = JSON.parse(raw);
  return { ...parsed, contentHash: new Uint8Array(parsed.contentHash) };
}

function clearCheckpoint() {
  localStorage.removeItem("waldrop:checkpoint");
}

What's in the checkpoint

interface UploadCheckpoint {
  blobId: string;             // Walrus content-addressed id
  sizeBytes: number;
  contentHash: Uint8Array;    // SHA-256 of original bytes
  fileName: string;
  contentType: string;
  encrypted: boolean;
  sealMarker?: string;        // hex, only set if encrypted
}

Everything the on-chain register_blob step needs. No bytes — the bytes live on Walrus, identified by blobId.

Why this is idempotent

Walrus storage is paid at PUT time, not register time. A failed register doesn't waste storage tokens — the blob persists on Walrus until its expiry. Retry as many times as you need.

One thing that's NOT recoverable

The Walrus PUT failing partway is not covered here — that's a separate problem. But Walrus is content-addressed, so a "retry from byte 0" is cheap if the bytes already partially landed (the publisher returns alreadyCertified immediately).

Edit this page on GitHub ↗
Waldrop · 2026cryptokarigar