Skip to content

Tag every transfer correctly. You still will not have the books.

This is the transaction from the home page, in full. That card shows the eight transfers your ledger sees; here is what the wallet actually did.

The transaction: 0xf732d5cc…9ff4b2e9e Ethereum, block 25515871. Aave v3 and Euler v2, through a solver. Public, not ours, not a customer’s. Open it in another tab and check me as you go.

It emitted 36 logs. Fourteen move a token, across 17 addresses, and six of those fourteen are solver plumbing, routing hops and other people’s business sharing the block. Drop those six and eight transfers remain.

Eight rows fit on a screen, and you could read them in a minute. Then you have to say which account each one belongs to.

Standard crypto ledgers key on the address. They find the eight transfers the wallet is party to and tabulate them with direction, token, amount, and an address on the other end. Cash cancels: USDC in equals USDC out, RLUSD in equals RLUSD out, to the wei.

Every amount there is right. Now go and book it.

Related transfers

The wallet is a party to every transfer below, so a ledger keyed on the address records them all. It is not wrong about any of them.

fromtotokenamount
0x0000…00000xb721…fa38variableDebtEthUSDC780,208.473253
0x09bd…faba0xb721…fa38USDC778,238.290021
0xb721…fa380x9bD5…2CbbUSDC186,534.550447
0xaF53…9BB20xb721…fa38RLUSD204,524.934814662449203000
0xb721…fa380x9bD5…2CbbUSDC591,703.739574
0xaF53…9BB20xb721…fa38RLUSD526,273.020496461240292674
0xb721…fa380x09bd…fabaRLUSD730,797.955311123689495674
0xb721…fa380x0000…0000variableDebtEthRLUSD730,794.568432444353918468

Each row lands in a queue with a blank where the account goes, and your controller has to fill it. Your tool is doing what a transfer allows: a transfer carries an amount and a counterparty, so the account has to come from outside the row.

They look up the address, find a Euler USDC vault, and write the obvious rule: USDC out to that address is a Euler deposit. Sensible. Now look at two of the rows: same direction, token, counterparty, contract, block. One is a debt repayment; the other is a collateral deposit, on a different account. Opposite sides of the balance sheet, in rows identical in every field a transfer carries. That rule books both the same way, one of them lands on the wrong side, and the journal still balances, so the close goes ahead. (Two more rows run the same trap backwards: identical RLUSD inflows, one a withdrawal, one a borrow, an asset and a liability wearing the same row.)

Tagging by address is filing your mail by the color of the envelope. Quick, tidy, and the tax bill ends up in with the birthday cards. To name the account you have to know how Euler and Aave keep their own books, at the contract level, which is a strange thing to ask of a controller closing the month.

Euler and Aave both know which sub-account that USDC repaid. They record it, and the transfer your ledger indexed carries the amount and stops there. Go to the source and the account is recoverable.

Read the protocols instead of the transfers and the eight rows come apart into this. The table is generated programmatically, not tagged by hand. It is the output, the golden our parser is regression-tested against.

What we book

Twelve entries, from six things that actually happened. Four of them land on two addresses the wallet-keyed ledger never looked at.

typeprotocolpositionsymbolamount
aaveBorrow
liabilityAave V3USDC-778,238.290021
assetUSDC778,238.290021
eulerRepay(a3d)
assetUSDC-186,534.550447
liabilityEuler0xb721…FA3DUSDC186,534.550447
eulerWithdraw(a3d)
assetEuler0xb721…FA3DRLUSD-204,524.934814662449203000
assetRLUSD204,524.934814662449203000
eulerDeposit(a3e)
assetUSDC-591,703.739574
assetEuler0xb721…FA3EUSDC591,703.739574
eulerBorrow(a3e)
liabilityEuler0xb721…FA3ERLUSD-526,273.020496461240292674
assetRLUSD526,273.020496461240292674
aaveRepay
assetRLUSD-730,797.955311123689495674
liabilityAave V3RLUSD730,797.955311123689495674

cashassetliability

Sort the twelve by account. Six are the wallet’s own cash, one leg per action, and they net to zero in each currency. Those six your ledger already had.

Six more are positions: what the wallet now owns and owes on Euler and Aave, four of them on sub-accounts your tool has never heard of. Every amount in those six is already in the transfer table at the top, printed to the same decimal. What a transfer does not carry is which account owes what, so the amounts survive the trip and the accounts are the part you have to rebuild.

Build a journal forward from the transactions and you have a claim. That is why we check it the other way: read each account’s balance straight from the chain, and reconcile the two, account by account. Hold both ends and the gap between them has to explain itself.

Eight accounts. Two land on exactly zero, and six carry a residue.

Account values are checked against the chain.

bookon chaindifference
Aave V3USDC-778,238.290021-778,238.33021-0.040189
walletUSDC000
EulerUSDC0xb721…FA3D186,534.550447186,534.167881-0.382566
EulerRLUSD0xb721…FA3D-204,524.934814662449203-204,524.808382350.126432312449203
walletRLUSD000
EulerUSDC0xb721…FA3E591,703.739574591,704.0723390.332765
EulerRLUSD0xb721…FA3E-526,273.020496461240292674-526,273.1553649286-0.134868467359707326
Aave V3RLUSD730,797.955311123689495674730,797.940324909-0.014986214689495674

They meet, account by account. Both cash accounts land on exactly zero, which is what your ledger already told you. Each of the six positions carries a block of accrued interest, a number no transfer moved, because interest emits no event. Reconciliation is where that number gets explained rather than buried. We take reconciliation apart on its own page. The same six positions are why a DeFi NAV is hard to strike, and why a fund runs a shadow NAV against the one its administrator gives it.

More of the accounting that breaks other tools: