5fd0d3c148
pay-server 自带 /_test 测试页:创建订单→显示收款地址+精确金额(可复制)
+过期倒计时→轮询状态到 paid。同源、无 CORS/CSP 摩擦,不碰营销站安全头;
纯调 POST /order + GET /order/{id},无私钥无密钥。生产收银台后续走独角数卡门面。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.2 KiB
5.2 KiB
pangolin-pay 加密货币交易引擎(钱包 + watcher + 收款侦测 + 归集)
#34 的第一块、可独立执行、能用真实小额 USDT 端到端验证的基础件。只做加密货币的"交易"核心—— 生成/派生地址、侦测链上到账、归集(sweep)——不含独角数卡对接、不含
/internal/codes/mint发码 (那是"发货侧",本引擎跑通后再接)。成果物:一个独立 Go 服务
pangolin-pay(部署在独立海外 VPS),对外提供"建单→给收款地址→侦测到账→ 标记已付"的 HTTP 接口 + 一套离线归集工具。跑通后,任何门面(独角数卡 / 自建下单页)都能挂上来。安全铁律(贯穿):热服务只持 xpub(watch-only 派生地址)+ TronGrid key + 金库收款地址,绝不持私钥; 私钥/助记词冷存,归集在离线端签名。参考知识:brain
notes/dev/crypto-hd-wallet-basics.html。关键常量(开工核对无误再写死):USDT-TRC20 合约
TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t(6 位小数); TRON 派生m/44'/195'/0'/0/i;TronGridhttps://api.trongrid.io。
Phase A — 钱包与密钥(全程离线)
- A.1 离线生成两套助记词(断网 Mac 跑 Ian Coleman standalone /
bip_utils,选 Coin=TRX、24 词、English):- 钱包 A · 运营收款;钱包 B · 冷备金库(另一套,独立冷存)
- A.2 导出钱包 A 的 account xpub(
m/44'/195'/0'的 Account Extended Public Key);取钱包 B 的地址0(T...)当归集目标 - A.3 冷存:助记词 A/B 分开手抄/钢板 + Bitwarden;只把 xpub_A + sweep 地址_B 放进
pangolin-pay配置(Bitwarden 管密钥) - A.4 交叉核对:B 阶段的 Go 派生实现算出的地址 0..4,必须与 Ian Coleman 显示的逐个一致(防派生实现不一致 → 收款地址对不上 → 收不到钱)
Phase B — 服务骨架 + HD 派生
- B.1 建 Go 服务
pangolin-pay(独立 cmd 或独立仓)。配置:xpub_A/sweep_addr_B/TRONGRID_API_KEY/ 确认数 / 订单超时。自带 SQLite - B.2 HD 派生模块:
tyler-smith/go-bip39+btcsuite/.../hdkeychain从 xpub_A watch-only 派生.../0/i公钥 → TRON 地址(公钥 → Keccak-256 取后 20 字节 → 前缀0x41→ Base58Check →T...)。单测:派生地址对齐 Ian Coleman(A.4) - B.3 数据模型:
pay_orders(order_no PK, sku, expect_amount, addr_index, address, status, tx_id, confirmations, created_at, expires_at)addr_cursor(next_index)— HD 派生游标,单调递增,地址不复用
Phase C — 收款(建单 + 侦测到账)
- C.1
POST /order:入参sku/基准金额 → cursor++ 派生新收款地址 → 写单(expect_amount做唯一尾数去重、expires=+15min)→ 返回{order_no, address, expect_amount, expires_at} - C.2
GET /order/{order_no}:返回status(pending/paid/expired)+ 到账信息 - C.3 TronGrid watcher(轮询):对每个 pending 单地址查 TRC20 转入
GET /v1/accounts/{addr}/transactions/trc20?only_confirmed=true&contract_address=<USDT>&limit=...(头带TRON-PRO-API-KEY)- 匹配:
to==address且token==USDT且value≥expect_amount(USDT 6 位小数,注意单位换算)且已确认 →status=paid、记tx_id
- C.4 幂等(同
tx_id只认一次)+ 超时 pending→expired(地址不复用)+ 崩溃恢复(从 DB 未决单继续轮询) - C.5 TronGrid 限流退避 + 重试;测试用 mock TronGrid 覆盖:金额匹配 / 确认门槛 / 超时 / 幂等 / 恢复
Phase D — 归集(sweep,离线签名)
- D.1 归集清单:watcher(xpub)列出有余额地址
[(index, address, balance)] - D.2 gas:从一个
gas 钱包给每个待归集地址打少量 TRX(TRC20 转账吃 energy;收款地址无 TRX);量大改能量租赁降本 - D.3 离线签名(离线机,导入钱包 A 助记词):派生
address_i私钥 → 构造 TRC20transfer(sweep_addr_B, balance)→ 离线签名 - D.4 广播:把签好的交易经 TronGrid
POST /wallet/broadcasttransaction上链(只有签名后的交易出门,私钥不出门) - D.5 半自动脚本(离线端)+ 归集 runbook;触发=阈值(攒够 X)或定时;热派生地址不长期囤大额
Phase E — 端到端验证(真实小额 USDT)
- E.1 真单:
POST /order→ 往返回的地址转 1 USDT(TRC20) → watcher 侦测 + 确认 →GET /order变paid - E.2 归集:对该地址执行 D 流程 → USDT 到达钱包 B 冷地址
- E.3 安全核对:
pangolin-pay进程/配置无任何私钥/助记词(只有 xpub);密钥不入 git(红线扫描);地址不复用
Verification(判据)
go test ./...:HD 派生对齐 Ian Coleman、watcher 匹配/确认/超时/幂等/恢复全绿。- 真机:一笔真 1 USDT 从建单→侦测→确认→归集到冷地址全通。
- 安全:热服务无私钥、密钥不入 git、每单唯一地址不复用——逐项核对。
不在本轮
- 独角数卡门面对接 + epay 支付口(下一块:接门面)。
/internal/codes/mintJIT 发码 + 兑换闭环(发货侧,#34 后续)。- HD 自动 sweep(先人工离线归集)、多币种(先只 USDT-TRC20)、watcher 高可用。