Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Custodial Wallet

English · 中文

A compact, extensible custodial wallet you can read end-to-end. Integrating parties (exchanges or other services) call an API to get custodial addresses, receive deposits (chain scanning credits a ledger), and request withdrawals (risk → sign → broadcast → confirm). It ships with two chain adapters — EVM (Ethereum Sepolia, native ETH + ERC20) and Solana (devnet, native SOL + SPL tokens) — a built-in ops console, append-only ledger auditing, deposit-address concentration, internal book transfers, reorg handling, and signed webhook callbacks.

Status: reference implementation. It runs single-process against one MySQL database, with internal boundaries (ports) drawn so the pieces most likely to become standalone services later — signing, scanning, risk — can be swapped without touching the core. Not professionally security-audited; do not run as-is in production. Known gaps and extension points are documented in Future improvements.

Features

  • Accounts & API — HMAC-signed integrator API; per-account API keys.
  • Deposits — continuous chain scanner + on-demand rescan (block / tx); native + tokens (ERC20 / SPL); confirmations, idempotent crediting, and reconcile mode to correct past entries.
  • Withdrawals — idempotency, balance lock, address validation, per-coin single/daily limits, auto-sign, broadcast, and gas-fee ledgering. Stuck EVM txs can be bumped (replace-by-fee).
  • Internal transfers — a withdrawal to a same-account address settles on the books instantly (no chain tx, no gas).
  • Concentration (sweeping) — move deposits into a hot wallet; native single-step, ERC20 two-step gas-station via a dedicated fee wallet, Solana SPL in one tx with the fee/hot wallet as separate fee payer (so empty deposit addresses need no SOL).
  • Reorg handling — block-hash continuity detection, rollback, and reversal of dropped deposits.
  • Webhooks — signed deposit.confirmed / withdrawal.* callbacks with retries and a delivery audit.
  • Ledger audit — every balance change is an append-only ledger_entries row; manual correction tools.
  • Ops console — a single-page admin UI at /admin (English / 繁體中文).

Architecture

The core depends on four ports (internal/port). Each has an in-process implementation today and a clear future replacement:

Port Today Future replacement
Signer signer.LocalSigner — secp256k1 (EVM) + ed25519 (Solana) keys, AES-GCM-encrypted in MySQL remote signer: tss-lib MPC + AWS KMS / Nitro Enclave
Chain chain/evm (go-ethereum) + chain/solana (solana-go) more per-chain adapters (BTC/Tron…)
DepositIngestor ingest.Service + internal HTTP /ingest/* scanners in any language POST the same JSON; later Kafka
RiskEngine risk.RuleEngine — per-coin limits velocity, SDN/AML, multi-sig routing

Two binaries share one object graph (wired in internal/app):

  • cmd/api — integrator API (HMAC) + internal/ops API + ops console.
  • cmd/indexer — continuous per-chain deposit/withdrawal scanner; also advances concentration and reorg checks.

See docs/architecture.md for the full design and flows.

Quick start

Prerequisites

  • Go 1.24+
  • MySQL 8 (a database named custodial_wallet)

Setup

cp .env.example .env      # set DB_PASSWORD, WALLET_KEK (openssl rand -hex 32), SEPOLIA_RPC, SOLANA_RPC
make migrate              # create tables + seed coins (ETH/USDC on Sepolia, SOL/USDC on Solana devnet)

Config lives in config.yaml (falls back to config.example.yaml); ${ENV} references are expanded from .env. Never commit .env — it holds the DB password and the key-encryption-key; rotate them before any deploy.

Run

make run-api        # serves :8080  (ops console at http://localhost:8080/admin)
make run-indexer    # continuous scanning for all configured chains

First deposit & withdrawal

make smoke starts the API and runs a fund-independent self-test (account creation, address generation, HMAC auth, a live Sepolia block rescan). The full funded path (needs Sepolia ETH from a faucet):

go run ./cmd/smoke account                              # -> api_key, api_secret
go run ./cmd/smoke address <secret> <apikey> SEPOLIA    # -> address A
# fund A from a Sepolia faucet, then:
go run ./cmd/smoke rescan-tx SEPOLIA <funding_tx_hash>  # detect + credit
go run ./cmd/smoke balances <secret> <apikey> A
go run ./cmd/smoke withdraw <secret> <apikey> SEPOLIA ETH <dest> 0.001 wd-1
go run ./cmd/smoke rescan-tx SEPOLIA <withdraw_tx_hash> # confirm

Ops console

GET /admin serves an internal single-page console: create accounts, generate wallets, query balances/deposits/ledger, submit withdrawals, run concentration, drive on-chain updates (rescan, reorg), manage coins, and inspect webhooks. It calls the unauthenticated /internal/v1/* endpoints (account creation, balance adjustments, withdrawals, coin config) — these have no authentication; bind them to localhost / an internal network and never expose them publicly. The UI auto-detects browser language (English / 繁體中文) and has a switcher.

API

Integrator API (/api/v1, HMAC-signed):

Method Path Body / Query
POST /addresses {chain[, type]}
GET /addresses
POST /withdrawals {client_withdraw_id, chain, coin, to, amount[, from]}
GET /withdrawals/:id
GET /deposits ?coin&address&status
GET /balances ?address

Request signing

X-Api-Key:    <api_key>
X-Timestamp:  <unix seconds>           # ±5 min window (replay protection)
X-Signature:  hex(HMAC_SHA256(secret, METHOD\nPATH\nTIMESTAMP\nSHA256(body)))

cmd/smoke and internal/httpapi.Sign implement this exactly. Webhook callbacks are signed the same way (server → integrator).

Documentation

中文文件:README · 架構 · 運營 · 可優化項

License

MIT

About

A simple but real custodial wallet reference implementation in Go (EVM): deposits, withdrawals, concentration, append-only ledger, reorg handling, signed webhooks — extensible via Signer/Chain/Ingestor/Risk ports.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages