Quickstart

Place a sealed order from code, check that the relay included it, then run the whole system on your own machine.

  1. buildOrder()local

    KasumiOrder for the open epoch

  2. signAndSeal()local

    EIP-712 signature, then timelock ciphertext

  3. submit()to relay

    envelope out, signed receipt back

  4. verifyInclusion()after cutoff

    receipt checked against the root

Four calls. Only the third sends anything, and what it sends is the envelope: ciphertext, its hash, a salted commitment, the epoch.

Place a sealed order#

The SDK is @kasumi/sdk in packages/sdk. It is consumed from the workspace as TypeScript source.

ts
import { KasumiClient, SIDE_BUY, toProtocolPrice } from "@kasumi/sdk";
import { privateKeyToAccount } from "viem/accounts";

const client = new KasumiClient({ relayUrl: "https://kasumisystems.com/api/v1" });
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);

const { markets, mode } = await client.config();
const aapl = markets.find((m) => m.symbol === "AAPL")!;

// Bound to the epoch that is open now, valid through its settlement deadline.
const order = await client.buildOrder({
  owner: account.address,
  market: aapl,
  side: SIDE_BUY,
  baseAmount: 10n ** 18n, // 1 token, raw units
  limitPrice: toProtocolPrice("333.00", aapl.baseDecimals, aapl.quoteDecimals),
});

// Signed, then encrypted, in this process. Nothing has been sent yet.
const sealed = await client.signAndSeal(order, (typedData) => account.signTypedData(typedData));

// Only the envelope leaves the machine. The receipt signature is checked before this returns.
const receipt = await client.submit(sealed.envelope);

The order is real: it can fill on Robinhood Chain if the owner holds the token it spends and has approved the settlement contract for it.

Keep sealed.cancelSecret. Revealing it to the relay before the cutoff cancels the order:

ts
await client.cancel(sealed);

Verify inclusion#

After the epoch closes the batch is fixed. Check that the relay kept its word:

ts
const check = await client.verifyInclusion(receipt /*, rootFromChain */);
// { ok, receiptSignatureValid, receivedBeforeCutoff, includedInRoot, censored }

Pass the root read from KasumiEpochManager.rootOf(epochId) as the second argument for a check that does not trust the relay. Without it the relay's own reported root is used, which only detects an inconsistent relay. censored: true means the relay signed a valid receipt and then left the order out of the root. The receipt is the evidence.

Read the result#

Once the epoch is decrypted and settled, every order in it is public:

ts
const res = await fetch(`https://kasumisystems.com/api/v1/epochs/${receipt.epochId}`);
const epoch = await res.json();
const mine = epoch.result?.orders.find((o) => o.commitment === receipt.orderCommitment);
// mine.outcome: "FILLED" | "PARTIAL" | "UNFILLED" | "REJECTED"

Run it locally#

Requirements: Node 22, pnpm 10, Foundry, Rust.

text
git clone --recurse-submodules <repo>
pnpm install
pnpm dev            # terminal and relay on http://localhost:3000

Local development needs no environment variables and sends no transactions. Reference prices are read from the Chainlink feeds on Robinhood Chain through the public RPC. docs/DEVELOPMENT.md in the repository has the details.

Run the tests#

Command What it runs
pnpm test:contracts Foundry unit and fuzz tests, plus the cross-check that replays SDK vectors through real settlement
pnpm test:matcher Rust matcher against fixtures/matching.json
pnpm test:sdk SDK unit tests, including a live drand round trip (KASUMI_OFFLINE=1 skips it)
pnpm --filter @kasumi/operator e2e A full epoch against contracts on a local anvil chain, with real drand
pnpm --filter @kasumi/web live-e2e The relay engine against contracts on a local anvil chain
pnpm --filter @kasumi/operator rehearse The same on a fork of Robinhood Chain mainnet with the real tokens and feeds
pnpm fixtures Regenerates the shared fixtures from the TypeScript reference

The three implementations are tied together by fixtures. fixtures/matching.json must produce identical result hashes in Rust and TypeScript. fixtures/crosscheck.json is produced by the SDK and replayed by contracts/test/CrossCheck.t.sol, so the contracts and the SDK agree on the EIP-712 digest, the commitment, the Merkle leaf and proof format, rounding and the result hashes.