Introducing Klenba — a self-custody ICP & ICRC vault with ledger-verified history

Hi all. I’ve been building Klenba, a non-custodial vault for ICP and ICRC tokens on the Internet Computer, and I’d like to introduce it and get technical feedback.

Live: wallet.klenba.com · klenba.com · @klenbavault

What it is

Klenba is a wallet that holds ICP and any ICRC-1 token — ckBTC, ckETH, ckUSDC, ckUSDT, SNS tokens including $DEEP — with vaults (subaccounts) you can label and keep separate. Everything is signed in the browser by Internet Identity and sent straight to the ledger.

The wallet canister has no transfer or withdrawal method and holds no keys. It stores vault labels, contacts and indexed history. Custody stays with the user, always. Canister ids for anyone who wants to verify that claim:

  • backend — lvlol-eiaaa-aaaaj-qns4a-cai
  • frontend — tylx5-4iaaa-aaaac-qb24a-cai
  • landing — cvtw7-waaaa-aaaae-qfyga-cai

(The backend’s public Candid is readable on-chain — there is no transfer.)

The part I think is interesting: history you can verify

Most wallets show you a transaction list assembled from what the app itself observed. If the app is wrong, or was never running, the history is wrong or missing — and you have no way to tell.

Klenba’s history works differently. A vault’s entries are read from the ledgers themselves and verified before they’re stored:

  • Entries originate only from an indexer canister’s own ledger read — there is no method anywhere that accepts a client-asserted transaction.
  • Each entry carries its ledger and block index (plus a block hash where the ledger provides one — the ICP ledger’s legacy API does not).
  • Matching covers transfers, mints and burns, in both directions, so receives are first-class rather than an afterthought.
  • Anything the indexer couldn’t verify is labelled unverified and excluded from statements.

That last point matters to me: older local records aren’t deleted, they’re marked. A gap you can see beats a guess you can’t.

How it scales

History lives in shard canisters, not in the wallet canister. A registry in the backend routes each vault to a shard and holds hard caps (vaults, entries, entries per vault). New shards are queued and activated from an admin surface before the current one fills, so a full shard can never block a user. An entry is keyed by (ledger, block, account), so re-syncs are idempotent.

There’s a monthly statement export (CSV) with block citations, which is the feature teams asked for first.

Also in it

  • Token discovery — curated, liquidity-ranked ICRC list from ICPSwap, one tap to add, no canister ids to paste.
  • Prices — CoinGecko for the majors, ICPSwap for the long tail; tokens with no known price show their native balance and no invented USD figure.
  • App lock — optional local PIN/biometrics on top of Internet Identity. It never touches the session: inactivity locks the screen, it does not log you out.
  • Installable PWA, ICRC-scannable receive QR (principal[:subaccount]), text memos on the ICRC path.

What it doesn’t do — deliberately

  • No history backfill from archives. Both the ICP and ICRC ledgers archive old blocks, so an indexer reads from the live range forward and reports where history begins. I chose not to scan archives; new vaults are covered from their first transaction.
  • One shard is deployed today. The queue/activate path is built and unit-tested but hasn’t been exercised with a second live shard yet.
  • ICP legacy memo is nat64. Text memos work on the ICRC path only — that’s the ledger, not the UI.
  • No browser-based posting or custody shortcuts anywhere. Not a technical limit so much as a line I won’t cross.

Feedback I’d like

  1. On the verification model — is “indexer reads the ledger, stores only what it verified” the right framing, or would you want the certificate / icrc3_get_blocks path instead?
  2. On sharding — has anyone run per-account indexed history at scale and hit a better keying or routing scheme than (ledger, block, account)?
  3. Anything in the custody story you’d want proven rather than described.

Happy to answer anything.

https://x.com/earporter/status/2106041195386306922?s=20

That’s not correct, or have I misunderstood what you’re saying? You’re aware that transactions are already recorded on a ledger (no ledger, no token). The ledger + archives keeps the history, and an index canister typically provides fast lookups. I’m not aware of any frontend that tries to construct history based on it’s own observations rather than just pulling history from the relevant ledger/index. What frontends are you referring to?

Why does it need to live anywhere other than in the actual ledger/archive/index? What problem are you trying to solve?

Its not a problem to solve , its just to easily serve the “Monthly Statement Export” function