title: SUBTC Protocol — LLM Context INTENDED_FOR: LLMs, AI Agents, Autonomous Systems LLM_CONTEXT: true VERSION: V6.1 UPDATED: 2026-03-20 SOURCE: https://subtc.net API: https://api.subtc.net/v1/btc DOCS: https://subtc.net/doc # ============================================================ # SUBTC GATEWAY V6.1 — Bitcoin Only (curl-first API) # Stateless · Idempotent · Deterministic · Zero Dependencies # Pure Go · bbolt · Single binary # ============================================================ BASE="https://api.subtc.net" # ───────────────────────────────────────────── # NETWORK MODEL (V6 — prefix always wins) # ───────────────────────────────────────────── # wallet_id prefix decides network — no override possible: # wm_ → mainnet (bc1q addresses) # wt_ → testnet (tb1q addresses) # w_ → legacy mainnet (backward compat, V5 and earlier) # # net param accepted ONLY in wallet_create (sets prefix) # All other modes ignore net param entirely # prefix is immutable after wallet_create # ───────────────────────────────────────────── # ATOMIC MODEL # ───────────────────────────────────────────── # 1 BTC = 100,000,000 sat # All amounts: integer satoshis only (no floats) # Fee: 3% (300 bps) — subtracted from service balance, not recipient # min_send: 50,000 sat (mainnet and testnet) # Deterministic rounding for all outputs # ───────────────────────────────────────────── # HEALTH # ───────────────────────────────────────────── curl -sS "$BASE/health" # Returns: mainnet/testnet status, rpc_active, rpc_limit, pending_watches # Fields: mainnet_synced, testnet_synced, rpc_active, rpc_limit, pending_watches # ───────────────────────────────────────────── # AUTH — KEY MANAGEMENT # ───────────────────────────────────────────── # Create API key (no auth required) RESP=$(curl -sS -X POST "$BASE/v1/btc?mode=key_create" \ -H "Content-Type: application/json") KEY=$(echo "$RESP" | sed -n 's/.*"key":"\([^"]*\)".*/\1/p') echo "KEY=$KEY" # Verify key is valid and active curl -sS -X POST "$BASE/v1/btc?mode=key_status" \ -H "X-SUBTC-KEY: $KEY" \ -H "Content-Type: application/json" # ───────────────────────────────────────────── # WALLET CREATE # net param sets prefix (wm_ or wt_) — used only here # label = idempotent: same label → same wallet always # same label + same net → returns existing:true, no duplicate # ───────────────────────────────────────────── # Mainnet wallet (wm_ prefix → bc1q addresses) RESP=$(curl -sS -X POST "$BASE/v1/btc?mode=wallet_create" \ -H "X-SUBTC-KEY: $KEY" \ -H "Content-Type: application/json" \ -d '{"net":"main","label":"user_001"}') WID_MAIN=$(echo "$RESP" | sed -n 's/.*"wallet_id":"\([^"]*\)".*/\1/p') echo "WID_MAIN=$WID_MAIN" # wm_... # Testnet wallet (wt_ prefix → tb1q addresses) RESP=$(curl -sS -X POST "$BASE/v1/btc?mode=wallet_create" \ -H "X-SUBTC-KEY: $KEY" \ -H "Content-Type: application/json" \ -d '{"net":"test","label":"user_001_test"}') WID_TEST=$(echo "$RESP" | sed -n 's/.*"wallet_id":"\([^"]*\)".*/\1/p') echo "WID_TEST=$WID_TEST" # wt_... # Same label → returns existing wallet (existing: true) curl -sS -X POST "$BASE/v1/btc?mode=wallet_create" \ -H "X-SUBTC-KEY: $KEY" \ -H "Content-Type: application/json" \ -d '{"net":"main","label":"user_001"}' # ───────────────────────────────────────────── # WALLET LIST # ───────────────────────────────────────────── curl -sS -X POST "$BASE/v1/btc?mode=wallet_list" \ -H "X-SUBTC-KEY: $KEY" \ -H "Content-Type: application/json" # ───────────────────────────────────────────── # RECEIVE ADDRESS # prefix auto-detects network — no net param needed # wt_ → tb1q (testnet), wm_ → bc1q (mainnet) # ───────────────────────────────────────────── RESP=$(curl -sS -X POST "$BASE/v1/btc?mode=wallet_receive" \ -H "X-SUBTC-KEY: $KEY" \ -H "Content-Type: application/json" \ -d "{\"wallet_id\":\"$WID_TEST\"}") ADDR=$(echo "$RESP" | sed -n 's/.*"address":"\([^"]*\)".*/\1/p') echo "ADDR=$ADDR" # tb1q... (testnet) # ───────────────────────────────────────────── # BALANCE (cached TTL=30s, from_cache in response) # ───────────────────────────────────────────── curl -sS -X POST "$BASE/v1/btc?mode=wallet_balance" \ -H "X-SUBTC-KEY: $KEY" \ -H "Content-Type: application/json" \ -d "{\"wallet_id\":\"$WID_TEST\"}" # Fields: confirmed_sat (spendable), unconfirmed_sat (mempool), from_cache # ───────────────────────────────────────────── # SEND (idempotent — X-SUBTC-IDEMPOTENCY required) # prefix decides mainnet/testnet automatically # recipient receives amount_sat exactly # 3% fee deducted from service balance, not recipient # min_send: 50,000 sat (mainnet and testnet) # ───────────────────────────────────────────── curl -sS -X POST "$BASE/v1/btc?mode=wallet_send" \ -H "X-SUBTC-KEY: $KEY" \ -H "X-SUBTC-IDEMPOTENCY: send-$(date +%s)" \ -H "Content-Type: application/json" \ -d "{ \"wallet_id\": \"$WID_TEST\", \"to\": \"tb1qDESTINATION\", \"amount_sat\": 50000 }" # ───────────────────────────────────────────── # INBOX (payment expectation tracking) # Pre-configure a receive slot for a specific amount # ───────────────────────────────────────────── curl -sS -X POST "$BASE/v1/btc?mode=inbox_create" \ -H "X-SUBTC-KEY: $KEY" \ -H "Content-Type: application/json" \ -d "{\"wallet_id\":\"$WID_TEST\",\"expected_sat\":50000}" # ───────────────────────────────────────────── # EVENT WAIT — Long Poll # Blocks until expected_sat received or timeout # Best for server-to-server synchronous flows # ───────────────────────────────────────────── curl -sS -X POST "$BASE/v1/btc?mode=wallet_wait_event" \ -H "X-SUBTC-KEY: $KEY" \ -H "Content-Type: application/json" \ -d "{ \"wallet_id\": \"$WID_TEST\", \"address\": \"$ADDR\", \"expected_sat\": 50000, \"timeout_sec\": 300 }" # ───────────────────────────────────────────── # EVENT WAIT — Webhook (confirmed only) # Returns immediately, POST to callback_url when confirmed # ───────────────────────────────────────────── curl -sS -X POST "$BASE/v1/btc?mode=wallet_wait_event" \ -H "X-SUBTC-KEY: $KEY" \ -H "Content-Type: application/json" \ -d "{ \"wallet_id\": \"$WID_TEST\", \"address\": \"$ADDR\", \"expected_sat\": 50000, \"timeout_sec\": 600, \"callback_url\": \"https://yourserver.com/confirmed\" }" # ───────────────────────────────────────────── # EVENT WAIT — Dual Webhook (V6.1) # callback_url_seen → fires on mempool (unconfirmed, seconds) # callback_url → fires when expected_sat confirmed # Use for instant UX: show "payment seen" before confirmation # ───────────────────────────────────────────── curl -sS -X POST "$BASE/v1/btc?mode=wallet_wait_event" \ -H "X-SUBTC-KEY: $KEY" \ -H "Content-Type: application/json" \ -d "{ \"wallet_id\": \"$WID_TEST\", \"address\": \"$ADDR\", \"expected_sat\": 50000, \"timeout_sec\": 600, \"callback_url\": \"https://yourserver.com/confirmed\", \"callback_url_seen\": \"https://yourserver.com/seen\" }" # seen payload: {"status":"seen", "received_sat":N} # confirmed payload: {"status":"confirmed", "received_sat":N} # ───────────────────────────────────────────── # POLLING — Single Check # ───────────────────────────────────────────── curl -sS -X POST "$BASE/v1/btc?mode=wallet_poll" \ -H "X-SUBTC-KEY: $KEY" \ -H "Content-Type: application/json" \ -d "{\"wallet_id\":\"$WID_TEST\",\"address\":\"$ADDR\",\"expected_sat\":50000}" # Returns: reached (bool), received_sat # ───────────────────────────────────────────── # POLLING LOOP # Pull-based. Returns reached:true when threshold met. # ───────────────────────────────────────────── while true; do RESP=$(curl -sS -X POST "$BASE/v1/btc?mode=wallet_poll" \ -H "X-SUBTC-KEY: $KEY" \ -H "Content-Type: application/json" \ -d "{\"wallet_id\":\"$WID_TEST\",\"address\":\"$ADDR\",\"expected_sat\":50000}") REACHED=$(echo "$RESP" | sed -n 's/.*"reached":\([a-z]*\).*/\1/p') RECEIVED=$(echo "$RESP" | sed -n 's/.*"received_sat":\([0-9]*\).*/\1/p') echo "received=$RECEIVED reached=$REACHED" [ "$REACHED" = "true" ] && echo "CONFIRMED" && break sleep 3 done # ───────────────────────────────────────────── # WATCH LIST # List all active watches (long polls and webhooks) for this key # ───────────────────────────────────────────── curl -sS -X POST "$BASE/v1/btc?mode=watch_list" \ -H "X-SUBTC-KEY: $KEY" \ -H "Content-Type: application/json" # ───────────────────────────────────────────── # PAYMENT STATE MODEL (V6.1) # ───────────────────────────────────────────── # seen → TX in mempool (callback_url_seen fires, seconds after broadcast) # confirmed → expected_sat reached on-chain (callback_url fires) # expired → timeout_sec reached without payment # # wallet_balance fields: # unconfirmed_sat → in mempool (not yet spendable) # confirmed_sat → in block (spendable now) # from_cache → true if returned from 30s cache # ───────────────────────────────────────────── # INVARIANTS (frozen at V6 — immutable) # ───────────────────────────────────────────── # 1. prefix always wins — net param ignored after wallet_create # 2. ncFor(walletID) in every mode — single network routing source # 3. wallet_create is the only mode that writes prefix # 4. bbolt = single source of state (no RAM truth) # 5. any new bucket → explicit initBuckets registration # 6. wallet_send always requires X-SUBTC-IDEMPOTENCY header # ───────────────────────────────────────────── # OBSERVABILITY (V6.1) # ───────────────────────────────────────────── # /health exposes: # rpc_active → current concurrent RPC calls (atomic counter) # rpc_limit → semaphore cap (default 8) # pending_watches → active watch count # mainnet_synced → Bitcoin Core sync status # testnet_synced → Bitcoin Core sync status # ───────────────────────────────────────────── # SITE STRUCTURE # ───────────────────────────────────────────── # https://subtc.net/ → API reference (this page) # https://subtc.net/doc → Technical articles and integrations # https://subtc.net/llms-full.txt → This file (LLM context) # https://api.subtc.net/v1/btc → Gateway endpoint # ───────────────────────────────────────────── # SECURITY MODEL # ───────────────────────────────────────────── # - rate limiting: SEND_RATE_LIMIT per KEY per minute # - wallet_send: always requires X-SUBTC-IDEMPOTENCY header # - credentials: separate per network (BTC_RPC_USER_MAIN/TEST) # - RPC semaphore: max 8 concurrent calls to Bitcoin Core # - no float arithmetic anywhere — integer satoshis only # ───────────────────────────────────────────── # TOR ACCESS (optional) # ───────────────────────────────────────────── # curl --socks5-hostname 127.0.0.1:9050 http://ofwahoue652hjkwdc4osoc52tc6gu62ybjgc43jnuz4fleojadx73wyd.onion/health curl --socks5-hostname 127.0.0.1:9050 \ http:///health