/** * @notice Fulfills an intent to be proven via storage proofs * @dev Validates intent hash, executes calls, and marks as fulfilled * @param intentHash The hash of the intent to fulfill * @param route The route of the intent * @param rewardHash The hash of the reward details * @param claimant Cross-VM compatible claimant identifier * @return Array of execution results from each call */function fulfill( bytes32 intentHash, Route memory route, bytes32 rewardHash, bytes32 claimant) external payable returns (bytes[] memory);/** * @notice Fulfills an intent and initiates proving in one transaction * @dev Executes intent actions and sends proof message to source chain * @param intentHash The hash of the intent to fulfill * @param route The route of the intent * @param rewardHash The hash of the reward details * @param claimant Cross-VM compatible claimant identifier * @param prover Address of prover on the destination chain * @param sourceChainDomainID Domain ID of the source chain where the intent was created * @param data Additional data for message formatting * @return Array of execution results */function fulfillAndProve( bytes32 intentHash, Route memory route, bytes32 rewardHash, bytes32 claimant, address prover, uint64 sourceChainDomainID, bytes memory data) public payable returns (bytes[] memory);
// On the source chainbool isFunded = intentSource.isIntentFunded(intent);require(isFunded, "Intent not funded");// Or check vault balance directlyaddress vaultAddress = intentSource.intentVaultAddress(intent);uint256 balance = IERC20(rewardToken).balanceOf(vaultAddress);
4
Approve Required Tokens
The Portal needs approval to pull tokens for execution:
// Approve Portal to spend your tokensfor (uint256 i = 0; i < route.tokens.length; i++) { IERC20(route.tokens[i].token).approve( route.portal, route.tokens[i].amount );}
For faster reward claiming, use fulfillAndProve to combine fulfillment and proof initiation:
// Calculate domain ID for the bridge// WARNING: Domain ID != Chain ID for some bridges!// Check your bridge's documentationuint64 sourceChainDomainID = getHyperlaneDomainId(sourceChainId);// Prover-specific data (e.g., bridge fees)bytes memory proverData = abi.encode( bridgeFeeAmount, otherProverParams);// Fulfill and prove in one callbytes[] memory results = portal.fulfillAndProve{value: totalValue}( intentHash, route, rewardHash, claimant, proverAddress, // Prover contract on destination chain sourceChainDomainID, // Bridge domain ID for source chain proverData // Bridge-specific data);
Domain ID vs Chain ID: The sourceChainDomainID parameter is NOT necessarily the same as the chain ID. Different bridge protocols use different domain ID systems:
// Intent: Transfer 1000 USDC to user on Arbitrum// Solver has USDC on Arbitrum and wants to earn rewards on Ethereumfunction fulfillUSDCTransfer( bytes32 intentHash, Route calldata route, bytes32 rewardHash) external { // Verify this is a token transfer to the expected recipient require(route.calls.length == 1, "Expected single call"); // Approve USDC for portal IERC20 usdc = IERC20(route.tokens[0].token); usdc.approve(address(portal), route.tokens[0].amount); // Fulfill bytes32 claimant = bytes32(uint256(uint160(msg.sender))); portal.fulfill(intentHash, route, rewardHash, claimant); // The portal will: // 1. Pull 1000 USDC from solver // 2. Execute the transfer to recipient // 3. Mark intent as fulfilled with our address as claimant}
The claimant parameter is a cross-VM compatible identifier:
EVM Addresses
Solana Addresses
Other VMs
For EVM chains, convert your address to bytes32:
// Your EVM address as claimantbytes32 claimant = bytes32(uint256(uint160(msg.sender)));// Or for a specific addressbytes32 claimant = bytes32(uint256(uint160(solverAddress)));
For Solana, use the public key directly:
// Solana public key (already 32 bytes)bytes32 claimant = solanaPublicKey;
For other virtual machines, convert to 32 bytes:
// Cosmos address (convert from bech32)bytes32 claimant = cosmosAddressToBytes32("cosmos1...");// Near account ID (hash if needed)bytes32 claimant = keccak256(abi.encodePacked("account.near"));