Self-hosted Crypto Payment
by @flacko2048
Add self-custodied crypto payment checkout to a Next.js + Supabase app. Accepts ETH, BTC, SOL, USDC, USDT and 25+ coins across 9 chains. No payment processor...
clawhub install self-hosted-cryptoπ About This Skill
name: self-hosted-crypto-payments description: > Add self-custodied crypto payment checkout to a Next.js + Supabase app. Accepts ETH, BTC, SOL, USDC, USDT and 25+ coins across 9 chains. No payment processor fees β HD wallet derivation, on-chain polling, zero third-party dependency. Use when: user asks to add crypto payments, accept Bitcoin/Ethereum/Solana, Web3 checkout, self-hosted crypto, no-middleman payments, blockchain payments, accept USDC/USDT. allowed-tools: - Bash - Read - Write - Edit - Glob - Grep env: required: - name: CRYPTO_MASTER_MNEMONIC description: "BIP39 mnemonic (12-24 words). Master key for HD wallet derivation. Server-only β never expose to client." sensitive: true - name: CRON_SECRET description: "32-char hex string. Protects /api/cron/check-crypto-payments from unauthorized calls." sensitive: true
Self-Hosted Crypto Payments
What This Skill Does
Adds a complete, production-ready crypto payment system to any Next.js 14+ App Router app backed by Supabase. No Stripe, no CryptAPI, no payment processor β you keep 100% of every payment.
Supported chains & coins: ETH, Base, Polygon, Arbitrum, Optimism, BNB Chain, Avalanche, Solana, Bitcoin β including USDC/USDT/DAI stablecoins on each chain.
Architecture: 1. User picks coin β server derives a unique HD wallet address (BIP44/84/SLIP-0010) 2. Server stores address + expected amount in DB with a 1-hour expiry 3. A cron job polls the actual blockchain every minute, verifies balance, confirms payment 4. On confirmation, credits/plan/addon is applied atomically to the user's profile
Security model: Mnemonic never touches client or source code. User cannot influence destination address or amount. Payment confirmation is blockchain-verified, not webhook-forgeable.
Files to Copy
All template files are in resources/. Copy them into your project at these paths:
| Resource file | Destination in your project |
|---|---|
| crypto-wallets.ts | src/lib/crypto-wallets.ts |
| crypto-checkout-route.ts | src/app/api/billing/crypto-checkout/route.ts |
| crypto-checkout-status-route.ts | src/app/api/billing/crypto-checkout/[id]/status/route.ts |
| check-crypto-payments-route.ts | src/app/api/cron/check-crypto-payments/route.ts |
| crypto-payment-modal.tsx | src/components/dashboard/crypto-payment-modal.tsx |
| 005_crypto_payments.sql | supabase/migrations/005_crypto_payments.sql |
| 006_crypto_direct.sql | supabase/migrations/006_crypto_direct.sql |
Installation Steps
1. Install dependencies
npm install ethers @scure/bip32 @scure/bip39 @scure/base @noble/hashes @solana/web3.js qrcode
npm install -D @types/qrcode
2. Run database migrations
In Supabase SQL editor (or via supabase db push), run both migration files in order:
005_crypto_payments.sql006_crypto_direct.sql3. Add environment variables
# Generate mnemonic: https://iancoleman.io/bip39/ (12 or 24 words β NEVER commit this)
CRYPTO_MASTER_MNEMONIC=word1 word2 word3 ... word12Protects the cron endpoint β generate with:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
CRON_SECRET=your-32-char-hex-string
4. Set up a cron job
The cron endpoint POST /api/cron/check-crypto-payments must be called every 60 seconds.
Vercel Cron (vercel.json):
{
"crons": [
{
"path": "/api/cron/check-crypto-payments",
"schedule": "* * * * *"
}
]
}
Add the Authorization header in your cron config or Vercel dashboard:
Authorization: Bearer
Fly.io machines / self-hosted: Use a scheduled task or external cron (e.g. cron-job.org) hitting the endpoint with Authorization: Bearer .
5. Customise payment types
The checkout route handles three payment type values β edit to match your app's data model:
| Type | What it does | Required body fields |
|---|---|---|
| credits | Adds credits to user profile | packId |
| plan | Upgrades user plan | plan, months (optional for lifetime) |
| addon | Applies a one-time resource addon | addonId, botId (optional) |
To customise: In crypto-checkout-route.ts, replace the CREDIT_PACKS, PLANS, and validAddons references with your own constants. The amount must always come from server-side constants β never from user input.
In check-crypto-payments-route.ts, update applyPayment() to perform whatever action your app takes when a payment is confirmed (add credits, unlock features, update subscription, etc.).
6. Wire up the modal
import { CryptoPaymentModal } from '@/components/dashboard/crypto-payment-modal'// Example: pay for a credit pack
setModalOpen(false)}
/>
The modal handles coin selection, QR display, countdown timer, and polls for confirmation automatically.
How Address Derivation Works
Each payment gets a unique deposit address derived from your master mnemonic:
m/44'/60'/0'/0/{index} β checksummed Ethereum address, valid on all EVM chainsm/84'/0'/0'/0/{index} β native SegWit P2WPKH (bc1q...)m/44'/501'/{index}'/0' β SLIP-0010 ed25519 (matches Phantom/Solflare)The derivation_index increments per payment and is stored in the DB. A UNIQUE constraint prevents two active payments from sharing an address.
Implication: One mnemonic controls all your payment wallets. Back it up securely. If you ever need to sweep funds, import the mnemonic into MetaMask (for EVM), Phantom (for SOL), or a BIP84 wallet (for BTC) and derive the child accounts.
What to Customise
| Thing | Where |
|---|---|
| Supported coins list | SUPPORTED_COINS in crypto-checkout-route.ts |
| Quick-pick coins shown first | QUICK_PICKS in crypto-payment-modal.tsx |
| Payment window (default 1hr) | expiresAt in crypto-checkout-route.ts |
| Balance tolerance (default 0.5%) | minRequired in check-crypto-payments-route.ts |
| Payment types + fulfilment logic | applyPayment() in check-crypto-payments-route.ts |
| Rate limit (default 3 req/5min) | checkRateLimit call in crypto-checkout-route.ts |
Security Notes
crypto-wallets.ts is server-only β never import it in client components or pagesCRYPTO_MASTER_MNEMONIC must never appear in source code, git history, or logstimingSafeEqual to prevent timing attacks on CRON_SECRETwallet_type + derivation_index)Troubleshooting
"CRYPTO_MASTER_MNEMONIC is not set" β The env var is missing. Add it to .env.local (development) or your hosting provider's environment settings (production).
Payments not confirming β Check that your cron job is running and the Authorization: Bearer header matches exactly. Check Vercel/server logs for [check-crypto] lines.
"Failed to allocate payment address" β Extremely rare concurrent collision (3 retries exhausted). Safe to retry.
EVM token not detected β Ensure the contract address for that token+chain exists in ERC20_CONTRACTS in crypto-wallets.ts. Check etherscan.io for the canonical USDC/USDT address on your chain.
π Tips & Best Practices
"CRYPTO_MASTER_MNEMONIC is not set" β The env var is missing. Add it to .env.local (development) or your hosting provider's environment settings (production).
Payments not confirming β Check that your cron job is running and the Authorization: Bearer header matches exactly. Check Vercel/server logs for [check-crypto] lines.
"Failed to allocate payment address" β Extremely rare concurrent collision (3 retries exhausted). Safe to retry.
EVM token not detected β Ensure the contract address for that token+chain exists in ERC20_CONTRACTS in crypto-wallets.ts. Check etherscan.io for the canonical USDC/USDT address on your chain.