Order format
What a trader signs: the fields, their units, and how an order is bound to one chain, one contract and one epoch.
struct KasumiOrder {
address owner;
address receiver;
address baseToken;
address quoteToken;
uint8 side; // 0 = BUY base with quote, 1 = SELL base for quote
uint128 baseAmount; // raw base-token units
uint128 minFillBase; // raw base-token units
uint128 limitPrice; // quote raw units per base raw unit, times 1e18
uint64 validAfter;
uint64 validUntil;
uint64 epochId;
uint256 nonce;
bool allowPartialFill;
bool allowExternalRouting; // signed, unused in v1
uint16 maxSlippageBps; // signed, unused in v1
uint16 maxOracleDeviationBps; // 0 = market-wide bound only
bytes32 salt;
}
Who
ownerreceiver
What
baseTokenquoteTokensidebaseAmount
Limits
limitPriceminFillBaseallowPartialFillmaxOracleDeviationBps
When
epochIdvalidAftervalidUntil
Replay and hiding
noncesalt
Signed, unused in v1
allowExternalRoutingmaxSlippageBps
Why the schema differs from the first sketch#
The first sketch had sellToken, buyToken, sellAmount, minBuyAmount and a separate limitPrice.
That was reviewed and changed for these reasons.
- Explicit side, base amount and limit price.
sellAmount/minBuyAmountalready define a price, so a separatelimitPricewas redundant and the two could disagree. A uniform-price auction also needs every order of a market expressed in the same quantity. Orders therefore name the market (baseToken,quoteToken), aside, a base quantity and a limit price. There is nominBuyAmount: the minimum a seller receives and the maximum a buyer pays follow from the limit price. - Raw units, no decimal normalisation.
limitPriceis quote raw units per base raw unit, scaled by 1e18. Differences in token decimals are absorbed into the price. For an 18-decimal Stock Token quoted in 6-decimal USDG at $211.40, the price is211_400_000. - Stock Token multiplier. Robinhood Chain Stock Tokens keep raw balances, allowances and transfers
fixed across corporate actions and expose a display multiplier (
uiMultiplier). Chainlink's Stock Token feeds price one raw token. Signing in raw units means no multiplier handling is needed anywhere in the protocol. - uint128 widths. Amounts and limit prices are uint128 so that any
amount * priceproduct fits in uint256 without overflow checks beyond Solidity's own. - Single-epoch orders. An order names exactly one
epochId. Once an epoch is decrypted its orders are public, so an order that rolled into later epochs would no longer be private. A client that wants a longer-lived order submits a fresh sealed order each epoch. "Good until timestamp" across epochs is not implemented in v1. - Nonces.
nonceis a full uint256 stored in a bitmap (nonceBitmap[owner][nonce >> 8], bitnonce & 0xff). The SDK picks random 256-bit nonces, which never collide and reveal nothing about how many orders a wallet has placed. One order fills at most once (it lives in one epoch and one market segment), so the fill sets the bit and no per-order filled-amount storage is needed. - Salt. 32 random bytes. It gives the order commitment enough entropy to hide the order.
allowExternalRouting,maxSlippageBps. Both are part of the signed struct so the schema does not have to change when routing is added. Neither has any effect in v1: orders only ever fill inside the batch.
Maximum spend follows from the struct. A BUY spends at most ceil(baseAmount * limitPrice / 1e18) quote,
which is the allowance it needs. A SELL spends at most baseAmount base.
EIP-712#
domain = { name: "Kasumi", version: "1", chainId, verifyingContract: KasumiSettlement }
digest = keccak256(0x1901 ‖ domainSeparator ‖ structHash(order))
The type string is in KasumiOrderLib.ORDER_TYPEHASH and ORDER_TYPES in the SDK. The domain separator
is cached at deployment and rebuilt if block.chainid changes, so a signature is bound to one chain and
one settlement contract.
Signatures are 65-byte ECDSA with low s, or ERC-1271 when owner has code.