Wallet Service Guide
๐ Overview
The Wallet Service is responsible for cryptocurrency wallet management across multiple blockchains (TON, TRON, Ethereum, BSC).
Port: 9092 (gRPC)
Status: โ ๏ธ Partial (Deposits working, Withdrawals blocked on Task 0.1)
๐๏ธ Architecture
Wallet Service
โโโ internal/
โ โโโ wallet/
โ โ โโโ service/
โ โ โ โโโ wallet.service.go - Main business logic
โ โ โ โโโ signer/
โ โ โ โโโ interfaces.go - Signer interface
โ โ โ โโโ factory.go - Signer factory
โ โ โ โโโ ton_signer.go - TON signing (Task 0.1)
โ โ โ โโโ tron_signer.go - TRON signing (Task 0.2)
โ โ โ โโโ twc/ - Trust Wallet Core (optional)
โ โ โโโ domain/
โ โ โ โโโ entity/
โ โ โ โ โโโ wallet.entity.go - Wallet model
โ โ โ โ โโโ wallet_root.entity.go - HD wallet root
โ โ โ โโโ repository/ - DB repositories
โ โ โ โโโ service/
โ โ โ โโโ ton_wallet_generator_v4r2.go - TON wallet gen
โ โ โโโ transport/
โ โ โโโ grpc/ - gRPC handlers
โ โโโ balance/ - Balance service (Task 0.5)
โ โโโ user/ - User service
โโโ migrations/ - Database migrations
โโโ cmd/
โโโ server/ - Main server
โโโ migrator/ - Migration runner
๐ Key Components
1. Wallet Generation
TON Wallet (v4R2) - WORKING โ
File: internal/wallet/domain/service/ton_wallet_generator_v4r2.go
generator := NewTONWalletGeneratorV4R2(encryptionKey)
wallet, root, err := generator.GenerateWallet(telegramID, "My TON Wallet", true)
// Creates:
// 1. 24-word mnemonic (BIP39 via tonutils-go)
// 2. ed25519 keypair
// 3. v4R2 wallet contract address (EQ... format)
// 4. Encrypts mnemonic (AES-256-GCM + PBKDF2)
// 5. Stores in database (wallet + wallet_root)
Generated Address Format:
- Mainnet: EQCa1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z
- Testnet: UQCa1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z
- Workchain: 0 (mainnet)
- Bounceable: Yes (default for user display)
TRON Wallet - TODO (Task 0.2)
Target: Generate proper T… addresses
2. Signer Interface
File: internal/wallet/service/signer/interfaces.go
type Signer interface {
Address(pub PubKey, chain Chain) (string, error)
SignTx(chain Chain, priv PrivKey, payload []byte) ([]byte, error)
}
type Deriver interface {
FromMnemonic(m string, path string, curve Curve) (PrivKey, PubKey, error)
}
Current Status: - TON Signer: โ Placeholder (Task 0.1) - TRON Signer: โ Placeholder (Task 0.2) - Trust Wallet Core: ๐ง Framework ready (optional)
3. Database Schema
Wallets Table
CREATE TABLE wallets (
id UUID PRIMARY KEY,
user_id UUID NOT NULL, -- Links to users.id
telegram_id BIGINT NOT NULL, -- Links to Telegram
wallet_type VARCHAR(20), -- 'hd' | 'connected' | 'hot'
status VARCHAR(20), -- 'active' | 'inactive' | 'pending'
address VARCHAR(255) NOT NULL, -- Blockchain address
public_key TEXT NOT NULL, -- Public key
network VARCHAR(50) NOT NULL, -- 'TON' | 'TRON' | 'ETH' | 'BSC'
derivation_path VARCHAR(100), -- BIP44 path (for HD)
xpub TEXT, -- Extended public key
is_default BOOLEAN DEFAULT FALSE, -- Default wallet for network
source VARCHAR(20), -- 'internal' | 'walletconnect'
created_at TIMESTAMP,
updated_at TIMESTAMP
);
Wallet Roots Table
CREATE TABLE wallet_roots (
id UUID PRIMARY KEY,
telegram_id BIGINT NOT NULL,
network VARCHAR(50) NOT NULL,
encrypted_mnemonic TEXT NOT NULL, -- AES-256-GCM encrypted
xpub TEXT NOT NULL,
encrypted_xprv TEXT NOT NULL,
created_at TIMESTAMP,
UNIQUE(telegram_id, network) -- One root per user per chain
);
Indexes:
- telegram_id (frequently queried)
- address (for deposit detection)
- user_id + network + is_default (default wallet lookup)
๐ Workflows
Deposit Flow
1. User requests deposit address
โ
2. WalletService.GetDepositAddress(userId, network)
โ
3. Check for existing default wallet:
SELECT * FROM wallets
WHERE user_id = ? AND network = ? AND is_default = true;
โ
4. If not exists, create:
- Generate mnemonic (tonutils-go)
- Derive keypair
- Create address (v4R2 for TON)
- Encrypt mnemonic
- Store in DB (wallet + wallet_root)
โ
5. Return address: "EQCa1b2c3d4e5f6g..."
โ
6. Watcher monitors blockchain
โ
7. Deposit detected โ emit event
โ
8. Accounting Service credits balance
โ
9. User notified via Telegram
Withdrawal Flow (Needs Task 0.1!)
1. User requests withdrawal: 50 TON to external address
โ
2. Accounting Service checks + locks balance
โ
3. WalletService.ProcessWithdrawal(userId, amount, toAddress)
โ
4. Get wallet root:
SELECT encrypted_mnemonic FROM wallet_roots
WHERE telegram_id = ? AND network = 'TON';
โ
5. Decrypt mnemonic (AES-256-GCM)
โ
6. Derive private key (tonutils-go)
โ
7. Sign transaction (Task 0.1 - CURRENTLY BROKEN!) โโโโ โ
signer := NewSigner("TON")
signature, err := signer.SignTx("TON", privateKey, txBytes)
// ERROR: "ton signer not implemented"
โ
8. Broadcast to blockchain
โ
9. Update withdrawal status (tx_hash)
โ
10. Accounting Service unlocks balance
โ
11. User notified via Telegram
๐ Security
Mnemonic Encryption
// Encryption (when wallet is created)
func (g *TONWalletGeneratorV4R2) EncryptData(mnemonic string) (string, error) {
// 1. Generate random salt (16 bytes)
// 2. Derive key (PBKDF2: 10,000 iterations, SHA256)
// 3. AES-256-GCM encryption
// 4. Return: hex(salt + ciphertext)
}
// Decryption (when signing withdrawal)
func (g *TONWalletGeneratorV4R2) DecryptData(encrypted string) (string, error) {
// 1. Decode hex
// 2. Extract salt (first 16 bytes)
// 3. Derive key (same PBKDF2 params)
// 4. AES-256-GCM decryption
// 5. Return plaintext mnemonic
// 6. MUST zero memory after use! โโโโ IMPORTANT
}
Key Management
// Encryption key from environment
encryptionKey := os.Getenv("WALLET_ENC_KEY")
// MUST be 32 bytes for AES-256
// MUST be different per environment (dev, staging, prod)
// MUST be rotated periodically
// MUST be stored securely (KMS/Vault in production)
Private Key Handling
// NEVER store private keys in database
// NEVER log private keys
// ALWAYS derive on-demand from mnemonic
// ALWAYS zero memory after use
// Good:
privateKey := deriveFromMnemonic(mnemonic)
defer zeroMemory(privateKey) // Zero after use
signature := sign(privateKey, tx)
return signature
// Bad:
privateKey := deriveFromMnemonic(mnemonic)
wallet.PrivateKey = privateKey // โ NEVER STORE
๐งช Testing
Unit Tests
cd services/wallet
go test ./internal/wallet/service/signer/ -v
go test ./internal/wallet/domain/service/ -v
Integration Tests
# Requires running database
go test -tags=integration ./...
Test Vectors (Golden Tests)
// Test with known mnemonic
mnemonic := "abandon abandon abandon ... art"
expectedAddress := "EQCa1b2c3d4e5f6g..."
generator := NewTONWalletGeneratorV4R2(testKey)
address, _ := generator.DeriveAddressFromMnemonic(mnemonic)
assert.Equal(t, expectedAddress, address)
๐ Metrics
# Wallet operations
wallets_created_total{network}
wallets_active_total{network}
deposit_addresses_generated_total{network}
# Signing operations (when Task 0.1 complete)
transactions_signed_total{network, type}
signing_duration_seconds{network}
signing_errors_total{network, error_type}
# Mnemonic operations
mnemonics_encrypted_total
mnemonics_decrypted_total
encryption_duration_seconds
decryption_duration_seconds
๐ Usage Examples
Create Wallet (gRPC)
req := &wallet_pb.CreateWalletRequest{
TelegramId: 123456789,
Network: "TON",
WalletType: "internal",
}
resp, err := walletClient.CreateWallet(ctx, req)
// Returns: {wallet_id, address, network}
Get Deposit Address (gRPC)
req := &wallet_pb.GetDepositAddressRequest{
UserId: "550e8400-...",
Network: "TON",
}
resp, err := walletClient.GetDepositAddress(ctx, req)
// Returns: {address: "EQCa1b2c...", network: "TON"}
Sign Withdrawal (After Task 0.1)
signer := signers.NewSigner(signers.Chain("TON"))
signature, err := signer.SignTx(
signers.Chain("TON"),
privateKey,
transactionBytes,
)
// Returns: Signed transaction ready for broadcast