Skip to content
Solvers

Intent and settlement reference

struct Intent {
address author; // whose escrow; receipts that satisfy it must be owned by this address
uint256 validBefore; // deadline, on the hub's clock
uint256 validAfter; // always 0
uint256 nonce; // unique per author
address srcMToken; // what it gives
uint256 srcAmount; // how much it escrows
Outcome outcome; // what it accepts
}
struct Outcome {
address[] mTokens;
uint256[] mAmounts;
OutcomeAssetStructure outcomeAssetStructure; // AnySingle | Any | All
FillStructure fillStructure; // Exactly | Minimum | PctFilled | ConcreteRange
}

The hub accepts two outcome shapes today, both AnySingle: the outcome lists one or more mTokens, each with its own amount or rate in mAmounts[i], and a solution pays the intent in exactly one of them.

  • Exactly (wire name Exact): mAmounts[i] is an exact amount. The intent must be spent completely and paid exactly that.
  • PctFilled (wire name PercentageFilled): mAmounts[i] is a rate, outcome units per 10^18 source units. Any fraction can fill; each fill must pay the author at least spent × rate / 10^18.

Orders built through the Khalani API always list a single outcome mToken.

The other outcome shapes are defined but not accepted yet. An intent’s state is Open, Solved or Cancelled; expired intents are cancelled.

struct Solution {
bytes32[] intentIds; // inputs: open intents, by id
Intent[] intentOutputs; // remainder children of partly filled inputs
Receipt[] receiptOutputs; // value paid out of the graph
MoveRecord[] spendGraph; // where each input's escrow goes
FillRecord[] fillGraph; // which receipt satisfies each input
}
struct Receipt { address mToken; uint256 mTokenAmount; address owner; bytes32 intentHash; }
struct MoveRecord { uint64 srcIdx; OutputIdx outputIdx; uint256 qty; } // input → output
struct OutputIdx { OutType outType; uint64 outIdx; } // Intent | Receipt
struct FillRecord { uint64 inIdx; uint64 outIdx; OutType outType; } // input ← receipt

Just-in-time intents are submitted alongside the solution (the second proposeSolution parameter) and referenced in intentIds like any other input. They must be consumed in the same transaction.

Hashes and signatures use each enum’s uint8 value. Medusa’s JSON takes only the names, and rejects numbers. Two names on the wire differ from the Solidity names.

Enum uint8 values JSON names
OutcomeAssetStructure AnySingle 0, Any 1, All 2 "AnySingle", "Any", "All"
FillStructure Exactly 0, Minimum 1, PctFilled 2, ConcreteRange 3 "Exact", "Minimum", "PercentageFilled", "ConcreteRange"
OutType Intent 0, Receipt 1 "Intent", "Receipt"
  • Intent id: the EIP-712 hashStruct of the intent, with no domain. The intent book’s getIntentId(intent) returns the same value, so an eth_call checks your encoding. The type string is:

    Intent(address author,uint256 validBefore,uint256 validAfter,uint256 nonce,address srcMToken,uint256 srcAmount,Outcome outcome)Outcome(address[] mTokens,uint256[] mAmounts,uint8 outcomeAssetStructure,uint8 fillStructure)
  • Intent signature: EIP-712 over the same struct under the KhalaniIntent domain described in Signing, whose separator is 0x6e8db7854b04f3651a03e71c732b1a9e8e41e68e2dea0e3ad621976c1409b722.

  • Solution hash: keccak256(abi.encode(solution)), with the whole solution encoded as one tuple, as Solidity’s abi.encode of the struct does. The encoding starts with a 0x…20 offset word, OutputIdx is encoded inline and every enum is a uint8. The tuple type is:

    (bytes32[],(address,uint256,uint256,uint256,address,uint256,(address[],uint256[],uint8,uint8))[],(address,uint256,address,bytes32)[],(uint64,(uint8,uint64),uint256)[],(uint64,uint64,uint8)[])

The hub checks the whole solution before applying it, and reverts all of it if any check fails:

  • Every input is Open and at or before its validBefore on the hub’s clock.
  • Conservation: for each input, the quantities moved out of it sum to its srcAmount.
  • Every input spends something and is satisfied by at least one receipt in fillGraph.
  • A spend into a receipt uses the input’s srcMToken, and its qty equals the receipt’s mTokenAmount. A spend into a remainder child has a qty equal to the child’s srcAmount. Each receipt is funded by exactly one move and claimed by at most one fill.
  • A receipt that satisfies an intent is owned by that intent’s author, in the intent’s outcome mToken.
  • The rate holds: a PctFilled input receives at least its rate on what it spent; an Exactly input is spent completely and receives exactly its amount.
  • Remainders: an input has at most one remainder child. The child must carry the parent’s author, srcMToken, validBefore, validAfter and outcome; only srcAmount and nonce differ (any nonce unused for that author).

An open order O, authored by the hub’s order contract, gives s = 1,000,000,000 of mToken S and accepts mToken D at rate r = 998000000000000000 (0.998), PctFilled. You fill f = 600,000,000 from your D inventory.

Your just-in-time intent J gives a = ceil(f × r / 10^18) = 598,800,000 of D and accepts S at rate floor(f × 10^18 / a), PctFilled, with validAfter 0 and a short validBefore.

intentIds [ O, J ]
receiptOutputs [ R0: { mToken: S, mTokenAmount: 600000000, owner: you, intentHash: J },
R1: { mToken: D, mTokenAmount: 598800000, owner: O.author, intentHash: O } ]
intentOutputs [ C: O with srcAmount 400000000 and a fresh nonce ]
spendGraph [ { srcIdx: 0, outputIdx: { Receipt, 0 }, qty: 600000000 }, // O → R0
{ srcIdx: 0, outputIdx: { Intent, 0 }, qty: 400000000 }, // O → remainder C
{ srcIdx: 1, outputIdx: { Receipt, 1 }, qty: 598800000 } ] // J → R1
fillGraph [ { inIdx: 0, outIdx: 1, outType: Receipt }, // R1 satisfies O
{ inIdx: 1, outIdx: 0, outType: Receipt } ] // R0 satisfies J

O is spent 600,000,000 + 400,000,000 = s and receives 598,800,000 ≥ 600,000,000 × 0.998. J spends all of a and receives 600,000,000 of S at its rate. The remainder C stays open with O’s terms, for the next solution.

On success, the subscription broadcasts SolutionAccepted with O and J solved and C new.

The same fill with concrete values, to check your encoding offline. S is the Arbitrum USDC mToken, D the Base USDC mToken, O’s author the order contract, and you are the example address 0x5b38da6a701c568545dcfcb03fcb875f56beddc4.

S 0xBfd80b874e2215f8d2Be0644c25271307Cc28076
D 0x6C3D9a02487D3B2d398bee6CB7c64bE0B38955cc
O { author: 0xa83d18aa3F32270762147e0E0F2CC3396A8f157A, validBefore: 1792443600, validAfter: 0, nonce: 1,
srcMToken: S, srcAmount: 1000000000,
outcome: { mTokens: [D], mAmounts: [998000000000000000], AnySingle, PctFilled } }
J { author: you, validBefore: 1792440000, validAfter: 0, nonce: 2,
srcMToken: D, srcAmount: 598800000,
outcome: { mTokens: [S], mAmounts: [1002004008016032064], AnySingle, PctFilled } }
C O with nonce 3 and srcAmount 400000000
Value Result
O’s id 0xc797464c9a24cc82b8cc0e4d466a58caa3502513ebd7814f34e9305b7112f037
J’s id 0x8e346366bbb72f460cd1c1de1690beefd13ce5a983dbec1372d78a1349386ecd
C’s id 0x2506c2d8ee7ebf592a4ee2b8fb844129b6df6373f38b23ba71ef639f91430ebf
J’s EIP-712 digest 0x5cba6caa7d0721b35110d1897fc28a99f6f35ca57343289fbc44119025f6365a
Solution hash 0x76f7f27454cfc380718880141cd9d42e44d9ccf385df6ff23d2bb7aee1625f45

The three ids are what the intent book’s getIntentId returns for these intents. The solution is the worked fill above, with receipts R0 and R1 carrying J’s and O’s ids as intentHash.