Overview
An intent progresses through several states from creation to completion. Understanding this lifecycle is crucial for all participants in the protocol.Intent Status
The protocol tracks intent rewards through the following states:Complete Lifecycle
InitialIntentPublished eventfunction publishAndFund(
Intent calldata intent,
bool allowPartial
) public payable returns (bytes32 intentHash, address vault)
Funded if fully fundedIntentPublished and IntentFunded eventsThe intent hash is deterministic and computed as:This ensures the same intent parameters always produce the same identifier.
function fund(
uint64 destination,
bytes32 routeHash,
Reward calldata reward,
bool allowPartial
) external payable returns (bytes32 intentHash)
msg.value providedFunded if all rewards are fully depositedIntentFunded event with completion statusmodifier onlyFundable(bytes32 intentHash) {
Status status = rewardStatuses[intentHash];
if (status == Status.Withdrawn || status == Status.Refunded) {
revert InvalidStatusForFunding(status);
}
if (status == Status.Funded) {
return;
}
_;
}
Users can check funding status with:This verifies the vault contains all required reward tokens and native currency.
function fulfill(
bytes32 intentHash,
Route memory route,
bytes32 rewardHash,
bytes32 claimant
) external payable returns (bytes[] memory)
block.timestamp <= route.deadlineclaimants[intentHash] = claimantIntentFulfilled eventbytes32 routeHash = keccak256(abi.encode(route));
bytes32 computedIntentHash = keccak256(
abi.encodePacked(CHAIN_ID, routeHash, rewardHash)
);
if (computedIntentHash != intentHash) {
revert InvalidHash(intentHash);
}
if (claimants[intentHash] != bytes32(0)) {
revert IntentAlreadyFulfilled(intentHash);
}
for (uint256 i = 0; i < route.calls.length; ++i) {
Call memory call = route.calls[i];
// Execute: call.target.call{value: call.value}(call.data)
}
Solvers must provide:
- Native tokens equal to
route.nativeAmount(via msg.value) - All ERC20 tokens specified in
route.tokens - Additional native tokens for proving if using
fulfillAndProve
function prove(
address prover,
uint64 sourceChainDomainID,
bytes32[] memory intentHashes,
bytes memory data
) public payable
function fulfillAndProve(
bytes32 intentHash,
Route memory route,
bytes32 rewardHash,
bytes32 claimant,
address prover,
uint64 sourceChainDomainID,
bytes memory data
) public payable returns (bytes[] memory)
IntentProven event// 8 bytes for chain ID + (32 bytes intentHash + 32 bytes claimant) per intent
bytes memory encodedClaimants = new bytes(8 + size * 64);
// Prepend chain ID
assembly {
mstore(add(encodedClaimants, 0x20), shl(192, chainId))
}
// Pack intent hash and claimant pairs
for (uint256 i = 0; i < size; ++i) {
bytes32 claimantBytes = claimants[intentHashes[i]];
// Pack into encodedClaimants...
}
// Send to prover
IProver(prover).prove{value: address(this).balance}(
msg.sender,
sourceChainDomainID,
encodedClaimants,
data
);
Domain ID vs Chain ID: The
sourceChainDomainID parameter is NOT the same as the chain ID. Each bridge provider uses their own domain ID mapping:- Hyperlane: Custom domain IDs that may differ from chain IDs
- LayerZero: Endpoint IDs with custom mapping
- Metalayer: Domain IDs specific to their routing system
- Polymer: Uses standard chain IDs
Initial or FundedWithdrawnIntentWithdrawn eventIProver.ProofData memory proof = IProver(reward.prover).provenIntents(
intentHash
);
address claimant = proof.claimant;
// If proven on different chain, challenge the proof
if (proof.destination != destination && claimant != address(0)) {
IProver(reward.prover).challengeIntentProof(
destination,
routeHash,
rewardHash
);
return;
}
function batchWithdraw(
uint64[] calldata destinations,
bytes32[] calldata routeHashes,
Reward[] calldata rewards
) external
Alternative Path: Refund
If an intent expires without fulfillment, the creator can recover their rewards.Refund Conditions
contracts/IntentSource.sol
- Checks deadline has passed:
block.timestamp >= reward.deadline - Verifies intent was not proven as fulfilled
- Updates status to
Refunded - Transfers all tokens from vault back to creator
- Emits
IntentRefundedevent
State Transitions
Timeline Constraints
- Route deadline: Intent must be fulfilled before
route.deadline - Reward deadline: Intent must be fulfilled before
reward.deadline - Refund eligibility: Can only refund after
reward.deadlinehas passed - Proof validity: Proof must match the destination chain ID
Best practice: Set
route.deadline slightly before reward.deadline to give solvers time to prove fulfillment before the intent becomes refundable.