Skip to content

A solver fills an intent by calling IntentSettlerV1.fill with the signed order, its Permit2 witness signature, a solver address, and opaque routerData. The solver address must implement IIntentFiller.

The code below is illustrative. It shows the protocol boundary and call order; it is not a working routing implementation.

Call IntentLens.preview(order) or batch orders with previewMany. The result is an observation stamped with observedAt and observedBlock:

Field Meaning
id keccak256(abi.encode(order))
cancelled cancelled[id] on the settler
nonceSpent Permit2’s unordered nonce bit
inWindow startTime <= now <= endTime
decayComplete now >= decayEndTime
floorNow required output at the observation time; 0 outside the window
ownerBalance owner’s tokenIn balance
ownerPermit2Allowance owner’s tokenIn allowance to Permit2
observedAt / observedBlock timestamp and block for the observation

IntentLens deliberately has no fillable boolean. Balance, allowance, nonce state, time, solver inventory, and venue liquidity can change before a transaction is included. Treat the lens as screening data, not a verdict.

In particular, nonceSpent does not mean filled: Permit2 sets the same bit when the owner invalidates the nonce. A matching IntentFilled event is needed to distinguish those cases.

The settler transfers order.amountIn of order.tokenIn to solver, then calls this callback. The callback must return at least requiredOut of order.tokenOut to msg.sender, which is the settler.

// Illustrative interface boundary from IIntentFiller.sol.
interface IIntentFiller {
function fillIntentCallback(
IntentOrder calldata order,
uint256 requiredOut,
bytes calldata data
) external;
}

An implementation uses data as its routing input and must return the output token to the callback caller before returning:

// Illustrative pseudocode. The venue route is solver-specific.
function fillIntentCallback(
IntentOrder calldata order,
uint256 requiredOut,
bytes calldata data
) external {
// Use order.amountIn of order.tokenIn and data to execute a route.
// Transfer at least requiredOut of order.tokenOut to msg.sender.
}

Returning exactly requiredOut is the expected behavior. The solver keeps its margin by not sending it to the settler. Any amount above the floor that does arrive is delivered to order.recipient; it is not refunded to the solver.

A valid announcement is only signature admission. Before spending gas, execute the exact fill(order, permit2Sig, solver, routerData) as an eth_call at the current chain state:

// Illustrative RPC shape; use your ABI encoder and provider.
const result = await provider.request({
method: 'eth_call',
params: [{
to: intentSettler,
data: encodeFillCall(order, permit2Sig, solver, routerData),
}, 'latest'],
});

The dry-run should cover the current time window, Permit2 signature and nonce, owner balance and allowance, callback behavior, route liquidity, and exact recipient delivery. A successful observation from IntentLens cannot replace this call.

If the dry-run succeeds, submit the same calldata as a transaction. fill is permissionless; the transaction caller need not be the order owner. The settler then validates the order, computes the current required output, pulls the input through Permit2, invokes the callback, measures receivedOut, and transfers everything received to the signed recipient.

The transaction reverts if the order is cancelled, outside its time window, or has a zero solver; if Permit2 cannot pull the exact input; if the callback returns less than the required floor; if recipient delivery is inexact; or if the settler’s token balances are not conserved. See settlement execution and the ABI reference.