Waldrop SDK · Guide

Cost & plans

Three things drive what an upload costs — storage size, retention epochs, and your subscription tier. The first two are paid in Walrus FROST per PUT; the third is paid in USDC per billing period via the dapp.

Two separate costs

01
Walrus storage (FROST)

Per-blob fee charged at PUT time. Scales with encoded size × epochs. Paid from the publisher's wallet — your wallet pays SUI for Sui gas only.

02
Waldrop subscription (USDC)

Recurring fee for plan tier — gates SEAL access, file-count cap, lifetime cap. Paid via the Waldrop dapp; SDK only reads the active subscription.

The storage math

encoded_bytes = raw_bytes × 5            // 5× redundancy upper bound
units_per_blob = ceil(encoded_bytes / 1 MiB)
total_units = units_per_blob × num_blobs

storage_cost = total_units × storage_price_per_unit_per_epoch × epochs
write_cost   = total_units × write_price_per_unit          // one-time
total        = storage_cost + write_cost

The 5× factor is RaptorQ erasure-coding redundancy. The exact factor depends on n_shards (variable across Walrus networks), but 5× is a safe upper bound the dapp + SDK both use.

storage_price_per_unit_per_epoch and write_price_per_unit come from the live Walrus System shared object — read via:

const pricing = await client.cost.getPricing();
// → { storagePricePerUnitFrost, writePricePerUnitFrost, currentEpoch, ... }

Worked example — 10 MiB for 26 epochs

raw_bytes      = 10 × 1 MiB
encoded_bytes  = 10 × 5 = 50 MiB
units_per_blob = ceil(50) = 50
total_units    = 50 × 1 = 50

storage_cost   = 50 × 1184 FROST × 26 = 1,539,200 FROST
write_cost     = 50 × 2368 FROST      =   118,400 FROST
total          =                        1,657,600 FROST
               = 0.001658 WAL

Run it yourself:

const r = await client.cost.estimate({
  bytesPerBlob: 10 * 1024 * 1024,
  epochs: 26,
});
console.log(r.totalWal);   // 0.001658

Whose wallet pays for storage?

The Walrus publisher does. The publisher is a service that:

  1. Receives your bytes via HTTP PUT
  2. Erasure-codes them into slivers
  3. Distributes slivers to storage nodes
  4. Pays the storage nodes in WAL from its own wallet
  5. Records the blob certificate on-chain
  6. Returns the blob_id to you

The public testnet publisher (publisher.walrus-testnet.walrus.space) absorbs the WAL cost — that's why it's free to use, with rate limits. For production you'd run your own publisher and fund it.

Your wallet pays SUI for gas on the register_blob Sui transaction — about 0.008 SUI per upload (~$0.0001 at testnet rates).

Subscription plans

Plans are managed by the contract's subscription module:

TierSEALFile capLifetime capNotes
Free (0)lowshortDefault — no encryption.
Starter (1)mediummediumFirst paid tier.
Pro (2)highlongMost use cases.
Enterprise (3)unlimitedmaximumCustom contract.

Exact caps are stored in PlanRegistry on chain — they change without a contract upgrade. The SDK reads SubscriptionSummary but doesn't surface the registry yet; check the dapp's plans page for current numbers.

How to forecast cost in a UI

Drop this into your upload wizard so the user sees the cost before they commit:

function CostRow({ size, epochs }: { size: number; epochs: number }) {
  const [r, setR] = useState<EstimateResult | null>(null);

  useEffect(() => {
    let live = true;
    client.cost.estimate({ bytesPerBlob: size, epochs }).then((v) => {
      if (live) setR(v);
    });
    return () => { live = false; };
  }, [size, epochs]);

  if (!r) return <Spinner />;
  if (!r.isAvailable) return <span></span>;

  return (
    <>
      {r.totalWal.toFixed(6)} WAL
      <span className="muted"> · {r.totalUnits} × 1 MiB · {epochs} epochs</span>
    </>
  );
}

For many estimates at once (a per-file table), fetch pricing once and pass it through to skip the per-call live read — see the Cost recipe.

Subscription lifecycle

The SDK only reads. To subscribe / upgrade / renew, build the PTBs with @mysten/sui directly — the dapp's lib/transactions.ts is the reference.

// Read-side, what the SDK does support:
const sub = await client.subscription.get({ owner });
const active = await client.subscription.isActive({ owner, currentEpoch });
const daysLeft = await client.subscription.daysUntilExpiry({
  owner, currentEpoch, epochDurationDays: 1,
});

When pricing isn't available

The on-chain Walrus System object can return partial state during network upgrades. client.cost.estimate exposes this:

const r = await client.cost.estimate({ … });
if (!r.isAvailable) {
  // pricing came back empty — show a "pricing unavailable, try again" UI
  return;
}

epochDays is 0 on testnet because epoch_duration_ms lives on a different Walrus shared object (StakingInner). Default to 1 day per epoch for testnet display.

Edit this page on GitHub ↗
Waldrop · 2026cryptokarigar