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.
| from | to | tokenvariableDebtEthRLUSD | amount-730,797.955311123689495674 |
|---|
| 0x0000…0000 | 0xb721…fa38 | variableDebtEthUSDC | 780,208.473253 |
| 0x09bd…faba | 0xb721…fa38 | USDC | 778,238.290021 |
| 0xb721…fa38 | 0x9bD5…2Cbb | USDC | 186,534.550447 |
| 0xaF53…9BB2 | 0xb721…fa38 | RLUSD | 204,524.934814662449203000 |
| 0xb721…fa38 | 0x9bD5…2Cbb | USDC | 591,703.739574 |
| 0xaF53…9BB2 | 0xb721…fa38 | RLUSD | 526,273.020496461240292674 |
| 0xb721…fa38 | 0x09bd…faba | RLUSD | 730,797.955311123689495674 |
| 0xb721…fa38 | 0x0000…0000 | variableDebtEthRLUSD | 730,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.
| type | protocol | position | symbolvariableDebtEthRLUSD | amount-730,797.955311123689495674 |
|---|
| aaveBorrow |
| liability | Aave V3 | — | USDC | -778,238.290021 |
| asset | — | — | USDC | 778,238.290021 |
| eulerRepay(a3d) |
| asset | — | — | USDC | -186,534.550447 |
| liability | Euler | 0xb721…FA3D | USDC | 186,534.550447 |
| eulerWithdraw(a3d) |
| asset | Euler | 0xb721…FA3D | RLUSD | -204,524.934814662449203000 |
| asset | — | — | RLUSD | 204,524.934814662449203000 |
| eulerDeposit(a3e) |
| asset | — | — | USDC | -591,703.739574 |
| asset | Euler | 0xb721…FA3E | USDC | 591,703.739574 |
| eulerBorrow(a3e) |
| liability | Euler | 0xb721…FA3E | RLUSD | -526,273.020496461240292674 |
| asset | — | — | RLUSD | 526,273.020496461240292674 |
| aaveRepay |
| asset | — | — | RLUSD | -730,797.955311123689495674 |
| liability | Aave V3 | — | RLUSD | 730,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.
| account | book | on chain | difference |
|---|
| Aave V3USDC | -778,238.290021 | -778,238.33021 | -0.040189 |
| walletUSDC | 0 | 0 | 0✓ |
| EulerUSDC0xb721…FA3D | 186,534.550447 | 186,534.167881 | -0.382566 |
| EulerRLUSD0xb721…FA3D | -204,524.934814662449203 | -204,524.80838235 | 0.126432312449203 |
| walletRLUSD | 0 | 0 | 0✓ |
| EulerUSDC0xb721…FA3E | 591,703.739574 | 591,704.072339 | 0.332765 |
| EulerRLUSD0xb721…FA3E | -526,273.020496461240292674 | -526,273.1553649286 | -0.134868467359707326 |
| Aave V3RLUSD | 730,797.955311123689495674 | 730,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: