Sealer-governed clients
Initialize with
initializeFromShyConfig. See the overview for the common setup pattern and uniform primitives.
| Client | Contract version | Required init options |
|---|---|---|
storeClient.js | shystore-v1 | deriveSealerKey, signMessage, getIdentityAttestation |
chatClient.js | shychat-v1 | deriveSealerKey (required when sealer.enabled: true) |
restClient.js | shyrest-v1 | deriveSealerKey, signMessage, getIdentityAttestation |
streamClient.js | shystream-v1 | deriveSealerKey, signMessage, getIdentityAttestation |
browserClient.js | shybrowser-v1 | deriveSealerKey |
Sealer-governed clients encrypt all List 1 payloads client-side (AES-GCM) before broadcast. The ABCI layer stores ciphertext verbatim — plaintext never reaches canonical state. No rescission pathway exists; erasure means the operator deletes the sealer key, making sealed payloads permanently undecryptable.
Every sealer-governed shyconfig must declare domains.private.console — assertStoreManifest, assertChatManifest, and assertStreamManifest all throw if absent.
storeClient
assertStoreManifest enforces: contract_version: "shystore-v1", product_type: "shystore", store block present, domains.private.console present.
import { initializeFromShyConfig } from 'shyware/sdk/web/storeClient.js'
const client = initializeFromShyConfig(shyconfig, {
getAuthHeaders: async () => ({ Authorization: `Bearer ${token}` }),
deriveSealerKey: async () => derivedKeyMaterial,
signMessage: async (msg) => ({ pubKeyHex, sigBytes }),
getIdentityAttestation: async (scope) => ({ idv_attestation_sig: sigBytes })
})
Bucket lifecycle
await client.createBucket({ scopingId: 'bucket-2026-q2', allowedCategories: ['auth_seed_totp'] })
await client.listBuckets()
await client.getBucket(scopingId)
await client.closeBucket({ scopingId, closingHeight: 42100 })
await client.getBucketClosure(scopingId) // → KMS-signed ClosureRecord
Store / reveal / rotate
const { submissionId, submissionNonce } = await client.storeSubmission({
scopingId, plaintext: { service: 'github.com', seed: 'BASE32...' }, category: 'auth_seed_totp'
})
const sealed = await client.revealStore({ scopingId, submissionId })
const plain = await client.revealAndDecryptStore({ scopingId, submissionId })
const { submissionId: newId } = await client.rotateStore({
scopingId, submissionId, newPlaintext: { seed: 'NEWBASE32...' }
})
Recovery receipts
await client.writeRecoveryReceipt(scopingId, submissionId)
const receipt = await client.readRecoveryReceipt(scopingId, submissionId)
Local sealing helpers
const sealed = await client.sealPayload(plaintext)
const plain = await client.openPayload(sealed)
chatClient
assertChatManifest enforces: contract_version: "shychat-v1", product_type: "shychat", identity.surface_model: "mail" | "chat", messaging block present, domains.private.console present.
deriveSealerKey is passed through at init and used when sealer.enabled: true in the shyconfig. If absent and sealer.enabled: true, queueDispatch throws at call time.
import { initializeFromShyConfig } from 'shyware/sdk/web/chatClient.js'
const client = initializeFromShyConfig(shyconfig, {
getAuthHeaders: async () => ({ Authorization: `Bearer ${token}` }),
deriveSealerKey: async () => derivedKeyMaterial // required when sealer.enabled: true
})
Mailboxes
await client.listMailboxes()
await client.getMailbox(mailboxId) // includeContent defaults true
await client.getMailbox(mailboxId, { includeContent: false })
await client.createMailbox({
label, address, routeHint,
accountLabel, // optional — multi-account deployments
accountScope, // optional
auditPolicy
})
Dispatch
await client.queueDispatch({
mailboxId,
recipientAddress,
subject,
body,
deliveryWindow, // optional — unix timestamp pair
contentClass: 'report',
payloadFormat: 'mail_text',
auditMode: 'delivery_commitment_only',
privateFields: { ... },
attachmentRefs: ['ipfs://...']
})
auditMode: "delivery_commitment_only" — only delivery continuity appears in canonical state, no subject or routing metadata.
Close and receipts
await client.attestClose(mailboxId)
await client.writeRecoveryReceipt(mailboxId)
await client.readRecoveryReceipt(mailboxId)
restClient
Composite of storeClient + chatClient for informant-intake deployments.
import { initializeFromShyConfig } from 'shyware/sdk/web/restClient.js'
Exposes the full storeClient bucket/submission surface and the full chatClient mailbox/dispatch surface on a single client. Access underlying clients directly when needed:
const store = client.getStoreClient()
const chat = client.getChatClient()
streamClient
Wraps storeClient for video sealing and live segment queueing. All bucket/submission operations are identical to storeClient — use bucketID where storeClient uses scopingId.
assertStreamManifest enforces: contract_version: "shystream-v1", product_type: "shystream", anon_layer.black_box_required: true, signing.required: true (not "none"), store block present, stream block with stream.provider present, domains.private.console present. Required flows: stream_event, stream_clip, stream_read, biometric_rederive, hop_route_store, hop_route_reveal.
import { initializeFromShyConfig } from 'shyware/sdk/web/streamClient.js'
const client = initializeFromShyConfig(shyconfig, {
getAuthHeaders: async () => ({ Authorization: `Bearer ${token}` }),
deriveSealerKey: async () => derivedKeyMaterial,
signMessage: async (msg) => ({ pubKeyHex, sigBytes }),
getIdentityAttestation: async (scope) => ({ idv_attestation_sig: sigBytes })
})
Bucket lifecycle — identical to storeClient
await client.createBucket({ scopingId: 'stream-session-42', allowedCategories: ['stream_event', 'stream_clip'] })
await client.closeBucket({ scopingId, closingHeight })
await client.getBucketClosure(scopingId)
Stream events and clips
await client.createStreamEvent({ bucketID: 'stream-session-42', payload: { event: 'stream_start', streamID, timestamp }, category: 'stream_event' })
await client.createStreamClip({ bucketID: 'stream-session-42', payload: { clipID, duration_ms, ref }, category: 'stream_clip' })
Both call storeClient.storeSubmission under the two-list invariant. Returns { submissionId, submissionNonce }.
Seal a video segment (VoD)
const { submissionId } = await client.sealVideoSegment({
bucketID: 'stream-session-42',
streamID: 'stream-abc',
sequence: 42,
segment: uint8Array, // or segmentRef: 'cdn://...'
mimeType: 'video/mp2t',
codec: 'h264',
startedAt: Date.now()
})
Live segment sealing (low-latency)
const { submissionId } = await client.sealLiveSegment({
bucketID, streamID, sequence, segment, startedAt, endedAt
})
Live queue — batch flush with jitter
For continuous live streams, use createLiveQueue to batch segment sealing with configurable flush intervals and jitter:
const queue = client.createLiveQueue({
bucketID: 'stream-session-42',
streamID: 'stream-abc',
minBatchSize: 3,
maxBatchSize: 25,
flushIntervalMs: 4000,
jitterMs: 900,
onFlush: (summary) => console.log(`flushed ${summary.count} segments`),
onError: (err) => console.error(err)
})
queue.enqueue({ sequence: 0, segment: chunk0 })
queue.enqueue({ sequence: 1, segment: chunk1 })
await queue.flush({ force: true }) // flush immediately
queue.stop() // cancel scheduled flush
Reveal and rotate
const plain = await client.revealStreamRecord({ bucketID, submissionId })
const plain = await client.revealVideoSegment({ bucketID, submissionId })
await client.rotateStreamRecord({ bucketID, submissionId, newPayload })
await client.rotateLiveSegment({ bucketID, submissionId, streamID, sequence, segment })
browserClient
assertBrowserManifest enforces: contract_version: "shybrowser-v1", sealer.mode: "sealed_storage". No anon_layer.required_flows are checked — browserClient is the only sealer-governed client without required flows.
deriveSealerKey is required at call time (not init time) — storeSealedBrowserData will throw if not supplied to createBrowserClient.
import { initializeFromShyConfig } from 'shyware/sdk/web/browserClient.js'
const client = initializeFromShyConfig(shyconfig, {
getAuthHeaders: async () => ({ Authorization: `Bearer ${token}` }),
deriveSealerKey: async () => derivedKeyMaterial
})
Local sealed storage
storeSealedBrowserData seals to localStorage (or memory in non-browser environments). It takes positional arguments, not an object:
const record = await client.storeSealedBrowserData(data, 'browser_session')
// isList2 = false by default; pass true to tag as List 2 identity attribute
const records = client.getStoredBrowserData() // all records, encrypted
const records = client.getStoredBrowserData('browser_session') // filtered by category
const decrypted = await client.getStoredBrowserDataDecrypted() // all records, decrypted
Submit List 2 identity attribute
Seals and POSTs an identity attribute (e.g. IP address) to the server's /list2-identity endpoint. Takes positional arguments:
await client.submitList2IdentityAttribute(ipAddress, 'ip_address')
List 1 payload and List 2 identity attribute are both sealed — the operator's sealer key is required to read either.
Structural guarantee
No rescission pathway (TxTypeAuthorityRescind is structurally unavailable for sealer-governed embodiments). Enumeration of "which participant produced which submission" is structurally impossible from canonical state alone. Erasure means the operator (reconciling authority) deletes the sealer key — canonical records persist but become permanently undecryptable. A compelled disclosure after key deletion produces irrecoverable ciphertext.