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

# Across Bridge Hook

> Post-intent hooks for cross-chain token bridging via Across Protocol

## Overview

The Across Bridge Hooks enable automatic cross-chain token bridging when intents are fulfilled. They integrate with [Across Protocol](https://across.to) to bridge tokens from the source chain to a destination chain, enabling seamless cross-chain fiat on-ramps.

**Available Versions:**

* `AcrossBridgeHook` - V1 implementation (works with Orchestrator)
* `AcrossBridgeHookV2` - V2 implementation (works with OrchestratorV2)

**Recommended Use Case:** Stablecoin-to-stablecoin bridging only (e.g., USDC on Base → USDC on Arbitrum)

## Key Features

### Cross-Chain Compatibility

Supports both EVM and non-EVM chains:

* **EVM chains:** Addresses are left-padded to bytes32
* **Non-EVM chains (Solana):** Native 32-byte addresses used directly

### Graceful Fallback

If bridging fails, funds are transferred to the recipient on the source chain instead of reverting. This ensures users always receive funds after making off-chain payments.

### Two-Phase Data Model

**Signal Time (Commitment):**

* Destination chain ID
* Output token address
* Recipient address
* Minimum output amount

**Fulfill Time (JIT Data):**

* Actual output amount
* Fill deadline
* Exclusive relayer (from Across API)
* Exclusivity period

## Important Limitations

<Warning>
  **Not Recommended for Volatile Assets**

  The Across Bridge Hook is designed for stablecoin-to-stablecoin routes only. Using it with volatile assets (ETH, WBTC, etc.) can cause failures:

  1. `minOutputAmount` is committed at signal time
  2. Actual `outputAmount` is provided at fulfill time
  3. If asset price drops between signal and fulfill, the minimum check fails
  4. User has already made off-chain fiat payment and cannot recover funds if fulfill reverts

  For stablecoins, price remains stable and checks reliably pass.
</Warning>

## Architecture

### Contract References

**V1:**

* **Location:** `contracts/hooks/AcrossBridgeHook.sol`
* **Interface:** `IPostIntentHook`
* **Orchestrator:** Single orchestrator address set at deployment

**V2:**

* **Location:** `contracts/hooks/AcrossBridgeHookV2.sol`
* **Interface:** `IPostIntentHookV2`
* **Orchestrator:** Uses `IOrchestratorRegistry` for multi-orchestrator support

### Dependencies

```solidity theme={null}
import { IAcrossSpokePool } from "../external/Interfaces/IAcrossSpokePool.sol";
import { IPostIntentHook } from "../interfaces/IPostIntentHook.sol"; // V1
import { IPostIntentHookV2 } from "../interfaces/IPostIntentHookV2.sol"; // V2
```

## Data Structures

### BridgeCommitment

Stored in `intent.data` (V1) or `intent.signalHookData` (V2) at signal time:

```solidity theme={null}
struct BridgeCommitment {
    uint256 destinationChainId;  // Target chain ID
    bytes32 outputToken;         // Token on destination (bytes32 for Solana support)
    bytes32 recipient;           // Recipient on destination (bytes32 for Solana support)
    uint256 minOutputAmount;     // Minimum acceptable output (slippage protection)
}
```

**Field Details:**

* **`destinationChainId`:** Chain ID where tokens will be received
  * Example: `42161` for Arbitrum, `8453` for Base
* **`outputToken`:** Token address on destination chain
  * EVM: Use `bytes32(uint256(uint160(tokenAddress)))` to convert
  * Solana: Use native 32-byte public key
* **`recipient`:** Where tokens will be sent on destination
  * EVM: Use `bytes32(uint256(uint160(recipientAddress)))` to convert
  * Solana: Use native 32-byte public key
* **`minOutputAmount`:** Minimum tokens to receive
  * Set to \~99.5% of expected output for stablecoins
  * Protects against unfavorable price movement

### AcrossFulfillData

Provided in `_fulfillIntentData` (V1) or `_fulfillHookData` (V2) at fulfill time:

```solidity theme={null}
// V1
struct AcrossFulfillData {
    bytes32 intentHash;              // Hash of intent being fulfilled
    uint256 outputAmount;            // Actual output amount
    uint32 fillDeadlineOffset;       // Seconds until fill deadline
    bytes32 exclusiveRelayer;        // Exclusive relayer from Across API
    uint32 exclusivityParameter;     // Exclusivity duration in seconds
}

// V2 (intentHash in context)
struct AcrossFulfillData {
    uint256 outputAmount;            // Actual output amount
    uint32 fillDeadlineOffset;       // Seconds until fill deadline
    bytes32 exclusiveRelayer;        // Exclusive relayer from Across API
    uint32 exclusivityParameter;     // Exclusivity duration in seconds
}
```

**Field Details:**

* **`intentHash`:** (V1 only) Intent identifier for event correlation
* **`outputAmount`:** How many tokens recipient receives on destination
  * Must be ≥ `minOutputAmount` or hook falls back to local transfer
* **`fillDeadlineOffset`:** Seconds from current block until fill expires
  * Typical range: 1800 (30 min) to 21600 (6 hours)
  * Longer for slower routes, shorter for fast routes
* **`exclusiveRelayer`:** Relayer with priority access (from Across suggested-fees API)
  * Ensures faster fulfillment by known relayers
* **`exclusivityParameter`:** How long exclusive relayer has priority (seconds)
  * Prevents fill competition during exclusivity window

## Usage

### Deployment

**V1:**

```solidity theme={null}
AcrossBridgeHook hook = new AcrossBridgeHook(
    usdcAddress,           // Input token (e.g., USDC on Base)
    orchestratorAddress,   // Orchestrator that will call this hook
    spokePoolAddress       // Across SpokePool on this chain
);
```

**V2:**

```solidity theme={null}
AcrossBridgeHookV2 hook = new AcrossBridgeHookV2(
    usdcAddress,                  // Input token (e.g., USDC on Base)
    orchestratorRegistryAddress,  // Registry of authorized orchestrators
    spokePoolAddress              // Across SpokePool on this chain
);
```

### Signal Intent with Bridge Hook

**V1 Example:**

```solidity theme={null}
// Prepare bridge commitment
BridgeCommitment memory commitment = BridgeCommitment({
    destinationChainId: 42161,  // Arbitrum
    outputToken: bytes32(uint256(uint160(arbitrumUSDC))),
    recipient: bytes32(uint256(uint160(recipientAddress))),
    minOutputAmount: 995e6  // 99.5% of 1000 USDC (allows 0.5% slippage)
});

// Signal intent with hook
Intent memory intent = Intent({
    owner: msg.sender,
    escrow: escrowAddress,
    depositId: depositId,
    amount: 1000e6,
    to: recipientAddress,
    timestamp: block.timestamp,
    paymentMethod: keccak256("Venmo"),
    fiatCurrency: keccak256("USD"),
    conversionRate: 1e18,
    payeeId: keccak256("user@venmo.com"),
    postIntentHook: address(acrossBridgeHook),
    data: abi.encode(commitment)
});

orchestratorV1.signalIntent(intent, referrer, referrerFee);
```

**V2 Example:**

```solidity theme={null}
// Prepare bridge commitment
BridgeCommitment memory commitment = BridgeCommitment({
    destinationChainId: 42161,
    outputToken: bytes32(uint256(uint160(arbitrumUSDC))),
    recipient: bytes32(uint256(uint160(recipientAddress))),
    minOutputAmount: 995e6
});

// Signal intent with hook
SignalIntentParams memory params = SignalIntentParams({
    escrow: escrowAddress,
    depositId: depositId,
    amount: 1000e6,
    to: recipientAddress,
    paymentMethod: keccak256("Venmo"),
    fiatCurrency: keccak256("USD"),
    conversionRate: 1e18,
    payeeId: keccak256("user@venmo.com"),
    referrer: referrer,
    referrerFee: referrerFee,
    postIntentHook: address(acrossBridgeHookV2),
    signalHookData: abi.encode(commitment),
    preIntentHookData: ""
});

orchestratorV2.signalIntent(params);
```

### Fulfill Intent with Bridge

**Get Across API Data:**

Before fulfilling, query Across suggested-fees API:

```javascript theme={null}
// Example: Fetch Across suggested fees
const response = await fetch(
  `https://app.across.to/api/suggested-fees?` +
  `inputToken=${baseUSDC}&` +
  `outputToken=${arbitrumUSDC}&` +
  `originChainId=8453&` +
  `destinationChainId=42161&` +
  `amount=1000000000` // 1000 USDC (6 decimals)
);

const { 
  totalRelayFee, 
  relayGasFeePct, 
  exclusiveRelayer, 
  exclusivityDeadline 
} = await response.json();

// Calculate output amount and exclusivity
const outputAmount = 1000000000n - totalRelayFee.total;
const fillDeadlineOffset = 3600; // 1 hour
const exclusivityParameter = exclusivityDeadline - Math.floor(Date.now() / 1000);
```

**V1 Fulfill:**

```solidity theme={null}
// Prepare fulfill data
AcrossFulfillData memory fulfillData = AcrossFulfillData({
    intentHash: intentHash,
    outputAmount: 998e6,  // From Across API
    fillDeadlineOffset: 3600,
    exclusiveRelayer: bytes32(uint256(uint160(relayerAddress))),  // From Across API
    exclusivityParameter: 60  // From Across API
});

orchestratorV1.fulfillIntent(
    intent,
    abi.encode(fulfillData),
    proof
);
```

**V2 Fulfill:**

```solidity theme={null}
// Prepare fulfill data (no intentHash needed)
AcrossFulfillData memory fulfillData = AcrossFulfillData({
    outputAmount: 998e6,
    fillDeadlineOffset: 3600,
    exclusiveRelayer: bytes32(uint256(uint160(relayerAddress))),
    exclusivityParameter: 60
});

FulfillIntentParams memory params = FulfillIntentParams({
    intentHash: intentHash,
    proof: proof,
    proofPos: 0,
    fulfillHookData: abi.encode(fulfillData)
});

orchestratorV2.fulfillIntent(params);
```

## Execution Flow

```mermaid theme={null}
sequenceDiagram
    participant User
    participant Orchestrator
    participant AcrossHook
    participant SpokePool
    participant AcrossRelayer
    participant Destination
    
    User->>Orchestrator: fulfillIntent()
    Orchestrator->>Orchestrator: Calculate fees
    Orchestrator->>AcrossHook: approve(netAmount)
    Orchestrator->>AcrossHook: execute()
    AcrossHook->>Orchestrator: transferFrom(netAmount)
    
    alt outputAmount >= minOutputAmount
        AcrossHook->>SpokePool: approve(netAmount)
        AcrossHook->>SpokePool: depositNow()
        
        alt Bridge Success
            SpokePool-->>AcrossHook: success
            AcrossHook->>AcrossHook: emit AcrossBridgeInitiated
            Note over AcrossRelayer,Destination: Off-chain: Relayer fills on destination
            AcrossRelayer->>Destination: Fill deposit
        else Bridge Reverts
            SpokePool-->>AcrossHook: revert
            AcrossHook->>User: transfer(netAmount)
            AcrossHook->>AcrossHook: emit FallbackTransfer
        end
    else outputAmount < minOutputAmount
        AcrossHook->>User: transfer(netAmount)
        AcrossHook->>AcrossHook: emit FallbackTransfer
    end
```

## Fallback Behavior

The hook implements graceful degradation to protect users:

### Fallback Triggers

1. **OUTPUT\_BELOW\_MINIMUM:** `outputAmount < minOutputAmount`
   * Price moved unfavorably between signal and fulfill
   * Common with volatile assets (reason not to use hook for non-stablecoins)

2. **BRIDGE\_CALL\_FAILED:** `spokePool.depositNow()` reverted
   * SpokePool paused
   * Route not supported
   * Across liquidity issues

### Fallback Action

When fallback is triggered:

1. Tokens are transferred to `intent.to` on the **source chain**
2. `FallbackTransfer` event is emitted with reason code
3. Transaction succeeds (does not revert)

**Rationale:** User has already made off-chain fiat payment. Reverting would lock their funds with no way to recover. Fallback ensures they receive tokens even if bridge fails.

### Example Fallback Event

```solidity theme={null}
event FallbackTransfer(
    bytes32 indexed intentHash,
    address indexed recipient,
    uint256 amount,
    FallbackReason reason  // OUTPUT_BELOW_MINIMUM or BRIDGE_CALL_FAILED
);
```

## Events

### AcrossBridgeInitiated

Emitted when bridge deposit successfully initiated:

```solidity theme={null}
event AcrossBridgeInitiated(
    bytes32 indexed intentHash,
    uint256 destinationChainId,
    bytes32 outputToken,
    bytes32 recipient,
    uint256 inputAmount,
    uint256 outputAmount,
    uint32 fillDeadlineOffset,
    bytes32 exclusiveRelayer,
    uint32 exclusivityParameter
);
```

### FallbackTransfer

Emitted when bridge cannot be initiated:

```solidity theme={null}
event FallbackTransfer(
    bytes32 indexed intentHash,
    address indexed recipient,
    uint256 amount,
    FallbackReason reason
);
```

## Admin Functions

Both V1 and V2 include rescue functions for recovering stuck funds:

### Rescue ERC20

```solidity theme={null}
function rescueERC20(
    address _token,
    address _to,
    uint256 _amount
) external onlyOwner;
```

### Rescue Native

```solidity theme={null}
function rescueNative(
    address payable _to,
    uint256 _amount
) external onlyOwner;
```

## Security Considerations

### Authorization

**V1:** Only the specific orchestrator set at deployment can call `execute()`

**V2:** Any orchestrator registered in `IOrchestratorRegistry` can call `execute()`

### Token Handling

1. **Exact Pulls:** Hook pulls exact approved amount from orchestrator
2. **Approval Hygiene:** Approvals to SpokePool are reset to 0 after operations
3. **No Stranded Funds:** Tokens are either bridged or sent to recipient (never stuck)

### Price Movement

For stablecoins, set `minOutputAmount` to \~99.5% of expected:

* Allows minor bridge fee variation
* Prevents DoS from tiny price movements
* Still protects against significant slippage

For volatile assets, this model is unsafe:

* Price can drop >0.5% in minutes
* minOutputAmount check will fail
* User loses access to funds after payment

### Reentrancy

Orchestr ator calls hook within reentrancy guard. Hook does not need additional protection.

## Cross-Chain Address Conversion

### EVM Addresses to bytes32

```solidity theme={null}
function _toBytes32(address addr) internal pure returns (bytes32) {
    return bytes32(uint256(uint160(addr)));
}

// Usage
bytes32 recipient = bytes32(uint256(uint160(0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb)));
```

### Solana Addresses

Solana public keys are already 32 bytes:

```javascript theme={null}
// JavaScript/TypeScript
import { PublicKey } from '@solana/web3.js';

const recipient = new PublicKey('DYw8j...').toBuffer(); // 32 bytes
const recipientBytes32 = '0x' + recipient.toString('hex');
```

## Integration Checklist

* [ ] Deploy hook with correct input token and spoke pool
* [ ] Verify spoke pool supports your destination chain
* [ ] Test bridge route with small amount first
* [ ] Integrate Across suggested-fees API for relayer parameters
* [ ] Set reasonable `fillDeadlineOffset` (1800-21600 seconds)
* [ ] Set `minOutputAmount` to \~99.5% of expected output
* [ ] Monitor `FallbackTransfer` events for failed bridges
* [ ] Implement fallback notification for users
* [ ] Test with both EVM and non-EVM destinations if needed

## Related Contracts

* [Hooks Overview](/contracts/hooks-overview) - Hook system architecture
* [Orchestrator](/contracts/orchestrator) - V1 orchestrator
* [OrchestratorV2](/contracts/orchestrator) - V2 orchestrator
* [Across Protocol Documentation](https://docs.across.to/) - External resource
