SDK reference

@kasumi/sdk is the TypeScript client and the reference implementation of the protocol logic. Everything the contracts hash or check has a counterpart here that produces the same bytes.

  • client.tsKasumiClient: build, sign, seal, submit, verify
  • order.tsstruct, EIP-712, commitment, units
  • sealing.tsdrand timelock, round math
  • envelope.tsenvelope, receipts, verifyInclusion
  • merkle.tsleaf, root, proof
  • schedule.tsepoch times
  • pipeline.tsopenEpoch: decrypt and validate
  • matcher.tsmatchEpoch and result hashes
The eight modules of @kasumi/sdk. A trading client needs only the first. The rest is the protocol itself, usable by a relay, an operator or an auditor.

The package lives in packages/sdk and is consumed from the workspace as source (import … from "@kasumi/sdk"). It depends on viem, tlock-js and drand-client. All integers that can exceed 2^53 are bigint.

KasumiClient#

A client for one relay. Signing and encryption happen in the calling process. The only thing sent to the relay is the envelope.

ts
new KasumiClient({ relayUrl: string, fetch?: typeof fetch })
Method Returns Notes
config(refresh = false) Promise<RelayConfig> Cached after the first call. Mode, chain id, settlement and epoch manager addresses, relay address, RPC and explorer URLs, sealing scheme, schedule, markets.
domain() Promise<KasumiDomain> { chainId, verifyingContract } for EIP-712.
buildOrder(ticket) Promise<KasumiOrder> Unsigned order for one epoch. Defaults: receiver = owner, the epoch open now, validUntil = that epoch's settle deadline, random 256-bit nonce and salt, allowPartialFill: true. Throws on a malformed order.
signAndSeal(order, sign) Promise<SealedOrder> Calls sign with the typed data, then encrypts to the epoch's decryption time taken from the schedule. Returns { envelope, order, signature, cancelSecret }.
submit(envelope) Promise<InclusionReceipt> Posts the envelope. Throws unless the receipt matches the envelope and is signed by the relay named in config().
cancel({ envelope, cancelSecret }) Promise<void> Private cancellation. Only while the epoch is open.
epoch(epochId) Promise<EpochSummary> GET /epochs/:id.
proof(epochId, commitment) Promise<OrderProof> Merkle proof for a commitment, once the epoch is closed.
verifyInclusion(receipt, root?) Promise<InclusionCheck> Checks the receipt against root, or against the relay's reported root when omitted.

sign is any function (typedData) => Promise<Hex>. A viem account works directly: (td) => account.signTypedData(td). A browser wallet or a smart account works as long as it returns an EIP-712 signature for the settlement domain.

ts
interface OrderTicket {
  owner: Address;
  receiver?: Address;
  market: { baseToken: Address; quoteToken: Address };
  side: 0 | 1;                    // SIDE_BUY | SIDE_SELL
  baseAmount: bigint;             // raw base-token units
  limitPrice: bigint;             // protocol price, see Units
  minFillBase?: bigint;
  allowPartialFill?: boolean;
  allowExternalRouting?: boolean; // signed, unused in v1
  maxSlippageBps?: number;        // signed, unused in v1
  maxOracleDeviationBps?: number; // 0 = market bound only
  epochId?: number;
  nonce?: bigint;
}

Orders and hashes#

Identical to KasumiOrderLib.sol and KasumiSettlement.sol. fixtures/crosscheck.json is replayed against the contracts to keep it that way.

Export Signature Purpose
orderTypedData (domain, order) Arguments for signTypedData.
orderDigest (domain, order) => Hex EIP-712 digest, equals KasumiSettlement.orderDigest.
orderCommitment (domain, order) => Hex keccak256(digest ‖ salt), the public commitment.
marketId (baseToken, quoteToken) => Hex keccak256(abi.encode(base, quote)).
recoverOrderSigner (domain, { order, signature }) => Promise<Address> ECDSA recovery.
orderShapeError (order) => string | null Structural checks that need no chain state.
encodeSealedPayload / decodeSealedPayload (payload) => Hex / (hex) => SealedPayload The plaintext that gets sealed: abi.encode(order, signature, cancelHash).
ORDER_TYPES, eip712Domain The EIP-712 type and domain (name: "Kasumi", version: "1").

Units#

Export Signature Purpose
toProtocolPrice (price: string, baseDecimals, quoteDecimals) => bigint "211.40" for an 18-decimal base and 6-decimal quote gives 211400000n.
fromProtocolPrice (price: bigint, baseDecimals, quoteDecimals) => number For display only.
maxQuoteSpend ({ baseAmount, limitPrice }) => bigint ceil(baseAmount * limitPrice / 1e18): the allowance a buy needs.
parseDecimal (value: string, decimals) => bigint Exact decimal parsing, no floats.
PRICE_SCALE, BPS, MAX_UINT128, SIDE_BUY, SIDE_SELL, PROTOCOL_VERSION Constants.

Sealing#

ts
interface SealingScheme {
  readonly id: number;
  readonly name: string;
  seal(plaintext: Hex, decryptTime: number): Promise<Hex>;
  unseal(ciphertext: Hex): Promise<Hex>;      // rejects before the key exists
  unlockTime(ciphertext: Hex): number;        // read from the ciphertext, without decrypting
}
Export Purpose
DrandTimelockScheme Scheme id 1. tlock (age format) to drand quicknet. seal needs no network access. unseal fetches the round's beacon from the drand endpoints and caches it, so one fetch opens a whole batch. round(ciphertext) returns the round a ciphertext is locked to.
DRAND_QUICKNET Chain hash, public key, genesis time and 3 second period, pinned in the source. Orders are encrypted to this key, so it is never taken from a relay.
drandRoundAtOrAfter(time) First round released at or after time.
drandRoundTime(round) Release time of a round.
schemeById(id) Scheme for an envelope's scheme field.
ciphertextHash(ciphertext) keccak256(ciphertext).

Envelope and receipts#

Export Signature Purpose
buildEnvelope ({ scheme, epochId, ciphertext, orderCommitment }) => Envelope Adds protocolVersion and ciphertextHash.
envelopeError (value: unknown) => string | null Structural validation. Rejects any field outside the six allowed ones, so plaintext metadata cannot ride along.
MAX_CIPHERTEXT_BYTES 4096
receiptTypedData, receiptDigest (chainId, receipt) EIP-712 for receipts, domain name "Kasumi Relay".
verifyReceiptSignature (chainId, receipt) => Promise<boolean>
verifyInclusion ({ chainId, receipt, merkleProof, root, expectedRelay }) => Promise<InclusionCheck> The standalone check. censored is true when the receipt is valid and the leaf is not under root.

Merkle#

Export Signature
leafHash (epochId: bigint, sequence: bigint, orderCommitment, ciphertextHash) => Hex
merkleRoot (leaves: Hex[]) => Hex
merkleProof (leaves: Hex[], index: number) => Hex[]
verifyMerkleProof (leaf, proof, root) => boolean

Sorted pairs, an unpaired node is promoted unchanged, leaves are double-hashed. Same as KasumiOrderLib.leaf and verifyProof.

Schedule#

Export Signature
epochTimes (schedule, epochId) => { epochId, openTime, closeTime, decryptTime, settleDeadline }
currentEpochId (schedule, now) => number, 0 before the first epoch
EpochSchedule, EpochStatus types

Opening a batch#

ts
openEpoch({ domain, times, entries, scheme, markets, cancellations, chain })
  => Promise<{ accepted, rejected, matchInput }>

Decrypts a committed batch and applies the validation rules in order (see Opening an epoch). chain is a ChainView:

ts
interface ChainView {
  isNonceUsed(owner, nonce): boolean | Promise<boolean>;
  spendable(owner, token): bigint | Promise<bigint>;       // min(balance, allowance)
  isValidContractSignature?(owner, order, signature): Promise<boolean>;  // ERC-1271
  isTransferBlocked?(order): boolean | Promise<boolean>;   // issuer blocklist
}

@kasumi/operator provides a ChainView backed by a real chain (createChainView).

Matching#

ts
matchEpoch(input: MatchInput): MatchResult

The reference auction, specified in Matching. Pure, integer only, independent of input order. Throws on input that violates the preconditions.

Export Purpose
fillHash, marketHash, resultHash The hashes KasumiSettlement and KasumiEpochManager compute onchain.
matchInputToJson, matchInputFromJson, matchResultToJson The JSON form shared with the Rust matcher.

Rust matcher#

matcher/ builds a CLI that takes the auction input as JSON and prints the result.

text
cargo build --release --manifest-path matcher/Cargo.toml
./matcher/target/release/kasumi-matcher input.json
./matcher/target/release/kasumi-matcher --check fixtures/matching.json

It was written from the specification without reference to the TypeScript code and reproduces every fixture hash. The operator can run it next to the TypeScript matcher and refuse to publish if the two disagree.