4bb92209ca
- store(SQLite,modernc 纯 Go):pay_orders + addr_cursor(HD 派生游标,地址不复用);
建单/查单/ListPending/MarkPaid(幂等,仅 pending→paid)/MarkExpired。
- pay 服务:CreateOrder 每单 NextAddrIndex→从 xpub watch-only 派生唯一收款地址→写 pending 单(TTL 15min)。
- tron:TronGrid 客户端读已确认 TRC20 到账(only_confirmed + USDT 合约,micro-USDT 整数)。
- watcher:Tick 先过期逾期单,再对每个 pending 单查到账、金额≥期望→MarkPaid;幂等(同 tx 只认一次)、
网络错误跳过下轮重试;Loop 定时轮询。
- httpapi:POST /order、GET /order/{orderNo}、/healthz;cmd/paywatch 用 env 装配 + 优雅退出。
- 测试:store/service/watcher(mock TronGrid)/httpapi 全绿——建单派生地址正确、到账侦测、
欠额不认、幂等、超时过期、404/400。热服务无私钥。
- README:安全模型 + Phase A 离线备钱包步骤 + 运行/API/测试。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pangolin-pay
自托管 USDT(TRC20) 收款服务。给每个订单派生一个唯一收款地址(watch-only,从账户 xpub 派生,不持私钥),轮询 TronGrid 侦测到账、确认后标记订单已付。归集(把钱扫到冷 钱包)是独立的离线步骤,本服务不碰私钥。
属 #34「独角数卡 + USDT 收款闭环」的加密货币交易引擎(计划见
docs/superpowers/plans/2026-07-09-crypto-tx-engine.md)。概念见 brainnotes/dev/crypto-hd-wallet-basics.html。
安全模型(必读)
- 本服务(热、联网)只持 account xpub——能派生收款地址、能查到账,拿不到任何私钥。被脱库也转不走钱。
- 私钥/助记词冷存;要动钱(归集)时才在离线端用私钥签名(见 seed.go,OFFLINE ONLY)。
- 密钥(xpub / TronGrid key)走 Bitwarden,不入 git、不写死。
Phase A —— 离线准备钱包(你在断网机器上做)
- 断网,用 Ian Coleman
bip39-standalone.html(或bip_utils)生成两套 24 词助记词: 钱包 A(运营收款)、钱包 B(冷备金库)。Coin=TRX、English。 - 取钱包 A 的 Account Extended Public Key(
m/44'/195'/0'的 xpub)→ 就是本服务的PAY_ACCOUNT_XPUB。 - 取钱包 B 的地址0(
T...)→ 归集目标(Phase D 用)。 - 交叉核对(关键):本仓自带的金标准向量(测试助记词
abandon…about)必须与 Ian Coleman 一致 —— 跑go test ./internal/wallet -run TestKnownVector -v,再在 Ian Coleman 里用同一测试 助记词、Coin=TRX 对照前 3 个地址。一致 = 派生实现可信;不一致 = 有 bug,别上线。 - 助记词 A/B 分开冷存 + Bitwarden。只把 xpub_A 交给本服务。
运行
export PAY_ACCOUNT_XPUB="xpub..." # 钱包 A 的 account xpub(必填)
export TRONGRID_API_KEY="..." # TronGrid key(建议)
export PAY_DB="pay.db" # SQLite 路径(默认 pay.db)
export PAY_ADDR=":8090" # 监听(默认 :8090)
export PAY_POLL_SECONDS="20" # 轮询间隔秒(默认 20)
# USDT_CONTRACT / TRONGRID_BASE 默认主网
go run ./cmd/paywatch
API
POST /order {"sku":"pro-year","amount":5000000} # amount = micro-USDT(1e-6)
→ 201 {"order_no","address","expect_amount","status":"pending","expires_at"}
GET /order/{orderNo} → 200 {..., "status":"pending|paid|expired","tx_id"}
GET /healthz → 200 ok
门面(独角数卡)下单时调 POST /order 拿收款地址;支付页轮询 GET /order/{id} 直到 paid。
Phase D —— 归集(离线,后续)
internal/wallet/seed.go 提供离线派生私钥的原语(PrivKeyHexFromMnemonic)。完整的 sweep
(构造 TRC20 transfer → 离线签名 → 广播)+ gas 处理 + runbook 在后续切片实现。
测试
go test ./...
wallet:派生一致性(xpub 路径 == 私钥路径)+ 金标准向量(需 A.4 核对)。store/pay/watcher/httpapi:建单/派生地址/侦测到账/幂等/超时/HTTP。