Non-custodial cross-chain atomic swap — Ethereum · Stellar · Solana
No validator set. No attester. No admin escape hatch.
Sepolia Contract · Stellar Testnet · CI
WaffleFinance locks funds in Hash Time-Lock Contracts (HTLCs) on each chain simultaneously. Settlement is a sha256 preimage reveal — not a multisig, not an attester signature.
If anything fails — coordinator down, resolver offline, RPC unavailable, frontend unreachable — locked funds either settle to the beneficiary or refund permissionlessly to the user. There is no state where funds are stuck under operator control.
Status: Live on testnet (Sepolia + Stellar testnet + Solana devnet). Mainnet gated until independent audit (Q1 2027).
| Chain | Asset | Status |
|---|---|---|
| Ethereum (Sepolia) | ETH | ✅ Live |
| Stellar | XLM | ✅ Live |
| Solana | SOL | ✅ Live |
User locks ETH (24h timelock) → Resolver locks XLM/SOL (12h timelock)
↓
User claims XLM/SOL, revealing secret
↓
Resolver claims ETH using secret ← Secret is now public on-chain
Both legs settle, or both legs refund. The 12h vs 24h timelock gap ensures the resolver's destination refund always expires before the user's source — so neither party can ever be stuck.
Funds move under exactly two conditions:
- A caller submits a preimage where
sha256(preimage) == hashlockbeforetimelock— funds go tobeneficiary timelockhas expired — anyone callsrefundOrderand funds return torefundAddress(always the original user)
Robust native-ETH payout. A beneficiary / refundAddress that is a smart contract may revert on receipt or exhaust the bounded gas stipend. Rather than letting that block a settlement backed by a valid preimage or an expired timelock, HTLCEscrow attempts a direct push and, if it fails, credits the amount to the recipient's pull-payment balance instead of reverting. The claim/refund still finalises (the preimage is revealed on-chain either way), and the recipient — and only that recipient — recovers the funds permissionlessly via withdraw(). This adds no custodial surface: credited funds are never pooled or operator-movable, and withdraw() can only return a caller's own balance, never locked order funds.
The coordinator is a metadata service that never signs transactions touching user funds. Resolvers stake into ResolverRegistry; misbehaviour is slashable on-chain.
| Attack vector | Validator-set bridge | WaffleFinance |
|---|---|---|
| Compromise off-chain signers | Funds lost | No effect — no signers |
| Compromise first-party attester | Funds lost | No effect — no attesters |
| Break sha256 | Safe | Funds at risk (breaks all of crypto) |
| Compromise chain consensus | Funds at risk | Funds at risk (inherited) |
| Contract | Chain | Address |
|---|---|---|
HTLCEscrow |
Sepolia | 0xb352339BEb…988bB178 |
ResolverRegistry |
Sepolia | 0x7D9ce70Aa4…1B6D1D99 |
wafflefinance-htlc |
Stellar testnet | CDIKSJKV…CTA6JK |
wafflefinance-resolver-registry |
Stellar testnet | CBSR7Z4M…Z4WGF |
| Anchor HTLC | Solana devnet | Pending deployment |
Four independent recovery mechanisms — each a backstop for the previous one.
| Layer | Trigger | Latency |
|---|---|---|
| On-chain HTLC refund | timelock expires; anyone calls refundOrder |
≤ 24h |
| Frontend refund dialog | "Refund" button in transaction history | User-driven |
| Automatic refund | Destination leg fails mid-request; relayer refunds inline | < 30s |
| Background watchdog | Swap pending > 5 min; background scanner fires | < 6 min |
Even with the coordinator, relayer, and frontend all offline, layer 1 alone is sufficient — the user calls refundOrder directly from any wallet.
contracts/ Solidity — HTLCEscrow + ResolverRegistry (Ethereum)
soroban/ Rust — Soroban HTLC + ResolverRegistry (Stellar)
packages/
sdk/ @wafflefinance/sdk — shared TS types, asset mappings,
state machine, Solana + Stellar + Ethereum HTLC clients
coordinator/ Order book service (SQLite/Postgres, REST, never holds keys)
src/
listeners/ Ethereum + Soroban + Solana event listeners
services/ OrderService, SecretService, QuoteService
persistence/ Schema, migrations, repository
server/ Express routes (/orders, /quotes, /secrets, /metrics)
state-machine/ Shared order state machine
migrations/
001_initial.sql Base schema
002_solana_support.sql Adds solana to Chain/Direction constraints
relayer/ Bridge relay service
src/
listeners/ Block polling, contract event poller
services/ Gas tracker, refund watchdog, XLM refund, recovery
resolver/ Open-source resolver runner + Docker image
frontend/ React + Vite dApp (Ethereum · Stellar · Solana)
e2e/ Cross-chain differential test harness
The supported build, test, lint, and smoke-test entry points for every package are documented in docs/COMMANDS.md — start there to find the right command for the package you're touching. New to the repo? Start with the Contributor Handbook instead — it maps package boundaries to validation checklists. For how the pieces fit together end to end, see docs/ARCHITECTURE.md.
Dev container (recommended): open the repo in VS Code with the Dev Containers extension. VS Code will prompt you to reopen in the container — it installs Node 22, pnpm, Rust, stellar-cli, and Foundry automatically.
Native setup — requirements: Node 22.5+, pnpm 8+, Rust stable + wasm32-unknown-unknown target, stellar-cli, Foundry.
git clone https://github.com/Waffle-finance/waffle-finance-core
cd waffle-finance-core
pnpm install
cp env.example .env # fill in RPC URLs and private keys# Build shared SDK (required before anything else)
pnpm --filter @wafflefinance/sdk build
# Compile + test Solidity contracts
pnpm --filter @wafflefinance/contracts exec hardhat test
# Test Soroban contracts
cd soroban && cargo test && cd ..
# Start coordinator
pnpm --filter @wafflefinance/coordinator dev
# Seed with demo data for local development (optional)
pnpm --filter @wafflefinance/coordinator seed-demo
# Start frontend
pnpm --filter @wafflefinance/frontend devSee docs/DEVELOPMENT.md for per-package commands, PostgreSQL setup, Stellar contract deployment, and troubleshooting notes.
See docs/OPERATIONS.md for deployment checklists, incident response runbooks, and monitoring guidance.
See docs/TECHNICAL_DEBT.md for the service-level technical debt register and roadmap — architectural gaps, known limitations, and planned improvements across all services.
See docs/QUALITY_GATE.md for the contract that keeps code, runtime config, and docs in sync — including a running list of drift found in the repo.
See docs/DEPLOYMENT_ROLLBACK_RUNBOOK.md for the rollback-first deployment procedure for the coordinator and relayer.
See docs/RELEASE_CONTRACT.md for the typed build/release contract covering every package, including known gaps in local release verification.
See docs/SMOKE_TEST_CONTRACT.md for the repo-wide smoke test contract spanning coordinator readiness, order announcement, SDK init, and the frontend entry point.
See docs/RPC_DEGRADATION_TEST_MATRIX.md for the deterministic multi-chain RPC degradation test matrix — proving the coordinator, relayer, and resolver degrade honestly under delayed, reset, and partial-receipt RPC conditions.
See docs/PERFORMANCE_BASELINE.md for the measurable performance baseline covering order lookup, announcement, event replay, and stale-order cleanup.
| Wallet | Chain | Hook |
|---|---|---|
| MetaMask | Ethereum | window.ethereum |
| Freighter | Stellar | useFreighter() |
| Phantom | Solana | useSolanaWallet() |
All three wallets can be connected simultaneously from the wallet menu. The bridge form automatically selects the correct wallets based on the chosen route.
The Solana leg is fully wired end-to-end:
- SDK —
SolanaHTLCClientinpackages/sdk/src/solana/handlescreateOrder,claimOrder,refundOrderwith real Anchor instruction builders and account deserialization. - Relayer —
ConfiguredSolanaIntegrationinrelayer/src/services/solana-contract.tssubmits real Solana transactions for lock, claim, and refund operations. - Coordinator —
SolanaListenerpolls RPC for HTLC program logs and forwardsOrderCreated,OrderClaimed,OrderRefundedevents intoOrderService. - DB —
Chaintype includes"solana",Directionincludes"eth_to_sol"and"sol_to_eth". Migration002_solana_support.sqlupgrades existing databases. - Frontend —
useSolanaWallet()handles Phantom connection. Route selector inBridgeFormexposes all four routes. - Asset mappings —
resolveSolanaAsset()andresolveEthereumTokenFromSolana()inpackages/sdk/src/assets/cover testnet (devnet USDC) and mainnet (native SOL).
To enable Solana settlement, set:
SOLANA_RPC_URL=https://api.devnet.solana.com
SOLANA_HTLC_PROGRAM=<your_program_id>
SOLANA_PRIVATE_KEY=<your_relayer_keypair>Anyone who stakes into ResolverRegistry can run a resolver.
docker run ghcr.io/wafflefinance/resolver:latest register
docker run ghcr.io/wafflefinance/resolver:latest runSee resolver/ for environment variable reference.
cp env.example .env
# Sepolia testnet
pnpm --filter @wafflefinance/contracts exec hardhat run scripts/deploy.ts --network sepolia
# Mainnet (after audit)
pnpm --filter @wafflefinance/contracts exec hardhat run scripts/deploy.ts --network mainnetDeployment addresses are written to deployments.<network>.json and picked up automatically by the coordinator and frontend.
| Layer | Tests | Framework |
|---|---|---|
| Soroban HTLC | 10 | Rust #[contracttest] |
| Soroban ResolverRegistry | 6 | Rust #[contracttest] |
| EVM HTLCEscrow | 15 | Hardhat + Chai |
| EVM ResolverRegistry | 6 | Hardhat + Chai |
| SDK | 8 | Vitest |
| Coordinator | 4 | Vitest |
All suites gate every pull request via GitHub Actions.
All environment variables across the monorepo packages are consolidated and validated using the shared @wafflefinance/config package (under packages/config). Invalid or missing values fail fast with clear, actionable validation messages at startup.
| Variable | Used by | Description |
|---|---|---|
ETHEREUM_RPC_URL |
relayer, coordinator | Sepolia or mainnet RPC |
RELAYER_PRIVATE_KEY |
relayer | ETH signing key |
RELAYER_STELLAR_SECRET |
relayer | Stellar signing key |
SOLANA_RPC_URL |
coordinator | Solana RPC endpoint |
SOLANA_HTLC_PROGRAM |
coordinator, relayer | Anchor program ID (leave blank to disable Solana) |
NETWORK_MODE |
relayer, frontend | testnet or mainnet |
VITE_MAINNET_ENABLED |
frontend | Set true post-audit to unlock mainnet UI |
Full reference in env.example.
MIT. See LICENSE.