Self-host & deploy
Stand up your own Arcora — env vars, infra, and the runbook.
The Arcora codebase is open source — github.com/arcoralabs/arcorapay. Most merchants will use the hosted version at arcorapay.xyz; if you need to self-host (compliance, branding, sovereignty), here's the shape.
Components
| Service | What it does |
|---|---|
packages/app (Next.js on Vercel) | Hosted checkout, merchant dashboard, REST API |
packages/contracts (Foundry) | ArcFXGateway — custody-escrow gateway. Live testnet deployment at 0x07BAC123…aE3a3 (recorded in packages/contracts/deployments/arc-testnet.json); deployed via script/Deploy.s.sol |
ops/relayer (single VPS) | Drains the Permit2 queue, runs kit.swap, calls settleInvoice |
ops/indexer (same VPS) | Watches gateway events, writes invoice status, enqueues webhooks |
| Postgres (Supabase or self-hosted) | Invoices, queue, merchant rows, compliance audit log |
Required env vars
App (packages/app)
POSTGRES_URL_NON_POOLING direct DB URL (used by API + indexer; e.g. Supabase :5432)
POSTGRES_URL pooled URL (used by Vercel functions; e.g. Supabase :6543)
GATEWAY_ADDRESS custody-escrow gateway (current ArcFXGateway address)
NEXT_PUBLIC_GATEWAY_ADDRESS same address, exposed to client components
USDC_ADDRESS on Arc
EURC_ADDRESS on Arc
ARC_TESTNET_RPC https://rpc.testnet.arc.network
NEXT_PUBLIC_RELAYER_ADDRESS the relayer EOA you'll fund
KIT_KEY Circle App Kit API key
PUBLIC_BASE_URL https://yourdomain
SESSION_SECRET 32+ bytes random; for SIWE cookie sealing
WEBHOOK_SECRET_AES_KEY 32 bytes; encrypts merchant webhook secrets at restOptional (compliance):
COMPLIANCE_PROVIDER noop | elliptic | trmlabs (default noop)
COMPLIANCE_API_KEY required for non-noop
COMPLIANCE_FAIL_OPEN_FOR_PAY default false (fail-closed for customer pay)
COMPLIANCE_FAIL_OPEN_FOR_INVOICE default true (fail-open for invoice creation)Relayer + indexer (VPS)
Same as app, plus:
RELAYER_PK relayer wallet private key (encrypted in DB; ops/relayer derives)
INDEXER_TICK_MS default 30000
INDEXER_REORG_BUFFER_BLOCKS default 5
RELAYER_TICK_MS default 5000Database
Drizzle migrations live at packages/app/lib/db/migrations/. Two-step apply:
vercel env pull .env.production.local(or copy your env)pnpm exec drizzle-kit migratefrompackages/app
If the __drizzle_migrations table is out of sync (manual SQL applied earlier), run the SQL files directly inside a transaction and backfill __drizzle_migrations once so future migrations apply cleanly.
Deploying the gateway
cd packages/contracts
forge script script/Deploy.s.sol \
--rpc-url $ARC_TESTNET_RPC \
--broadcast --slow --verify \
--verifier-url $ARC_EXPLORER_URL \
--etherscan-api-key $ARC_EXPLORER_KEYThen verify with cast receipt (status=1) and cast code (non-empty bytecode) before trusting the broadcast file, repoint every consumer env (Vercel + VPS daemons), and restart the daemons. We run an internal deploy checklist covering exactly that ground before every redeploy — do the same before mainnet.
Operational reality
- Single-instance relayer — the canonical deploy runs one relayer on one VPS. Multi-relayer with rolling failover is on the v1.x ops list.
- Foundry broadcast can lie on Arc testnet — always verify with
cast receipt(status=1) ANDcast code <addr>(non-empty bytecode). - Drizzle tracking on prod is empty — historical SQL was applied directly, so
drizzle-kit migratewould try to re-apply 0000–0005. Backfill__drizzle_migrationsonce or keep applying SQL directly.