wallet-service Architecture

duobao backend — sole balance authority: USDT + BTC ledger, deposits, withdrawals, reconciliation

wallet-service (schema: one_wallet, port 6673) — SOLE balance authority: direct DB writes from other services are FORBIDDEN gateway-service upstream router JWT verify + header AUTHENTICATED (JWT) /api/v1/wallet/* GET /api/v1/wallet/balance GET /api/v1/wallet/balance/btc GET /api/v1/wallet/transactions POST /api/v1/wallet/withdraw/apply GET /api/v1/wallet/deposit-addr GET /api/v1/wallet/whitelist POST /api/v1/wallet/whitelist v1.0 done INTERNAL FEIGN /internal/v1/wallet/* GET /account/{uid} GET /account-btc/{uid} GET /check-balance POST /freeze (refId idempotent) POST /unfreeze POST /debit POST /credit POST /credit-btc POST /deposit POST /trial-bonus/grant POST /physical-forfeit guard: ServiceTokenAuthFilter UserHeaderVerifyFilter v1.0 done ADMIN / INTERNAL GET /internal/v1/wallet/ transactions GET /internal/v1/wallet/ deposit-address GET /internal/v1/wallet/ treasury/wallets v1.0 done SERVLET FILTERS ServiceTokenAuthFilter internal Feign auth UserHeaderVerifyFilter HMAC X-User-Id from gateway CONTROLLERS WalletController /api/v1/wallet/* WalletInternal /internal/v1/wallet/* WithdrawWhitelist /api/v1/wallet/whitelist WithdrawCallback /internal/callback SERVICES (business logic) WalletLedger Svc debit/credit/freeze opt-lock WalletAccount Svc account read/init WalletDeposit Svc claim / address lookup WalletDeposit AddrSvc derive + persist addr WalletBonus Svc trial/register bonus WalletReconcile Svc daily reconcile job WalletLedger QuerySvc paged tx queries WalletAccount BtcSvc BTC sat balance WalletDeposit ClaimSvc event idempotent claim FEIGN CALLEE CLIENTS (wallet-service calls outbound) IChainService broadcast/btcAddr IUserService getUser / status IGameService getOrder / winner IAdminService submitForAudit / audit IAgentService (v1.1+) getUpline for commission EVENT PRODUCER WalletEventProducer wallet.deposit.credited · chain.btc.exposure.alert (BtcExposureMonitor @Scheduled) EVENT CONSUMERS ChainDepositConsumer chain.deposit.confirmed + chain.deposit.btc.confirmed + user.registered (bonus) WithdrawCallback Consumer wallet.withdraw.broadcasted · wallet.withdraw.confirmed · wallet.withdraw.failed · game.round.sold_out · game.draw.completed game.round.canceled (refund) · prize.physical.forfeited MYBATIS MAPPERS WalletAccount Mapper WalletAccount BtcMapper WalletTransaction Mapper WalletWithdraw RequestMapper WalletTreasury Mapper WalletWithdraw WhitelistMapper WalletAddress BindingMapper WalletDeposit RequestMapper WalletDeposit AddressMapper WalletReconcile DailyMapper v1.0 done — 10 mappers + flyway_schema_history (V1..V4 migrations) MySQL 8 schema: one_wallet • wallet_account (USDT, opt-lock) • wallet_account_btc (sat, opt-lock) • wallet_transaction (append-only) • wallet_withdraw_request • wallet_withdraw_whitelist • wallet_address_binding • wallet_deposit_request • wallet_deposit_address (TD-65) • wallet_treasury • wallet_reconcile_daily migrations V1..V4 v1.0 done Redis 7 namespace: wallet: • idempotency:{eventId} TTL 24h • withdrawal rate-limit per uid • deposit-address cache (read) v1.0 done Kafka broker kafka:29092 (compose) • produces 2 topics • consumes 9 topics v1.0 done Kafka Events — wallet-service PRODUCES wallet.deposit.credited partition keyuid triggerafter successful credit() consumed byuser(push) · agent(v1.1+ commission) chain.btc.exposure.alert partition keylevel (AlertLevel) triggerBtcExposureMonitor @Scheduled 1/min consumed byadmin-service · sms-service CONSUMES (9 topics via @KafkaListener) chain.deposit.confirmedUSDT deposit credit chain.deposit.btc.confirmedBTC sat credit game.round.sold_outopen escrow pot game.draw.completedprize payout credit game.round.canceledrefund per ticket wallet.withdraw.*broadcasted/confirmed/failed SM prize.physical.forfeited70% forfeit USDT credit user.registeredregister bonus (if enabled) LEGEND Controllers Services / sibling svc Auth filters DB / Mappers Events / channels External services Helpers / lanes Service boundary Cache / limiter line Kafka publish v1.1 deferred path v1.0 done v1.1 deferred conditional bean

What wallet-service owns

  • • Sole authority on wallet_account.balance — no other service may write it directly
  • • Append-only wallet_transaction ledger; every balance change leaves a row
  • • USDT + BTC balances, freeze/unfreeze, debit/credit with idempotency keys
  • • Withdrawal 7-state machine (PENDING → REVIEWING → APPROVED → BROADCASTING → BROADCASTED → CONFIRMED / CHAIN_FAIL)
  • • Deposit address persistence per (uid, chain) — TD-65
  • • Treasury wallet catalog + daily reconcile job
  • • BTC exposure monitor (BtcExposureMonitor @Scheduled, produces chain.btc.exposure.alert)

Kafka events produced + consumed

  • Produces: wallet.deposit.credited — after each successful credit
  • Produces: chain.btc.exposure.alert — BTC threshold monitor @Scheduled 1/min
  • Consumes: chain.deposit.confirmed, chain.deposit.btc.confirmed — on-chain deposit credit
  • Consumes: game.round.sold_out, game.draw.completed, game.round.canceled
  • Consumes: wallet.withdraw.broadcasted / .confirmed / .failed — state machine
  • Consumes: prize.physical.forfeited, user.registered (register bonus)

External dependencies

  • • MySQL one_wallet — 10 tables, V1..V4 migrations
  • • Redis — consumer idempotency SETNX, withdrawal rate-limit, address cache
  • • Kafka — 2 produced, 9 consumed
  • • Feign: IChainService (withdraw broadcast, BTC addr), IUserService, IGameService, IAdminService
  • • Feign: IAgentService (v1.1+ commission upline lookup)