Headless TypeScript server for cross-chain swap fee comparison across multiple providers.
pnpm installCreate a .env file:
ETH_PRIVATE_KEY=0x...
BTC_PRIVATE_KEY=K... or L... # WIF format
ALCHEMY_API_KEY=your-alchemy-api-key
NEAR_INTENTS_JWT=your-jwt-token
GARDEN_API_KEY=your-garden-api-key
BTC_API_BASE=https://your-esplora-node.com
pnpm go # Production run (no hot reload)
pnpm go --execute # Force execute swaps
pnpm go --no-execute # Quotes only (no execution)
pnpm dev # Development with hot reload
pnpm quote:near -- --amount 1000 # One-off Near Intents quote (default USDC -> BTC)pnpm quote:near -- --amount 1000
pnpm quote:near -- --amount 1000 --input USDC --output BTC --rawShows a live countdown to the next swap cycle:
💱 Execute swaps: YES
👀 Settlement watcher started
⏳ Next: EVM → BTC in 1h 59m 45s
Live dashboard at http://localhost:3457 - auto-refreshes every 5 seconds. Click any swap journey to see full details including transaction hashes and explorer links.
| Variable | Description |
|---|---|
ETH_PRIVATE_KEY |
EVM private key (0x...) for signing transactions |
BTC_PRIVATE_KEY |
Bitcoin private key in WIF format (starts with K, L, or 5) |
ALCHEMY_API_KEY |
Alchemy API key for Ethereum mainnet |
NEAR_INTENTS_JWT |
JWT token from Near Intents Partners Portal |
GARDEN_API_KEY |
API key for Garden Finance (optional) |
BTC_API_BASE |
Esplora-compatible Bitcoin API base URL (optional, defaults to mempool.space) |
| Provider | Supported Pairs | Notes |
|---|---|---|
| Rift | BTC ↔ CBBTC, USDC, ETH | Native BTC swaps |
| Relay | BTC ↔ CBBTC, USDC, ETH | Cross-chain relay |
| THORChain | BTC ↔ USDC, ETH | No CBBTC support |
| Chainflip | BTC ↔ USDC, ETH | No CBBTC support |
| Garden | BTC ↔ CBBTC | CBBTC only |
| Near Intents | BTC ↔ CBBTC, USDC, ETH | Via 1Click API |
Enable/disable providers in src/constants.ts:
const PROVIDERS = {
rift: false,
relay: false,
thorchain: false,
chainflip: false,
garden: false,
nearintents: true,
}Runs for 7 days, alternating every 2 hours between BTC→EVM and EVM→BTC directions.
Configure swap pairs and USD amounts in src/constants.ts. Amounts are defined in USD and resolved to token amounts at runtime using live prices.
Swaps are executed non-blocking. A background watcher polls for settlement status every 30 seconds:
────────────────────────────────────────────────────────────
🔍 Settlement Check (1 pending)
────────────────────────────────────────────────────────────
⏳ [🌐NearIntents] CBBTC→BTC (0.0002) | 5m | PROCESSING
────────────────────────────────────────────────────────────
Settlements timeout after 24 hours if not completed.
All activity is logged to data.csv in the project root with a type column:
quote- Quote data (timestamp, provider, tokens, amounts, fees)swap- Executed swaps (swap ID, status)settlement- Settlement results (payout tx hash, actual output amount)
src/
index.ts # Entry point & scheduler
constants.ts # Timing, swap definitions, config
account.ts # Wallet config, BTC/EVM sending
csv.ts # CSV logging
prices.ts # Token price fetching
server.ts # Dashboard HTTP server
settlement-tracker.ts # Background settlement watcher
provider-health.ts # Per-provider swap health tracking
providers/
types.ts # Common provider interface
rift.ts # Rift SDK
relay.ts # Relay Protocol
thorchain.ts # THORChain
chainflip.ts # Chainflip
garden.ts # Garden Finance
nearintents.ts # Near Intents (1Click API)
Implement the provider interface in src/providers/:
export const myprovider = {
name: 'MyProvider',
supportsSwap(inputToken: string, outputToken: string): boolean,
getQuote(params: SwapParams): Promise<{ quote: Quote, execute: () => Promise<SwapResult> }>,
checkSettlementOnce(swapId: string, verbose: boolean): Promise<SettlementResult | null>,
getStatusString(swapId: string): Promise<string>,
}Then register in src/settlement-tracker.ts and enable in src/index.ts.