Audience: Core contributors, auditors, product · Version: 1.0 · Status: Active — specification guides implementation (repo contains dev skeletons, not production-ready protocol code)
See also: README · Architecture · Technology stack · Timeline · Contributing
Duration: 8–12 weeks (indicative)
Implementation status: Phase 1 feature-complete locally (Stages 1–6 done) — contracts, backend services, frontend, E2E tests, load tests, and gas benchmarks all complete. Next milestone: testnet deployment (Stage 8).
Target launch (documented): Q3 2026
Enable users to swap tokens across Ethereum and Base chains using simple intent expressions, with automatic matching or solver fallback. Focus on flawless UX and 100% reliability.
Smart Contracts:
IntentSettler.sol(one deployment on Ethereum, one on Base — same logic; constructor takes optionalChainPeerRegistry)ChainPeerRegistry.sol(one deployment per chain — EID table + route allowlist; see Architecture)SolverAuction.sol(simple auction mechanism)- LayerZero OApp integration (
setPeerper remote EID, in addition to registry EIDs)
Supported Assets:
- Ethereum: ETH, USDC, USDT (ERC-20)
- Base: USDC, ETH (native + ERC-20)
Cross-Chain Messaging:
- LayerZero V2 as primary transport
- Fallback: Simple refund mechanism
Backend Systems:
- Intent Indexer (monitors events)
- Matching Engine (finds opposite intents)
- Solver Auction Orchestrator
- Order Book Database
Frontend:
- Web UI (React)
- MetaMask integration
- Intent status tracking
- Transaction history
Testing:
- Unit tests (contracts + backend)
- Integration tests (full flow)
- Testnet deployment
- Mainnet staging
- Solana support (Phase 2)
- Encrypted intents (Phase 2)
- Intent chaining (Phase 2)
- Governance token (Phase 2)
- NFT/LP token support (Phase 2)
- Mobile app (Phase 3)
- 10+ chain support (Phase 3)
User Story:
As a user,
I want to submit an intent to swap ETH on Ethereum for USDC on Base,
So that the protocol can find a match or execute through solvers
Acceptance Criteria:
- User can input source amount, destination token, and deadline
- System validates balance/allowance on source chain
- System estimates gas costs and displays total fee
- User signs intent with MetaMask
- Intent is submitted to Ethereum settlement contract
- System generates unique intent hash
- Event is emitted and indexed in order book
Technical Details:
// User calls:
IntentSettler.submitIntent({
sourceChain: 1, // Ethereum
destChain: 8453, // Base
sourceToken: 0xETH,
sourceAmount: 1e18, // 1 ETH
destToken: 0xUSDC_BASE,
minDestAmount: 2400e6, // 2400 USDC
deadline: now + 300 // 5 min
})
// System:
1. Transfer sourceToken to contract (escrow)
2. Emit IntentSubmitted event
3. Off-chain indexer catches event
4. Add to order bookDefinition of Done:
- Smart contract function works on testnet
- Gas estimation accurate within 5%
- Events properly indexed
- User can see intent in UI after submission
User Story:
As a user with an intent to swap ETH for USDC,
I want the system to find another user wanting to swap USDC for ETH,
So we can transact directly without DEX slippage
Acceptance Criteria:
- Matching engine finds opposite intents in real-time —
MatchingLoopticks every 5s and runsfindOppositeIntent(Stage 4) - Matching accounts for prices (no user forced into bad trade) — both-sides
minDestAmountenforced off-chain AND re-validated on the destination chain via the LZ payload (Stage 3 R-16) - Only non-expired intents are matched —
findOppositeIntentfilters bydeadline > now - Users receive notification of match —
WS /ws?intentHash=broadcastsStateChangeevents from the publishing repository (Stage 4) - Matched intents transition through the on-chain state machine — Phase 1 is atomic so
Pending → Matched → Settled(theLockedenum is reserved for Phase 2B) - Settlement begins within 5 seconds of match — matcher tick cadence + on-chain
executeMatching(LZ delivery time depends on the cross-chain transport)
Matching Algorithm (Pseudocode):
def find_match(intent_a):
"""Find matching intent for intent_a"""
# Find opposite chains and tokens
candidates = order_book.filter(
source_chain == intent_a.dest_chain and
dest_chain == intent_a.source_chain and
source_token == intent_a.dest_token and
dest_token == intent_a.source_token
)
# Filter by time (not expired)
candidates = filter(c => c.deadline > now, candidates)
# Filter by price
valid_matches = []
for candidate in candidates:
# Candidate must get at least minDestAmount from intent_a
if intent_a.sourceAmount >= candidate.minDestAmount:
# Intent_a must get at least minDestAmount from candidate
if candidate.sourceAmount >= intent_a.minDestAmount:
valid_matches.append(candidate)
# Choose best match for intent_a (highest received amount)
if valid_matches:
return max(valid_matches, key=lambda x: x.sourceAmount)
return NoneDefinition of Done:
- Matching engine returns correct matches 100% of time
- No invalid matches (violating price constraints)
- Matching latency <5 seconds
- Tested with 100+ intent pairs
User Story:
As a user with an intent that can't be matched directly,
I want solvers to compete to execute my intent,
So I get the best price if no direct match exists
Acceptance Criteria:
- Auction automatically triggers after 30 seconds if no match —
AuctionOrchestratorcallsIntentSettler.openAuctionaftersubmittedAtBlockTs + AUCTION_DELAY(Stage 4) - Solvers can query unmatched intents via API —
GET /api/intents/auctioning?chainId=returns the active set (Stage 4) - Solvers submit signed proposals with output amount —
POST /api/solver/proposalsverifies the on-chainSolverAuction.proposalDigestECDSA signature before persisting (Stage 4); reference solver bot inbackend/src/bot/solver-bot.ts - Protocol selects highest-price proposal —
SolverAuction.selectWinnerranks deterministically byproposedOutputAmount(Stage 3) - Winning solver executes on-chain —
AuctionOrchestratorcallsexecuteWinningProposalafter the auction window closes (Stage 4) - User receives expected tokens — verified end-to-end on testnet (Stage 8)
Solver Proposal Format (matches contracts/src/SolverAuction.sol):
struct SolverProposal {
address solver; // recorded from msg.sender at submitProposal
uint256 proposedOutputAmount;
uint256 solverFeeBps; // basis points (100 bps = 1%)
bytes signature; // EIP-191/EIP-712 signed by solver (validated in Stage 3)
}The intent hash is the mapping key (mapping(bytes32 => SolverProposal[])),
so it is not duplicated inside the struct. The solver field is set on-chain
at submission time so off-chain readers can identify the bidder without
recovering the signature.
Solver Auction Workflow:
t=0s: Intent submitted, no match found
t=30s: Auction begins, solvers notified
t=30-60s: Solvers submit proposals
t=60s: Auction closes, best proposal selected
t=60-120s: Winning solver executes
t=120-180s: Cross-chain settlement completes
Definition of Done:
- Solver auction mechanism works on testnet
- Multiple solvers can submit proposals
- Best proposal always selected
- Winning solver execution succeeds
User Story:
As a protocol,
I need to atomically settle matched intents across Ethereum and Base,
So tokens move correctly on both chains
Acceptance Criteria:
- Ethereum settlement contract sends LayerZero message to Base (Stage 2:
_lzSend(EXECUTE_MATCH)fromexecuteMatching) - Base receives message and validates source via OApp peer check (Stage 2:
_handleExecuteMatch) - Tokens are released on Base to Ethereum user (Stage 2: trusted source-user address read from
EXECUTE_MATCHpayload, populated from source storage on Ethereum) - Base sends confirmation back to Ethereum (Stage 2:
_lzSend(CONFIRM)from_handleExecuteMatch) - Ethereum confirms and releases tokens to Base user (Stage 2:
_handleConfirmreleases todestUserfrom CONFIRM payload, populated from Base storage) - If one chain fails, the source user can refund via
refundIfLzTimeoutafterLZ_TIMEOUT = 30 minutes(Stage 2) - Extensibility: settlement paths use
ChainPeerRegistry+ OAppsetPeer, not hardcoded branches;intent.destChainIddrives routing throughregistry.lzEidForChain(...) -
ChainPeerRegistrydeployed on Ethereum and Base with correct LayerZero EIDs andsetRouteSupportedfor Phase 1 corridors — Stage 8 (deploy script) - Settlement completes within 5 minutes on real LayerZero — measured during testnet (Stage 8); unit-test round-trip via
MockLzEndpointis instantaneous
Settlement States (canonical names match the on-chain enum):
Pending → Matched ──[LZ CONFIRM delivery]──→ Settled
If LayerZero never delivers within LZ_TIMEOUT (30 minutes):
Matched ──[refundIfLzTimeout]──→ Refunded
Phase 1 settlement is atomic — the destination chain validates and
releases tokens in a single transaction within _lzReceive, with no
observable "Locked" window. The Locked enum value is reserved for
Phase 2B async-settlement designs.
Code Skeleton:
// IChainPeerRegistry registry — per-chain deployment; use registry.lzEidForChain(...) for _lzSend
// Phase 1: Lock on source chain
function executeMatching(bytes32 ethIntentHash, bytes32 baseIntentHash) {
// Lock ETH on Ethereum
Intent memory ethIntent = intents[ethIntentHash];
require(ethIntent.state == IntentState.MATCHED);
ethIntent.state = IntentState.LOCKED;
// Send message to destination chain (LayerZero dstEid from registry, not a literal)
// Include source chain id for return-path routing — avoids hardcoding “confirm to Ethereum”
bytes memory payload = abi.encode(
uint8(1), // messageVersion — increment when payload shape changes
baseIntentHash,
ethIntent.sourceAmount,
ethIntent.user,
ethIntent.sourceChainId
);
_lzSend(registry.lzEidForChain(ethIntent.destChainId), payload, options);
}
// Phase 2: On destination chain — receive from source (LayerZero delivers _srcEid + payload)
function _lzReceive(uint32 _srcEid, bytes memory payload) {
// require(registry.isTrustedPeer(_srcEid));
(
uint8 messageVersion,
bytes32 remoteIntentHash,
uint256 amount,
address userEth,
uint256 originalSourceChainId
) = abi.decode(payload, (uint8, bytes32, uint256, address, uint256));
require(messageVersion == 1, "bad version");
Intent memory baseIntent = intents[remoteIntentHash];
require(baseIntent.state == IntentState.MATCHED);
baseIntent.state = IntentState.LOCKED;
// Release USDC to Ethereum user on Base
USDC_BASE.transfer(userEth, baseIntent.sourceAmount);
_lzSend(registry.lzEidForChain(originalSourceChainId), abi.encode(remoteIntentHash), options);
}
// Phase 3: Confirm on source chain (receive from destination)
function _lzReceiveConfirm() {
ethIntent.state = IntentState.SETTLED;
ETH.transfer(baseIntentUser, ethIntent.sourceAmount);
}Definition of Done:
- Cross-chain message delivery reliable
- Atomicity: both chains settle or both refund
- Timeout works correctly
- No stuck funds or partial settlements
- Adding a later chain is primarily new deployment +
setPeer/ route config, not a rewrite of settlement logic (see Architecture — Multi-chain extensibility)
Pages:
-
Dashboard/Landing
- Connect Wallet button
- Quick stats (total volume, users, recent trades)
- Link to swap interface
-
Swap Interface
- Input: Amount + Token on source chain
- Input: Destination chain + token
- Display: Min received (with slippage %), fee, time estimate
- Button: "Create Intent"
- Loading: "Matching your intent..." with progress
-
Intent Status Page
- Shows intent state: MATCHING → MATCHED → LOCKED → SETTLED
- Real-time updates via WebSocket
- Link to both transactions (Ethereum + Base)
- Ability to cancel (if not yet matched)
-
Transaction History
- Table of past intents
- Status, amounts, fees, timestamps
- Etherscan links
UI Requirements:
- Mobile responsive
- Dark mode
- Clear error messages
- Loading states
- Animation on successful settlement
Definition of Done:
- All pages functional — Stage 5.Z complete: across-style swap with combined token+chain picker, state-driven status page with live solver-bid feed and per-state tx-hash explorer chips, paginated history, glass-card design system; all five routes build green
- Works on MetaMask — wagmi v3 multi-wallet picker (MetaMask SDK / Coinbase / WalletConnect / Safe / Injected); end-to-end verified on local-stack
- Responsive design (mobile, tablet, desktop) — picker stacks single-column on mobile, swap-amount font scales, footer + header reflow at 375px
- No console errors — CORS middleware in
backend/src/server.tsallowlists localhost:3000 + 127.0.0.1:3000 (closed #29) - UX tested with 5+ users — Phase 1 milestone, after testnet deploy (Stage 8)
User Story:
As a user,
I want to cancel my intent if it hasn't been matched,
So I can get my tokens back
Acceptance Criteria:
- Only
PendingorAuctioningintents can be cancelled by user - After
intent.deadline, intent can be cancelled by anyone (permissionless cleanup) - Cancellation refunds tokens to
intent.refundTo(orintent.userifrefundTo == address(0)) - Gas cost for cancellation is bounded — measured ~50k incremental (~280k including the test setup that deploys + submits + cancels)
The original "<50k total" target was unrealistic — two SSTOREs alone consume 25–40k gas. The measured incremental cost (post-warm storage, excluding setup/deploy) is ~50k, which is the right number to compare against L1 typical fees.
Definition of Done:
- Cancellation works on testnet
- Only valid intents can be cancelled
- Refund is accurate
Language: Solidity 0.8.20+
Framework: Foundry or Hardhat
Dependencies:
- OpenZeppelin Contracts 5.0
- LayerZero OApp (V2)
Chain IDs:
- Ethereum: 1 (mainnet) / 11155111 (sepolia testnet)
- Base: 8453 (mainnet) / 84532 (sepolia testnet)
Language: Node.js / TypeScript
Framework: Express or Fastify
Database: PostgreSQL
Message Queue: Redis (for event processing)
Blockchain Interaction: ethers.js v6
Services:
- Event Indexer (TypeScript service listening to events)
- Matching Engine (runs every 5 seconds)
- Auction Orchestrator (manages solver auction lifecycle)
- API Server (exposes intents, allows solver queries)
Framework: React 18+
Web3 Integration: wagmi + viem
Wallet: MetaMask
Styling: Tailwind CSS or similar
State Management: TanStack Query
Real-time: WebSocket for intent status
Smart Contracts:
- IntentSettler.submitIntent() validation
- Matching logic (multiple test cases)
- Solver auction winner selection
- Cross-chain message handling
- Cancellation logic
Backend:
- Matching algorithm edge cases
- Order book state management
- Proposal ranking
Target: 90%+ code coverage
Full End-to-End Flows:
Test 1: Direct P2P Match
1. User A submits intent: 1 ETH on Eth → 2400 USDC on Base
2. User B submits intent: 2400 USDC on Base → 1 ETH on Eth
3. System finds match within 5 seconds
4. Cross-chain settlement completes
5. User A has 2400 USDC on Base
6. User B has 1 ETH on Ethereum
Test 2: No Match → Solver Auction
1. User A submits intent
2. System waits 30 seconds, no match found
3. Solver auction begins
4. Solver submits proposal
5. Settlement completes
Test 3: Intent Expiration
1. User submits intent with 2-minute deadline
2. Deadline passes
3. User cancels intent
4. Tokens refunded
Test 4: Partial Match (Price Mismatch)
1. User A: 1 ETH → 2400 USDC min
2. User B: 2300 USDC → 1 ETH min
3. System: No match (User A wouldn't accept 2300)
- 100 concurrent intents
- 50 intents/second submission rate
- Matching latency <5 seconds even under load
- Reentrancy checks
- Integer overflow/underflow
- Invalid signature detection
- Replay attack prevention
- Double-settlement prevention
-
Sepolia Testnet (Ethereum + Base)
- Deploy all contracts
- Test with 50+ testnet transactions
- Internal testing for 1 week
-
Goerli/Sepolia Staging (if needed)
- Test with multiple solvers
- Test recovery flows
- 1+ week live staging
-
Production Mainnet
- Begin with $1k daily limit per user
- Gradual ramp: $1k → $10k → $100k → unlimited
- Monitor for 2 weeks before full launch
- Smart contracts deployed to Sepolia
- Backend indexer running
- Frontend connected to testnet
- Internal testing with 10+ transactions
Go/No-Go Decision: All tests pass + no critical issues
- Same as testnet but with more realistic load
- Deploy 2-3 test solvers
- Test failure scenarios (LayerZero downtime, etc.)
- Fine-tune gas costs
Go/No-Go Decision: All edge cases handled
Week 7:
- Deploy contracts to Ethereum + Base
- Transfer $100k liquidity to first settlement
- Limited launch: invite-only, 100 users max
- Monitor for 24 hours
Week 8:
- Open to public
- Gradually increase transaction limits
- Daily monitoring of settlement reliability
- All unit tests pass (100%)
- All integration tests pass (100%)
- No smart contract vulnerabilities (audit)
- Gas costs within 20% of estimate
- Matching latency <5 seconds consistently
- Zero failed settlements (100% success rate)
- Average settlement time 3-5 minutes
- P2P match rate >60%
- User satisfaction >4.5/5 (if surveyed)
- $10k+ daily volume
- Zero security incidents
- $100k+ daily volume by month 3
- 1000+ active users
- <0.15% average slippage (better than bridges)
- Multiple solvers competing
| Risk | Probability | Impact | Mitigation |
|---|---|---|---|
| LayerZero downtime | Medium | High | Implement fallback (simple refund path) |
| Smart contract bug | Low | Critical | Audit + extensive testing + gradual rollout |
| Solver censorship | Medium | Medium | Easy solver SDK, incentive program |
| Matching engine bug | Low | High | 100% test coverage, peer review |
| Risk | Probability | Impact | Mitigation |
|---|---|---|---|
| Solo founder burnout | Medium | High | Hire co-founder/developer early |
| Market adoption slow | High | Medium | Strong marketing, partnerships |
| Regulatory scrutiny | Low | High | Conservative design, legal review |
Hard Dependencies:
- LayerZero V2 deployed on Base (already live)
- USDC on Base (already live)
- ETH bridge to Base (already live)
External Factors:
- Base network stability (assumed 99.9%)
- LayerZero reliability (assumed 99.5%)
- Ethereum network not under attack
Week 1-2: Smart contract development + testing
Week 3-4: Backend development (indexer, matching, API)
Week 5: Frontend development
Week 6: Integration testing + deployment
Week 7: Testnet launch + internal testing
Week 8: Security audit
Week 9: Staging environment
Week 10: Mainnet launch (limited)
Week 11: Monitor + optimize
Week 12: Full public launch
| Version | 1.1 |
| Last updated | 2026-05-09 |
| Status | Phase 1 feature-complete locally. Contracts (94 Foundry tests), backend (109 unit + 4 E2E tests), frontend (Stage 5.Z), gas benchmarks all done. Open items: testnet deploy (Stage 8), internal security review (Stage 7), external audit before high-volume mainnet. |
| Owner | Maintainers (see README) |