Skip to content

Cell Seal: Frontend ​

Repo cell-seal-web. pnpm workspace. Vite, React 19, TypeScript, Tailwind 4, TanStack Query, Freighter. Design: design/README.md. Every cryptographic operation goes through sdk.md. The frontend does no cryptography of its own. The two apps are the apps of the sandbox, the reference tokenization platform. They are not the product (integration.md section 1). A platform integrates the SDK into its own apps. This document shows one way to do it. Section 4 to section 8 are the rules any such app must keep.

1. Workspace ​

cell-seal-web/
|-- packages/
|   |-- sdk/            @cell-seal/sdk
|   `-- ui/             @cell-seal/ui: color tokens, shared components
`-- apps/
    |-- fund-console/   11 screens plus the key holder page
    `-- investor-app/   7 screens

The two apps are built and deployed separately, on two different origins. The investor app is the public surface. The fund console can sit behind the private network of whoever runs the sandbox.

2. Design system ​

Tokens come from the variables in design/cell-seal.pen:

TokenValueUsed for
--background#0A0B0DPage background
--card#121418Cards, tables, rails
--secondary#1A1D23Inputs, selected rows, pills
--border / --border-strong#252932 / #343943
--foreground / --muted-foreground / --dim#EDEFF2 / #858C99 / #5A606C
--primary / --primary-subtle#C8F94E / #2E3A16Primary action, selected nav item, private figures
--success / --warning / --destructive / --info#4ADE80 / #FBBF24 / #F87171 / #60A5FAStatuses
FontInter. JetBrains Mono with tabular-nums for every amount, address and order reference

Text on a --primary background is always #0A0B0D, never white.

Components of @cell-seal/ui: Shell, SetupShell, PageHead, Pill, Button, Field, Segmented, DataTable, StepList, SummaryCard, Kpis, PrivateTag, ProofProgress, SealedGate.

3. Routes ​

3.1 Fund console ​

RouteScreenReadsWrites
/setup/fund01 Fund detailsnonefund-registry.create_fund, then POST /funds
/setup/keys02 Who can read amountsGET /funds/{id}/dkgops.dkg.start, invite key holders. Each holder runs holder.dkg.join on /keys
/investors03 InvestorsGET /funds/{id}/investorsfund-policy.set_investor, then PATCH /investors/{a}
/04 Dashboardorders, calendar, current windownone
/orders05 OrdersGET /funds/{id}/ordersnone
/orders/:id06 Subscription, 08 RedemptionGET /orders/{id}ops.orders.attest, hold, release
/nav07 NAVcalendar, awaiting_price ordersops.nav.publish, then ops.nav.priceOrders
/settlement09 Settlementcurrent window and historyops.window.plan, then ops.window.close
/register10 Registerops.register.build()export file
/audit11 Audit accessdisclosure requests, unlock logops.disclosure.request, remind, ops.disclosure.cancel
/keysKey holderrequests awaiting my approvalholder.requests.approve

3.2 Investor app ​

RouteScreenReadsWrites
/welcome01 Set up accountnoneconnect wallet, seal.unlock(), POST /me/box-key
/funds02 FundsGET /fundsPOST /funds/{id}/access-requests
/funds/:id03 Fund detailGET /funds/{id}, token.balance()none
/funds/:id/invest04 Investcalendar, cash balanceorders.createSubscription
/funds/:id/redeem07 Redeemtoken.balance()orders.createRedemption
/orders/:id05 OrderGET /orders/{id}orders.cancel
/portfolio06 Portfoliotoken.balance() per fund, GET /ordersnone

/funds and /funds/:id work without login and with the wallet locked: they show public data only. The Your position card appears only after unlock.

4. Two kinds of data, two ways of managing them ​

Public data and metadataDecrypted data
ExamplesFund list, NAV, order status, calendarAmounts, balances, shares, payouts, register
SourceREST API@cell-seal/sdk, after unlock
CacheTanStack Query, staleTime per kindMemory only. gcTime: 0. Never persisted
When the wallet is lockedStill shownReplaced by SealedGate: a blurred box with an Unlock button
In URLs, logs, analytics, error messagesAllowedNever

SealedGate wraps every place that displays a private figure. It guarantees one thing: there is no render path that shows an amount without passing through the SDK's unlocked state.

5. Amounts ​

RuleContent
Typebigint at scale 10^7 throughout. Never number for money or shares
InputDecimal string to bigint through the @cell-seal/ui parser. Reject more than 7 decimal places
DisplayIntl.NumberFormat on the integer and fractional parts separately. Always monospace, tabular-nums
Computing shares, payoutIn the SDK, in integers, with a single rounding rule: round down so the investor receives less, the remainder stays with the fund. This rule must match the future price-linking circuit
Estimates before strikeLabelled estimated, computed at the latest NAV

6. Asynchronous state of an on-chain action ​

Every button that creates a transaction goes through the same state chain, shown by ProofProgress:

idle -> syncing -> merging? -> proving -> awaiting_signature -> submitting -> confirming -> done | failed
StateWhat the user seesNotes
syncing"Checking your balance"The SDK syncs events and checks the openings
merging"Preparing your balance"Only when spendable is insufficient. A separate transaction that needs a signature
proving"Preparing your order, a few seconds" with a progress barDuration estimated from seal.capabilities.threads
awaiting_signature"Confirm in your wallet"
submitting, confirming"Sending", "Waiting for the network"done only when the indexer has seen the event, not when the RPC accepted the transaction
failedMessage by SDK error code (sdk.md section 7)Always with a clear next action

Leaving the page between proving and confirming is blocked by a confirmation dialog. After a reload, the SDK restores the wallet lock state and the UI shows the pending work again.

7. Per screen: behaviour to get right ​

Fund console ​

ScreenBehaviour
01 Fund detailsOne click runs the chain: generate ops_root, derive the settlement account's privacy key, seal.platform.deployFund, ops.accounts.register for the share wrapper and the cash wrappers, create the envelope for the creator. A failure at any step can be resumed from that step
02 Who can read amountsEach key holder opens /keys on their own device to join the DKG. Ready means a valid share commitment has been submitted. Allows continuing with 2 of 2 and re-running the DKG when the third party is ready
03 InvestorsApprove calls fund-policy.set_investor on chain first, then writes to the backend. Holding comes from the register on the client
04 DashboardTo do is built from order states and the calendar: set price, confirm money, pay out, approve investor, close window. Each row links straight to the screen that handles it
06 Subscription orderConfirm money received signs an attestation with the wallet. No on-chain transaction at this step
07 NAVPublish price calls fund-registry.publish_nav. After confirmation the SDK computes the result for each order and sends result_ct. The preview table is computed on the client
08 Redemption orderMark as paid signs a cash_paid attestation. The investor's bank details are decrypted from the OrderSecret
09 SettlementClose window is enabled only when next_close has been reached and no order in the period is still awaiting_cash or held. Shows the plan before running: net, order count, the Privacy level. Progress is shown per transaction in the chain
10 RegisterBuilt entirely on the client from decrypted legs and verified openings. Rows with an unverified secondary transfer are flagged
11 Audit accessCreate a request: reader, scope, period, expiry. Approval status per key holder. History is the unlock log

Investor app ​

ScreenBehaviour
01 Set up accountThree steps: connect wallet, KYC, Sign with wallet. If the SDK throws E_NONDETERMINISTIC_SIGNER, switch to the raw root flow with its mandatory backup step. The single-point-of-failure warning is mandatory under the primitive's spec
02 FundsThe button on each card follows access status: Invest, Request access, Access requested
03 Fund detailEvery figure outside the Your position card is public. The Fund size by week chart is built from WindowClosed
04 InvestChoosing EURC shows the sealed cash balance. If there is none, lead to the top-up flow, seal.token(cashWrapper).wrap(amount), with the L9 advice: round amounts, ahead of time. If a DvP order is already pending, disable this choice and say why
05 OrderWire details are decrypted from the investor's copy of the ciphertext. The Cancel button appears only before cut-off
06 PortfolioTotal value = sum of balance x NAV over funds, computed on the client
07 RedeemQuick buttons 25%, 50%, All are computed on spendable + receiving. The SDK merges automatically if needed

8. Browser-side security ​

TopicRule
Cross-origin isolationCross-Origin-Opener-Policy: same-origin, Cross-Origin-Embedder-Policy: require-corp. Needed for multithreaded proving. Also blocks embedding third-party resources
CSPdefault-src 'self'. connect-src only to the API, RPC and indexer. script-src 'self' 'wasm-unsafe-eval'. No inline scripts
Third partiesNo analytics, no session recorder, no fonts or scripts from a CDN. A third-party script in the page is an amount leak path
StoragelocalStorage holds UI preferences only. Encrypted openings live in IndexedDB. No key is ever stored
Error reportingSelf-hosted error reporter. A filter strips every numeric field and every string matching an amount format before sending
Auto-lockAfter 15 minutes of inactivity, or when the tab has been hidden for more than 5 minutes
Build integrityThe bundle hash is published with every release. Whoever runs the sandbox can build from source

9. Testing ​

LayerHow
ComponentsVitest plus Testing Library. SealedGate has its own test asserting that no amount is rendered while the wallet is locked
End to endPlaywright on the local network, real verifier, test wallet signing automatically. Standard scenario: create fund, approve investor, subscribe, set price, close window, redeem, pay out, register matches
Amount leaksThe end-to-end test captures every network request and asserts that no body, URL or header contains any amount used in the scenario
DevicesMeasure proving time on a desktop, an ordinary laptop and a 4 GB phone before every release, and compare with primitives.md section 8
Against the designCompare screenshots with design/screens/ for all 18 screens

Cell Private technical documentation.