> ## 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 vault contract works

> Escrow contract that holds intent rewards until claimed or refunded

The Vault is an escrow contract that holds intent rewards until they can be claimed by solvers or refunded to creators. Each intent has its own dedicated vault, deployed deterministically using CREATE2 for predictable addressing.

## Architecture

Vaults use a minimal proxy pattern (ERC-1167) for gas-efficient deployment:

```
Vault Implementation (deployed once)
    ↓
Proxy (deployed per intent via CREATE2)
    ↓ delegates to
Vault Implementation
```

**Key Properties:**

* One vault per intent, deployed on-demand
* Deterministic addresses computed from intent hash
* Only the Portal can manage vault operations
* No persistent state between intents (stateless implementation)

## Deployment

### CREATE2 Determinism

Vault addresses are computed using CREATE2 with the intent hash as salt:

```solidity theme={null}
address vaultAddress = keccak256(
    abi.encodePacked(
        CREATE2_PREFIX,        // 0xff (standard) or 0x41 (TRON)
        portalAddress,         // Deployer
        intentHash,            // Salt
        keccak256(             // Init code hash
            abi.encodePacked(
                type(Proxy).creationCode,
                abi.encode(vaultImplementation)
            )
        )
    )
)
```

### On-Demand Deployment

Vaults are deployed lazily when first needed:

* **Intent Publishing**: Address is computed but not deployed
* **First Funding**: Vault deployed if not already exists
* **Withdrawal/Refund**: Vault deployed if not already exists

This saves gas when intents are published but never funded.

### Chain Compatibility

The CREATE2 prefix adapts to different chains:

* **Standard EVM Chains**: `0xff` prefix
* **TRON Mainnet** (728126428): `0x41` prefix
* **TRON Testnet** (2494104990): `0x41` prefix

## Access Control

```solidity theme={null}
modifier onlyPortal()
```

All vault operations are restricted to the Portal contract that deployed the implementation. This ensures:

* Only validated intents can withdraw funds
* Refunds require proper deadline checks
* Token recovery follows protocol rules

## Funding

### Standard Funding

Called during `Portal.publishAndFund()` or `Portal.fund()`:

```solidity theme={null}
function fundFor(
    Reward calldata reward,
    address funder,
    IPermit permit
) external payable onlyPortal returns (bool fullyFunded)
```

**Funding Process:**

1. Check native token balance against `reward.nativeAmount`
2. For each ERC20 token:
   * Attempt permit-based transfer first (if permit contract provided)
   * Fall back to standard `transferFrom` with allowance
3. Return true only if all tokens fully funded

**Partial Funding:**
The function transfers as much as available based on:

* Funder's token balance
* Funder's allowance/permit to vault
* Existing vault balance

### Permit-Based Funding

Vaults support gasless approvals via permit contracts (e.g., Permit2):

**Permit Transfer:**

```solidity theme={null}
function _fundFromPermit(
    address funder,
    IERC20 token,
    uint256 rewardAmount,
    IPermit permit
) internal returns (uint256 remaining)
```

Queries permit contract for allowance and transfers tokens directly without requiring prior `approve()` call.

**Standard Transfer (Fallback):**

```solidity theme={null}
function _fundFrom(
    address funder,
    IERC20 token,
    uint256 remainingAmount
) internal returns (uint256 remaining)
```

Uses standard ERC20 `transferFrom` based on existing allowance.

**Funding Strategy:**

1. Check current vault balance
2. Try permit transfer if permit contract provided
3. Fall back to standard transfer for any remaining amount
4. Return unfunded amount

## Withdrawal

Solvers claim rewards after intent fulfillment and proof verification:

```solidity theme={null}
function withdraw(
    Reward calldata reward,
    address claimant
) external onlyPortal
```

**Withdrawal Process:**

1. Transfer all reward ERC20 tokens to claimant (up to reward amounts)
2. Transfer native tokens to claimant (up to reward amount)
3. Use actual balance (may be less than reward if partially funded)

**Safety Features:**

* Uses `SafeERC20.safeTransfer` for token transfers
* Minimum of reward amount and actual balance prevents over-withdrawal
* Native transfers use low-level call with success check
* Reverts on failed native transfer with `NativeTransferFailed` error

## Refund

Creators reclaim rewards after intent expiry:

```solidity theme={null}
function refund(Reward calldata reward) external onlyPortal
```

**Refund Process:**

1. Transfer all ERC20 token balances to `reward.creator`
2. Transfer all native token balance to `reward.creator`
3. No amount restrictions (returns full vault contents)

**Key Differences from Withdrawal:**

* Returns actual balance, not limited to reward amounts
* Always transfers to `reward.creator`
* Portal validates deadline before calling

## Token Recovery

Recover tokens mistakenly sent to vault:

```solidity theme={null}
function recover(address refundee, address token) external onlyPortal
```

**Use Cases:**

* User accidentally sent tokens directly to vault address
* Tokens sent to wrong vault
* Extra tokens beyond reward amounts

**Restrictions (enforced by Portal):**

* Cannot recover zero address (native tokens)
* Cannot recover any token in `reward.tokens` array
* Intent must have zero native rewards OR already be claimed/refunded

## Fund Flow

### Publishing Flow

```
User → Portal.publishAndFund()
    ↓
Portal → Vault.fundFor()
    ↓
Vault ← User (tokens via transferFrom/permit)
```

### Withdrawal Flow

```
Solver → Portal.withdraw()
    ↓
Portal → Prover (verify proof)
    ↓
Portal → Vault.withdraw()
    ↓
Vault → Solver (claimant address)
```

### Refund Flow

```
Creator → Portal.refund()
    ↓
Portal (validates deadline/proof)
    ↓
Portal → Vault.refund()
    ↓
Vault → Creator
```

## Security Model

### Immutable Portal

The portal address is set at construction and cannot be changed:

```solidity theme={null}
address private immutable portal;
```

This creates a strong binding between vault and portal, preventing:

* Unauthorized withdrawals
* Fake refunds before expiry
* Bypassing protocol validation

### Balance-Based Logic

All transfers use actual balance rather than storing state:

* No accounting variables to manipulate
* Always transfers real tokens held by vault
* Cannot withdraw more than vault contains

### SafeERC20 Integration

Uses OpenZeppelin's SafeERC20 for all token transfers:

* Handles non-standard ERC20 returns
* Prevents silent transfer failures
* Reverts on insufficient balance

### Minimal Proxy Pattern

Using ERC-1167 minimal proxies provides:

* **Gas Efficiency**: \~2,500 gas to deploy vs \~200,000 for full contract
* **Security**: Implementation cannot be changed after proxy deployment
* **Predictability**: Deterministic addresses enable pre-funding

## Integration Examples

### Computing Vault Address

```solidity theme={null}
// From Portal
Intent memory intent = /* ... */;
address vault = portal.intentVaultAddress(intent);

// Or with components
bytes32 intentHash = portal.getIntentHash(
    destination,
    route,
    reward
);
address vault = portal.intentVaultAddress(
    destination,
    abi.encode(route),
    reward
);
```

### Pre-Funding Vault

Users can send tokens directly to the computed vault address before publishing:

```solidity theme={null}
// Compute vault address
address vault = portal.intentVaultAddress(intent);

// Send tokens to vault
IERC20(tokenAddress).transfer(vault, amount);

// Publish without funding (vault already has tokens)
portal.publish(intent);
```

**Note:** This skips the `Funded` status check, so verify funding manually with `portal.isIntentFunded()`.

### Partial Funding

```solidity theme={null}
// User has limited balance
uint256 userBalance = token.balanceOf(user);
uint256 rewardAmount = 1000e18;

// Approve partial amount
token.approve(address(portal), userBalance);

// Publish with partial funding allowed
portal.publishAndFund{value: nativeAmount}(intent, true);
// allowPartial = true

// Check funding status
bool funded = portal.isIntentFunded(intent);
// Returns false if partially funded
```

### Using Permit for Gasless Funding

```solidity theme={null}
// User signs permit offchain
IPermit permit = IPermit(permitContract);

// Relayer calls publishAndFundFor
portal.publishAndFundFor(
    intent,
    true,              // allowPartial
    user,              // funder
    address(permit)    // permit contract
);

// Vault uses permit.transferFrom instead of token.transferFrom
```

## Error Handling

* `NotPortalCaller(address)`: Caller is not the authorized Portal contract
* `NativeTransferFailed(address, uint256)`: Failed to send native tokens to recipient
* `ZeroRecoverTokenBalance(address)`: Attempted to recover token with zero balance

## Gas Considerations

### Deployment Costs

* **Full Vault Contract**: \~200,000 gas
* **Minimal Proxy**: \~2,500 gas (98.75% savings)
* **Implementation**: One-time cost, shared across all vaults

### Operation Costs

**Funding:**

* Native transfer: \~21,000 gas
* ERC20 transfer: \~50,000 gas per token
* Permit transfer: \~70,000 gas per token (includes permit verification)

**Withdrawal:**

* Per token: \~50,000 gas
* Native transfer: \~21,000 gas

**Refund:**

* Similar to withdrawal costs
* No amount validation overhead

### Optimization Strategies

1. **Batch Operations**: Use `Portal.batchWithdraw()` to amortize vault deployment costs
2. **Pre-Funding**: Send tokens to vault address before publishing to skip funding transaction
3. **Single Token Rewards**: Minimize token array length in rewards
4. **Standard Transfers**: Use regular approvals instead of permits when possible (lower gas)

## Cross-Chain Considerations

### Address Consistency

Vault addresses are deterministic across chains with same:

* Portal deployment address
* Intent parameters (destination, route, reward)
* CREATE2 prefix (chain-specific)

**Example:**

```
Ethereum: 0x1234...5678 (0xff prefix)
TRON:     0x4321...8765 (0x41 prefix, different address)
```

### Native Token Handling

Each chain's native token is handled uniformly:

* Ethereum: ETH
* Polygon: MATIC
* Arbitrum: ETH
* TRON: TRX

Vaults treat all as "native amount" in `reward.nativeAmount`.

## Best Practices

### For Intent Creators

1. **Verify Funding**: Call `portal.isIntentFunded()` before expecting fulfillment
2. **Adequate Approvals**: Ensure sufficient allowance for all reward tokens
3. **Gas Budgeting**: Include native amount for destination chain execution
4. **Deadline Buffer**: Set `reward.deadline` with buffer for proof verification time

### For Solvers

1. **Check Vault Balance**: Verify vault is fully funded before fulfilling
2. **Gas Estimation**: Account for withdrawal transaction costs in profitability calculations
3. **Claimant Address**: Ensure claimant address is correct and can receive tokens
4. **Token Compatibility**: Verify all reward tokens are standard ERC20

### For Integrators

1. **Address Prediction**: Compute vault addresses offchain to display to users
2. **Event Monitoring**: Watch `IntentFunded` events to track funding progress
3. **Error Handling**: Handle partial funding scenarios gracefully
4. **Permit Integration**: Support gasless approvals for better UX
