> ## Documentation Index
> Fetch the complete documentation index at: https://docs.eco.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How the portal contract works

> Main entry point for intent publishing, fulfillment, and reward claiming

The Portal is the single contract you interact with on every chain. It handles intent publishing, fulfillment, reward claiming, and [ERC-7683](/routes/architecture/erc-7683) compatibility. It holds no funds between transactions, all rewards sit in deterministically-deployed per-intent vaults.

## Architecture

```
Portal
├── Source-chain operations (intent publishing and management)
│   └── OriginSettler (ERC-7683 origin functionality)
└── Destinationchain operations (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:**

```solidity theme={null}
function publish(Intent calldata intent) 
    public returns (bytes32 intentHash, address vault)
```

Creates an intent without funding. Emits `IntentPublished` event.

**Atomic Publishing with Funding:**

```solidity theme={null}
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:**

```solidity theme={null}
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:

```solidity theme={null}
function fund(
    uint64 destination,
    bytes32 routeHash,
    Reward calldata reward,
    bool allowPartial
) external payable returns (bytes32 intentHash)
```

**Funding for Others:**

```solidity theme={null}
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:

```solidity theme={null}
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:**

```solidity theme={null}
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

Consult the bridge provider's documentation for correct domain IDs.

### 4. Proving (Destination Chain)

Generate proofs for fulfilled intents:

```solidity theme={null}
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:

```solidity theme={null}
function withdraw(
    uint64 destination,
    bytes32 routeHash,
    Reward calldata reward
) public
```

Transfers rewards from vault to the claimant address specified during fulfillment.

**Batch Withdrawal:**

```solidity theme={null}
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:

```solidity theme={null}
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:**

```solidity theme={null}
function open(OnchainCrossChainOrder calldata order) external payable
```

Creates and funds an intent from an onchain order structure.

**Gasless Order Opening:**

```solidity theme={null}
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:**

```solidity theme={null}
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:**

```solidity theme={null}
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

```solidity theme={null}
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

```solidity theme={null}
function getRewardStatus(bytes32 intentHash) 
    public view returns (Status status)
```

### Funding Validation

```solidity theme={null}
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:

```solidity theme={null}
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:

```solidity theme={null}
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:

1. Native tokens via `payable` function calls
2. ERC20 tokens via `SafeERC20.safeTransferFrom`
3. Excess ETH refunded automatically

### Destination Chain Execution

The Portal delegates call execution to an `Executor` contract:

1. Transfers tokens from solver to Executor
2. Executor performs calls with delegated tokens
3. 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

```solidity theme={null}
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`:

```solidity theme={null}
mapping(bytes32 => bytes32) public claimants;
```

This supports:

* EVM addresses (20 bytes, left-padded)
* Solana addresses (32 bytes)
* Other blockchain address formats

## Events

### IntentPublished

```solidity theme={null}
event IntentPublished(
    bytes32 indexed hash,
    uint64 destination,
    bytes route,
    address indexed creator,
    address indexed prover,
    uint256 deadline,
    uint256 nativeAmount,
    TokenAmount[] tokens
);
```

### IntentFunded

```solidity theme={null}
event IntentFunded(
    bytes32 indexed intentHash,
    address indexed funder,
    bool complete
);
```

### IntentFulfilled

```solidity theme={null}
event IntentFulfilled(
    bytes32 indexed intentHash,
    bytes32 claimant
);
```

### IntentProven

```solidity theme={null}
event IntentProven(
    bytes32 indexed intentHash,
    bytes32 claimant
);
```

### IntentWithdrawn

```solidity theme={null}
event IntentWithdrawn(
    bytes32 indexed hash,
    address indexed recipient
);
```

### IntentRefunded

```solidity theme={null}
event IntentRefunded(
    bytes32 indexed hash,
    address indexed recipient
);
```

### IntentTokenRecovered

```solidity theme={null}
event IntentTokenRecovered(
    bytes32 indexed intentHash,
    address indexed recipient,
    address indexed token
);
```

### Open (ERC-7683)

```solidity theme={null}
event Open(
    bytes32 indexed orderId,
    ResolvedCrossChainOrder resolvedOrder
);
```

### OrderFilled (ERC-7683)

```solidity theme={null}
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

```solidity theme={null}
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

```solidity theme={null}
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

```solidity theme={null}
// After proof verification on source chain
portal.withdraw(destination, routeHash, reward);
```

### Batch Operations

```solidity theme={null}
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);
```
