Our synthetic vault holds 1000 units of an asset and issues claims against those units. A wrapper represents 400 of them and a farm holds that wrapper claim. Adding the vault balance to both representations reports 1800 units even though the system still holds only 1000 units of the underlying asset.
We build this example as an independent custody inventory. Every contract label and quantity is invented. The code uses JavaScript records to distinguish an asset balance from a receipt that represents an existing balance and does not query a chain or reproduce an adapter for a particular project.
Choose where the measurement stops
A TVL measurement needs a declared boundary around assets held by the system. A receipt can describe ownership inside that boundary without adding another asset. Following every claim and adding its represented quantity counts the same custody several times.
We choose underlying custody as the counting boundary in this fixture. Receipt rows remain in the inventory for inspection but contribute zero to this total. A different dashboard can count deposited receipt tokens directly if it excludes the underlying position and applies the appropriate valuation once.
function custodyTotal(rows) {
const seen = new Set();
let total = 0n;
for (const row of rows) {
if (row.kind !== "asset") continue;
const key = [row.chain, row.asset, row.custodian].join(":");
if (seen.has(key)) continue;
seen.add(key);
total += row.units;
}
return total;
}The deduplication key includes chain identity and asset identity along with custodian identity. Two discovery paths reaching the same balance do not produce two assets. Two different custodians holding the same token still represent distinct balances and should not collapse into one record merely because their asset identifiers match.
Separate inventory from valuation
The first output is a quantity by asset. A dollar total requires a price and a timestamp context as well. Assuming one unit of every asset equals one dollar turns a quantity report into an unstated price model.
We leave valuation outside the fixture so the double counting error remains visible before prices enter the calculation. A complete adapter can reconcile its raw balances and then apply the platform's pricing rules. An unpriced asset should stay explicitly unpriced instead of disappearing into a zero value that suggests the balance itself was empty.
We need to preserve the path from each reported balance to the custody that supports it because a dashboard that accepts both an underlying token and a wrapper token can produce a numerically tidy total while counting one economic position several times through different contract interfaces and different discovery paths.
The same discipline applies to a wrapper exchange rate. A receipt quantity is not necessarily the underlying quantity. If an adapter unwraps the receipt for valuation it must use the conversion rule appropriate to the measured state and avoid adding both representations afterward.
Category labels carry accounting policy
A staking category may track a platform token deposited into a dedicated contract. That quantity can have a different methodological meaning from assets deposited into a lending vault. Keeping the categories separate makes the measurement policy inspectable before anybody adds the numbers together.
Our fixture includes a second custodian holding 75 more units of the same underlying asset. These are real additional synthetic units inside the chosen boundary. The total becomes 1075 after deduplicating a repeated discovery of the original vault and excluding receipt rows.
Underlying boundary: 1000; receipt-inclusive sum: 1800
6 assertions passedThe naive representation total remains useful as a negative control. We assert that it differs from the custody total so a future refactor that accidentally starts counting receipts makes the failure visible.
Read one coherent state
For an EVM adapter the balance read and any share conversion should refer to a coherent block. A current conversion rate applied to an old receipt balance can describe a position that never existed. Historical reporting therefore needs historical calls or another explicitly documented source for past state.
The fixture checks duplicate discovery and distinct custodians but does not validate oracle prices or contract ABIs. Integration tests need representative pool interfaces and failures such as a removed pool or a contract returning unexpected data. A successful RPC response establishes transport success and still needs semantic validation.
Draw a nested synthetic vault and choose one custody boundary before writing its aggregation. Moving a receipt between wrappers should change the ownership path while leaving the underlying quantity inside that boundary unchanged.