Hot Wallet Development
Exchange hot wallet is online crypto storage system that automatically processes user withdrawals. It's always internet-connected, so it represents main attack vector. Compromise between convenience and security solved through proper architecture: minimal hot balances, main reserve in cold.
Architecture
HD Wallet for Ethereum and EVM Networks
Hot wallet is master hot wallet address where funds from deposit addresses consolidate. For Ethereum-based networks — one address for entire exchange or multiple for parallel withdrawal processing.
type HotWallet struct {
address common.Address
keyManager KeyManager // abstraction over HSM or keystore
client *ethclient.Client
nonceTrack *NonceTracker // nonce management
gasTrack *GasTracker
}
// NonceTracker — critical component
// PostgreSQL stores last used nonce
// For parallel withdrawals need atomic nonce issuance
type NonceTracker struct {
db *DB
mu sync.Mutex
pool chan uint64 // pre-fetched nonce pool
}
func (nt *NonceTracker) Next(ctx context.Context) (uint64, error) {
nt.mu.Lock()
defer nt.mu.Unlock()
// Get next nonce atomically
var nonce uint64
err := nt.db.QueryRow(ctx,
"UPDATE hot_wallet SET nonce = nonce + 1 RETURNING nonce - 1"
).Scan(&nonce)
return nonce, err
}
Nonce management is one of main technical complexities. With parallel transactions, guarantee two workers don't get same nonce. Solution: atomic increment in database with mutex protection.
Multi-Currency Hot Wallet
Different assets require different approaches.
| Network | Wallet Type | Specifics |
|---|---|---|
| Ethereum / EVM | EOA or Smart Contract Wallet | ERC-20 via transfer/transferFrom |
| Bitcoin | P2WPKH (SegWit) | UTXO model, coin selection |
| Tron | TRX/TRC-20 | Bandwidth/Energy model |
| Solana | ED25519 keypair | SPL tokens, compute units |
| Ripple | Shared address + destination tag | Tag mandatory |
# Bitcoin hot wallet — UTXO coin selection
class BitcoinHotWallet:
def create_withdrawal(self, to_address: str, amount_sat: int, fee_rate: int):
# Coin selection — select minimum UTXO set to cover amount + fee
utxos = self.wallet.utxos()
selected, change = self.select_coins(utxos, amount_sat, fee_rate)
tx = self.wallet.transaction_create(
outputs=[(to_address, amount_sat)],
utxos=selected,
fee=self.estimate_fee(len(selected), fee_rate),
replace_by_fee=True, # RBF for fee bump ability
)
return tx
def select_coins(self, utxos, amount, fee_rate):
# Simple strategy: smallest first (minimize UTXO set)
utxos.sort(key=lambda u: u.value)
selected = []
total = 0
for utxo in utxos:
selected.append(utxo)
total += utxo.value
fee = self.estimate_fee(len(selected), fee_rate)
if total >= amount + fee:
change = total - amount - fee
return selected, change
raise InsufficientFunds()
Hot Wallet Security
HSM Integration
Hot wallet private key should never be plain text in server memory. Protection levels:
Level 1 (minimal): Encrypted keystore on disk (AES-256), password from environment variable or secrets manager. Key decrypted on startup and stored in memory.
Level 2 (recommended): HashiCorp Vault Transit Secrets Engine. Key never leaves Vault — server sends data to sign, receives signed transaction back.
import vault "github.com/hashicorp/vault/api"
type VaultSigner struct {
client *vault.Client
keyName string
}
func (vs *VaultSigner) SignTransaction(txHash []byte) ([]byte, error) {
// Vault Transit: sign data without key export
path := fmt.Sprintf("transit/sign/%s", vs.keyName)
secret, err := vs.client.Logical().Write(path, map[string]interface{}{
"input": base64.StdEncoding.EncodeToString(txHash),
"hash_algorithm": "sha2-256",
"signature_algorithm": "pkcs1v15",
"prehashed": true,
})
// Parse ECDSA signature and convert to Ethereum format
return parseVaultSignature(secret.Data["signature"].(string))
}
Level 3 (maximum): Hardware HSM (Thales Luna, AWS CloudHSM, YubiHSM). Signature executed in hardware chip, private key physically unextractable. Cost from $20,000/year. For most exchanges Vault is optimal compromise.
Limits and Control
type WithdrawalLimiter struct {
db *DB
cache *redis.Client
}
// Daily limit of automatic withdrawals from hot wallet
func (wl *WithdrawalLimiter) CheckAndConsume(amount decimal.Decimal, currency string) error {
key := fmt.Sprintf("hot_wallet:daily_limit:%s:%s", currency, today())
// Atomic increment with limit check
script := redis.NewScript(`
local current = redis.call('GET', KEYS[1])
if current == false then current = 0 end
local new_val = tonumber(current) + tonumber(ARGV[1])
if new_val > tonumber(ARGV[2]) then
return redis.error_reply("LIMIT_EXCEEDED")
end
redis.call('SETEX', KEYS[1], 86400, new_val)
return new_val
`)
_, err := script.Run(context.Background(), wl.cache,
[]string{key}, amount.String(), DAILY_LIMIT.String()).Result()
return err
}
On limit exceed — withdrawal goes to queue requiring manual operator approval (refill from warm wallet).
Balance Management and Refilling
Hot Wallet Balance Monitoring
type BalanceWatcher struct {
hotWallet *HotWallet
threshold decimal.Decimal // minimum threshold
target decimal.Decimal // target balance after refill
alerter Alerter
}
func (bw *BalanceWatcher) Watch(ctx context.Context) {
ticker := time.NewTicker(5 * time.Minute)
for {
select {
case <-ticker.C:
balance := bw.hotWallet.GetBalance()
if balance.LessThan(bw.threshold) {
bw.alerter.Alert(Alert{
Level: "CRITICAL",
Message: fmt.Sprintf("Hot wallet balance low: %s. Need topup from warm wallet", balance),
})
// Automatic refill from warm wallet
bw.requestTopup(bw.target.Sub(balance))
}
case <-ctx.Done():
return
}
}
}
ERC-20 Token Consolidation
Deposit addresses accumulate tokens. Sweep process consolidates them.
Development Timeline
| Component | Timeline |
|---|---|
| ETH/ERC-20 hot wallet | 3–4 weeks |
| Bitcoin UTXO wallet | 3–4 weeks |
| HSM/Vault integration | 1–2 weeks |
| Sweep automation | 2–3 weeks |
| Monitoring dashboard | 1–2 weeks |
| Testnet testing | 2–3 weeks |
Full multi-currency hot wallet with HSM — 3–4 months.







