Skip to content

Reconciling the book to the chain

Your book tied to the onchain snapshot, with the difference named rather than hidden.

There are two ways to know what an address holds.

Read the balance. Ask the chain what the address owns right now. Fast and simple, but it is one number from one source, with nothing to check it against. When it is wrong, nothing in the system is in a position to notice.

Build the book. Take every transaction, work out what it did, and post both sides. Now the position is not handed to you, it is derived from evidence.

Most tools do one of these well. The value isn’t in doing one of them better. It’s in doing both, and then comparing them.

A reconciliation compares two numbers that were arrived at independently. It is only as good as the weaker of the two.

With only a balance, there is nothing to reconcile to. You can report the number and watch it move, and you cannot tell a number that is right from a number that agrees with itself.

With only a ledger keyed on the address, there is nothing to reconcile with. The entries might be immaculate, but no account in them matches a question a node can answer. There is a total, and nothing underneath it the chain can confirm.

THE CHAIN SNAPSHOT THE BOOK LEDGER DIFFERENCE wallet USDC 0 wallet RLUSD 0 Euler collateral 0 Aave USDC debt interest
Snapshot on the left, ledger on the right, compared per account. They agree to zero everywhere except the debt, and there the difference has a name.

We key a position inside a protocol on the wallet, the protocol, the symbol, the contract and the sub-account. That is not a label, it is the address of a question: it names a specific contract holding a specific position, which is exactly what gets handed to a node when something asks what is in there. Tokens you simply hold are keyed on the token, because that is the whole question there is to ask about them.

That is our argument, not a law of nature: a number keyed on an address gives you nothing to ask the chain with. The address is not the account.

Our own parser proved it. We keyed the Aave debt in a hard transaction, decoded on the underlying reserve, which for a USDC debt is the USDC contract. It read fine, and the balance fetch blew up, because nothing at the USDC contract knows the first thing about a debt. Re-key it onto the debt token and the question lands: same transaction, same amounts, same book. The difference between checkable and not was one field in an account name.

A borrow emits a mint event on the debt token. The number is right there in the log, and any competent indexer picks it up. So is the USDC that lands in the wallet a moment later:

USDC
the mint event says 780,208.473253
the wallet receives 778,238.290021
we book as the borrow 778,238.290021

Both are on the chain. Both are true. Only one of them is a borrow.

The event’s number is the principal plus interest you had already accrued. Aave settles up whenever the account is touched, and hands you the settlement and the drawdown added together. It is not a borrow, and the difference is 1,970.18.

Booking the event does not blow up. Your debt balance still lands on the chain’s, because Aave built the event so that it would. You get a correct balance made of the wrong things, and a reconciliation that compares your debt to the chain waves it through, because there is nothing there for it to catch.

A balance can agree with the chain and still be made of the wrong things.

What catches it is the other leg. Plain USDC in a wallet does not rebase, does not accrue, and has no index to hide anything in, so that account reconciles to zero. Not four cents. Zero.

That leaves the 1,970.18 needing somewhere to go, and there are two places for it. You invent USDC that never arrived, and the wallet catches you. Or you put it where it belongs, which is interest. One of those is money you can spend and the other is what it cost you. A book that has to balance is forced to tell them apart. A book that does not have to balance never gets asked.

We take the principal and throw the bigger number away on purpose. We walked into it ourselves before we ever found it in anybody else’s book. When the reconciliation runs, that choice is what gets tested, and we come out 0.040189 from the chain.

It is interest. One block of it, on a twenty-five-million-dollar position, accruing while the block was being mined.

No transaction announced it, because interest does not emit an event. It becomes true block by block while nothing happens. A ledger built purely from transactions will never contain it, however perfect the parsing.

That leftover is not sloppiness. Your position earns continuously, you accrue on a schedule, and the difference between the two is a quantity we measure, name, and book. That is accrual accounting, not an approximation. It is what every ledger since the Medici has done. A system that claimed a per-block-exact underlying figure would not be more accurate. It would just be re-deriving the chain and calling it a book.

A difference isn’t an error until you know what it’s made of.

On a vault position, your share count only moves when you deposit or withdraw, and both of those are transactions you booked. So your shares minus the chain’s shares should be zero. Not close to zero. Zero. A missed transaction has nowhere to hide, because interest does not mint shares.

On a debt position you lose that zero. A debt token rebases, so what comes back is one number that grows every block with no share count next to it. A small booking error and a little accrued interest move the same number, and nothing separates them.

It would be flattering to blame the instrument. Aave keeps an index-normalised balance internally, a scaled quantity that only moves when you borrow or repay, exactly like a vault share, because the protocol needs a stable unit to allocate interest against. The anchor is right there on the chain. We do not read it yet. Reading it would make a debt position provable to the penny; it needs a change in the parser and a change in reconciliation, and it is work we have not done. Today’s numbers reconcile without it, but we are not going to tell you a thing we have not built is a thing that cannot be built.

A few more honest limits.

A gap does not shout. It gets computed, recorded against the account, counted, and put in front of a person. That is deliberate, and it is the same reason booking the interest is a manual step. It is not the same as failing loudly.

Coverage depends on the chain. On EVM and on Sui we read the balance at a point in time, which is a genuinely independent second source. On Solana we do not: we carry a balance forward from the transactions we ingested, so the chain-side number is derived from our own data, and a transfer we never ingested cannot be caught by comparing our book to a number computed from what we ingested. We’d rather name the chain than hide behind the word “reconciled”.

Funds in transit are not tracked. Mid-bridge, your value has left the wallet and has not arrived, so we hold a placeholder and expect it back.

When we cannot read a position at all, we record it as unknown. We don’t quietly fall back to the number in our own book, because a reconciliation whose two sides become the same number can’t fail, and a check that can’t fail isn’t a check.

Every serious tool in this market will tell you it reconciles, and most of them mean something by it. The question is not do you reconcile. It is what are the two things you are comparing, and could both of them be wrong in the same direction at once?

A top-line attestation compares a total to a total. It cannot tell you a position got modelled as a token count instead of a debt, because both sides of that comparison carry the same mistake, and they will agree with each other all day long.

Two numbers that were wrong together aren’t a reconciliation. They’re a coincidence with a tick next to it.