Sealed Storage policies — developer integration
How to encrypt under these policies, build the approve transaction for Seal key servers, discover content, and add a new policy. The full on-chain surface is in the API reference. The TypeScript side is @meddleware/sealed-storage-client (policy registry + SealController).
Normative integration rules
- Identity bytes MUST match the on-chain decoder bit-for-bit.
nft_gate:[32-byte gate object id][nonce]— the gate id is the canonical 32-byte big-endian address (0x123→ 30 zero bytes,0x01,0x23).timelock:[8-byte big-endian u64 unlock_ms][nonce]. Usesealed-storage-client'sbytes.tshelpers; do not hand-roll encoders. Both repos assert the same conformance vector (sealed-storage-client/tests/conformance-vectors.json↔ theconformance_*Move tests) — a layout change MUST update both sides together.
- Persist the identity verbatim. The Seal identity (hex) is part of the ciphertext's
SealedManifest; it cannot be recomputed because it contains a random nonce. - Call the matching approve variant.
seal_approveforAccessNFT,seal_approve_soulboundforSoulboundAccessNFT; pass the sharedPolicyConfig(deployments.json→policyConfigId), the gate and the requester's NFT as object arguments. - Never treat
sealed_contentpointers as authenticated.publishis permissionless and does not validategate_idor the publisher's relationship to the gate. Filter or curate listings in the UI (e.g. only show pointers whosepublisheris the gate's known operator). - Disambiguate aborts by
(module, code).nft_gate,timelockandconfigall use code1, andaccess_gatereuses small integers too.configcode1(E_WRONG_VERSION) means the called package version has been retired — rebuild the PTB against the currentpublished-at. - Surface a paused gate distinctly.
nft_gatecode4(E_GATE_PAUSED) means the gate is paused and its policy blocks decryption while paused — tell the user access resumes when the operator unpauses, rather than reporting a missing or invalid pass. - Seal to soulbound gates. The holder of a transferable
AccessNFTcan freeze or share it (public_freeze_object), after which anyone can present it and the policy approves, for all content of that gate. Content that must stay private to its buyers belongs on a soulbound gate;sealed-storage-clientrefuses to seal to a transferable gate unlessallowTransferableGates: true.
The approve PTB (what the key server dry-runs)
import { Transaction } from '@mysten/sui/transactions'
const tx = new Transaction()
tx.moveCall({
target: `${SEAL_POLICIES_PKG}::nft_gate::seal_approve`, // or seal_approve_soulbound
arguments: [
tx.pure.vector('u8', identityBytes), // [gate id (32)][nonce]
tx.object(POLICY_CONFIG_ID), // shared version object (deployments.json)
tx.object(GATE_ID),
tx.object(NFT_ID), // must be owned by the requesting address
],
})
const txBytes = await tx.build({ client, onlyTransactionKind: true })
// hand txBytes to SealClient.fetchKeys / decrypt together with the SessionKeyFor timelock: target: ${PKG}::timelock::seal_approve, arguments [tx.pure.vector('u8', identityBytes), tx.object(POLICY_CONFIG_ID), tx.object('0x6')]. sealed_content::publish takes PolicyConfig first as well.
Discovering content
Index SealedContentPublished events by gate_id, or enumerate shared SealedContent objects. Each carries blob_id (Walrus ciphertext), seal_id (identity hex), label and publisher.
Adding a policy
A new policy is a new module with its own seal_approve* entry function — existing modules are never edited — plus one new provider in sealed-storage-client whose buildId/buildApprove mirror it and whose verifyId checks the identity layout (required; decrypt calls it before every approve). A policy function MUST take &PolicyConfig second and call config::check_version first, MUST take only immutable references, MUST NOT transfer, create, mutate or emit anything, and MUST length-check every identity byte it reads.
Liveness
Decryption depends on the Seal key-server committee (a threshold of servers must be reachable); if it is not, decryption fails closed. Encryption needs only the committee's public keys.
Canonical source: @meddleware/sealed-storage-sui → docs/onchain/dev-guide.md — maintained with the Move package; edit it there, not on this site.