Wormhole deposits by recipient, for wallets that should not have to ask Quantus's indexer #22

Closed
opened 2026-09-16 14:04:32 +00:00 by grenade · 0 comments
Owner

Why

The Quantus browser extension (quantus/extension#14) is gaining a read-only wormhole balance. A wormhole address's System.Account balance is everything it ever received: an exit credits the exit account and records a nullifier, and nothing debits the wormhole address. What is still spendable is the deposits whose nullifier N(secret, transfer_count) has not been used. So a wallet needs, for each of its wormhole addresses, every Wormhole::NativeTransferred to it: transfer_count to compute the nullifier, amount to add up, and leaf_index for a later proof.

The chain has no reverse index from recipient to leaves (ZkTree::Leaves is keyed by leaf index). The mobile wallet asks Quantus's Subsquid (sqm.quantus.com), which tells Quantus which addresses belong to one person. This observer already stores every one of those events in chain_event, with the recipient in accounts[] under a GIN index. What's missing is a way to ask for them by recipient:

  • /v1/chains/{chain}/events/Wormhole/NativeTransferred pages through every deposit on the chain.
  • /v1/chains/{chain}/accounts/{address} returns one address at a time, mixed with all its other activity, newest first. A miner's reward address has tens of thousands of deposits interleaved with MinerRewarded.

What

GET /v1/chains/{chain}/wormhole/deposits?to=<addr>,<addr>,…&after=<height>:<event_index>&limit=<n>

  • to: up to 100 addresses, SS58 or 0x hex. A malformed one is a 400 that names it.
  • Deposits oldest first, so a client can cache what it has and resume from a cursor, with next set when there are more.
  • Each deposit: to (hex) and its SS58, amount as a decimal string (it is a u128), transfer_count, leaf_index, height, event_index, at.
  • The response carries indexed_from / indexed_to. A testnet still backfilling has a partial answer, and a client must be able to say so instead of showing a low balance as if it were complete. Clients can cross-check the count against the chain's Wormhole::TransferCount.
  • CORS: any origin, GET only, on this route alone. Browser-extension origins can't go on an allowlist, because Chrome's id depends on the install and Firefox's is a random per-install uuid. The data is public chain data and the route is read-only. The site's own allowlist is unchanged.

Not in scope

  • Nullifiers. Checking whether a deposit is spent means computing its nullifier from the secret. Sending nullifiers anywhere, even to our own infra, would let the recipient pair a deposit with its exit, so the client checks them against the chain itself.
## Why The Quantus browser extension (quantus/extension#14) is gaining a read-only wormhole balance. A wormhole address's `System.Account` balance is **everything it ever received**: an exit credits the exit account and records a nullifier, and nothing debits the wormhole address. What is still spendable is the deposits whose nullifier `N(secret, transfer_count)` has not been used. So a wallet needs, for each of its wormhole addresses, every `Wormhole::NativeTransferred` to it: `transfer_count` to compute the nullifier, `amount` to add up, and `leaf_index` for a later proof. The chain has no reverse index from recipient to leaves (`ZkTree::Leaves` is keyed by leaf index). The mobile wallet asks Quantus's Subsquid (`sqm.quantus.com`), which tells Quantus which addresses belong to one person. This observer already stores every one of those events in `chain_event`, with the recipient in `accounts[]` under a GIN index. What's missing is a way to ask for them by recipient: - `/v1/chains/{chain}/events/Wormhole/NativeTransferred` pages through every deposit on the chain. - `/v1/chains/{chain}/accounts/{address}` returns one address at a time, mixed with all its other activity, newest first. A miner's reward address has tens of thousands of deposits interleaved with `MinerRewarded`. ## What `GET /v1/chains/{chain}/wormhole/deposits?to=<addr>,<addr>,…&after=<height>:<event_index>&limit=<n>` - `to`: up to 100 addresses, SS58 or 0x hex. A malformed one is a 400 that names it. - Deposits oldest first, so a client can cache what it has and resume from a cursor, with `next` set when there are more. - Each deposit: `to` (hex) and its SS58, `amount` as a decimal string (it is a u128), `transfer_count`, `leaf_index`, `height`, `event_index`, `at`. - The response carries `indexed_from` / `indexed_to`. A testnet still backfilling has a partial answer, and a client must be able to say so instead of showing a low balance as if it were complete. Clients can cross-check the count against the chain's `Wormhole::TransferCount`. - **CORS: any origin, GET only, on this route alone.** Browser-extension origins can't go on an allowlist, because Chrome's id depends on the install and Firefox's is a random per-install uuid. The data is public chain data and the route is read-only. The site's own allowlist is unchanged. ## Not in scope - **Nullifiers.** Checking whether a deposit is spent means computing its nullifier from the secret. Sending nullifiers anywhere, even to our own infra, would let the recipient pair a deposit with its exit, so the client checks them against the chain itself.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: blackbeard/observer#22