Waldrop SDK · Recipe

Share an encrypted blob with another address

Grant a Sui address permission to decrypt your encrypted blobs. Two granularities: store-level (every blob in the BlobStore) or per-blob (one specific blob). The SDK reads both ACLs but doesn't write them yet — build the PTBs yourself with @mysten/sui + the exported PACKAGE_ID.

Sharing only matters for encrypted blobs

Plaintext blobs are public-readable by blob_id — anyone with the id can fetch the bytes. Sharing is a SEAL-decryption gate, so it only applies to blobs uploaded with encrypted: true.

Option A — Bundle viewers into the upload tx

Cheapest. One transaction grants access at the moment of registration.

const result = await client.blob.upload({
  data,
  fileName: "shared.txt",
  contentType: "text/plain",
  epochs: 26,
  senderAddress,
  subscriptionId,
  signer,
  blobStoreId,
  encrypted: true,
  initialShareViewers: [
    "0xabc…",                          // grant per-blob decrypt access
    "0xdef…",
  ],
});

Each viewer becomes an add_blob_share(store, GlobalConfig, marker, viewer) Move call appended to the same PTB as register_blob. Atomic — either everything lands or nothing does.

Option B — Per-blob share after the fact

Build the PTB yourself:

import { Transaction } from "@mysten/sui/transactions";
import { bcs } from "@mysten/sui/bcs";
import { PACKAGE_ID, SHARED_OBJECTS } from "@waldrop/sdk";

function hexToBytes(hex: string): Uint8Array {
  const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
  const out = new Uint8Array(clean.length / 2);
  for (let i = 0; i < out.length; i++) {
    out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
  }
  return out;
}

const tx = new Transaction();
tx.setSender(senderAddress);
tx.moveCall({
  target: `${PACKAGE_ID}::storage::add_blob_share`,
  arguments: [
    tx.object(blobStoreId),
    tx.object(SHARED_OBJECTS.globalConfig),
    tx.pure(bcs.vector(bcs.u8()).serialize(Array.from(hexToBytes(sealMarker)))),
    tx.pure.address(viewerAddress),
  ],
});

await signer.signAndExecuteTransaction({ transaction: tx });

sealMarker is the 16-byte hex value from UploadBlobResult.sealMarker (or BlobRef.sealMarker if you fetched the blob first). The contract uses it as the allowlist key — the viewer can decrypt this blob but not the rest of the store.

Option C — Store-level share (whole BlobStore)

Use this when a viewer needs access to every blob you ever store:

const tx = new Transaction();
tx.setSender(senderAddress);
tx.moveCall({
  target: `${PACKAGE_ID}::storage::add_viewer`,
  arguments: [
    tx.object(blobStoreId),
    tx.object(SHARED_OBJECTS.globalConfig),
    tx.pure.address(viewerAddress),
  ],
});

await signer.signAndExecuteTransaction({ transaction: tx });

Reading the ACL

The SDK exposes the read side:

// Store-level viewers (excludes the owner — that's implicit)
const viewers = await client.blob.listViewers({ owner: "0x…" });

// Owner check — mirrors the on-chain `seal_approve` predicate
const allowed = await client.blob.canView({
  owner: "0x…",
  address: viewerAddress,
});

Per-blob shares aren't exposed via a dedicated method yet — they're stored as a separate table keyed by seal_marker. Query the BlobStore object's per_blob_shares field directly if you need it.

Removing access

Same Move functions, remove_* flavor:

tx.moveCall({
  target: `${PACKAGE_ID}::storage::remove_blob_share`,
  arguments: [
    tx.object(blobStoreId),
    tx.pure(bcs.vector(bcs.u8()).serialize(Array.from(markerBytes))),
    tx.pure.address(viewerAddress),
  ],
});

tx.moveCall({
  target: `${PACKAGE_ID}::storage::remove_viewer`,
  arguments: [
    tx.object(blobStoreId),
    tx.pure.address(viewerAddress),
  ],
});
Forward secrecy: there is none

Revoking a viewer prevents future seal_approve calls, but a viewer who already pulled the key from the SEAL servers can keep decrypting. If you need true revocation, re-encrypt under a fresh identity and upload a new blob.

Granularity at a glance

01
Per-blob share

Keyed by 16-byte SEAL marker. Viewer can decrypt one specific blob.

02
Store-level share

Stored on the BlobStore. Viewer can decrypt every blob you'll ever put there — past + future.

Edit this page on GitHub ↗
Waldrop · 2026cryptokarigar