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, verifyorder.tsstruct, EIP-712, commitment, unitssealing.tsdrand timelock, round mathenvelope.tsenvelope, receipts, verifyInclusionmerkle.tsleaf, root, proofschedule.tsepoch timespipeline.tsopenEpoch: decrypt and validatematcher.tsmatchEpoch and result hashes
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.
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.
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#
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#
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:
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#
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.
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.