Technical Overview技术总览 V1.0 2026-05-13 For technical leads面向技术负责人

A blockchain micro-lottery, dismantled. 一份链上夺宝平台,拆开来看

Duobao is a Telegram-native ticket draw where users buy 1 USDT seats and the winner takes ~85% of the pool. The interesting part is not the game — it's the nine-service split, the pluggable draw engine, and the discipline around custody. This page walks the system end-to-end. 夺宝是一个原生于 Telegram 的购票开奖平台,用户花 1 USDT 买一个号, 中奖者拿走奖池约 85%。有意思的不是玩法本身——是九服务拆分、可插拔的开奖引擎、 以及围绕私钥托管的纪律。本页从头到尾走一遍这套系统。

9 Backend services后端服务
3 Draw modes (1 shipped)开奖模式(1 已上线)
2 Live chains (BSC + BTC)已上线链(BSC + BTC)
21 Kafka topicsKafka topic
Source of truth:权威来源: docs/PRD-V1.0.md · docs/3-arch/{overview,backend,on-chain}.md · Swagger/SpringDoc API artifact · docs/8-tech-debt.md
Code:代码: duobao-backend/ (Spring Boot · Java 21 · Gradle multi-module)(Spring Boot · Java 21 · Gradle 多模块)  ·  Contracts:合约: duobao-contracts/ (Foundry · Solidity 0.8.26)
Ch. 01

What it is是什么

the elevator pitch电梯演讲

A user opens the Telegram mini-app, deposits USDT on BSC (or BTC on Bitcoin), buys one or more 1-USDT seats in a round, and waits for the round to sell out. When it does, the platform draws a winner via a commit-reveal seed that anyone can verify locally — no gas, no Chainlink, no on-chain selection. Winnings settle to the platform balance ledger immediately; withdrawals are a separate, audited path with multi-sig custody on the cold-tier. 用户打开 Telegram 小程序,把 BSC 上的 USDT (或 Bitcoin 上的 BTC)充值进来,在某一轮里买一个或多个 1 USDT 号码, 等本轮售罄。售罄之后,平台用 commit-reveal 种子开奖,任何人都可以本地验证 —— 不消耗 gas、不依赖 Chainlink、不上链选号。奖金即时落到平台余额账本; 提现走独立、可审计的路径,冷钱包多签托管。

The product surface is small. The plumbing is the point: nine independent Spring Boot services, each with its own MySQL schema, talk over Feign (synchronous) and Kafka (asynchronous, at-least-once with consumer-side idempotency). Sign-service holds the private keys and signs blindly. Wallet-service is the only source of truth for balances. Chain-service is the only thing that talks to a node. Everyone else is forbidden from doing those things — by code, by review, and by boot validators that refuse to start a prod profile when a stub is still wired in.

v1.0 boundary Two chains shipped (BSC EVM + BTC deposits via PSBT signing). TRON declared in the enum but stubbed to 501. Ethereum mainnet is config-ready but not a v1.0 launch chain. VRF and full-chain draw modes are coded as interfaces and stubbed bodies; v1.0 ships the BACKEND commit-reveal mode only.
Ch. 02

Four locked decisions四个锁定的决策

pre-dating the doc pipeline早于文档流水线

These were settled before the architecture docs ran. Every later document honors them; reversing one is a re-architecture, not a refactor. 这四条早在架构文档启动之前就定下了。后续每一份文档都遵循它们; 反转任何一条都属于重新设计架构,不是重构。

01

Nine-service split, financial isolation at the schema九服务拆分,在 schema 层做资金隔离

gateway · user · game · wallet · chain · sign · agent · admin — plus auxiliary risk & message services. Per-service MySQL schema, no cross-service joins, no shared transactions. Wallet-service owns the balance ledger as the single source of truth; sign-service owns the private keys with no access to business state. Cross-service consistency rides on Kafka + idempotency keys, not 2PC. gateway · user · game · wallet · chain · sign · agent · admin,加上辅助的 risk 和 message。每个服务一个独立 MySQL schema, 不允许跨服务 join,不共享事务。wallet-service 是余额账本的唯一权威来源;sign-service 持有私钥, 但完全不接触业务状态。跨服务一致性靠 Kafka + 幂等键保证,不用 2PC。

Why → blast radius containment. A bug in agent-service can never corrupt a balance. 原因 → 爆炸半径收敛。agent-service 里的 bug 永远污染不了余额。

02

Pluggable draw engine via a single interface通过单一接口实现可插拔的开奖引擎

One DrawEngine contract in duobao-api, three implementations (BACKEND, VRF_ONLY, FULL_CHAIN) wired by @ConditionalOnProperty against one.draw.mode sourced from Nacos. v1.0 ships BACKEND; the other two are real classes with stub bodies that throw FEATURE_NOT_IMPLEMENTED so swapping modes never requires touching game-service. duobao-api 里一个 DrawEngine 接口,三种实现 (BACKEND / VRF_ONLY / FULL_CHAIN), 通过 @ConditionalOnProperty 读取 Nacos 里的 one.draw.mode 切换。 v1.0 上线 BACKEND;另外两个是真实类但桩体抛 FEATURE_NOT_IMPLEMENTED, 这样切换模式永远不需要改 game-service。

Why → defer the on-chain VRF risk without writing a future migration. 原因 → 把链上 VRF 风险推迟,同时不需要写未来的迁移代码。

03

Multi-chain via ChainAdapter, one adapter per dialect多链通过 ChainAdapter 解耦,每条方言一个适配器

A single interface (isAddressValid, getBalance, scanBlock, broadcastSignedTx, getReceipt, requiredConfirmations) plus per-chain dialect classes. Adding a chain means writing one adapter and one Nacos config block — business code is untouched. v1.0 ships EvmChainAdapter (BSC, ETH, Anvil) and a parallel BtcChainAdapter; TRON is a StubChainAdapter returning 501. 单一接口(isAddressValid / getBalance / scanBlock / broadcastSignedTx / getReceipt / requiredConfirmations)加每条链方言类。新增一条链 = 写一个 adapter + 一段 Nacos 配置, 业务代码不动。v1.0 上线 EvmChainAdapter(BSC / ETH / Anvil)和并行的 BtcChainAdapter;TRON 是 StubChainAdapter,返回 501

Why → chains are a moving target. The interface won't move. 原因 → 链一直在变,接口不变。

04

Environment and chain are orthogonal — never collapsed环境与链是正交的——永远不合并成一个字段

env ∈ {LOCAL, TESTNET, STAGING, PRODUCTION} and executionChain ∈ {ETH_LOCAL, ETH_SEPOLIA, ETH_MAINNET, BSC_LOCAL, BSC_TESTNET, BSC_MAINNET, TRON_LOCAL, TRON_TESTNET, TRON_MAINNET, BTC_LOCAL, BTC_TESTNET, BTC_MAINNET} are independent enums. Any environment can target any chain; Nacos namespace isolates per-env config, and a chain_config.${executionChain} block holds per-chain knobs. Common mistakes — "testnet means Sepolia," "prod means mainnet" — are designed out. env ∈ {LOCAL, TESTNET, STAGING, PRODUCTION} 和 executionChain ∈ {ETH_LOCAL, ETH_SEPOLIA, ETH_MAINNET, BSC_LOCAL, BSC_TESTNET, BSC_MAINNET, TRON_LOCAL, TRON_TESTNET, TRON_MAINNET, BTC_LOCAL, BTC_TESTNET, BTC_MAINNET} 是互相独立的枚举。任何环境都可以指向任何链; Nacos namespace 按环境隔离配置,chain_config.${executionChain} 段保存每条链的旋钮。 常见错误——"testnet 就是 Sepolia"、"prod 就是 mainnet"——在设计阶段就排除了。

Why → STAGING on mainnet for pre-launch capital tests is a real workflow. 原因 → STAGING 跑在主网上做预发资金测试是真实的工作流。

Ch. 03

Service map服务地图

who owns what谁负责什么

A dozen modules live under duobao-backend/. Most are production-ready, a couple are partial (clearly demarcated), a couple are stub-only. The map below groups by tier — edge, money, chain, auxiliary. duobao-backend/ 下大约一打模块。大多数已生产就绪, 少数处于部分实现(明确标注),个别只有桩。下面的地图按层分组——边缘、资金、链路、辅助。

gateway-serviceprod
Auth (JWT verify · jti revoke), routing, rate-limit, Telegram initData validation, injects X-User-Id downstream.
no DB · trust root
user-serviceprod
Telegram one-tap login, KYC state, session, JWT issuance with refresh-token rotation.
db: one_user
admin-servicepartial
Ops dashboard BFF: activity config, withdrawal approval, audit, RBAC, TOTP. ~70% endpoints DTO-only stubs (TD-38).
db: one_admin + read-only views
game-serviceprod
Rounds, ticket allocation, order state machine (PENDING → SUBMITTED → PAID / REJECTED), draw routing across three modes.
db: one_u_game
wallet-serviceprod
Balance ledger (single source of truth), deposit credit, freeze/unfreeze, withdrawal lifecycle, daily reconciliation.
db: one_wallet · append-only ledger
agent-serviceprod
RefCodes, referral binding, agent flagging. Commissions deferred to v1.1.
db: one_agent
chain-servicepartial
Block scanning, deposit detection, withdrawal broadcast, contract calls. EVM complete; BTC deposit scan stubbed (TD-24); TRON 501 (TD-61); sweeper stubbed (TD-60).
db: one_chain
sign-serviceprod
KMS-only signing: ECDSA for EVM, PSBT for BTC. Takes (keyId, payload) — never business params. Every call audited.
db: one_sign · forensic audit
message-servicelive
In-app inbox + Telegram bot push. v1.0 channels = Inbox + TG push.
db: one_message
risk-serviceplanned
Fraud detection placeholder. Code shell exists; integration with game / wallet event streams scheduled for v1.1.
db: one_risk
task-serviceremove
Legacy compatibility wrapper. Marked for deletion 2026-05-13; nothing in v1.0 depends on it.
slated for git rm
edge / auth money path chain layer auxiliary
Trust invariant wallet-service is the only writer of balances; sign-service is the only holder of private keys; chain-service is the only thing that speaks to an RPC node. If any other module starts doing any of these, the change is rejected at review.
Ch. 04

Three draw modes, one shipped三种开奖模式,只上线一种

pluggable randomness可插拔的随机性

The product spec asks for three trust models, ranked by how much of the draw lives on-chain. v1.0 ships only the leftmost; the other two are present as interface implementations with stub bodies so a future flip is a config change, not a rewrite. 产品规格要求三种信任模型,按"开奖逻辑落在链上的比例"排序。 v1.0 只上线最左边那个;另外两个以接口 + 桩体的方式存在, 未来切换是改一行配置,不是重写代码。

BACKEND VRF_ONLY FULL_CHAIN
Trust model Platform commit-reveal off-chain. EIP-191 signed commit published before the round; seed revealed post-draw. Anyone verifies SHA-256 locally. Chainlink VRFv2.5 randomness; winner selected off-chain in wallet-service from the verified random word. On-chain selection & payout. DrawCoordinator contract executes; USDT transfers from DuobaoVault directly to winner.
Gas budget 0 — no on-chain ops in the draw path requestRandom ≈ 150 k · fulfillRandomWords ≈ 200 k VRF + selection + payout ≈ 800 k
Latency < 1 s (DB write + Kafka emit) 1–3 min (Chainlink callback) + selection window 1–3 min VRF + block confirms
Failure mode Operator never reveals → audit + admin manual override. Reveal leaked early → seed rotation + round void. LINK subscription depleted → fall back to BACKEND. 30-min timeout → DRAW_TIMEOUT state. Same as VRF + payout reentrancy (mitigated: nonReentrant + SafeERC20).
v1.0 status shipped stub (TD-59) stub (TD-59)
// duobao-api · the single contract every mode honors
public interface DrawEngine {
    DrawResult draw(DrawContext ctx);   // initiate
    DrawMode   supportedMode();
    default boolean supportsAsyncCallback() { return false; }
}

// game-service · wiring
@Bean
@ConditionalOnProperty(name = "one.draw.mode", havingValue = "BACKEND")
DrawEngine backendEngine(...) { return new BackendCommitRevealEngine(...); }
Why ship the weakest mode first Commit-reveal is cryptographically auditable, costs zero gas, and removes a Chainlink-availability dependency from the launch checklist. The price is operator discipline — the seed cannot leak before draw close. For micro-stakes (1 USDT seats) the tradeoff is the right call; for large pools, flip one.draw.mode when VRF lands.
Ch. 05

Chain layer链路层

one interface, many dialects一个接口,多种方言

Every chain interaction goes through a single ChainAdapter interface. A new chain is one adapter + one Nacos config block. Business code doesn't know if it's talking to BSC or Bitcoin. 所有链上交互走单一 ChainAdapter 接口。 新增一条链 = 一个 adapter + 一段 Nacos 配置。业务代码完全不知道自己在跟 BSC 还是 Bitcoin 说话。

public interface ChainAdapter {
    ExecutionChain executionChain();
    boolean   isAddressValid(String address);
    BigInteger getBalance(String address, String token);
    List<ChainEvent> scanBlock(long fromBlock, long toBlock);
    String     broadcastSignedTx(byte[] signedTx);
    Optional<TxReceipt> getReceipt(String txHash);
    int        requiredConfirmations();
}

Dialect comparison

DialectSigningAddressConfirmsToken modelv1.0
EVM (BSC) ECDSA secp256k1 + low-s + EIP-191 0x + 40 hex, checksum-insensitive 15 ERC-20 USDT, gasPrice fixed 3–5 gwei live
EVM (ETH / Sepolia) ECDSA + EIP-1559 (maxFeePerGas + maxPriority) same as BSC 12 ERC-20 USDT, dynamic fee config-ready
TVM (TRON) ECDSA over TRX RLP (no EIP-191) base58check T… 19 TRC-20 USDT (no bool return) 501 (TD-61)
BTC PSBT v0 · BIP-143 P2WPKH sighash · ECDSA bech32 bc1q… (SegWit-only) 6 UTXO model, no token concept sign ✓ · scan ⚠ (TD-67)
LOCAL (Anvil) ECDSA same as BSC same as EVM 1 (instant) Anvil-deployed test USDT dev

Smart contracts

Three UUPS proxies under duobao-contracts/, all owned by a Gnosis Safe 3-of-5 wired through a 24-hour Timelock. No upgrade can sneak through; any single signer can cancel mid-window.

ContractResponsibilityKey guards
DuobaoVault Holds USDT pool per chain. withdraw(user, amount, nonce, sig) for backend-signed exits; payout(winner, amount) for on-chain modes (v1.1). nonReentrant, processedNonce[] idempotency, EMERGENCY_ROLE pause
DrawCoordinator Chainlink VRFv2.5 client. requestRandom(roundId) + async fulfillRandomWords callback. (v1.0: deployed but unused.) Subscription guard, replay protection
AccessRegistry Central role matrix: OPERATOR · WITHDRAWER · SWEEPER · EMERGENCY. Role-grant via Timelock only
VRF readiness Nacos already holds vrf.{coordinatorAddress, keyHash, subscriptionId, minimumRequestConfirmations} per chain. VrfRequestService exists with a deterministic mock (SHA-256(executionChain:gameId:requestId)) so non-prod environments can exercise the callback flow without burning LINK.
Ch. 06

Data & event spine数据与事件主干

where state lives状态住在哪里

Twelve isolated schemas, no cross-service joins. Consistency rides on Kafka topics with at-least-once delivery and consumer-side idempotency keyed on ref_id. Three retries then DLQ + alert. 十几个互相隔离的 schema,不允许跨服务 join。一致性靠 Kafka topic 的至少一次投递 + 消费侧用 ref_id 做幂等。重试 3 次后进 DLQ 并告警。

Load-bearing tables

wallet_account · wallet-service User balance ledger. Denormalized; reconciled daily against the append-only wallet_transaction.
wallet_transaction · wallet-service Append-only double-entry. Invariant: ∑ debits == ∑ credits; verified by integration test.
wallet_withdraw_request · wallet-service State machine: PENDING_REVIEW → APPROVED → BROADCASTED → CONFIRMED / FAILED. Large amounts require admin approval before sign.
wallet_deposit_address · wallet-service Cache of (uid, executionChain) → derived address. UNIQUE constraint; avoids re-deriving from KMS on every deposit page-load (TD-65).
oneu_game_round · game-service Round lifecycle. Status transitions emit Kafka.
oneu_order · game-service Ticket purchase. Idempotency on ref_id at write time.
chain_deposit_event · chain-service Observed on-chain Transfer events. Natural key (txHash, from, to, amount); flushed to wallet via Kafka after confirms.
chain_broadcast · chain-service Every signed tx broadcast: hash, nonce, status. Used for reconciliation against on-chain reality.
sign_audit · sign-service Append-only. Logs hash of every KMS call (payload not stored). Indexed on payload_hash for replay-detection forensics (TD-21).
one_audit · admin-service Append-only — INSERT only, no UPDATE / DELETE permitted at the DB role level. Every admin action.

Core Kafka topics (selected)

user.registered user-service → agent-service writes referral binding.
chain.deposit.confirmed chain-service → wallet-service credits balance after N confirmations.
wallet.deposit.credited wallet-service → user-service push + agent-service commission trigger (v1.1).
game.order.paid game-service → wallet-service freeze; admin audit log entry.
game.round.sold_out game-service → routes to DrawEngine implementation; chain-service if VRF/FULL_CHAIN.
game.draw.completed game-service → wallet-service credits winners, user-service notifies, agent-service marks settlement.
wallet.withdraw.requested wallet-service → admin-service queues for approval if over threshold.
wallet.withdraw.broadcasted wallet-service → chain-service records broadcast; tx tracker watches confirms.
wallet.withdraw.confirmed chain-service → wallet-service marks complete, user-service notifies.
21 topics total Full list in docs/5-contract/events.md. Every consumer is required to be idempotent on ref_id; 3 retries then DLQ → PagerDuty.
Ch. 07

Security posture安全姿态

where the paranoia lives偏执住在哪里

Four pillars: KMS custody, JWT with revocation, idempotency at every money seam, and a service-to-service auth model that assumes the internal network is hostile. 四根支柱:KMS 私钥托管、可吊销的 JWT、 每一处资金接缝的幂等、以及"假设内网就是敌对的"的服务间认证模型。

01KMS custodyKMS 私钥托管

  • Private keys live in AWS KMS (ECC_SECP256K1, asymmetric, non-extractable). sign-service calls kms:Sign; never holds key material.
  • EVM signing: KMS returns DER → sign-service decodes, applies low-s canonical form, recovers v (TD-20).
  • BTC HD seed encrypted at rest in KMS; decrypted once at @PostConstruct, derivation runs in-memory, buffer cleared after sign (TD-17).
  • Operator + hot / warm / cold wallets each in their own KMS key; cold wallet operations route through Gnosis Safe 3-of-5 + 24 h Timelock.
  • ChainBootValidator & SignBootValidator refuse non-local profile when stub flags are set — fail-closed boot.

02JWT & sessionJWT 与会话

  • HS256 HMAC v1.0 (asymmetric RS256 candidate, TD-55). Issuer: user-service. Verifier: gateway-service.
  • Access TTL 15 min; refresh TTL 7 days; opaque server-side refresh tokens with replay-detection on /auth/v1/refresh (TD-53).
  • jti revocation: gateway checks jti:${jti} against Redis blacklist on every request; user-service writes on logout / refresh (TD-54).
  • Telegram initData HMAC verified against bot token on every login; bot token in Secrets Manager.

03Idempotency seams幂等接缝

  • Deposits: keyed by (txHash, from, to, amount) — natural uniqueness from chain.
  • Withdrawals: backend-generated nonce stored in DuobaoVault.processedNonce[]; chain-level guard, can't be replayed even with a leaked signature.
  • Orders: Feign X-Idempotency-Key header; gateway injects if absent; game-service de-dupes on ref_id.
  • SMS: per-IP / per-60s rate-limit via Redis fixed window.
  • Kafka consumers: all keyed on ref_id; replay-safe by contract.

04Internal auth内部认证

  • Service-to-service Feign carries a short-TTL X-Service-Token from Nacos; gateway injects when bridging external → internal.
  • sign-service isolation: accepts only (keyId, payload). No raw business params reach it. Whitelist of callers (chain, wallet).
  • sign_audit table stores hash of every signing request — full forensic trail if a key is suspected compromised.
  • mTLS upgrade tracked (TD-19); current short-token model acceptable for the closed VPC.
  • Address binding currently TOFU; signed-message challenge planned for v1.1 (TD-26).
No legacy carry-over The legacy ../one/ codebase shipped with Fastjson 1.2.62 (CVE-2022-25845 RCE), predictable block.prevrandao, unverified JWTs, and 21 credentials in git history. duobao-latest is a from-scratch rewrite — zero shared code. The legacy repo is a spec to read, not a codebase to fork.
Ch. 08

Ops posture运维姿态

deploy & observe部署与观测

Four environments, six chain enums, fully orthogonal. Nacos is the config plane, AWS Multi-AZ + MSK is the data plane, Prometheus + business-metric dashboards are the observability plane.

Environment matrix

EnvChains activeStack shapePurpose
LOCAL Anvil + BSC testnet docker-compose · single host · 9 services + MySQL + Redis + Kafka + Nacos + Anvil Dev workstation. bin/bootstrap.sh one-command (Batch H in progress).
TESTNET Sepolia + BSC testnet + Anvil 1 instance / service · single AZ · MSK single-broker QA integration, contract-deploy rehearsal.
STAGING Mainnet (BSC + ETH) optional Multi-AZ · production scale-down replica Pre-prod simulation with real chain RPCs (small caps).
PRODUCTION BSC + BTC (v1.0) EC2 t3.medium · Multi-AZ RDS · ElastiCache Redis cluster · MSK Kafka · Chainlink mainnet (deferred) Live user traffic.

Config plane (Nacos, three tiers)

TierWhatReload
L0 — bootstrap Service DB endpoint, KMS endpoint, RPC nodes, Telegram bot id. Source: env vars + bootstrap.yml. Restart only
L1 — runtime one.draw.mode, prize ratio, per-chain confirms, fee strategy, sweep threshold, operator address. Hot — affects next draw
L2 — business Rounds, users, orders, withdrawal queue. Lives in MySQL, not Nacos. Per-write

Observability

LayerSignals
Infra Micrometer → Prometheus. QPS · p99 latency · error rate per endpoint. CPU / mem / heap per service.
Business Tickets sold/min · GMV · VRF success % (when wired) · withdrawal queue depth · refund rate.
On-chain Hot wallet balance · cold wallet multisig pending count · LINK subscription balance · broadcast nonce holes · failed-tx ratio.
Treasury Daily 4-way reconciliation: wallet ledger ↔ on-chain balance ↔ USDT settlement ↔ admin audit. Drift > 1e-6 USDT pages on-call.
Smoke entrypoints (verified 2026-05-08) ./gradlew testApp · backend unit + Testcontainers integration.
scripts/smoke-e2e.sh · full flow: login → deposit → ticket → draw → withdraw.
docker-compose up + bin/bootstrap.sh · local 9-service + Anvil contract deploy.
Ch. 09

Production readiness生产就绪度

what's real, what's stub什么是真的、什么是桩

Honest matrix. Anything stub returns a safe default (501, empty list, log to stdout); a boot validator refuses prod profile when a stub flag is set. Anything partial works for the core path but has a documented seam. 诚实矩阵。标 stub 的全部返回安全默认值 (501、空列表、写 stdout);启动期校验器在 stub 旗标还在的情况下拒绝以 prod profile 启动。 标 partial 的对核心路径可用,但有明确文档化的缝隙。

CapabilityStatusWhat's missingTech-debt anchor
User auth (TG one-tap login, JWT, TOTP) prod
Deposits — EVM (BSC, ETH) prod
Deposits — BTC partial Passive scan loop stubbed; user pastes txHash manually. TD-24
Deposits — TRON stub No adapter; StubChainAdapter returns 501. TD-61
Draw — BACKEND (commit-reveal) prod
Draw — VRF_ONLY / FULL_CHAIN stub Chainlink VRFv2.5 integration + DrawCoordinator deployment. TD-59
Withdrawals — EVM USDT prod Multi-sig cold path live; signed nonce idempotency.
Withdrawals — BTC partial sign-service PSBT ✓; chain-service RPC + UTXO selection ⚠. TD-67
KMS signing — EVM + BTC PSBT prod Both closed 2026-05-13. TD-17, TD-20
Auto-sweep (hot wallet collection) stub Job exists, throws FEATURE_NOT_IMPLEMENTED. Manual sweep ok for launch. TD-60
Admin business endpoints partial P0 subset (draws / RBAC / audit export) ≈ 3 working days to wire. ~70% endpoints DTO-only. TD-38
SMS dispatch (Aliyun / Twilio / Tencent) stub Real SDK adapters; dev logs to stdout. TD-32
Risk engine planned Code shell only. Integration with game / wallet event streams scheduled v1.1.
Audit trail (append-only) prod DB-role enforced INSERT-only; cryptographic hash on every entry.
Reconciliation (4-way daily) prod Wallet ledger ↔ on-chain ↔ USDT settle ↔ admin audit; pages on drift.
CI/CD pipeline stub No .github/workflows/ yet. Manual ./gradlew testApp for now. flagged in progress.md

Top-5 deferred items by impact

#ItemCostBlocks
1 TD-59 — DrawCoordinator + Chainlink VRFv2.5 5–7 days Non-BACKEND draw modes
2 TD-61 — TRON ChainAdapter (trident-java + TRC20 + base58check) 3–4 days TRON deposits / withdrawals
3 TD-67 — chain-service BTC RPC + UTXO selection + PSBT constructor 4–5 days BTC withdrawal — 后续开放 (out of v1.0)
4 TD-60 — Auto-sweep job (cron + threshold + KMS signer) 2–3 days Capital efficiency only — launchable without
5 TD-38 (P0 subset) — Admin draws / RBAC / audit-export endpoints 3 days (P0); 6–8 days full Launch operations
Ch. 10

What's interesting有意思的设计

things to raise an eyebrow at值得挑眉的几点

Ten observations a senior engineer is likely to find non-obvious or unusually disciplined for a project at this stage. Order is rough; weight your own. 十条观察,资深工程师在这个阶段大概率会觉得"不显然"或"对这个阶段的项目而言异常自律"。 顺序是大致的,自行赋权。

i.

Environment and chain are independent enums, all the way down环境与链是相互独立的枚举,贯穿到底

Most projects collapse "testnet = Sepolia" and "prod = mainnet." Here, env and executionChain are orthogonal; STAGING can target BSC mainnet, LOCAL can target Sepolia. Nacos namespaces isolate per-env, per-chain config blocks isolate per-chain knobs. Means real capital tests happen in STAGING before launch.

ii.

Commit-reveal beats VRF for v1.0 stakes在 v1.0 这个体量下,commit-reveal 优于 VRF

For 1-USDT seats, paying ~200k gas per draw to Chainlink doesn't pencil out. So v1.0 ships a platform-signed commit (EIP-191) before the round, reveals the seed after, and anyone can locally verify SHA-256(seed) matches. Zero gas. Auditable. The trade is operator discipline — leaking the seed early voids the round. For micro-stakes this is the right call.

iii.

Sign-service refuses to know what it's signingsign-service 拒绝知道自己在签什么

sign-service accepts only (keyId, payload) — never business parameters. chain-service and wallet-service pre-validate, pre-authorize, then send a hashable blob. sign_audit table records the hash of every signing call. If a key is suspected leaked, there's a complete forensic trail of what was signed by whom. Most teams don't go this far.

iv.

Schema-level financial isolation, not "logical microservices"在 schema 层做资金隔离,不是"逻辑上的微服务"

Each service has its own MySQL schema with its own credentials and its own migration ownership. wallet-service literally cannot read one_user.user_info — its DB user has no grant on that schema. Cross-service consistency is Kafka + idempotency, not 2PC. Doesn't permit accidental data leaks; does require event-sourcing maturity.

v.

Hand-rolled BIP-84 PSBT — BTC 后续开放手写 BIP-84 PSBT(BTC 后续开放)

BTC deposit/withdrawal is out of v1.0 scope (后续开放) — v1.0 USDT rails cover BSC / ETH / TRON only. The retained blueprint: when opened, BTC withdrawal will not use a third-party custody API; sign-service would implement PSBT v0 encoding, BIP-143 P2WPKH sighash, and BIP-84 derivation (m/84'/0'/uid') itself — non-custodial UX without vendor lock-in, at the cost of more audit surface.

vi.

Multi-tier custody with a 24-hour kill window多层托管 + 24 小时熔断窗口

Hot wallet (KMS-signed, small cap). Warm (2-of-3 multisig, medium cap). Cold (Gnosis Safe 3-of-5 + 24h Timelock, large cap). Large withdrawals queue for a full day; any of 5 signers can cancel mid-window. Institutional-grade custody plumbing at a micro-stakes product — that's a product-trust bet, not an engineering reflex.

vii.

Append-only audit, enforced at the DB role level仅追加的审计日志,在 DB role 层强制

one_audit and wallet_transaction are INSERT-only at the PostgreSQL role grant level — not just "we promise not to update." An ORM bug, a rogue admin script, or a SQL injection cannot UPDATE or DELETE an audit row. Combined with a cryptographic hash chain, the audit is regulator-grade.

viii.

Boot validators fail-closed on stub flags启动校验器在 stub 旗标存在时拒绝启动

ChainBootValidator and SignBootValidator refuse to come up on a non-local profile when a stub is still wired. You can't accidentally deploy a build where SMS goes to stdout or VRF returns SHA-256(...). The stubs are tracked, named, and physically prevented from reaching production.

ix.

Contract upgrades route through Timelock, no exceptions合约升级一律走 Timelock,无例外

UUPS proxies, owner is a Gnosis Safe, Safe transactions go through a 24h Timelock. No "we can upgrade in an emergency" backdoor. Storage layout is gap-guarded. Etherscan source verification is a release-checklist mandate, not a wish. The contract security thinking is closer to a DEX than a consumer app.

x.

Documentation is contract, not aspiration文档是契约,不是愿望

Every doc has a Status: LOCKED frontmatter. Changes require an ADR. Code reviews check against docs. The whole project followed a strict pipeline (0-feasibility → 1-product → 2-requirements → 3-arch → 5-contract → 6-standards → 7-test-plan → 8-sprint → 9-ops) before any application code landed. Rare at this stage; rarer still that it survived contact with implementation.