The full pass in a real browser: build, load, fund, pay yourself both ways, sweep, connect, and try to break the settings. Work top to bottom; each step depends on the last actually working, and stopping to fix is cheaper than pushing through with a broken layer underneath.
Everything in this pass is free. Friendbot funds the bench wallet, the bench pays you, and every account involved lives on Stellar testnet, where nothing has value. If a step appears to need real money, that is a bug in this document.
Record what you find in the "Results" table at the bottom, including the things that worked, because a green line is evidence and an empty line is not.
- Chrome 116 or newer (the manifest's minimum).
- No second wallet and no faucet tab needed: the test bench carries its own throwaway Stellar wallet and funds it from friendbot on demand.
- Two terminals:
npm run build # -> dist/
npm run demo:dev # -> the test bench, usually :5173Load dist/ via chrome://extensions → Developer mode → Load unpacked.
The riskiest moment: a throw during service-worker registration means the extension never starts, and Chrome reports it only in the SW console.
- The card appears with no red "Errors" button.
- Click service worker → the console is clean. Top-level imports (the
Stellar SDK, the
@opaquecash/stellarstealth path) evaluate here; a failure shows up as a red trace, not as a broken UI. - Open the popup. Boot completes (no permanent spinner, no "Couldn't start").
- Fresh install shows Welcome, not a sign-in dead end. (A sign-in
notice means
capabilities.localVaultis not reaching the shell.) - Get started → terms → passcode. A passcode under 8 characters is refused inline.
- The 12 words appear. Write them down. You need them in step 10.
- Continue is disabled until the confirmation box is ticked.
- After confirming you land on the wallet, not back at Welcome.
- Close the popup mid-backup and reopen it. You should return to the backup step with the same 12 words, and finishing should still reach the success screen rather than jumping straight to Home.
- Lock (Settings → Lock wallet) and unlock. Wrong passcode gives an inline error; five wrong ones start a countdown.
- Receive shows a plain Stellar address (
G..., 56 characters) and a QR, not a meta-address. This is what makes Kivuli payable by ordinary wallets: any Freighter user, exchange, or friend can pay this with a normal payment. - "New address" hands out a different one; the old one keeps working.
- "Copy your meta-address" below it copies the long stealth form, for senders that speak the protocol (the bench does, in step 7).
- Copy works. Keep the receive address on your clipboard.
- Nothing in the UI claims mainnet or any other chain: this build is Stellar testnet only.
Open the bench. It is the counterparty a solo tester does not otherwise have: an ordinary Stellar wallet that can pay you, plus the dApp for provider QA.
- The bench shows its own throwaway wallet address.
- Fund with friendbot. The balance appears (friendbot grants 10,000 testnet XLM). No card, no faucet queue, no cost.
- The bench reports Stellar testnet as its network. There is nothing to switch: the bench and the wallet both live on testnet.
- Paste the receive address from step 3 (not the meta-address), amount
25XLM, Send. This is the case that used to be invisible: one ordinary payment, from an ordinary wallet, with no announcement at all. - The bench prints the transaction hash, and stellar.expert (testnet) shows the payment landing at that address.
- In Kivuli, the payment shows within a scan cycle (~2 min), or immediately via Settings → Advanced → State logs → Check for money now. It is found by watching the ladder, so no announcement was needed.
- The amount matches what you sent.
- Sweep it. Note: a fresh wallet has no per-site account to sweep into until you have connected to a site. If the destination list is empty, do step 8 first and come back. Pasting an address works either way.
- The sweep confirms on stellar.expert and the balance moves. Expect slightly less than the full amount: the swept account keeps its base reserve and pays the transaction fee.
This is the product thesis in one line: money reached you at an address nobody can link to you, and you moved it without ever revealing the link.
- Paste the meta-address from step 3 into the bench's stealth send. Two legs are expected, not a double-spend: a payment funding a one-time account, then a Soroban invocation publishing the announcement through the stealth announcer contract.
- The bench prints the one-time address, the payment hash, and the announce hash; stellar.expert shows both.
- In Kivuli, the note appears within a scan cycle (or via Check for money now). This one was found from the announcement: the one-time address is not on your ladder, so its discovery proves the scanner is matching announcer events, filtering by view tag, and reconstructing the key.
- Sweep it too, or leave it for step 10's restore check.
In the bench, connect Kivuli this time. This is where the per-origin model gets its first real test.
- The approval prompt names the origin and shows the address this site will get. What you approve is what the site receives.
- The address is not any address you have seen elsewhere: not a receive address, not a note, not a sweep destination.
-
getAddressreturns that same address;getNetworkreports TESTNET and the testnet passphrase. -
signMessageraises the approval UI and returns a signature. -
signTransaction: the bench builds a payment from the per-site account. The prompt decodes it (operation, amount in XLM, destination, fee). Approving returns signed XDR that submits successfully. The account needs funding first: sweep to it (step 6) if you have not. - Source mismatch: the bench's mismatch test submits a transaction whose source account is not the per-site account. The prompt must warn that the site is asking the wallet to sign for an account it was never granted.
- Per-origin uniqueness: open the bench on a second origin (serve it
at
127.0.0.1instead oflocalhost, or any other origin) and connect. The address differs from the first origin's. Two origins, two unlinkable accounts, one recovery phrase.
- Custom RPC grant flow: Settings → Advanced → the Stellar Testnet RPC field. Paste a custom endpoint and save: Chrome raises a host-permission prompt (the manifest pre-authorizes only the built-in endpoints). Dismiss it → the old endpoint is kept with an inline explanation, never a half-saved endpoint that fails every request. Approve it → the override saves, and scanning keeps working. Clear the field → the built-in default returns.
- Auto-lock: set the shortest interval, leave the wallet idle, and confirm it locks on its own. Unlock returns you to where you were, and a connected bench survives the lock (requests queue behind unlock).
- Settings → Security → Change passcode: the old one is required, five wrong tries throttle, and the new one unlocks after a lock.
- Settings → Advanced → State logs: counts and timings, no addresses. "Copy for support" produces JSON you would be comfortable pasting.
- Menu → Expand view opens a full browser tab; the side panel toggle in Settings → Advanced routes the toolbar icon to Chrome's side panel and back. One wallet, several views: a change made in one shows up in the others.
Do this last: it replaces the wallet.
- Settings → Security → Recovery phrase → passcode → the same 12 words.
- Lock → Forgot passcode? → tick "I have my 12 words" → enter the phrase. Nothing is erased at this point: type a phrase with one word wrong and it is refused on the spot, with the wallet still intact.
- Restore with the phrase from step 2 and set a new passcode. The same meta-address comes back.
- The receive ladder comes back too. The addresses are derived from the phrase, so the restored wallet re-derives them, re-adopts the ones the chain has seen, and the payments from steps 5 and 7 reappear (as swept history or as live notes, matching what you did with them).
- The restored wallet starts clean otherwise: no site connections or imported assets carried over from before.
- Reconnect the bench: the same origin gets the same per-site address as before the restore, because it derives from seed + origin.
If the meta-address differs after restore, stop: key derivation is not deterministic across a restore, and nothing else matters until it is.
| # | Step | Result | Notes |
|---|---|---|---|
| 1 | Loads / SW clean | ||
| 2 | Onboarding + resume | ||
| 3 | Receive | ||
| 4 | Bench + friendbot | ||
| 5 | Plain payment | ||
| 6 | Scan + sweep | ||
| 7 | Stealth send + announcement discovery | ||
| 8 | Per-site wallet + provider smoke tests | ||
| 9 | Settings passes | ||
| 10 | Reveal + restore + ladder recovery |
- Testnet only. No real funds exist anywhere in this pass, and the wallet refuses to pretend otherwise.
- XLM moves; issued assets show. Money that arrives as an issued asset is visible on its note but the sweep and the stealth rail are XLM-only in this build.
- A note below the dust floor (base reserve plus a fee) cannot pay for its own sweep. It shows as unmovable rather than being hidden.
- Activity records only what this wallet itself did. A payment arriving, or a dApp spending from its own account, is not in the list.
- One network. Mainnet is deliberately absent until the Opaque protocol's
security register is signed off (
src/lib/config.tsis where the row would land). - No custom network definitions, only custom endpoints for the built-in network, each behind a runtime permission grant.