Smart contracts for Sequence Trails - a cross-chain intent execution system that enables seamless token swaps and transfers across different blockchain networks using attestation-based validation.
Trails Contracts provides a secure, efficient infrastructure for executing cross-chain intents through Sequence v3 wallets. The system uses off-chain attestations from trusted signers to authorize complex multi-step operations including token swaps, bridges, and conditional execution flows.
Location: src/TrailsRouter.sol
A consolidated router contract that combines three key functionalities:
-
Multicall Routing - Delegates calls to Multicall3 while preserving msg.sender context
execute()- Execute multiple calls in sequence via delegatecallpullAndExecute()- Pull user's full ERC20 balance, then execute multicallpullAmountAndExecute()- Pull specific ERC20 amount, then execute multicall
-
Balance Injection - Dynamically inject token balances into calldata
injectSweepAndCall()- Sweep tokens from msg.sender, inject balance into calldata, execute callinjectAndCall()- Read balance from delegatecall context, inject into calldata, execute call- Useful for protocols that require exact balance amounts (e.g., Aave deposits)
-
Token Sweeping - Conditional token recovery and fee collection
sweep()- Transfer all tokens/ETH from wallet to recipientrefundAndSweep()- Refund partial amount, sweep remaindervalidateOpHashAndSweep()- Sweep only if prior operation succeeded (via sentinel check)
Key Features:
- Implements
IDelegatedExtensionfor Sequence wallet integration - Uses OpenZeppelin's SafeERC20 for secure token handling
- Supports both native ETH and ERC20 tokens
- Delegatecall-only execution for security (enforced by
onlyDelegatecallmodifier)
Use Cases:
- Cross-chain token swaps with automatic fee collection
- Conditional refunds when bridge operations fail
- Balance-dependent protocol interactions (DeFi deposits/withdrawals)
Location: src/TrailsRouterShim.sol
A lightweight shim layer that wraps router calls and records execution success using storage sentinels.
Functionality:
- Forwards calls to TrailsRouter via delegatecall
- Sets success sentinel (
TrailsSentinelLib.successSlot(opHash)) when operation completes - Enables conditional execution patterns (e.g., "sweep fees only if swap succeeded")
- Bubbles up router errors with
RouterCallFailedfor debugging
Use Cases:
- Origin chain: Wrap LiFi bridge calls to track success
- Enable
validateOpHashAndSweep()to conditionally collect fees - Support fallback logic in intent execution flows
Location: src/TrailsIntentEntrypoint.sol
An EIP-712 signature-based entrypoint for depositing tokens to intent addresses with user authorization.
Functionality:
depositToIntent()- Transfer tokens to intent address with EIP-712 signature verificationdepositToIntentWithPermit()- Same as above, but includes ERC-2612 permit for gasless approval- Prevents replay attacks via intent hash tracking
- Enforces deadline for time-bounded intents
Intent Structure:
Intent(
address user, // User authorizing the deposit
address token, // ERC20 token to deposit
uint256 amount, // Amount to deposit
address intentAddress, // Destination intent wallet
uint256 deadline // Intent expiration timestamp
)Use Cases:
- Gasless token deposits to intent wallets
- User-authorized funding of cross-chain operations
- Batched intent execution with permit signatures
Location: src/libraries/TrailsSentinelLib.sol
Manages storage sentinels for conditional execution tracking.
successSlot(opHash)- Computes storage slot for operation success flagSUCCESS_VALUE- Constant value indicating successful execution- Namespaced storage to avoid collisions (
org.sequence.trails.router.sentinel)
This diagram reflects the current implementation in the Trails backend:
- Main origin and destination calls use
onlyFallback = false,behaviorOnError = IGNOREthroughTrailsRouterShim - TrailsRouterShim sets
TrailsSentinelLib.successSlot(opHash)toTrailsSentinelLib.SUCCESS_VALUEon success - Origin success runs
validateOpHashAndSweep(opHash, token, feeCollector)withDelegateCall=true,onlyFallback = false,behaviorOnError = IGNORE - On origin revert,
refundAndSweep(token, user, originTokenAmount, feeCollector)runs withDelegateCall=true,onlyFallback = true,behaviorOnError = IGNORE - Destination refund uses
sweep(user)as part of the main path (not fallback) withDelegateCall=true,onlyFallback = false,behaviorOnError = IGNORE
- On failure: An onlyFallback call runs only if the immediately previous call reverted AND used IGNORE behavior
- On success: All onlyFallback calls are skipped (emit CallSkipped events)
flowchart TD
start["Start: execute(payload)"] --> origin_main["Origin main call onlyFallback=false, onError=IGNORE through TrailsRouterShim"]
%% Origin success branch
origin_main --> origin_validate_op_hash_and_sweep["Origin validateOpHashAndSweep(opHash, token, feeAccount)"]
origin_validate_op_hash_and_sweep -- "success" --> origin_sweep["Origin sweep(feeCollector) DelegateCall, onlyFallback=false, onError=IGNORE"]
origin_sweep -- "success" --> origin_swap_and_bridge_success["Origin token swap/bridge success"]
origin_validate_op_hash_and_sweep -- "revert" --> origin_revert_error["errorFlag=true → run origin onlyFallback calls"]
origin_revert_error --> origin_refund_and_sweep["Origin refundAndSweep(token, user, originAmount, feeCollector) refund to user (approvals handled internally) DelegateCall, onlyFallback=true, onError=IGNORE"]
origin_swap_and_bridge_success --> dest_main
%% Destination main call
dest_main["Destination main call (if present) onlyFallback=false, onError=IGNORE"]
dest_main -- "success" --> done["✅ Complete"]
dest_main -- "revert" --> dest_sweep["Dest sweep(user) refund to user (approvals handled internally) DelegateCall, onlyFallback=false, onError=IGNORE"]
dest_sweep --> done
The entrypoint supports decoupled fee payments that can leverage existing allowances (e.g., leftover permit allowance from a prior deposit) or set allowance on the fly via ERC-2612 permit.
-
payFee
- Signature:
payFee(address user, address feeToken, uint256 feeAmount, address feeCollector) - Preconditions:
feeAmount > 0,feeToken != address(0),feeCollector != address(0), and sufficient allowance for the entrypoint - Effects: Transfers
feeAmountoffeeTokenfromusertofeeCollector - Emits:
FeePaid(user, feeToken, feeAmount, feeCollector)
- Signature:
-
payFeeWithPermit
- Signature:
payFeeWithPermit(address user, address feeToken, uint256 feeAmount, address feeCollector, uint256 deadline, uint8 v, bytes32 r, bytes32 s) - Preconditions:
feeAmount > 0,feeToken != address(0),feeCollector != address(0),block.timestamp <= deadline - Effects: Executes ERC-2612 permit for
feeAmountand then transfers tokens fromusertofeeCollector - Emits:
FeePaid(user, feeToken, feeAmount, feeCollector)
- Signature:
- Use
depositToIntentWithPermitwith apermitAmountgreater than the depositamountto leave leftover allowance. - Call
payFeeto consume the leftover allowance for fee collection.
// Deposit with a larger permit to leave leftover allowance
entrypoint.depositToIntentWithPermit(
user,
token,
depositAmount,
totalPermit, // > depositAmount, leaves leftover allowance
intentAddress,
deadline,
permitV,
permitR,
permitS,
sigV,
sigR,
sigS
);
// Later: collect fee using leftover allowance
entrypoint.payFee(user, token, feeAmount, feeCollector);FeePaid(address indexed user, address indexed feeToken, uint256 feeAmount, address indexed feeCollector)is emitted on successful fee payment.payFeerequires prior allowance (e.g., leftover fromdepositToIntentWithPermit).payFeeWithPermitsets the allowance atomically using ERC-2612 before transferring.
All contracts are deployed via ERC-2470 Singleton Factory for deterministic addresses across chains.
| Contract | Address |
|---|---|
| TrailsRouter | 0x9C5f5548e74c7a810109316224D4431E692613e8 |
| TrailsIntentEntrypoint | 0xF1Cb5D0E6197adF31c969a595f1Fc23F6A4124b5 |
| TrailsRouterShim | 0x5D4C6AF414c1f9cE650b08D528ef678d2C266a58 |
Note: TrailsRouter consolidates the functionality of TrailsTokenSweeper, TrailsBalanceInjector, and TrailsMulticall3Router into a single deployment.
# Deploy TrailsRouter
forge script script/TrailsRouter.s.sol --rpc-url $RPC_URL --broadcast --verify
# Deploy TrailsRouterShim (requires TrailsRouter address)
forge script script/TrailsRouterShim.s.sol --rpc-url $RPC_URL --broadcast --verify
# Deploy TrailsIntentEntrypoint
forge script script/TrailsIntentEntrypoint.s.sol --rpc-url $RPC_URL --broadcast --verify# Clone repository
git clone https://github.com/0xsequence/trails-contracts
cd trails-contracts
# Install foundry, or see https://book.getfoundry.sh/getting-started/installation
make install-foundry
# Install dependencies via git submodules
make deps
# Build contracts
make build# Run all tests
make test- TrailsRouter, TrailsRouterShim, and TrailsTokenSweeper MUST only be called via delegatecall
- Direct calls are blocked by
onlyDelegatecallmodifier - Ensures operations execute in wallet's context with proper access control
- Off-chain signers attest to operation parameters before execution
- Sequence wallets validate attestations against configured
imageHash - Prevents unauthorized operations even if wallet is compromised
- All ERC20 operations use OpenZeppelin's SafeERC20
- Handles non-standard tokens (e.g., USDT, missing return values)
forceApproveprevents issues with tokens requiring zero approval first
- TrailsIntentEntrypoint uses OpenZeppelin's ReentrancyGuard
- State changes occur before external calls
- No reentrancy vectors in delegatecall contracts (stateless)
- TrailsSentinelLib uses namespaced storage slots
- Prevents collisions with Sequence wallet storage
- Namespace:
keccak256("org.sequence.trails.router.sentinel")
The Trails API backend (written in Go) orchestrates intent execution using these contracts:
- Intent Construction - Builds Sequence v3 wallet transactions with nested calls
- Attestation Generation - Signs operation parameters with configured signer
- Transaction Assembly - Combines user operations, router calls, and fallback logic
- Execution Monitoring - Tracks sentinel values to determine success/failure
- Fee Collection - Uses
validateOpHashAndSweep()for conditional fee sweeps
See trails-api/lib/intentmachine for backend implementation.