Skip to content

Cell Seal: Architecture ​

The root document of the technical design. Every contract, service, table and package name in the other documents comes from here. Read overview.md, glossary.md and primitives.md first.

1. Four design principles ​

#PrincipleTechnical consequence
P1Cell Seal cannot read any amountEvery amount leaves the browser as ciphertext. Encryption, decryption and proofs happen only on the client. Any backend the SDK talks to is amount-blind: it stores and relays ciphertext and holds no key
P2The chain is the source of truthThe status of an on-chain leg changes only when the event archive sees a final event. Any database, the sandbox's or the platform's, is a read model, rebuildable from the chain and from ciphertext
P3The rail knows nothing about fund businessRail-layer contracts have no notion of NAV, cut-off, dealing day. Those live in the tokenization platform's system, and in the sandbox in its backend and fund console
P4Compose, do not rewriteCommitments, circuits, verifier, freeze come from OpenZeppelin's Confidential Token. Cell Seal writes only what primitives.md section 4 and section 5 list

The product is the on-chain layer (section 3.1), the SDK (section 3.3, @cell-seal/sdk) and the onboarding procedure (onboarding.md). The backend (section 3.2) and the two apps (section 3.4) are the sandbox: a reference tokenization platform with the rail integrated, run for evaluation and read as a worked integration. Section 3 opens with the table that assigns every component to one of the two. Ownership boundary, token requirements and who mints and burns: integration.md.

2. System diagram ​

Browser: the only place that sees plaintextSandbox backend: amount-blindStellar, Sorobansigned transaction, with proofciphertext, order metadatagetEventsevents older than the RPC windowStellar RPCFund console (sandbox)Investor app (sandbox)Key-holder client@cell-seal/sdkkeys, proofs, decryption, sealed boxgateway: REST + authorders: state machineindexer: event archivePostgresfund token: the platform'sseal-token: share wrapperseal-token: cash wrappersettlementfund-policyseal-auditorseal-verifierfund-registry

In an integration, the platform's own apps take the place of the two sandbox apps and the platform's own backend takes the place of the sandbox backend. Both talk to the same SDK, and the chain layer is identical. What the SDK needs from that backend is listed in sdk.md section 2.1.

3. Components ​

ComponentScopeWho runs it in production
Contracts, section 3.1ProductThe platform, one contract set, from the audited wasm hashes
@cell-seal/sdk, section 3.3ProductThe platform, inside its own apps and backend
Onboarding procedure, onboarding.mdProductThe platform, once per fund
Event archive and order storeHost services the SDK needsThe platform, in its own infrastructure
Backend, section 3.2Sandbox, referenceNobody for real funds. Its indexer is a reference event archive a platform may run as is
Fund console, investor app, @cell-seal/ui, section 3.4SandboxNobody for real funds

3.1 On-chain: repo cell-seal-contracts (Rust, Soroban) ​

ContractInstancesRoleSource
fund token1 per fundThe platform's SEP-41 token: SAC or Soroban token. The platform's issuer mints and burns. Cell Seal has no rights on it. share-token in the prototype is a sample token for tests and Case 1Platform. Sample: OpenZeppelin fungible
seal-token1 per fund (share wrapper) and 1 per stablecoin (cash wrapper)Sealed balances and transfers. Cell Seal's hooksOpenZeppelin ConfidentialToken trait, concrete contract written by Cell Seal
seal-verifier1 per platformVerifies UltraHonk proofs, holds the verification key per CircuitTypeNethermind rs-soroban-ultrahonk + OpenZeppelin trait
seal-auditor1 per platformRegistry of viewing keys by auditor_id. One auditor_id per fundConfidentialAuditor trait
fund-policy1 per platformis_authorized(account, token): per-fund whitelist + KYC attestationCell Seal
settlement1 per platformThe single gate for every sealed transfer. Attaches the order reference. DvP. Closes the settlement windowCell Seal
fund-registry1 per platformCatalogue of funds and the public data of eachCell Seal

Details: contracts.md. Requirements the fund token must meet: integration.md section 3.

3.2 Sandbox backend: repo cell-seal (Rust workspace: axum, sqlx, tokio, Postgres) ​

Part of the sandbox, not of the product. It is the backend of the reference platform and shows one way to host the SDK: an amount-blind order store, an event archive and the jobs around them. A platform has its own backend and provides the host services there. The one crate a platform may take as is is indexer, the reference implementation of the event archive.

CrateRole
gatewayREST API, wallet-signature authentication, role-based authorization, rate limit
ordersState machine of orders and legs, dealing calendar, NAV, settlement window, attestations
indexerPermanent archive of every event of the Cell Seal contracts. API for wallets to recover state
jobsSupply reconciliation after every WindowClosed (T1a to T1c), storage TTL extension, reminders
coreTypes, configuration, Stellar RPC client, error codes
authChallenge, session, roles

Details: backend.md.

3.3 SDK: @cell-seal/sdk (TypeScript) ​

Product. Key derivation, core cryptography, proof generation in a Web Worker, wallet state, contract clients, sealed box, threshold decryption, register reconstruction. The only code that touches private keys and plaintext amounts. Lives in the cell-seal-web workspace with the sandbox apps but is published and versioned on its own. Details: sdk.md.

3.4 Sandbox apps: repo cell-seal-web (pnpm workspace: Vite, React, TypeScript, Tailwind) ​

PackageRole
@cell-seal/uiDesign system of the sandbox, colour tokens from design/
apps/fund-consoleThe fund's 11 screens, plus the key-holder page
apps/investor-appThe investor's 7 screens

Details: frontend.md, design/README.md.

4. Trust boundaries ​

ZoneSeesNever sees
Investor's browserThat investor's own balance and ordersOther investors' numbers
Fund ops' browserEvery order of the fund (through the fund's box key and the settlement account's key)The k-of-n viewing key. Investors' keys
Key-holder clientThat party's own key share. The plaintext of the approved scopeOther parties' key shares
Backend (the sandbox's, or the platform's host services)Who placed an order, order type, time, status, NAV, addressesEvery amount, every private key, the settlement account's root
Chain, observersAddresses, timestamps, order references, NAV, total supply, the window netThe amount of any order, the balance of any holder

If the backend is compromised, the attacker gets metadata and ciphertext, not amounts. This is why P1 is enforced by cryptography and not by access policy.

5. Data classification ​

ClassExamplesStored inForm
PublicNAV, dealing calendar, total supply, net of closed windows, addresses, order referencesChain, fund-registry, DBPlaintext
MetadataWhich investor an order belongs to, type, status, time, payment methodDBPlaintext, role-scoped access
Sealed on-chainTransfer amounts, balancesseal-tokenCommitment + ciphertext for the recipient and for the auditor
Sealed off-chainAmount of a fiat order, payout, net of the open window, bank detailsDBSealed box to the box key of the fund and of the investor
SecretGrumpkin sk, root, key share, private box keyBrowser memory. Encrypted envelope in the DBNever leaves the client in the clear

6. The accounts of a fund ​

AccountTypeUsed for
Issuer adminThe issuer's Stellar multisig accountIssuer of the fund token on the platform: mint, burn on the platform's rail. Admin of the share wrapper: freeze, clawback, configuration. Co-signs close_window
Settlement accountA Stellar account of the fund, registered in the share wrapper and the cash wrappersReceives redemption shares, delivers subscription shares, receives and pays stablecoin, acts as spender for DvP orders
Fund opsPersonal account of each staff memberLogin, signing attestations. Signer of the settlement account
Key holderAccounts of issuer, TA, regulatorHold a share of the viewing key. Approve disclosure requests

The settlement account has several operators, so it does not use a signer root (a key derived from one specific signer's signature) but a raw root of 32 bytes, generated when the fund is created and handed to each fund ops member as an encrypted envelope. See keys.md section 3.

7. The two timelines of an order ​

The primitive forces the two moments apart, and every later document uses exactly these two words:

MomentHappens whenWhat takes place
StrikeDealing day, after cut-offNAV is published. Every pending order is priced. Nothing changes hands
SettlementWhen the settlement window closesThe issuer has funded the public net into the settlement account. close_window wraps it, then the share legs of subscriptions are delivered and the DvP legs are executed

Reason: before the window closes, the net shares of the period are not yet wrapped, so the settlement account does not hold enough sealed shares to deliver every subscription. settlement neither mints nor burns. The issuer does both on the platform's rail, before and after close_window (integration.md section 4):

net = sum of subscription shares - sum of redemption shares

net > 0 :  issuer funds net public into the settlement account (mint in Case 1, transfer in Case 2)
           then close_window: deposit(net) into the wrapper                      -+
net < 0 :  close_window: withdraw(|net|) to the settlement account                -+-> sealed balance = sum of subscription shares
           then issuer burns or retires |net| on the platform's rail              |
net = 0 :  no public operation                                                    -+
                                                                                  -> deliver everything to the subscriptions -> 0

Redemption shares reach the settlement account at any time during the period and are the source of the netting.

8. Data flow of an order ​

Shown with the sandbox's components. In an integration the platform's app and backend play the same two roles and the SDK calls are the same.

8.1 Subscription paid by bank transfer (DoP) ​

Encrypt amount into a sealed box to the fund's box key and to its ownPOST /orders {fund, kind=sub, pay=bank, amount_ct, order_id}Bank transfer details (encrypted for the investor), reference codeGET /orders, decrypt the amount in the browserPOST /orders/{id}/attest {cash_received, signature}shares = amount / NAV - fee, computed in the browsersettlement.submit_leg(order_id, share wrapper, settlement account to investor, data)Event LegSettled(order_id) through the indexerOrder = donemerge() before the next spendInvestor transfers money outside the systemStrike: fund ops publish NAVSettlement: issuer funds the net, closing the window wraps itInvestor appBackendFund consoleChain

8.2 Redemption paid out by bank transfer (DoP) ​

The investor builds the proof and calls settlement.submit_leg(order_id, share wrapper, investor to settlement account, data) as soon as the order is placed. The settlement account is the recipient, so the fund console decrypts the amount with its key. After strike, fund ops make the bank transfer and attest cash_paid. The shares sitting in the settlement account are netted when the window closes.

8.3 DvP with stablecoin ​

When placing the order, the side that must pay first delegates exactly the needed part to the settlement account with set_spender (cash for a subscription, shares for a redemption). At settlement, fund ops, signing as the settlement account, call settlement.settle_dvp(order_id, legs): the pull leg uses confidential_transfer_from, the pay leg uses confidential_transfer, in the same invocation, hence atomic. Only the settlement account signs. The investor need not be online. The seal-token hook forces every transfer through settlement, so the settlement account cannot pull funds while skipping the other leg. Details and limits: cryptography.md section 5.

8.4 Secondary transfer ​

Both investors are online. Each builds the proof for its own leg and signs one authorization entry. one transaction calls settlement.settle_pair(order_id, leg_a, leg_b). The receiving side sends the opening of the leg it received to the fund (encrypted to the fund's box key) so the register updates without the k-of-n viewing key. See cryptography.md section 7.

9. Deployment ​

EnvironmentStellar networkPurpose
localQuickstart containerDevelopment. Real verifier, real proofs
testnetTestnetIntegration and sandbox. Pilot with a sample fund
mainnet-pilotMainnetCapped pilot. Value cap configured in settlement

The product is deployed per platform: the platform deploys the contract set from the audited wasm hashes with its own admin key, and installs the SDK from the package registry. The sandbox runs under Docker Compose (gateway, indexer, postgres, the two apps). Cell Seal hosts one instance on testnet for evaluation and a platform can run its own copy from source.

Circuit artifacts (ACIR, verification key) are pinned by hash in the SDK and compared with the key registered in seal-verifier at startup. On a hash mismatch the SDK refuses to build proofs.

10. Reading order ​

#DocumentAnswers
0integration.md, onboarding.mdWhat the platform owns, what Cell Seal provides. The two onboarding cases step by step. Fund token requirements. Who mints, who burns
1contracts.mdEach contract: storage, functions, events, errors, authorization, invariants
2cryptography.mdWhat is taken from the primitive, the DvP protocol, threshold disclosure, openings
3keys.mdEvery key: generation, storage, recovery, rotation, loss
4sdk.mdThe @cell-seal/sdk API
5backend.mdSandbox backend: services, API, data model, state machine, reference indexer
6frontend.mdSandbox apps: routes, state, amount handling
7security.mdThreat model, invariants, leak catalogue, assurance plan, test strategy

Cell Private technical documentation.