Documentation Index
Fetch the complete documentation index at: https://eco.com/docs/llms.txt
Use this file to discover all available pages before exploring further.
The Portal is the main entry point for the Eco Routes, combining intent publishing, fulfillment, reward claiming, and ERC-7683 compatibility in a unified contract. It inherits from both IntentSource (source chain operations) and Inbox (destination chain operations).
Architecture
Portal
├── IntentSource (intent publishing and management)
│ └── OriginSettler (ERC-7683 origin functionality)
└── Inbox (fulfillment and proving)
└── DestinationSettler (ERC-7683 destination functionality)
The Portal does not hold funds between transactions. All rewards are escrowed in deterministically-deployed vault contracts.
Intent Lifecycle
1. Publishing (Source Chain)
Users create intents specifying execution instructions for the destination chain and rewards for solvers.
Direct Publishing:
function publish(Intent calldata intent)
public returns (bytes32 intentHash, address vault)
Creates an intent without funding. Emits IntentPublished event.
Atomic Publishing with Funding:
function publishAndFund(Intent calldata intent, bool allowPartial)
public payable returns (bytes32 intentHash, address vault)
Creates and funds an intent in a single transaction.
Universal Format Publishing:
function publish(
uint64 destination,
bytes memory route,
Reward memory reward
) public returns (bytes32 intentHash, address vault)
Cross-VM compatible publishing using bytes format for route data.
2. Funding (Source Chain)
Intents can be funded separately after creation:
function fund(
uint64 destination,
bytes32 routeHash,
Reward calldata reward,
bool allowPartial
) external payable returns (bytes32 intentHash)
Funding for Others:
function fundFor(
uint64 destination,
bytes32 routeHash,
Reward calldata reward,
bool allowPartial,
address funder,
address permitContract
) external payable returns (bytes32 intentHash)
Uses permit contracts for gasless token approvals. Cannot be used for intents with native token rewards.
3. Fulfillment (Destination Chain)
Solvers execute intent instructions on the destination chain:
function fulfill(
bytes32 intentHash,
Route memory route,
bytes32 rewardHash,
bytes32 claimant
) external payable returns (bytes[] memory)
Parameters:
intentHash: Unique identifier of the intent
route: Execution instructions and token requirements
rewardHash: Hash of reward structure for verification
claimant: Cross-VM compatible identifier for reward recipient (bytes32)
Atomic Fulfillment with Proving:
function fulfillAndProve(
bytes32 intentHash,
Route memory route,
bytes32 rewardHash,
bytes32 claimant,
address prover,
uint64 sourceChainDomainID,
bytes memory data
) public payable returns (bytes[] memory)
Executes intent and initiates cross-chain proof in one transaction.
⚠️ Important: sourceChainDomainID is NOT the chain ID. Each bridge uses its own domain ID system:
- Hyperlane: Custom domain IDs
- LayerZero: Endpoint IDs
- Metalayer: Domain IDs specific to routing
Consult the bridge provider’s documentation for correct domain IDs.
4. Proving (Destination Chain)
Generate proofs for fulfilled intents:
function prove(
address prover,
uint64 sourceChainDomainID,
bytes32[] memory intentHashes,
bytes memory data
) public payable
Sends cross-chain message to source chain verifying intent execution. Can batch multiple intents for gas efficiency.
5. Claiming Rewards (Source Chain)
After proof verification, solvers claim rewards:
function withdraw(
uint64 destination,
bytes32 routeHash,
Reward calldata reward
) public
Transfers rewards from vault to the claimant address specified during fulfillment.
Batch Withdrawal:
function batchWithdraw(
uint64[] calldata destinations,
bytes32[] calldata routeHashes,
Reward[] calldata rewards
) external
Efficiently claim multiple rewards in one transaction.
6. Refunds (Source Chain)
Creators can reclaim rewards after expiry:
function refund(
uint64 destination,
bytes32 routeHash,
Reward calldata reward
) external
Only succeeds if:
- Intent has expired (
block.timestamp >= reward.deadline)
- Intent was not fulfilled on the correct destination chain
ERC-7683 Compatibility
The Portal implements both ERC-7683 origin and destination settler interfaces.
Origin Functions
Direct Order Opening:
function open(OnchainCrossChainOrder calldata order) external payable
Creates and funds an intent from an on-chain order structure.
Gasless Order Opening:
function openFor(
GaslessCrossChainOrder calldata order,
bytes calldata signature,
bytes calldata /* originFillerData */
) external payable
Creates an intent using EIP-712 signature. Validates:
- Signature matches
order.user
openDeadline not passed
originSettler matches contract address
originChainId matches current chain
Order Resolution:
function resolve(OnchainCrossChainOrder calldata order)
public view returns (ResolvedCrossChainOrder memory)
function resolveFor(GaslessCrossChainOrder calldata order, bytes calldata)
public view returns (ResolvedCrossChainOrder memory)
Converts Eco orders into ERC-7683 standard format.
Destination Functions
ERC-7683 Fulfillment:
function fill(
bytes32 orderId,
bytes calldata originData,
bytes calldata fillerData
) external payable
Parameters:
orderId: Intent hash from origin chain
originData: Encoded (bytes route, bytes32 rewardHash)
fillerData: Encoded (address prover, uint64 source, bytes32 claimant, bytes proverData)
Decodes data and calls fulfillAndProve() internally.
Vault System
The Portal uses deterministic CREATE2 deployment for intent vaults.
Computing Vault Address
function intentVaultAddress(Intent calldata intent)
public view returns (address)
function intentVaultAddress(
uint64 destination,
bytes memory route,
Reward calldata reward
) public view returns (address)
Returns the deterministic vault address without deployment.
Vault States
Vaults track intent lifecycle through status enum:
Initial: Intent created but not funded
Funded: Fully funded and ready for fulfillment
Withdrawn: Rewards claimed by solver
Refunded: Rewards returned to creator
function getRewardStatus(bytes32 intentHash)
public view returns (Status status)
Funding Validation
function isIntentFunded(Intent calldata intent)
public view returns (bool)
function isIntentFunded(
uint64 destination,
bytes memory route,
Reward calldata reward
) public view returns (bool)
Checks if vault contains sufficient tokens and native currency.
Token Recovery
Recover tokens mistakenly sent to vaults:
function recoverToken(
uint64 destination,
bytes32 routeHash,
Reward calldata reward,
address token
) external
Restrictions:
- Cannot recover zero address
- Cannot recover any token in
reward.tokens
- Intent must have zero native rewards OR already be claimed/refunded
Intent Hashing
The Portal uses a deterministic hashing scheme:
function getIntentHash(Intent memory intent)
public pure returns (
bytes32 intentHash,
bytes32 routeHash,
bytes32 rewardHash
)
Formula:
routeHash = keccak256(abi.encode(route))
rewardHash = keccak256(abi.encode(reward))
intentHash = keccak256(abi.encodePacked(destination, routeHash, rewardHash))
This allows verification on destination chains without transmitting full intent data.
Execution Model
Source Chain Execution
The Portal transfers tokens from users to vaults:
- Native tokens via
payable function calls
- ERC20 tokens via
SafeERC20.safeTransferFrom
- Excess ETH refunded automatically
Destination Chain Execution
The Portal delegates call execution to an Executor contract:
- Transfers tokens from solver to Executor
- Executor performs calls with delegated tokens
- Executor has no persistent state between transactions
This isolation ensures:
- Portal storage is protected during arbitrary calls
- Failed calls don’t corrupt Portal state
- Executor can be upgraded independently
Security Features
Replay Protection
- Intent hashes are unique (include salt in route)
- Fulfilled intents cannot be fulfilled again
- Withdrawn/refunded intents cannot be withdrawn/refunded again
- ERC-7683 gasless orders use nonces
Validation
Publishing:
- Cannot republish withdrawn/refunded intents
- Validates reward structure consistency
Fulfillment:
- Verifies intent hash matches route and reward
- Checks portal address in route matches contract
- Validates deadline hasn’t passed
- Prevents zero claimant address
- Requires minimum native token amount
Withdrawal:
- Validates intent is proven on correct destination
- Challenges proofs for incorrect destinations
- Prevents withdrawal before proof
Refund:
- Only after deadline expiry
- Only if not proven on correct destination
- Prevents refund of claimed intents
Reentrancy Protection
All external calls are made through:
SafeERC20 for token transfers
- Isolated
Executor for user-specified calls
- State updates before external interactions
Cross-VM Compatibility
The Portal supports both EVM and non-EVM chains:
Address Conversion
import {AddressConverter} from "./libs/AddressConverter.sol";
// EVM address to bytes32
bytes32 universalId = address.toBytes32();
// bytes32 to EVM address
address evmAddress = bytes32Id.toAddress();
Claimant Identifiers
On destination chains, claimants are stored as bytes32:
mapping(bytes32 => bytes32) public claimants;
This supports:
- EVM addresses (20 bytes, left-padded)
- Solana addresses (32 bytes)
- Other blockchain address formats
Events
IntentPublished
event IntentPublished(
bytes32 indexed hash,
uint64 destination,
bytes route,
address indexed creator,
address indexed prover,
uint256 deadline,
uint256 nativeAmount,
TokenAmount[] tokens
);
IntentFunded
event IntentFunded(
bytes32 indexed intentHash,
address indexed funder,
bool complete
);
IntentFulfilled
event IntentFulfilled(
bytes32 indexed intentHash,
bytes32 claimant
);
IntentProven
event IntentProven(
bytes32 indexed intentHash,
bytes32 claimant
);
IntentWithdrawn
event IntentWithdrawn(
bytes32 indexed hash,
address indexed recipient
);
IntentRefunded
event IntentRefunded(
bytes32 indexed hash,
address indexed recipient
);
IntentTokenRecovered
event IntentTokenRecovered(
bytes32 indexed intentHash,
address indexed recipient,
address indexed token
);
Open (ERC-7683)
event Open(
bytes32 indexed orderId,
ResolvedCrossChainOrder resolvedOrder
);
OrderFilled (ERC-7683)
event OrderFilled(
bytes32 indexed orderId,
address indexed filler
);
Error Handling
ArrayLengthMismatch(): Array parameters have mismatched lengths
ChainIdTooLarge(uint256): Chain ID exceeds uint64 maximum
InsufficientFunds(bytes32): Incomplete funding when partial not allowed
InsufficientNativeAmount(uint256, uint256): Insufficient native tokens for execution
IntentAlreadyExists(bytes32): Cannot republish existing intent
IntentAlreadyFulfilled(bytes32): Intent already fulfilled
IntentExpired(): Past route deadline
IntentNotClaimed(bytes32): Cannot refund claimed intent
IntentNotFulfilled(bytes32): Intent not in fulfilled state
InvalidClaimant(): Zero address claimant
InvalidHash(bytes32): Computed hash doesn’t match provided hash
InvalidOriginChainId(uint256, uint256): Chain ID mismatch in ERC-7683 order
InvalidOriginSettler(address, address): Settler address mismatch in ERC-7683 order
InvalidPortal(address): Route portal doesn’t match contract
InvalidRecoverToken(address): Cannot recover specified token
InvalidSignature(): EIP-712 signature verification failed
InvalidStatusForFunding(Status): Cannot fund withdrawn/refunded intent
InvalidStatusForRefund(Status, uint256, uint256): Invalid refund conditions
InvalidStatusForWithdrawal(Status): Cannot withdraw non-funded intent
OpenDeadlinePassed(): ERC-7683 order opening deadline exceeded
TypeSignatureMismatch(): ERC-7683 orderDataType mismatch
ZeroClaimant(): Claimant cannot be zero
Integration Examples
Creating and Funding an Intent
Intent memory intent = Intent({
destination: 42161, // Arbitrum
route: Route({
salt: keccak256(abi.encode(user, nonce)),
deadline: block.timestamp + 1 hours,
portal: destinationPortalAddress,
nativeAmount: 0.1 ether,
tokens: tokenAmounts,
calls: calls
}),
reward: Reward({
creator: msg.sender,
prover: hyperProverAddress,
deadline: block.timestamp + 1 hours,
nativeAmount: 0.05 ether,
tokens: rewardTokens
})
});
portal.publishAndFund{value: totalNativeAmount}(intent, false);
Fulfilling on Destination
bytes32 intentHash = /* from IntentPublished event */;
Route memory route = /* from event data */;
bytes32 rewardHash = keccak256(abi.encode(reward));
bytes32 claimant = bytes32(uint256(uint160(solverAddress)));
// Approve tokens for Portal
for (uint i = 0; i < route.tokens.length; i++) {
IERC20(route.tokens[i].token).approve(
address(portal),
route.tokens[i].amount
);
}
// Fulfill and prove
portal.fulfillAndProve{value: route.nativeAmount + bridgeFee}(
intentHash,
route,
rewardHash,
claimant,
proverAddress,
sourceChainDomainID,
proverData
);
Claiming Rewards
// After proof verification on source chain
portal.withdraw(destination, routeHash, reward);
Batch Operations
uint64[] memory destinations = new uint64[](3);
bytes32[] memory routeHashes = new bytes32[](3);
Reward[] memory rewards = new Reward[](3);
// Populate arrays...
portal.batchWithdraw(destinations, routeHashes, rewards);