A full-stack decentralised application for swapping USDC, EURC, and cirBTC on Arc Network Testnet — powered by Circle's App Kit SDK, with on-chain fee collection and real-time analytics.
Arc Swap is a production-grade DApp with a hardened Express API backend and a React frontend. Every interaction is validated on both ends.
Swap USDC, EURC, and cirBTC on Arc Network Testnet via Circle's App Kit SDK. Real-time quotes before every execution.
Get precise pre-swap estimates with exchange rate, platform fee breakdown, and estimated output before committing any transaction.
Every quote calculates price impact and displays it colour-coded: green (<1%), amber (1–3%), red (>3%). High-impact swaps show prominent warnings.
Choose from 0.1%, 0.5%, or 1.0% slippage presets or enter a custom value. Settings are applied per-session with a visual badge when changed.
A 0.3% (30 BPS) platform fee is deducted from every swap. Fees are collected to a secure backend wallet and tracked in a dedicated dashboard.
Every executed swap is written to PostgreSQL via Drizzle ORM. History survives server restarts and is available via REST API.
Connects with MetaMask (injected) out of the box. WalletConnect v2 is supported when a project ID is configured, enabling mobile wallets.
A real-time dashboard displays cumulative fee totals per token and a chronological log of every fee event, auto-refreshing every 30 seconds.
CORS whitelisting, rate limiting (estimate: 30/min, execute: 5/min, global: 300/15min), input validation, and no silent error fallbacks.
Arc Swap is organised as a pnpm workspace monorepo. The API server and frontend are independent artifacts sharing a typed contract via code-generated hooks.
/api/User Wallet │ connects via MetaMask / WalletConnect ↓ React Frontend (Vite) │ wagmi hooks → useEstimateSwap / useExecuteSwap (generated) │ 1. Fee transfer: user sends 0.3% → fee wallet (ERC-20 approve + transferFrom) │ 2. Swap call: effectiveAmountIn sent to Circle App Kit ↓ Express API /api/swap/estimate /api/swap/execute │ validates input, checks rate limit, calculates fee │ calls Circle App Kit → Arc Testnet RPC │ writes SwapHistory + FeeEarning records to Postgres ↓ Arc Network Testnet (Chain ID 5042002) └─ transaction confirmed → explorerUrl returned
All tokens are ERC-20 contracts deployed on Arc Network Testnet. Obtain testnet balances from the Circle faucet or Arc testnet faucet.
| Property | Value |
|---|---|
| Network Name | Arc Testnet |
| Chain ID | 5042002 |
| RPC URL | https://rpc.testnet.arc.network |
| Block Explorer | https://testnet.arcscan.app |
| Native Currency | ETH (18 decimals) |
| Fee Wallet | 0xf4a14B84108885AF2f18843DD18761706e47d5F6 |
All endpoints are prefixed with /api. The spec is defined in OpenAPI 3.1 and client hooks are generated via Orval.
Returns server status. Used by the proxy for health checks.
{ "status": "ok" }
Returns a pre-swap quote including platform fee deduction, estimated output, exchange rate, and price impact. Does not execute any transaction.
// Request { "tokenIn": "USDC", "tokenOut": "EURC", "amountIn": "10.00" } // Response { "tokenIn": "USDC", "tokenOut": "EURC", "amountIn": "10.00", "effectiveAmountIn": "9.97", // after 0.3% fee "estimatedAmountOut":"9.94", "exchangeRate": "0.997000", "fee": "0.000000", // network gas "platformFee": "0.030000", // 0.3% of amountIn "platformFeeAddress":"0xf4a1...d5F6", "priceImpact": "0.30" // % }
Executes the swap using the backend wallet via Circle App Kit. Persists the swap record and fee event to the database.
// Request (same shape as estimate) { "tokenIn": "USDC", "tokenOut": "EURC", "amountIn": "10.00" } // Response { "success": true, "transactionHash": "0xabc123...", "explorerUrl": "https://testnet.arcscan.app/tx/0xabc123...", "tokenIn": "USDC", "tokenOut": "EURC", "amountIn": "9.97", "amountOut": "9.94", "timestamp": "2026-05-14T04:32:00.000Z" }
Returns the 50 most recent swaps from PostgreSQL, ordered by most recent first.
{ "swaps": [ ...SwapResult ] }
Returns USDC, EURC, and cirBTC balances for a given address (or the backend wallet if omitted).
GET /api/wallet/balances?address=0x1234...
{
"address": "0x1234...",
"balances": [
{ "token": "USDC", "balance": "7.461800", "symbol": "USDC" },
{ "token": "EURC", "balance": "0.000000", "symbol": "EURC" },
{ "token": "cirBTC", "balance": "0.000000", "symbol": "cirBTC" }
],
"network": "Arc Testnet"
}
Returns cumulative fee totals grouped by token and a list of the 20 most recent fee events.
{
"totals": [
{ "token": "USDC", "totalFee": "0.150000", "eventCount": 5 }
],
"recent": [
{
"id": 1,
"token": "USDC",
"feeAmount": "0.030000",
"transactionHash": "0xabc...",
"swapAmountIn": "10.00",
"createdAt": "2026-05-14T04:32:00.000Z"
}
]
}
Returns public configuration values consumed by the frontend on startup.
{
"feeWalletAddress": "0xf4a14B84108885AF2f18843DD18761706e47d5F6",
"platformFeeBps": 30
}
| Endpoint | Limit | Window |
|---|---|---|
POST /api/swap/estimate | 30 requests | 1 minute |
POST /api/swap/execute | 5 requests | 1 minute |
| All endpoints (global) | 300 requests | 15 minutes |
Arc Swap collects a flat 0.3% platform fee on every swap. The fee is deducted from amountIn before the swap is executed, so the displayed output always reflects the post-fee amount.
amountIn = 100 USDC platformFee = 100 × 0.003 = 0.30 USDC → fee wallet effectiveAmountIn = 99.70 USDC → Circle App Kit swap estimatedAmountOut ≈ 99.40 EURC → user receives
The project runs on Node.js 24 in a pnpm workspace. All commands should be run from the repo root.
git clone https://github.com/osr21/arc-swap.git cd arc-swap pnpm install
Create a .env file or set the following secrets in your deployment environment:
DATABASE_URL=postgresql://user:pass@host:5432/dbname CIRCLE_KIT_KEY=your_circle_app_kit_key WALLET_PRIVATE_KEY=0xYOUR_BACKEND_WALLET_PRIVATE_KEY SESSION_SECRET=random_32_char_string # Optional — enables WalletConnect in the frontend VITE_WALLETCONNECT_PROJECT_ID=your_project_id
Get a Circle App Kit key at console.circle.com. Get a free WalletConnect Project ID at cloud.walletconnect.com.
pnpm --filter @workspace/db run push
This creates the swap_history and fee_earnings tables using Drizzle Kit.
# API server (port 8080) pnpm --filter @workspace/api-server run dev # Frontend (port 19345) pnpm --filter @workspace/arc-swap run dev
The DApp will prompt you to add the network automatically, or add it manually:
| Network Name | Arc Testnet |
| Chain ID | 5042002 |
| RPC URL | https://rpc.testnet.arc.network |
| Explorer | https://testnet.arcscan.app |
| Currency Symbol | ETH |
# Full typecheck (libs + all packages) pnpm run typecheck # Regenerate API hooks from OpenAPI spec pnpm --filter @workspace/api-spec run codegen # Build API server pnpm --filter @workspace/api-server run build # Schema changes → push to DB pnpm --filter @workspace/db run push
Arc Swap implements multiple layers of protection on both the client and server. No secrets are ever sent to the frontend.
Only requests from configured Replit domains (and localhost in dev) are accepted. All other origins are rejected.
Three tiers: estimate (30/min), execute (5/min), global (300/15min) — preventing abuse and front-running.
Token symbols are validated against an allowlist. Amounts are checked for range (0.01–1,000,000) and numeric validity server-side.
The backend wallet private key is stored as an environment secret and normalised at runtime. It is never logged or returned to clients.
Pino logger (never console.log) with request/response serialisers that strip sensitive fields before writing to stdout.
Express trust proxy is enabled so rate limiting correctly identifies clients behind Replit's reverse proxy.
API responses are validated against auto-generated Zod schemas before being returned, catching contract drift at runtime.
No silent fallbacks. Every failure surface is explicit — from invalid tokens to swap execution failures — with structured error responses.