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
- 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_blockspath instead? - On sharding — has anyone run per-account indexed history at scale and hit a better keying or routing scheme than
(ledger, block, account)? - Anything in the custody story you’d want proven rather than described.
Happy to answer anything.