Building a blockchain-based centralized wallet is a complex yet rewarding engineering challenge—especially when aiming to bridge digital currencies with real-world usability. This article dives into the architecture and implementation details of a lightweight blockchain wallet developed in late 2016, designed to support Bitcoin and Ethereum-based token transfers with a vision of enabling seamless offline cryptocurrency payments. While some technical approaches may have evolved since then, the foundational concepts remain highly relevant for modern wallet development.
The system described here leverages Ruby on Rails 5 as the backend API layer, integrates full blockchain nodes via JSON-RPC, and uses message queues and background jobs to ensure reliability and responsiveness. Whether you're exploring blockchain wallet development, designing secure cryptocurrency transaction systems, or studying centralized wallet architecture, this guide offers practical insights grounded in real-world implementation.
System Architecture Overview
At its core, the wallet operates as a centralized service that interacts with blockchain networks through dedicated full nodes. The backend is built using Rails 5, exposing a RESTful API to mobile and web clients. This design ensures clean separation between frontend interfaces and blockchain logic.
Two primary blockchain clients are used:
- Bitcoin Core for Bitcoin transactions
- Go-Ethereum (geth) for Ethereum and ERC-20 token operations
Both are controlled via JSON-RPC:
- Bitcoin Core uses the official Bitcoin JSON-RPC API)
- Ethereum interactions are handled through the ethereum-ruby gem
To monitor on-chain activity, such as incoming deposits, the system employs RabbitMQ as a message queue. Scripts running on the node servers listen for address events and push notifications into the queue whenever funds are received. This event-driven approach decouples blockchain monitoring from business logic processing, improving scalability and fault tolerance.
👉 Discover how leading platforms securely manage blockchain interactions today.
Additionally, exchange rate data is fetched regularly from external APIs and cached in Redis. A scheduled job updates these rates every minute using Sidekiq-Cron and Active Job, ensuring fast response times for currency conversion requests within the app.
Core Keywords Identified:
- Blockchain wallet development
- Centralized wallet architecture
- Cryptocurrency transaction system
- Bitcoin and Ethereum integration
- RESTful API for wallets
- JSON-RPC blockchain interaction
- Real-time transaction monitoring
- Secure cryptocurrency transfer
Key Functional Modules
The wallet system is organized into three primary modules: user management, blockchain account handling, and transaction processing. Each plays a critical role in delivering a secure and functional experience.
User Information Module
User authentication combines Clearance for basic sign-up and login flows with Ruby-JWT (JSON Web Tokens) for stateless session management. The ApplicationController includes a before_action filter to authenticate each request:
before_action :authenticate_request!
def authenticate_request!
begin
fail NotAuthenticatedError unless user_id_included_in_auth_token?
@current_user = User.find(decoded_auth_token[:user_id])
rescue JWT::ExpiredSignature
raise AuthenticationTimeoutError
rescue JWT::VerificationError, JWT::DecodeError
raise NotAuthenticatedError
end
endThis JWT-based approach enables secure, scalable authentication across distributed services—ideal for mobile apps and microservices.
For profile management, Paperclip handles avatar uploads. User avatars are stored with hashed URLs and multiple image styles (e.g., thumb, medium), enhancing performance and security.
SMS verification is powered by an external service (originally Yunpian), abstracted through a custom sms gem. Upon registration or phone binding, the system sends a verification code and stores it securely with the user’s phone record.
Blockchain Account Module
Each user can open accounts for different cryptocurrencies—primarily Bitcoin and Ethereum-based tokens. The data model uses Single Table Inheritance (STI) in Rails:
- Base
Accountmodel - Subclasses like
BitcoinAccount,EthereumAccount
When an account is created, an after_create callback generates a corresponding blockchain address. But instead of storing the address directly in the account table, a separate Address model is used—with STI supporting two types:
- Deposit Address: Automatically generated for receiving funds
- Withdrawal Address: Manually added by users for sending funds externally
This separation allows users to register multiple withdrawal destinations while maintaining a single deposit address per currency.
👉 See how modern wallets streamline multi-address management securely.
Exchange Rate Management
Real-time fiat-to-crypto conversion is essential for usability. The system retrieves exchange rates from third-party providers (e.g., CoinMarketCap or exchange APIs) at regular intervals. To avoid latency during user requests, all rate data is cached in Redis.
The process involves two steps:
- Fetch raw exchange rates (e.g., BTC/USD, ETH/EUR)
- Normalize all values to a default fiat currency (e.g., CNY or USD)
Using Sidekiq-Cron, a background job runs every minute to refresh Redis with updated rates. Clients then query the local cache instead of external APIs, reducing load and improving speed.
A dedicated gem, currency_switcher, simplifies currency conversion logic within the application layer.
Cryptocurrency Transaction System
The transaction module forms the heart of any digital wallet. It supports three key operations:
- Top-up (Deposit)
- Withdrawal
- Peer-to-peer Wallet Transfers
Each follows a strict state lifecycle managed via the AASM (Acts As State Machine) gem.
State Management with AASM
Transactions progress through defined states to ensure auditability and consistency:
aasm :column => :state do
state :canceled
state :launch
state :checked
state :pending
state :confirmed
event :cancel do
transitions from: :launch, to: :canceled
end
event :check do
transitions from: :launch, to: :checked
end
event :pend do
transitions from: [:launch, :checked], to: :pending
end
event :confirm do
transitions from: [:launch, :pending], to: :confirmed
end
endThis state machine governs both incoming and outgoing transactions.
Deposit Flow
- User sends crypto to their assigned deposit address.
- Node script detects incoming transaction via
walletnotify(Bitcoin) or custom listener (Ethereum). - Message is published to RabbitMQ.
- Wallet service consumes message, creates transaction record in
launchstate. - TXID is stored in Redis.
- Background job checks confirmation count periodically.
- Once required confirmations are reached (e.g., 6 for Bitcoin), status updates to
confirmed.
Withdrawal Flow
- User initiates withdrawal to a pre-registered address.
- Transaction starts in
launchstate. - Admin or automated system performs
check(fraud/AML screening). - Approved withdrawals move to
pending. - Signed transaction is broadcast to the network.
- After sufficient confirmations, status becomes
confirmed.
Unconfirmed transactions remain visible in the UI with real-time status updates.
Internal Transfers
Wallet-to-wallet transfers occur instantly within the system database—no blockchain confirmation needed. These are labeled as wallet transfers and appear immediately in both sender and receiver histories.
Best Practices & Lessons Learned
While building this system, several key lessons emerged:
- Always use BigDecimal for monetary fields to prevent floating-point errors.
- Never expose private keys or seed phrases in logs or APIs.
- Validate all blockchain callbacks against replay attacks.
- Use idempotent operations where possible to handle duplicate messages.
- Monitor node health and implement fallback strategies.
Though newer tools like Web3.js, Hardhat, or dedicated wallet SDKs now simplify parts of this workflow, understanding the underlying mechanics remains crucial for debugging and security auditing.
👉 Explore secure transaction handling on advanced blockchain platforms.
Frequently Asked Questions (FAQ)
Q: Is this wallet non-custodial or custodial?
A: This is a custodial (centralized) wallet—the private keys are managed server-side, giving the service control over funds. Users rely on the platform's security and trustworthiness.
Q: How are private keys stored securely?
A: While not detailed in the original post, best practice involves encrypted storage (e.g., using KMS or HSMs), air-gapped signing environments, and strict access controls.
Q: Can this architecture support ERC-20 tokens?
A: Yes—since it uses a Go-Ethereum node, any ERC-20 token can be supported by adding token contract addresses and balance checking logic.
Q: Why use RabbitMQ instead of polling?
A: RabbitMQ enables real-time event processing without constant polling, reducing latency and server load when detecting incoming transactions.
Q: What happens if the blockchain node goes down?
A: The system would fail to process new transactions until connectivity is restored. Redundant nodes and health checks are recommended for production use.
Q: How does this compare to modern DeFi wallets?
A: Unlike non-custodial DeFi wallets (e.g., MetaMask), this solution prioritizes ease of use and centralized control—ideal for beginners but requiring greater trust in the provider.
In summary, developing a centralized blockchain wallet requires careful integration of backend APIs, blockchain nodes, real-time messaging, and financial-grade security practices. Despite being built years ago, this architecture still reflects sound engineering principles applicable to today’s crypto applications.