A multi-wallet USDT ledger has a classification problem that is easy to miss: the same internal movement appears as an outgoing transfer in one owned wallet and an incoming transfer in another.
If those wallet histories are aggregated without an ownership model, the principal may be counted as both an expense and income.
This post describes a small, deterministic data model for handling that case across TRON and BSC.
1. Keep network identity in every key
An address string is not a complete identity.
TRON addresses and EVM addresses use different formats, while the same 0x... address can exist on BSC, Ethereum, Polygon, Base, or another EVM network.
Use a network-qualified key:
function addressKey(network, address) {
return `${network.toLowerCase()}:${address.toLowerCase()}`;
}
The same rule should apply to transaction identifiers:
function transferKey(network, txHash, logIndex = 0) {
return `${network.toLowerCase()}:${txHash.toLowerCase()}:${logIndex}`;
}
Including a log index is useful on EVM networks when one transaction emits multiple token-transfer events.
2. Maintain an explicit ownership registry
Do not infer ownership from recent activity or transfer direction.
const owned = new Map([
["tron:T_SAMPLE_OPERATIONS", { name: "TRON operations" }],
["tron:T_SAMPLE_PAYMENTS", { name: "TRON payments" }],
["bsc:0xsampleoperations", { name: "BSC operations" }],
["bsc:0xsampletreasury", { name: "BSC treasury" }],
]);
In production, normalize addresses according to the network before adding them to the registry.
The ownership list should be user-controlled because public blockchain data cannot reliably explain the business relationship behind an address.
3. Classify principal movement before calculating totals
A minimal transfer record might look like this:
{
network,
txHash,
logIndex,
timestamp,
tokenContract,
from,
to,
amount,
fee,
confirmations
}
Then classification becomes deterministic:
function classifyTransfer(tx, owned) {
const fromOwned = owned.has(addressKey(tx.network, tx.from));
const toOwned = owned.has(addressKey(tx.network, tx.to));
if (fromOwned && toOwned) return "internal";
if (fromOwned) return "outgoing";
if (toOwned) return "incoming";
return "external";
}
Only incoming and outgoing principal movements should contribute to ordinary receipt and payment totals.
An internal movement should remain visible in the ledger for traceability, but its principal should not be added to revenue or expense.
4. Treat network fees separately
The principal transfer and the network fee are different accounting events.
For an internal transfer:
- principal movement: internal, excluded from ordinary income/expense
- network fee: possible operating cost, retained separately
Combining both into one signed amount makes later reconciliation harder.
5. Separate blockchain facts from business meaning
The blockchain can confirm:
- timestamp
- network
- token contract
- sender and recipient
- amount
- transaction hash
- confirmation state
It cannot confirm whether the transfer represents a customer payment, supplier payment, refund, treasury movement, or test.
Keep business metadata in a separate layer:
{
expectedAmount,
counterpartyId,
invoiceId,
note,
status: "reconciled" | "still_due" | "overpaid" | "review"
}
This prevents a label correction from rewriting the underlying public transaction record.
6. Start with a bounded range
A safer onboarding sequence is:
- add one owned public address
- read a recent bounded period
- validate the network and token contract
- label known counterparties
- add the remaining owned wallets
- reclassify owned-to-owned transfers
- compare expected and actual payments
- import older history only when needed
This makes failures easier to isolate. A zero result might otherwise mean the wrong network, the wrong token contract, an incomplete provider response, or simply no activity.
7. Do not request wallet authority for reconciliation
This workflow uses public transaction data. It does not require a wallet connection, seed phrase, private key, or signing permission.
I built an interactive sample of the multi-wallet deduplication flow using example wallets and transfers. It can be reviewed without registration or a real address:
Open the no-sign-up multi-wallet example
Disclosure: I built U Ledger. The link is a product guide and sample workflow, not a trading, custody, exchange, or investment service.
Top comments (0)