I specced an on-chain credit ledger for a compute network, wrote the mint, and then the token program refused to initialize it. The two extensions I needed (non-transferable credits, plus a hook on every movement so the chain could run the accounting) are mutually exclusive on a single Solana mint. The runtime rejects the initialize instruction. That rejection sent me back to the design, and I came out the other side with the ledger off-chain and only a daily checkpoint on-chain.
This post is why I think that is the right architecture for a usage-accounting ledger in general, not just the workaround for one runtime constraint. It is specific to compute-metering credits (earned for serving inference, spent for consuming it), and most of it generalizes to any high-frequency internal accounting system that someone reaches for a blockchain to secure.
What a credit ledger actually is
Start with the workload, because the workload is what should pick the substrate.
A usage credit ledger is a meter. It has two write paths: a contributor serves a job and earns credits, a user consumes a job and spends them. There is a third, smaller path for verification adjustments and refunds. Writes are append-heavy. Reads are dominated by one question: what is this account's balance right now. Each event carries a tiny value, a fraction of a cent of compute, and the events arrive at a rate set by network traffic rather than by anything financial.
Put rough numbers on it. A network serving 10,000 inference requests a day generates at least 10,000 spend events, a comparable number of earn events, and some verification deltas on top. Call it 20,000 to 30,000 ledger writes a day at small scale. At real scale you multiply that by two or three orders of magnitude. This is the shape of a phone company's call-detail records or a cloud provider's billing meter. None of those run on a blockchain, and the reasons they don't are the reasons a credit ledger shouldn't either.
The fee argument is the weakest one
People assume the problem with an on-chain ledger is gas, so I want to deal with that first and set it aside, because on a cheap chain it mostly doesn't hold.
Solana's base fee is 5,000 lamports per signature. Even with priority fees during contention you are paying fractions of a cent per transaction. Thirty thousand writes a day at those rates is a rounding error against any real infrastructure budget. If raw transaction cost were the only objection, I would have kept the ledger on-chain and moved on.
The real objections are three, and none of them is about fees: a protocol constraint, a migration trap, and a liveness coupling.
The protocol constraint
This is the one that physically stopped me, so it is worth being precise.
A usage credit should be non-transferable. You do not want an internal metering unit trading on a secondary market as a speculative asset, because the moment it does, the price of compute on your network starts moving with a token chart instead of with the cost of compute. Solana's Token-2022 program supports this directly through the NonTransferable extension.
An on-chain ledger also means custom logic runs on every credit movement. In Token-2022 that is the TransferHook extension: a program you control that fires on each transfer and does your accounting.
You cannot initialize a single mint with both. NonTransferable and TransferHook are mutually exclusive at the runtime level, and the initialize instruction fails if you try to combine them. The logic is consistent once you see it: a hook that fires on transfer is meaningless on a token that can never transfer. So you get one or the other. You can have non-transferable credits with no movement logic, or transferable credits with a hook. The combination an internal metering credit actually wants is the one the program will not let you build. That alone took the on-chain ledger off the table before I reached any economic argument.
The migration trap
A live balance system on-chain is the hardest component in the whole network to change.
Any program upgrade that touches how balances are represented is a migration of live, real-value state, with all the replay and ordering surface that implies. You cannot test it the way you test a service, because the thing you are mutating is everyone's money-equivalent at once. The scale precedent here is Helium's own L1-to-Solana move in April 2023: migrating a live token and balance system across chains is a multi-quarter, high-risk operation that a small team executes once and dreads.
If the ledger lives on-chain, every schema change inherits a slice of that risk. If the ledger lives off-chain, a schema change is a database migration you can stage, dry-run against a copy, and roll back. I would rather own that problem in Postgres than in a program upgrade.
The liveness coupling
If every debit is a transaction, the product's ability to settle a credit is coupled to the base layer's ability to land a transaction.
Solana has had multi-hour degradation events. During one, an on-chain ledger cannot record a spend. So either the network stops settling credits until the chain recovers, or it queues the events somewhere off-chain and replays them later, at which point you have reinvented an off-chain ledger under worse conditions than if you had designed one on purpose. The dependency runs backwards. A meter should keep metering through an outage in something it does not control.
What Helium actually did
The precedent worth copying is sitting right there, and most of the networks I look at miss it.
Helium does not put its high-frequency accounting on-chain. Proof-of-Coverage and data-usage accounting run off-chain through oracles. Solana is the settlement and state anchor. Data Credits, the unit users burn to move bytes, are non-transferable and priced at a fixed $0.00001 per packet, and the per-event question of who covered what and who used how many bytes does not hit the chain one transaction at a time. When Helium moved its L1 onto Solana, it kept that split intact.
The lesson I take from the closest structural analog to what I am building: the chain secures the anchor, and the accounting stays in a system built for accounting.
The design: hash chain plus daily checkpoint
Here is the architecture I landed on.
The ledger is an append-only log in an ordinary database. Each entry includes the hash of the previous entry, which makes the log a hash chain. You cannot alter a past entry without rewriting every entry after it, and any such rewrite is detectable by recomputing the chain from any earlier known-good hash forward. This is the same construction a blockchain uses internally, applied to a private log.
Once a day, you build a Merkle tree over the current balance set, take the single Merkle root, and publish that root on-chain through a tiny program whose only job is to store roots with timestamps. The program holds 32 bytes per checkpoint and does nothing else.
That on-chain root is the commitment. It records, with the chain's finality behind it, the exact state of the ledger at the checkpoint. Later, anyone can be handed an inclusion proof: here is your account entry, here is the Merkle path, here is the root the chain recorded that day. They verify the path against the on-chain root and confirm their balance without trusting the operator's word for it.
What the checkpoint buys
Three things, mapping one-to-one onto the three objections.
Auditability without per-event cost. The public root is a commitment the operator cannot quietly walk back. A member who suspects their balance was altered requests an inclusion proof and checks it against the chain themselves. You get the property people actually wanted from an on-chain ledger, which is that nobody can rewrite history in the dark, without paying to write every line of history on-chain.
Lower migration risk. This is the one that changed my own roadmap. With an on-chain ledger, shipping the credit system meant launching a live balance migration, which made it the single highest-risk primitive in the build. With checkpoints, shipping means deploying a program that stores 32-byte roots. The risky work shrinks from migrating a live balance system to publishing a checkpoint. Those are different sizes of problem, and the second one I can ship in an afternoon and sleep after.
Liveness tolerance. The checkpoint cadence is daily, so a multi-hour base-layer degradation delays a root and changes nothing a user sees. The ledger keeps recording the whole time. The next checkpoint catches up. The settlement path never touched the chain to begin with, so an outage in the chain cannot stall it.
Honest limits
This design has real costs, and I would rather name them than have a reader find them.
Between checkpoints, you trust the operator. The hash chain makes tampering detectable after the fact, but a dishonest operator can still serve a wrong balance for up to one checkpoint interval before the next root exposes the divergence, and detection requires someone to actually request and verify a proof. Shorter intervals shrink the window and cost more transactions. I think daily is right for a metering credit whose balances move in small increments, and I would shorten the interval before it became a real exposure.
The hash chain proves integrity, not correctness. It proves the log was not altered. It says nothing about whether a given entry should have been written. Whether a contributor genuinely served the job they were credited for is a separate problem, solved by verification sampling, and no ledger structure substitutes for that.
Availability of the off-chain log is your responsibility. The on-chain root is permanent. The log it commits to lives in your database, and if you lose the log, the root commits to something you can no longer reconstruct. So the off-chain store needs the durability discipline of any system of record: replication, backups, tested restores, the boring parts that decide whether the clever parts survive.
And this approach assumes the credit never needs trustless real-time finality. If a unit genuinely has to be final on a public ledger the instant it moves, an exchange-traded asset for example, checkpoints are the wrong tool and you should pay the on-chain cost in full. The case where checkpoints fit is a usage credit that lives and dies inside the network and never trades.
What this means for what I am building
I am building Potluck, a peer-to-peer AI compute network, and the credit ledger is the layer that accounts for compute served and compute consumed. We run it off-chain as a hash chain and publish a daily Merkle root on-chain. I arrived here the long way, by speccing an on-chain ledger first, hitting the Token-2022 mutual-exclusivity wall, then the migration risk, then the liveness coupling. The off-chain-with-checkpoints design answered all three with one structure.
The general claim I will stand behind: a usage-accounting ledger is a meter, and a meter wants a database with a cryptographic commitment over it, not a blockchain carrying every event. Put the trust anchor on-chain. Keep the meter where meters run.
Rob writes the Local AI Engineering Notes series on strake.dev. He is also building Potluck AI, the peer-to-peer AI compute network referenced in this post, and Strake, a GitHub Action deploy gate.