Deployment and operations
Where the contracts are deployed, how the relay is configured in each mode, the runbook that was followed for mainnet, and what operating it involves.
- KasumiEpochManager
0xc18aeb9ed90549b9995754aff56b7195f85848e7- KasumiChainlinkOracle
0x97c422c167da9ac46ee953dab2f171a9ad767e59- KasumiSettlement
0x24e687f7e0be7bc4c4afcd65dd7a22dc87f3f399- owner
0xB5D4634a3951aea316EbeBb048D99160feCBd7Ae- relay
0xc12a6B154057B34D93f0452Fb691037B2C09a8bd- matcher
0xffcd13AA8C59d0e82763ceF2619c7d78b946c996
Mainnet deployment#
The relay anchors each batch root with KasumiEpochManager.commit and the matcher settles through
KasumiSettlement. Balances and allowances are read from the chain.
The contracts were deployed to Robinhood Chain mainnet on 2026-10-02. They are unaudited.
| Contract | Address |
|---|---|
KasumiEpochManager |
0xc18aeb9ed90549b9995754aff56b7195f85848e7 |
KasumiSettlement |
0x24e687f7e0be7bc4c4afcd65dd7a22dc87f3f399 |
KasumiChainlinkOracle |
0x97c422c167da9ac46ee953dab2f171a9ad767e59 |
Roles, schedule and market parameters are recorded in deployments/robinhood-mainnet.json (explained in
deployments/README.md).
What has been verified on mainnet: the wiring and roles read back correctly, and one epoch (epoch 6) ran
through the deployed contracts with a single order from an unfunded wallet. The relay key committed the
batch (0x8cddb439d7f5dd5f781115c97ad21fa87330d457c61a4d473b8ed861455728ac), the order was rejected for
insufficient funds, and the matcher key published the empty result
(0xf602b20d6aef82b92a8066f3beeaed19222a3621ec8c78f96a3fbc77b9349d55), which settled the epoch. No trade
with real tokens has settled on mainnet yet.
Robinhood Chain parameters#
| Mainnet | Testnet | |
|---|---|---|
| Chain id | 4663 | 46630 |
| Public RPC (rate-limited) | https://rpc.mainnet.chain.robinhood.com |
https://rpc.testnet.chain.robinhood.com |
| Explorer | https://robinhoodchain.blockscout.com |
https://explorer.testnet.chain.robinhood.com |
| Gas token | ETH | ETH |
Robinhood Chain is an Arbitrum Orbit chain. block.number returns an L1 block estimate; the contracts use
block.timestamp only. No Stock Token addresses were found for the testnet, so a testnet deployment needs
mock tokens and a mock oracle.
Contracts#
cd contracts
forge build
forge test
Deploy#
script/Deploy.s.sol deploys KasumiEpochManager, KasumiChainlinkOracle and KasumiSettlement and wires
the relay, matcher and settlement roles. The account that broadcasts is the owner of all three.
| Variable | Required | Default | Meaning |
|---|---|---|---|
KASUMI_RELAY |
yes | address allowed to call commit |
|
KASUMI_MATCHER |
yes | address allowed to call postMatch, amendMarket and settleMarket |
|
KASUMI_OWNER |
no | the broadcaster | a different final owner. The script then starts a two-step transfer on all three contracts and that owner must call acceptOwnership() on each. |
KASUMI_EPOCH_DURATION |
no | 30 | seconds |
KASUMI_REVEAL_DELAY |
no | 12 | seconds between cutoff and decryption time |
KASUMI_SETTLE_WINDOW |
no | 120 | seconds after decryption time to settle |
KASUMI_START_TIME |
no | now + 300 | unix time the first epoch opens |
KASUMI_SEQUENCER_FEED |
no | none | Chainlink L2 sequencer uptime feed for the oracle adapter. Unset disables the check. |
KASUMI_SEQUENCER_GRACE |
no | 3600 | seconds after a sequencer restart during which no price is served |
forge script script/Deploy.s.sol --rpc-url $RPC_URL --broadcast --private-key $DEPLOYER_PRIVATE_KEY
The sequencer uptime feed and grace period are constructor arguments of the oracle adapter and cannot be changed later. Whether Chainlink publishes a sequencer uptime feed on Robinhood Chain has not been verified. If there is none, leave the variable unset: the adapter then skips the check, and prices that were fresh before a sequencer outage are accepted as soon as it restarts, within the feed age limits.
The reveal delay must leave enough time for the commit transaction to land before the decryption time. If
it does not land, the epoch cancels and its orders are revealed without executing (see
THREAT_MODEL.md). The commit deadline is judged by the sequencer's block.timestamp
while the key is released on wall-clock time, so the delay also has to absorb any lag between the two.
List a market#
The deploy script lists no markets. Listing is a separate, deliberate step. Only list a token after verifying its contract and its transfer behaviour.
Verification procedure for each token and feed:
- Take the token address from the issuer's registry (for Stock Tokens,
https://api.robinhood.com/rhj/assets) and the feed address from Chainlink's feed directory. - Read back onchain:
symbol()anddecimals()on the token,description()anddecimals()on the feed, and onelatestRoundData(). They must match what you expect to list. - Check that the feed prices the same unit the order is signed in. Chainlink's Stock Token feeds price one
raw token (equity price times
uiMultiplier), which is what Kasumi uses. - Check transfer behaviour: no fee on transfer,
transferFrombetween two user wallets through a third party works. The settlement contract reverts if the amount received differs from the amount pulled.
The mainnet addresses in apps/web/src/server/config.ts (AAPL, NVDA, TSLA, SPY, USDG and their feeds)
were read back this way on 2026-10-02. Re-verify before listing; Stock Tokens are upgradeable.
script/ListMarkets.s.sol does the listing for the four mainnet Stock Token markets against USDG. It
refuses to run on any chain but 4663. Before it sends anything it reads every token and feed back from the
chain and reverts on a mismatch: symbol(), decimals() (18 for Stock Tokens, 6 for USDG), feed
decimals() (8), a positive feed answer, and a readable oraclePaused() that is false. For each market it
then calls KasumiChainlinkOracle.setFeeds(base, USDG, baseFeed, usdgFeed, baseMaxAge, quoteMaxAge, true, false) and KasumiSettlement.setMarket(base, USDG, true, maxDevBps, maxOracleAge), and finally requires a
non-zero price from the adapter.
| Variable | Default | Meaning |
|---|---|---|
KASUMI_SETTLEMENT |
required | settlement contract; the oracle adapter is read from it |
KASUMI_MAX_DEVIATION_BPS |
300 | market-wide bound on the clearing price against the oracle |
KASUMI_BASE_MAX_AGE |
93600 | seconds, limit on the equity feed round inside the adapter |
KASUMI_QUOTE_MAX_AGE |
93600 | seconds, limit on the USDG/USD round inside the adapter |
KASUMI_MAX_ORACLE_AGE |
93600 | seconds, the settlement contract's own limit on the base round |
KASUMI_REQUIRE_FRESH |
true | fail if the adapter returns no price right after listing. Set false to list while the equity market is closed. |
The Chainlink aggregators use opcodes newer than this repository's compile target (paris), and forge
simulates a script under the compile target's rules, so this one script has to be simulated as cancun.
Give it its own output and cache directories so the paris artifacts in contracts/out are not replaced:
FOUNDRY_EVM_VERSION=cancun FOUNDRY_OUT=/tmp/kasumi-list/out FOUNDRY_CACHE_PATH=/tmp/kasumi-list/cache \
KASUMI_SETTLEMENT=$SETTLEMENT \
forge script script/ListMarkets.s.sol --rpc-url $RPC_URL --broadcast --private-key $OWNER_PRIVATE_KEY
Why 26 hours for the ages. Observed onchain on 2026-10-02: the USDG/USD feed updates once a day, weekends included (86,401 to 86,427 seconds between the last seven rounds). The equity feeds update on a price move while the US market is open and stop when it closes (gaps between the last seven AAPL rounds ran from 20 minutes to 16 hours on weekdays). A base limit of minutes would halt a quiet market for most of the day. With 26 hours every market still halts over a weekend. The price of this choice is that a settlement can use an equity price up to a day old; the market-wide deviation bound and each order's own limit are what protect a trader in that case.
maxOracleDeviationBps must be between 1 and 9999 and maxOracleAge must be non-zero. The adapter returns
no price if either feed is older than its own limit; maxOracleAge in setMarket is then applied to the
base feed's timestamp. Both tokens must have code. Flag a token as a Stock Token only if it implements
oraclePaused(): the Stock Tokens on mainnet answer it, USDG does not, so USDG is not flagged.
removeFeeds(marketId) takes a market's price away, which stops it settling. To list any other pair, call
setFeeds and setMarket directly with the same arguments.
Users fund by approving the settlement contract for the token they spend.
Web app and relay on Vercel#
The public address is https://kasumisystems.com. The Vercel alias kasumi-zeta.vercel.app serves the same
deployment and is the fallback if the domain is unavailable. Site metadata, robots.txt and sitemap.xml use
the domain as the canonical origin.
The Next.js app in apps/web serves the terminal, the docs and the relay API. Set the Vercel project's
root directory to apps/web; the build needs the rest of the repository (the SDK workspace package and
docs/), which Vercel includes by default.
Relay and site:
| Variable | Default | Meaning |
|---|---|---|
KASUMI_MODE |
unset | must be live for a deployed relay: it turns on onchain commit and settlement. |
KASUMI_RELAY_KEY |
required | private key that signs inclusion receipts and sends commit. Its address must be allowed by setRelay. |
DATABASE_URL |
unset | Postgres connection string (Neon, HTTP driver). Used when no Redis is configured. The relay creates its own kasumi_* tables on first use. |
KV_REST_API_URL, KV_REST_API_TOKEN |
unset | Upstash Redis REST endpoint and token. UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN are also read. |
ROBINHOOD_CHAIN_RPC_URL |
public mainnet RPC | RPC used for reference prices and for every chain read and transaction. The public RPC is rate-limited; use a dedicated endpoint for a live relay. |
ROBINHOOD_CHAIN_EXPLORER_URL |
https://robinhoodchain.blockscout.com |
explorer base URL given to clients for transaction links |
NEXT_PUBLIC_PRIVY_APP_ID |
the project's Privy app id | Privy application used for wallet login in the terminal. A Privy app id is public. The deployment's origin must be allowed in the Privy dashboard. |
NEXT_PUBLIC_X_URL |
https://x.com/ |
link behind the X button in the footer |
Chain and operator:
| Variable | Default | Meaning |
|---|---|---|
KASUMI_EPOCH_MANAGER |
required | KasumiEpochManager address. The epoch schedule is read from it. |
KASUMI_SETTLEMENT |
required | KasumiSettlement address. It becomes the EIP-712 verifyingContract. |
KASUMI_MATCHER_KEY |
required for the inline operator | private key that sends postMatch, amendMarket and settleMarket. Its address must be set with setMatcher on both contracts. |
KASUMI_OPERATOR |
inline |
inline: this server sends the commit and settlement transactions. external: it does not, and a standalone operator process must. |
KASUMI_OPERATOR_TOKEN |
unset | bearer token for GET /api/v1/operator/epochs/:id. Without it that endpoint always answers 401. |
CRON_SECRET |
unset | if set, GET /api/v1/cron requires Authorization: Bearer <secret>. Vercel Cron sends it automatically. |
Set keys and tokens as sensitive variables (vercel env add <NAME> production --sensitive). Environment
changes take effect on the next deployment.
Storage is chosen in this order: Redis if configured, else Postgres if DATABASE_URL is set, else process
memory. On a serverless host each instance has its own memory, so a hosted relay needs Redis or Postgres. The hosted
relay uses a Neon Postgres database provisioned through the Vercel Marketplace
(vercel integration add neon). All relay endpoints are served by one route handler
(apps/web/src/app/api/v1/[...path]/route.ts). Every storage key is prefixed with the settlement
address, so two deployments sharing one database cannot read each other's state.
The epoch schedule comes from the epoch manager and is re-read every five minutes.
Running the relay locally without chain access is described in docs/DEVELOPMENT.md in the repository.
Operating the live relay#
With KASUMI_MODE=live and KASUMI_OPERATOR=inline (the default) the web relay is also the operator. It
uses packages/operator (documented in packages/operator/README.md) to send the transactions.
Per epoch that has at least one order:
- After the cutoff and before the decryption time, the relay key sends
commit(epochId, root, orderCount). - After the drand round for the decryption time is published, the matcher key sends
postMatchwith one hash per market that trades, then onesettleMarketper market. The epoch becomes SETTLED onchain when every published market has settled. An epoch in which nothing crosses settles with an empty result.
Epochs without orders cost nothing and send nothing.
Three things drive those steps. Each one calls the same idempotent function, and a store lock keeps the two keys from sending concurrent transactions:
- The instance that accepted the epoch's first order. It stays alive after responding (
after()), sleeps until the cutoff, commits, sleeps until the drand round, and settles. The route'smaxDurationis 300 seconds for this reason. - Any client polling
GET /api/v1/state. The terminal polls every two seconds. - A cron job.
apps/web/vercel.jsonschedulesGET /api/v1/cronevery minute. SetCRON_SECRETso only the scheduler can call it.
What can go wrong, and what happens:
- The commit misses its window. The window is the reveal delay (15 seconds on mainnet). If no instance, poller or cron run is alive inside it, or the RPC rejects the transaction, the contract refuses a late commit. The epoch cancels and its orders become public without executing. Nothing can be traded from them, but their intent is revealed. The cron job alone is not enough to prevent this: it runs once a minute.
- Settlement is interrupted.
settleEpochresumes from chain state, so the next poll or cron run continues. If the settlement window (300 seconds on mainnet) passes first, markets that already settled stay settled and the rest of the epoch cancels. - A market's settlement reverts. The operator re-matches that market and amends the published result;
see
packages/operator/README.md. A blocklisted party or a paused Stock Token makes transfers revert. The operator reads the issuer's registry before matching: orders whose owner or receiver is blocked are rejected asTRANSFER_BLOCKED, and a paused market is halted for the epoch instead of being published. - A key runs out of gas. Commits or settlements fail and epochs cancel. Watch the relay and matcher balances. Rough costs are in the next section.
- The RPC rate-limits. The public RPC returned Cloudflare 403 responses to one client on this project after heavy use. A live relay should use a dedicated RPC endpoint.
The relay and matcher private keys are held as environment variables on the host. Anyone who can read the project's environment can censor, cancel epochs by not committing, or pick results within the limits in THREAT_MODEL.md. They cannot move user funds outside a valid signed order.
To run the operator as a separate process instead, set KASUMI_OPERATOR=external and
KASUMI_OPERATOR_TOKEN on the relay, and start packages/operator with the same token (see its README).
Do not run both against the same keys.
Checking a live relay#
apps/web/scripts/live-e2e.mts deploys the contracts with mock tokens to a local anvil chain and drives
the server's own functions through one epoch: three orders, a private cancellation, commit,
drand decryption, settlement, and balance checks. It needs anvil, forge build output in
contracts/out, and network access to drand.
cd contracts && forge build && cd ..
pnpm --filter @kasumi/web live-e2e
apps/web/scripts/mainnet-smoke.mts runs one real epoch on mainnet with an order from an empty throwaway
wallet. The order is rejected for insufficient funds, so nothing trades, but the relay key sends a real
commit and the matcher key a real postMatch. It reads the addresses from
deployments/robinhood-mainnet.json and costs a little gas.
cd apps/web
KASUMI_RELAY_KEY=0x... KASUMI_MATCHER_KEY=0x... npx tsx scripts/mainnet-smoke.mts
Do not run it while the hosted relay is live with the same keys: two operators on one key race on nonces.
Mainnet deployment, as performed#
This is the procedure that was run on 2026-10-02. It was first rehearsed end to end on a local fork of
Robinhood Chain mainnet with pnpm --filter @kasumi/operator rehearse (see packages/operator/README.md);
the gas figures below are from that rehearsal. The values actually used are noted at each step.
Three keys: a deployer that is also the owner, a relay key and a matcher key. Keep them outside the repository. The commands read them from the environment.
Fund the deployer on Robinhood Chain (chain id 4663).
Account What it pays for Gas ETH at 0.031 gwei Suggested funding deployer / owner three deployments, four wiring calls, listing four markets 6,994,052 0.00022 0.002 relay one commitper epoch that has orders82,021 0.0000026 0.003 (about 1,000 epochs with headroom) matcher postMatchplus onesettleMarketper market that trades134,025 + about 412,000 for a 2-fill market, about 128,000 more per extra fill 0.000034 for the rehearsed epoch (two markets, five fills) 0.005 (about 150 such epochs at the current price, with 5x headroom for gas price moves) Robinhood Chain charged no L1 data fee when this was measured:
gasUsedForL1was 0 on recent mainnet receipts andNodeInterface.gasEstimateL1Componentreturned 0. If that changes, real costs rise with calldata size (asettleMarketwith three fills carries about 3.1 kB). Epochs without orders cost nothing. A user'sapprovecosts about 58,000 gas for USDG and 64,000 for a Stock Token.Deploy. The broadcaster becomes the owner.
cd contracts export RPC_URL=https://rpc.mainnet.chain.robinhood.com KASUMI_RELAY=0xc12a6B154057B34D93f0452Fb691037B2C09a8bd KASUMI_MATCHER=0xffcd13AA8C59d0e82763ceF2619c7d78b946c996 \ KASUMI_EPOCH_DURATION=30 KASUMI_REVEAL_DELAY=15 KASUMI_SETTLE_WINDOW=300 \ forge script script/Deploy.s.sol --rpc-url $RPC_URL --broadcast --private-key $DEPLOYER_PRIVATE_KEYResult:
KasumiEpochManager0xc18aeb9ed90549b9995754aff56b7195f85848e7,KasumiChainlinkOracle0x97c422c167da9ac46ee953dab2f171a9ad767e59,KasumiSettlement0x24e687f7e0be7bc4c4afcd65dd7a22dc87f3f399, owner0xB5D4634a3951aea316EbeBb048D99160feCBd7Ae. The first epoch opened at unix time 1790947925. The reveal delay is 15 seconds and the settlement window 300 seconds, longer than the script defaults, because a serverless relay commits and settles more slowly than a dedicated process. No sequencer uptime feed was configured.Note the three addresses it prints and the time the first epoch opens (five minutes after the script runs unless
KASUMI_START_TIMEis set). The broadcast record is written tocontracts/broadcast/Deploy.s.sol/4663/run-latest.json.List the four markets. Run this while the US equity market is open so that every feed is fresh; otherwise add
KASUMI_REQUIRE_FRESH=false.FOUNDRY_EVM_VERSION=cancun FOUNDRY_OUT=/tmp/kasumi-list/out FOUNDRY_CACHE_PATH=/tmp/kasumi-list/cache \ KASUMI_SETTLEMENT=0x24e687f7e0be7bc4c4afcd65dd7a22dc87f3f399 \ forge script script/ListMarkets.s.sol --rpc-url $RPC_URL --broadcast --private-key $DEPLOYER_PRIVATE_KEYThis was run with the defaults while the US market was open: AAPL, NVDA, TSLA and SPY against USDG, deviation bound 300 bps, all three ages 93,600 seconds.
Check what is onchain before anyone trades.
cast call <epoch manager> "owner()(address)" --rpc-url $RPC_URL cast call <epoch manager> "isRelay(address)(bool)" <relay address> --rpc-url $RPC_URL cast call <epoch manager> "matcher()(address)" --rpc-url $RPC_URL cast call <epoch manager> "settlement()(address)" --rpc-url $RPC_URL cast call <settlement> "matcher()(address)" --rpc-url $RPC_URL cast call <settlement> "oracle()(address)" --rpc-url $RPC_URLOn mainnet these returned the owner, relay, matcher and settlement addresses above, and
markets(marketId)for AAPL returned the token pair, enabled, 300 and 93600.Fund the relay and matcher addresses from the deployer.
cast send <relay address> --value 0.003ether --rpc-url $RPC_URL --private-key $DEPLOYER_PRIVATE_KEY cast send <matcher address> --value 0.005ether --rpc-url $RPC_URL --private-key $DEPLOYER_PRIVATE_KEYOn mainnet only 0.002 ETH was available in total, so the relay received 0.0005 ETH and the matcher 0.0009 ETH. That covers a few dozen epochs with orders and must be topped up for sustained use. By this point the public RPC was answering
castwith a Cloudflare 403 from the deploying machine, so the two transfers were sent with a short viem script instead; plain JSON-RPC requests still worked.Point the operator at the deployment. For the hosted app, set
KASUMI_MODE=live,KASUMI_EPOCH_MANAGER,KASUMI_SETTLEMENT,KASUMI_RELAY_KEY,KASUMI_MATCHER_KEY,CRON_SECRET,KASUMI_OPERATOR_TOKENandNEXT_PUBLIC_PRIVY_APP_IDon the Vercel project (keys and tokens as sensitive variables) and redeploy; see "Web app and relay on Vercel" and "Operating the live relay". For the standalone runner instead (packages/operator/README.md):RPC_URL,EPOCH_MANAGER,SETTLEMENT,RELAY_PRIVATE_KEY,MATCHER_PRIVATE_KEY,RELAY_API_URL,OPERATOR_TOKENandKASUMI_ALLOW_MAINNET=1.Run one epoch before announcing anything:
apps/web/scripts/mainnet-smoke.mts(see "Checking a live relay"). On mainnet this was epoch 6: commit0x8cddb439d7f5dd5f781115c97ad21fa87330d457c61a4d473b8ed861455728ac, order rejectedINSUFFICIENT_FUNDS,postMatch0xf602b20d6aef82b92a8066f3beeaed19222a3621ec8c78f96a3fbc77b9349d55, epoch status SETTLED with root0xf5f6cc342d78cd36c66acfd4f31cbe31fc5ebb23f144230c1348aa85029cc542and order count 1.
What the rehearsal established about the real tokens (fork of mainnet, 2026-10-02):
approveplustransferFromby the settlement contract between two ordinary wallets works for the AAPL Stock Token and for USDG, and the amounts that move are exactly the raw amounts requested. No fee, no rebasing: the balance guard insettleMarketholds.uiMultiplier()was 1.000566 for AAPL, 1.000775 for NVDA, 1.0 for TSLA and 1.001718 for SPY. Balances, allowances and transfers are in raw units, and the Chainlink feed prices one raw token.- Blocklist. With an address forced onto the registry's blocklist on the fork, a Stock Token transfer
reverts (error selector
0x75e91ce7, carrying the blocked address) when the blocked address is the sender of the funds, the recipient, ormsg.sender. If the settlement contract itself were blocked, no Stock Token market could settle. - Pause. A per-token
pause()makes that token's transfers revert (selector0x1309a563) and leaves the other Stock Tokens working. The registry'spause()stops every Stock Token at once. Both were triggered on the fork by forcing roles through storage; who holds those roles on mainnet was not established. - Either condition reverts that market's
settleMarket. Other markets are unaffected. At the time of the rehearsal the operator's re-match only dropped orders that failed its own re-validation (nonce, balance, allowance) and did not read the blocklist, so a market with a blocked party failed its retries and was withdrawn for the epoch. The operator has since been made aware of both (seepackages/operator/README.md): the extended rehearsal covers a party blocked before decryption and one blocked afterpostMatch, and both epochs settle. - ERC-2612
permitworks on the AAPL Stock Token (domain name "Apple • Robinhood Token", version "1"). Kasumi v1 does not use it. - A full epoch settled through the real
KasumiChainlinkOracleand the real feeds: five orders in two markets, cleared at the oracle price, balances moved exactly as matched, one raw USDG unit of dust.
Open items after the mainnet deployment#
The contracts were deployed to mainnet at the project owner's instruction with these items open. None of them has been done. Each is a reason to keep amounts small.
- External audit of
KasumiEpochManager,KasumiSettlement,KasumiChainlinkOracleandKasumiOrderLib. - Legal review. Stock Tokens are tokenised debt securities issued by a Robinhood entity and are not offered to US persons. Whether a third-party venue may match them, for whom, and under which licences must be settled first. Kasumi is not affiliated with, endorsed by or operated by Robinhood.
- Key management. Today the owner is a single key on one machine, and the relay and matcher keys are
environment variables on the web host. Move the owner behind a multisig or timelock (two-step transfer),
and the relay and matcher keys into an HSM or KMS, with rotation through
setRelayandsetMatcher. - Dedicated RPC. The live relay uses the public rate-limited RPC unless
ROBINHOOD_CHAIN_RPC_URLis set. - Gas funding. The relay and matcher accounts hold very little ETH. Top them up and alert on low balances.
- A real trade. No trade with real tokens has settled on mainnet. Settle one small trade end to end through the terminal (wallet login, approval, order, settlement) before relying on the deployment.
- Commit liveness. The commit depends on a serverless instance, a polling client or the cron job being alive inside the 15-second reveal window. A dedicated operator process removes that dependency.
- Legal pages. The privacy, terms, cookies and risk pages are unreviewed draft templates with placeholders for the legal entity.
- Oracle limits.
ListMarkets.s.soluses 26 hours for both feed ages and formaxOracleAge, because the equity feeds only update on a price move and the USDG feed once a day (see "List a market"). That lets a settlement use an equity price up to a day old. Decide whether that is acceptable per market, and the market-wide deviation bound with it. The contracts enforce the 26-hour limits. - Timing. Mainnet runs 30-second epochs with a 15-second reveal delay. That delay was not chosen from
measured commit latency or sequencer timestamp lag. Measure both and call
reconfigureif the margin is thin. - Sequencer uptime feed. Find out whether one exists on Robinhood Chain and deploy the oracle adapter with it if so.
- Open review findings. Work through the open and accepted items in SECURITY_REVIEW.md.
- Monitoring. Alert on missed commits, epochs that reach
CANCELLED, settlement reverts,MarketAmendedevents, result hash mismatches, oracle staleness, drand beacon delay, and dust growth. - Independent verification. Run a second party's matcher against each public batch and compare result hashes.
- Token behaviour. Re-verify each listed token after any issuer upgrade. The blocklist and pause behaviour is known from a fork of mainnet (see above), not from the issuer; who holds those roles was not established.
- Rate limiting and abuse. The limits are 12 orders per IP per epoch and 120 orders per epoch in live mode. Ciphertexts are anonymous and every committed epoch costs the operator gas, so spam control needs a real design (stake, fees or allowlisted clients).