Balance and Transaction History

Balance and Transaction History

There are different ways you can track balances for both wallets and sub-account wallets, and this guide will discuss two major approaches.

Method 1: Creating a dedicated property for user balances

This approach involves creating a dedicated property for each user that stores their balance. This property can be updated whenever a user makes a deposit, withdrawal, conversion, transfer, etc.

Benefits to this Approach

  • Performance: Retrieving the balance is very fast because it’s a single, pre-calculated value. This minimizes computational cost, especially in high-traffic scenarios where many users check balances frequently.
  • Simplified Queries: You only need to access the balance property to get a user's balance, reducing database complexity and simplifying the codebase.
  • Reduced Load on Historical Data: Since balance doesn’t depend on calculating historical transactions, the system can operate without querying the transaction history, improving database performance.
  • Better Scalability: The approach is generally more scalable for large volumes of users, as it doesn’t rely on intensive calculations every time the balance is requested.

Potential Drawbacks

  • Risk of Inconsistency: If there’s any failure in updating the balance after a transaction, or if there’s a bug, the stored balance can become inconsistent with the actual transaction history, leading to potential inaccuracies. You also have to ensure your logic takes into account, every possible scenario that could affect the balance.
  • Reconciliation Challenges: If you ever need to verify or audit a balance (for example, during a bug fix or system audit), reconciling this stored balance with the transaction history can be challenging.
  • Complex Transaction Rollbacks: If a transaction needs to be reversed or adjusted (e.g., due to a refund or error), it requires careful handling to adjust the balance without errors, which may complicate your transaction and rollback logic.

Method 2: Calculating the Balance Based on Transaction History

In this approach, the balance is dynamically calculated by summing up all transactions in the user’s wallet history every time it’s requested.

Benefits

  • Accuracy and Reliability: Since the balance is always derived from actual transaction history, this approach guarantees consistency between the balance and the transactions, reducing the risk of discrepancies in the data.
  • Easier Auditing: Because the balance is directly tied to transaction history, auditing and debugging are simpler; you can quickly verify correctness by examining all transactions.
  • Simplified Rollbacks and Adjustments: Any transaction adjustments (such as rollbacks) automatically reflect in the balance since it’s derived from the transaction list, simplifying error handling.

Drawbacks

  • Performance Overheads: Calculating balances by summing transactions every time can be computationally expensive, especially if users have a large transaction history. This can slow down performance and impact user experience.
  • Database Load: Frequent balance checks would require querying and summing up the transaction history, increasing the load on the database, particularly for users with long transaction histories or when handling many users simultaneously. -nPoor Scalability: This approach can become a bottleneck as the platform grows and transaction histories increase in volume. Scaling this method could require caching strategies or limiting transaction history queries to remain performant.
  • Increased Complexity with Multiple Coins: If users hold multiple cryptocurrencies in the same wallet, this approach requires you to group and calculate separate balances for each currency, which adds complexity to the querying and calculation process.

Potential Hybrid Solution

Having considered both methods, what, then, is the solution? To address the shortcomings of each approach, we can use a hybrid method that combines the best aspects of both, minimizing their drawbacks. Here’s how this can be done:

  • Keeping a Primary Balance Property with Periodic Reconciliation: Maintain a dedicated balance property but run periodic background reconciliation tasks to validate the balance against the transaction history, ensuring consistency.
  • Balance Caching: Calculate the balance based on transaction history but cache it and only refresh periodically or when a new transaction is made. This way, you reduce frequent calculations but still ensure accuracy by syncing with transaction history.

With this, you can manage and track activities on user transactions as well as update balances without performance overheads.

Bonus Content

Below is an example object showing a transaction log:

{
  "from": "0x9F81Cd3dc61bA48F969E1aD0da2091D22904E6F6",
  "to": "0x8CB97A23dca921355538Ae9892dd1037bF4D0305",
  "amount": "1",
  "transactionHash": "0x575aa990357c67a15465bc0980c08fbffb663d5d2711aa77da590579370054c1",
  "transferType": "onchain",
  "symbol": "PAY",
  "network": "bsc",
  "timestamp": "2024-11-06T22:47:32.352Z",
  "note": null,
  "metadata": {
    "_id": "6706b64d00928f4ad4284e14"
  },
  "eventType": "wallet.deposit"
}

Here’s the transaction log summary in a table format:

FieldExample ValueDescription
from0x9F81Cd3dc61bA48F969E1aD0da2091D22904E6F6The wallet address that initiated the transaction (sender).
to0x8CB97A23dca921355538Ae9892dd1037bF4D0305The wallet address receiving the transaction (recipient).
amount1The amount of cryptocurrency being transferred in this transaction.
transactionHash0x575aa990357c67a15465bc0980c08fbffb663d5d2711aa77da590579370054c1A unique hash identifying this specific transaction on the blockchain.
transferTypeonchainSpecifies the type of transfer, with "onchain" indicating it was processed on the blockchain.
symbolPAYThe ticker symbol for the cryptocurrency being transferred.
networkbscIndicates the blockchain network, "bsc" for Binance Smart Chain.
timestamp2024-11-06T22:47:32.352ZThe date and time when the transaction occurred, in ISO 8601 format.
notenullAn optional field for additional transaction details; null means no notes were added.
metadata{ "_id": "6706b64d00928f4ad4284e14" }A nested object containing metadata, with _id as a unique database identifier.
eventTypewallet.depositSpecifies the type of transaction event, here indicating a deposit into the recipient’s wallet.