Ethereum ETH Exchange Wallet Development – Part 3: Coin Consolidation

·

In cryptocurrency exchange operations, efficient and secure asset management is critical. One essential component of this process is coin consolidation—the systematic transfer of fragmented user deposits from hot wallets to secure cold storage. This not only reduces exposure to potential hacks but also streamlines accounting and improves overall fund control.

This article dives into the technical workflow behind Ethereum (ETH) coin consolidation, covering transaction generation, broadcasting, and confirmation tracking. We'll explore how exchanges aggregate small incoming deposits, construct optimized transactions, and ensure reliable fund movement—all while minimizing gas costs and avoiding duplicate processing.

Whether you're building a wallet system or enhancing an existing exchange infrastructure, understanding this backend mechanism is crucial for scalability and security.

👉 Discover how professional platforms manage Ethereum transactions efficiently.


Understanding the Need for Coin Consolidation

When users deposit ETH into an exchange, funds typically arrive in small, frequent amounts across multiple addresses. Over time, this leads to fragmented balances spread across numerous hot wallet addresses. Leaving these funds unmanaged increases security risks and operational inefficiencies.

To mitigate this:

The goal of coin consolidation is to automatically collect scattered ETH from deposit addresses and send them to a centralized cold wallet—securely and cost-effectively.


Step 1: Generating Consolidation Transactions

Tracking Deposit Records

All incoming deposits are recorded in a database table called t_tx. Each entry includes:

Example data:

| id | tx_hash | deposit_addr | amount_eth | status |
|----|---------|--------------|------------|--------|
| 1  | 0x1     | 0xa          | 0.11       | 0      |
| 2  | 0x2     | 0xb          | 0.12       | 0      |
| 3  | 0x3     | 0xa          | 0.08       | 0      |
| 4  | 0x4     | 0xc          | 0.17       | 0      |

Notice that address 0xa appears twice. Instead of sending two separate transactions, we combine both deposits into a single transfer—reducing gas fees and blockchain congestion.

Database Schema for Outbound Transactions

Once consolidation plans are generated, they are stored in the t_send table:

CREATE TABLE `t_send` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `related_type` tinyint(4) NOT NULL COMMENT '1: consolidation, 2: withdrawal',
  `related_id` int(11) unsigned NOT NULL COMMENT 'Reference ID',
  `tx_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Transaction hash',
  `from_address` varchar(128) NOT NULL DEFAULT '' COMMENT 'Sender address',
  `to_address` varchar(128) NOT NULL DEFAULT '' COMMENT 'Receiver (cold wallet)',
  `balance` bigint(20) NOT NULL COMMENT 'Amount in Wei',
  `balance_real` varchar(128) NOT NULL COMMENT 'Amount in Ether',
  `gas` bigint(20) NOT NULL COMMENT 'Gas limit',
  `gas_price` bigint(20) NOT NULL COMMENT 'Gas price in Wei',
  `nonce` int(11) NOT NULL COMMENT 'Transaction nonce',
  `hex` varchar(2048) NOT NULL COMMENT 'Signed raw transaction hex',
  `create_time` bigint(20) NOT NULL COMMENT 'Creation timestamp',
  `handle_status` tinyint(4) NOT NULL COMMENT 'Processing status',
  `handle_msg` varchar(1024) NOT NULL DEFAULT '' COMMENT 'Error/log message',
  `handle_time` bigint(20) NOT NULL COMMENT 'Last handled timestamp',
  PRIMARY KEY (`id`),
  UNIQUE KEY `related_id` (`related_id`,`related_type`) USING BTREE,
  KEY `tx_id` (`tx_id`) USING BTREE,
  KEY `t_send_from_address_idx` (`from_address`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

This schema ensures traceability, prevents duplication via unique constraints, and supports fast lookups during execution.

Core Processing Logic

Here’s how the system generates consolidation transactions:

  1. Fetch Pending Deposits: Query all records in t_tx where status = 0.
  2. Group by Address: Aggregate amounts by deposit_addr using a map: map[address]sum(amount).
  3. Process Each Group:

    • Retrieve the private key for the deposit address (securely from a key management system).
    • Ensure the total balance exceeds estimated gas costs.
    • Determine the correct nonce:

      • Use the maximum of:

        • Last used nonce from the database (+1)
        • Current nonce from Ethereum node via RPC (eth_getTransactionCount)
    • Construct and sign the transaction using Ethereum’s types.NewTransaction and SignTx.

Key Code Snippet: Signing the Transaction

var data []byte
tx := types.NewTransaction(
    uint64(nonce),
    coldAddress,
    big.NewInt(sendBalance),
    uint64(gasLimit),
    big.NewInt(gasPrice),
    data,
)
chainID, err := ethclient.RpcNetworkID(context.Background())
if err != nil {
    hcommon.Log.Warnf("RpcNetworkID err: [%T] %s", err, err.Error())
    return
}
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(big.NewInt(chainID)), privateKey)
if err != nil {
    hcommon.Log.Warnf("Signing error: [%T] %s", err, err.Error())
    return
}
rawTxBytes := new(types.Transactions).GetRlp(0)
rawTxHex := hex.EncodeToString(rawTxBytes)
txHash := strings.ToLower(signedTx.Hash().Hex())
  1. Store Signed Transaction:

    • For the first record in a group: insert full transaction data into t_send.
    • For others: link via related_id without duplicating hex data.
  2. Update Status: Mark original deposits as “transaction generated” to prevent reprocessing.

👉 Learn how leading crypto platforms optimize transaction workflows.


Step 2: Broadcasting Transactions

After generating signed raw transactions, the next step is broadcasting them to the Ethereum network.

Deserializing Stored Transactions

Each pending transaction is retrieved from t_send, then decoded:

rawTxBytes, err := hex.DecodeString(sendRow.Hex)
if err != nil {
    hcommon.Log.Errorf("Decode error: [%T] %s", err, err.Error())
    return
}
tx := new(types.Transaction)
err = rlp.DecodeBytes(rawTxBytes, &tx)
if err != nil {
    hcommon.Log.Errorf("RLP decode error: [%T] %s", err, err.Error())
    return
}

Sending via RPC

Using an Ethereum client (e.g., Geth or Infura), broadcast with eth_sendRawTransaction:

txHash := common.HexToHash(txHashStr)
tx, isPending, err := client.TransactionByHash(ctx, txHash)
if err != nil {
    return nil, err
}
if isPending {
    return nil, nil // Still in mempool
}

Upon success:


Step 3: Monitoring Transaction Confirmations

Even after broadcast, a transaction isn’t final until it’s included in a block.

Confirmation Workflow

  1. Query all records in t_send with status “sent” but not yet confirmed.
  2. Use TransactionByHash to check inclusion:

    • If isPending == false and tx != nil, the transaction has been mined.
  3. Update status to “confirmed”.
  4. Mark associated deposit records in t_tx as fully consolidated.

This loop runs periodically (e.g., every minute) to ensure timely reconciliation.


Core Keywords


Frequently Asked Questions (FAQ)

Q: Why is coin consolidation necessary for exchanges?
A: It enhances security by moving funds off hot wallets and reduces long-term gas costs by batching small deposits into fewer transactions.

Q: How does merging deposits from the same address save gas?
A: Instead of paying gas for multiple small transfers, one larger transfer incurs only a single base fee, improving cost efficiency.

Q: What happens if a consolidation transaction fails?
A: The system detects failure via RPC checks, logs the error, and retries with adjusted gas parameters or corrected nonce.

Q: Can this system handle high-volume deposit traffic?
A: Yes—by using indexed database queries, parallel processing, and rate-limited RPC calls, it scales well under load.

Q: Is there a risk of double-spending during consolidation?
A: No—each transaction uses a unique nonce derived from both local records and blockchain state, preventing collisions.

Q: How often should consolidation run?
A: Typically every few minutes to balance responsiveness with efficiency. Real-time triggers can be added for large deposits.

👉 Explore advanced tools for managing Ethereum transaction pipelines at scale.