Intent and settlement reference
Intent
Section titled “Intent”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 nameExact):mAmounts[i]is an exact amount. The intent must be spent completely and paid exactly that.PctFilled(wire namePercentageFilled):mAmounts[i]is a rate, outcome units per 10^18 source units. Any fraction can fill; each fill must pay the author at leastspent × 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.
Solution
Section titled “Solution”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 → outputstruct OutputIdx { OutType outType; uint64 outIdx; } // Intent | Receiptstruct FillRecord { uint64 inIdx; uint64 outIdx; OutType outType; } // input ← receiptJust-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" |
Hashes
Section titled “Hashes”-
Intent id: the EIP-712
hashStructof the intent, with no domain. The intent book’sgetIntentId(intent)returns the same value, so aneth_callchecks 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
KhalaniIntentdomain described in Signing, whose separator is0x6e8db7854b04f3651a03e71c732b1a9e8e41e68e2dea0e3ad621976c1409b722. -
Solution hash:
keccak256(abi.encode(solution)), with the whole solution encoded as one tuple, as Solidity’sabi.encodeof the struct does. The encoding starts with a0x…20offset word,OutputIdxis encoded inline and every enum is auint8. 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)[])
Validation rules
Section titled “Validation rules”The hub checks the whole solution before applying it, and reverts all of it if any check fails:
- Every input is
Openand at or before itsvalidBeforeon 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 itsqtyequals the receipt’smTokenAmount. A spend into a remainder child has aqtyequal to the child’ssrcAmount. 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
PctFilledinput receives at least its rate on what it spent; anExactlyinput 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,validAfterandoutcome; onlysrcAmountandnoncediffer (any nonce unused for that author).
A worked fill
Section titled “A worked fill”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 → R1fillGraph [ { inIdx: 0, outIdx: 1, outType: Receipt }, // R1 satisfies O { inIdx: 1, outIdx: 0, outType: Receipt } ] // R0 satisfies JO 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.
Test vectors
Section titled “Test vectors”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 0xBfd80b874e2215f8d2Be0644c25271307Cc28076D 0x6C3D9a02487D3B2d398bee6CB7c64bE0B38955ccO { 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.