End-to-end testing framework for the Eco Routes intent lifecycle on mainnet chains across EVM and Solana networks.
| From / To | OPT | BASE | ARB | POL | INK | SOL | ETH | RON |
|---|---|---|---|---|---|---|---|---|
| Optimism | - | P | P | P | P | P | P | - |
| Base | P | - | P | P | P | P | P | P |
| Arbitrum | P | P | - | P | P | P | P | - |
| Polygon | P | P | P | - | P | P | P | - |
| Ink | P | P | P | P | - | P | P | - |
| Solana | P | P | P | P | P | - | P | - |
| Ethereum | P | P | P | P | P | P | - | P |
| Ronin | - | P | - | - | - | - | P | - |
P = Pass, F = Fail, - = Not applicable. 46/46 routes passing.
| Category | Passed | Failed | Total |
|---|---|---|---|
| Core EVM (5 chains) | 20 | 0 | 20 |
| Solana to/from EVM | 10 | 0 | 10 |
| Ethereum to/from EVM | 10 | 0 | 10 |
| Ethereum to/from Solana | 2 | 0 | 2 |
| Ethereum to/from Ronin | 2 | 0 | 2 |
| Base to/from Ronin | 2 | 0 | 2 |
Independent, open-source personal project built to explore cross-chain intent infrastructure using the publicly documented Eco Routes protocol. Not affiliated with, commissioned by, or contracted through Eco — this is self-directed work released under the MIT license.
This system tests the complete intent lifecycle:
PUBLISHED → FUNDED → FULFILLED → PROVEN → WITHDRAWN
Using real mainnet infrastructure with public solvers - no local solver required.
The system supports two quote provider modes:
QUOTE_PROVIDER=api
QUOTES_API_URL=https://quotes-preprod.eco.com- Aggregates quotes from multiple solvers
- Supports all EVM and Solana routes
- Best for testing route availability
QUOTE_PROVIDER=solver
SOLVER_URL=https://solver-magenta.eco.com- Connects directly to a specific solver
- Portal/prover addresses come from solver response
- Best for testing specific solver behavior
Every test run verifies that on-chain balances change by the expected amounts. The verification is provider-aware and adapts its strategy depending on the quote mode.
STEP 1 Get quote
STEP 1.5 Snapshot user (+ solver in solver mode) balances
STEP 2 Publish and fund intent
STEP 3 Wait for fulfillment on destination chain
STEP 3.5 Verify user received exact destinationAmount (STRICT)
STEP 4 Wait for proof on source chain
STEP 5 Wait for solver withdrawal
STEP 5.5 Verify solver received sourceAmount on source chain
| Assertion | Solver mode | API mode |
|---|---|---|
| User received destinationAmount | Strict (0%) | Strict (0%) |
| Solver identity known upfront | Yes | No |
| Solver "before" snapshot | Yes | No |
| Solver reward delta verified | Strict (0%) | Informational only |
| Solver address logged | Yes | Yes (from claimant) |
| IntentWithdrawn event verified | Yes | Yes |
- User verification: Must match the quote's
destinationAmountexactly. Any deviation, even 1 wei, fails the test. - Solver verification (solver mode): Before/after delta must match the
sourceAmount. The solver wallet is known from the/api/v1/blockchain/chainsendpoint. - Solver verification (API mode): The solver is unknown until fulfillment. The address is discovered from the
IntentFulfilledevent'sclaimantfield, and a post-withdrawal balance is logged for traceability (no assertion, since there is no "before" snapshot).
- All balance calculations use
BigIntto avoid floating-point precision issues. formatUnitsis only used for display, never for comparisons.- RPC calls use exponential backoff retry (1s, 2s, 4s) to handle 429 rate limits.
- Solana balance queries return
BigInt(0)for non-existent ATAs instead of throwing. - The
claimantfield inIntentFulfilledis encoded asbytes32; the service extracts the standard 20-byte address from the zero-padded value.
┌──────────────────────────────────────────────────────────────────────┐
│ E2E Test Suite │
├──────────────────────────────────────────────────────────────────────┤
│ tests/e2e/ │
│ └── universal.e2e.test.ts Parameterized test runner │
│ tests/helpers/ │
│ └── balance-assertions.helper Snapshot + verify balance deltas │
├──────────────────────────────────────────────────────────────────────┤
│ Core Services │
├──────────────────────────────────────────────────────────────────────┤
│ QuoteService Get quotes from API or direct solver │
│ IntentPublisher Publish & fund intents on-chain │
│ EventMonitor Watch for lifecycle events │
│ SvmPublisher Solana-specific intent publishing │
│ SolverConfigService Dynamic chain/token config from solver API │
│ BalanceService ERC20 + SPL token balance queries with retry │
│ RouteDiscoveryService Discover valid routes via quote probing │
├──────────────────────────────────────────────────────────────────────┤
│ Blockchain Adapters │
├──────────────────────────────────────────────────────────────────────┤
│ EVM: Optimism, Base, Arbitrum, Polygon, Ink, Ethereum, Ronin │
│ SVM: Solana │
└──────────────────────────────────────────────────────────────────────┘
| Chain | ID | Type | Token | Primary Token Address |
|---|---|---|---|---|
| Optimism | 10 |
EVM | USDC | 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85 |
| Base | 8453 |
EVM | USDC | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
| Arbitrum One | 42161 |
EVM | USDC | 0xaf88d065e77c8cC2239327C5EDb3A432268e5831 |
| Polygon | 137 |
EVM | USDC | 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359 |
| Ink | 57073 |
EVM | USDG | 0xe343167631d89B6Ffc58B88d6b7fB0228795491D |
| Ethereum | 1 |
EVM | USDC | 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 |
| Ronin | 2020 |
EVM | USDC | 0x0B7007c13325C48911F73A2daD5FA5dCBf808aDc |
| Solana | 1399811149 |
SVM | USDC | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v |
- Node.js 18+
- Funded wallet with:
- Native tokens for gas on each chain
- USDC/stablecoins for testing (~$1-5 per chain)
- For Solana routes:
routes-cliinstalled
npm installCopy .env.example to .env and configure:
# Required: EVM wallet
PRIVATE_KEY=0x...
WALLET_ADDRESS=0x...
# Required for Solana routes
SVM_PRIVATE_KEY=... # base58 format
SOLANA_WALLET=...
SOLANA_RECIPIENT=...
# Quote provider configuration
QUOTE_PROVIDER=solver # 'api' or 'solver'
QUOTES_API_URL=https://quotes-preprod.eco.com
SOLVER_URL=https://solver-magenta.eco.com
# Chain enable flags
ENABLE_POLYGON=true
ENABLE_INK=true
ENABLE_ETHEREUM=false # Disabled by default
ENABLE_RONIN=false # Disabled by default (CCIP issues)
# Optional: Custom RPC URLs
OPTIMISM_RPC=https://optimism.drpc.org
BASE_RPC=https://mainnet.base.org
ARBITRUM_RPC=https://arb1.arbitrum.io/rpcUse the universal test runner to test any specific route:
npm run test:route <source> <destination>Examples:
npm run test:route base eth # Base → Ethereum
npm run test:route sol arb # Solana → Arbitrum
npm run test:route eth ron # Ethereum → Ronin
npm run test:route pol ink # Polygon → InkValid chain aliases:
opt- Optimismbase- Basearb- Arbitrumpol- Polygonink- Inksol- Solanaeth- Ethereumron- Ronin
Validation: If you mistype a chain name, you'll get a helpful error:
npm run test:route basse eth
# Error: Invalid source chain "basse"
# Valid options: opt, base, arb, sol, pol, ink, eth, ronRun all valid route tests sequentially (to avoid nonce collisions):
npm run test:all # Run all 46 valid routes
npm run test:all:safe # Skip Ronin-as-source routes (44 routes)Ronin routes are restricted:
- Ronin can only route to/from:
eth,base - Other Ronin combinations (e.g.,
ron opt) will error with an explanation
Invalid route example:
npm run test:route ron arb
# Error: Ronin can only route to: eth, base. Got: arb1. Get Quote → Portal/Prover addresses from API
2. Publish Intent → Portal.publish(encodedRoute)
3. Fund Intent → Portal.fund() or publishAndFund()
4. Fulfilled → Solver fulfills on destination chain
5. Proven → HyperProver submits proof on source chain
6. Withdrawn → Solver withdraws reward
1. Get Quote → Portal/Prover addresses from API
2. Publish Intent → Portal.publish(encodedRoute)
3. Fund Intent → Portal.fund() or publishAndFund()
4. Fulfilled → Solver fulfills on destination chain
5. Withdrawn → CCIP proves automatically, solver withdraws
Note: Ronin routes use Chainlink CCIP for cross-chain proving instead of HyperProver.
| Event | Topic Hash |
|---|---|
IntentPublished |
0x43974895be1bcec7344337863fa7de24a0d1c315c0a994f663fe0ee220ddc8e4 |
IntentFunded |
0xc1ed05721d27ad6b2555d61388ac393b120f5cc0e6009e53230e02c68e60064a |
IntentFulfilled |
0xc471de166a60c0b81727dfa2f57d4fc3ad1b45b057c1f034b7058365613bde8d |
IntentProven |
0xe6d8040a8a6bc519f4e5a42fb2677067c929ddbf2cca9287a44b23fb617a6f00 |
IntentWithdrawn |
0xbb062c23e818de8ea9c157514eb098052cf36904bbe431cd50d4ec92264ca3ac |
Reports are automatically generated in ./reports/ after each test run:
e2e-report-{timestamp}.json- Detailed JSON report with full lifecycle datae2e-report-{timestamp}.md- Human-readable Markdown reportjest-report.html- Interactive HTML report (generated by Jest)
Report Contents:
- Test summary (total, successful, failed, partial)
- Success rate and average durations
- Phase-by-phase timings (publish → fund → fulfill → prove → withdraw)
- Individual intent details with transaction hashes
- Error messages and failure points
Viewing Reports:
# After running tests, check the reports directory
ls -lt reports/
# View the latest Markdown report
cat reports/e2e-report-*.md | tail -100Reports are automatically cleaned up after generation - temporary data is not persisted between runs.
- Single intent test: 10 minutes
- Parallel tests: 15 minutes
- Solana routes: Extended timeout for cross-chain bridge delays
The solver API requires lowercase EVM addresses. The system automatically normalizes addresses, but if you see this error, verify your .env addresses are correct.
- Check wallet has sufficient token balance
- Verify token addresses are correct
- Minimum amount is typically 0.05 USDC (50000 in 6 decimals)
Ronin uses the CCIP prover instead of HyperProver. All four Ronin routes (ETH→RON, BASE→RON, RON→ETH, RON→BASE) pass. CCIP proving can take longer than HyperProver, so these routes have extended timeouts.
- Ensure
routes-cliis properly configured - Check Solana RPC connectivity
- Verify SPL token accounts are initialized
# Type check
npm run typecheck
# Lint
npm run lint
# Build
npm run buildMIT