Skip to content

aayusha59/chaingate

Repository files navigation

Self-Custody Crypto Payment System

A backend API for accepting cryptocurrency payments on your own apps — no Stripe, no Coinbase, no third-party custody. Funds go directly to your wallet.

How It Works

The server derives unique deposit addresses from your extended public key (xpub) using HD wallet derivation. It monitors the blockchain for incoming payments, tracks confirmations, and notifies your app via webhooks. Your private keys never touch the server.

                                                  ┌─────────────┐
                                                  │  Your App   │
                                                  │  (Merchant) │
                                                  └──────┬──────┘
                                                         │
                                              POST /invoices
                                              (create payment request)
                                                         │
                                                         ▼
┌──────────┐     ┌──────────────────────────────────────────────────────┐
│          │     │            Self-Custody Payment Server               │
│ Customer │     │                                                      │
│          │     │  ┌─────────┐  ┌──────────┐  ┌────────────────────┐  │
│  Sends   │────▶│  │ Invoice │  │ Address  │  │   Block Scanner    │  │
│  crypto  │     │  │ Service │  │ Deriver  │  │  (per-chain poll)  │  │
│  to addr │     │  └────┬────┘  └────┬─────┘  └────────┬───────────┘  │
│          │     │       │            │                  │              │
└──────────┘     │       │     xpub ──┘       Detects tx │              │
                 │       ▼                               ▼              │
                 │  ┌──────────┐              ┌──────────────────┐     │
                 │  │ Postgres │◀─────────────│ Webhook Service  │     │
                 │  │   (DB)   │              │ (HMAC + retries) │     │
                 │  └──────────┘              └────────┬─────────┘     │
                 │                                     │               │
                 └─────────────────────────────────────┼───────────────┘
                                                       │
                                            POST webhook callback
                                            (payment.confirmed)
                                                       │
                                                       ▼
                                                ┌─────────────┐
                                                │  Your App   │
                                                │ (notified!) │
                                                └─────────────┘

Live Demo

Tech Stack

Layer Technology
Runtime Node.js + TypeScript
Framework Express.js
Database PostgreSQL + Prisma ORM
Blockchain ethers.js v6 (EVM chains)
Validation Zod
Logging Winston
Testing Jest + ts-jest
Deployment Docker + docker-compose

Quick Start

With Docker (recommended)

# Clone and configure
cp .env.example .env
# Edit .env with your xpub, API key, and RPC URLs

# Start everything
docker-compose up -d

# API is live at http://localhost:3100
curl http://localhost:3100/api/v1/health

Without Docker

# Prerequisites: Node.js 22+, PostgreSQL 16+

# Install dependencies
npm install

# Configure environment
cp .env.example .env
# Edit .env with your database URL, xpub, etc.

# Run database migrations
npx prisma migrate dev

# Start the server
npm run dev

API Reference

All endpoints except health checks require the X-API-Key header.

Health

Method Endpoint Description
GET /api/v1/health Liveness check
GET /api/v1/health/ready Readiness (DB check)

Invoices

Method Endpoint Description
POST /api/v1/invoices Create invoice + deposit address
GET /api/v1/invoices List invoices (with filters)
GET /api/v1/invoices/:id Get invoice details + payments
POST /api/v1/invoices/:id/cancel Cancel unpaid invoice

Create Invoice:

curl -X POST http://localhost:3100/api/v1/invoices \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{
    "chainId": "ethereum",
    "amount": "0.05",
    "currency": "ETH",
    "expiresIn": 3600
  }'

Response:

{
  "id": "uuid",
  "status": "created",
  "chainId": "ethereum",
  "depositAddress": "0x...",
  "amount": "0.05",
  "currency": "ETH",
  "expiresAt": "2024-01-01T01:00:00.000Z"
}

Payments

Method Endpoint Description
GET /api/v1/payments/:id Get payment details
GET /api/v1/payments/invoice/:invoiceId List payments for an invoice

Webhooks

Method Endpoint Description
POST /api/v1/webhooks Register endpoint
GET /api/v1/webhooks List endpoints
GET /api/v1/webhooks/:id Get endpoint details
PUT /api/v1/webhooks/:id Update endpoint
DELETE /api/v1/webhooks/:id Deactivate endpoint
GET /api/v1/webhooks/:id/deliveries View delivery history

Chains

Method Endpoint Description
GET /api/v1/chains List supported chains
GET /api/v1/chains/:id/status Scanner health status

Invoice Lifecycle

  created ──▶ pending ──▶ confirming ──▶ confirmed
     │                                      │
     ▼                                      ▼
  expired                              overpaid / underpaid
Status Meaning
created Invoice created, awaiting payment
pending Transaction detected (0 confirmations)
confirming Has confirmations but below chain threshold
confirmed Payment reached required confirmations
expired No payment received before expiry
overpaid Received more than expected
underpaid Received less than expected

Webhook Events

Event Fired when
payment.detected Transaction seen on-chain (0 conf)
payment.confirmed Reached confirmation threshold
invoice.expired Invoice expired without payment

Webhooks are signed with HMAC-SHA256. Verify using the shared secret:

const crypto = require('crypto');

function verifyWebhook(body, secret, signature) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected, 'hex'),
    Buffer.from(signature, 'hex')
  );
}

Supported Chains

Chain Chain ID Confirmations Block Time
Ethereum 1 12 ~12s
Polygon 137 30 ~2s
BSC 56 15 ~3s
Arbitrum 42161 1 ~250ms

Chains are activated by providing CHAIN_{NAME}_RPC_URL in your environment. Only chains with an RPC URL configured will be scanned.

Self-Custody Model

You generate xpub from your hardware wallet / seed phrase
                │
                ▼
      Server receives xpub (public key only)
                │
                ▼
      Derives deposit addresses: m/44'/60'/0'/0/{index}
                │
                ▼
      Customer sends crypto to derived address
                │
                ▼
      Funds arrive in YOUR wallet ── you control the keys

The server uses HDNodeVoidWallet from ethers.js — it can derive addresses but cannot sign transactions or move funds. Your private keys stay in your hardware wallet.

Running Tests

npm test              # Run all tests
npm run test:watch    # Watch mode
npm run lint          # Type check

Environment Variables

See .env.example for all configuration options. Key variables:

Variable Required Description
DATABASE_URL Yes PostgreSQL connection string
API_KEY Yes API authentication key (min 16 chars)
XPUB Yes Extended public key (must start with xpub)
CHAIN_*_RPC_URL No RPC endpoint to activate a chain

License

MIT

About

api for self custodial crypto payments (wip)

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages