🎁 Get the FREE AI Skills Starter Guide β€” Subscribe β†’
BytesAgainBytesAgain
πŸ¦€ ClawHub

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...

Versionv1.0.1
Downloads365
Stars⭐ 1
TERMINAL
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.sql
  • 006_crypto_direct.sql
  • 3. Add environment variables

    # Generate mnemonic: https://iancoleman.io/bip39/ (12 or 24 words β€” NEVER commit this)
    CRYPTO_MASTER_MNEMONIC=word1 word2 word3 ... word12

    Protects 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:

  • EVM (ETH, Base, Polygon, etc.): m/44'/60'/0'/0/{index} β€” checksummed Ethereum address, valid on all EVM chains
  • Bitcoin: m/84'/0'/0'/0/{index} β€” native SegWit P2WPKH (bc1q...)
  • Solana: 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 pages
  • CRYPTO_MASTER_MNEMONIC must never appear in source code, git history, or logs
  • The cron endpoint uses timingSafeEqual to prevent timing attacks on CRON_SECRET
  • Payment amounts are sourced from server-side constants β€” users cannot manipulate them
  • Address uniqueness is enforced at the DB level (unique index on wallet_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.