Order format

What a trader signs: the fields, their units, and how an order is bound to one chain, one contract and one epoch.

solidity
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

  • owner
  • receiver

What

  • baseToken
  • quoteToken
  • side
  • baseAmount

Limits

  • limitPrice
  • minFillBase
  • allowPartialFill
  • maxOracleDeviationBps

When

  • epochId
  • validAfter
  • validUntil

Replay and hiding

  • nonce
  • salt

Signed, unused in v1

  • allowExternalRouting
  • maxSlippageBps
The seventeen signed fields, grouped by what they constrain. The EIP-712 domain adds the chain id and the settlement contract, so a signature is valid in exactly one place.

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/minBuyAmount already define a price, so a separate limitPrice was 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), a side, a base quantity and a limit price. There is no minBuyAmount: the minimum a seller receives and the maximum a buyer pays follow from the limit price.
  • Raw units, no decimal normalisation. limitPrice is 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 is 211_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 * price product 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. nonce is a full uint256 stored in a bitmap (nonceBitmap[owner][nonce >> 8], bit nonce & 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#

text
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.