+ Docs
How it
actually works.
Key derivation, stealth payments, viewing keys, the API and how to run your own instance. Written from the source, and honest about the parts that are not deployed yet.
The shape of it01
Your wallet
The only credential. One signature derives every key the app uses, and none of them are stored anywhere but this tab's memory.
Shielded notes
Balances live as encrypted notes, not as a number on an address. The chain sees a transfer; it does not see whose balance grew.
Stealth addresses
Each payment lands on a fresh address derived from your published meta-address. Two payments to @you share nothing on chain.
Viewing keys
A read-only key you issue, so an accountant can recognise your payments and never move them. Scoping and expiry are planned.
Keys02
- 01
Sign in
You sign a short message naming your address and the moment it was issued. The server recovers the signer, checks it matches, and sets a signed session cookie. It never sees a private key.
- 02
Unlock
A second signature, over a fixed message, is used as seed material. The app derives a spending key and a viewing key from it with HMAC-SHA256 and domain strings, then throws the signature away.
- 03
Publish
The two public halves are concatenated into your meta-address and stored on your account, so anyone can pay you. The private halves never leave the tab.
- 04
Lock
Closing the tab is locking. There is nothing to log out of on our side: the keys were never there.
Stealth payments03
- 01
The payer looks you up
Your handle resolves to your meta-address: a spending public key and a viewing public key, side by side.
- 02
A one-time address
The payer generates an ephemeral key, derives a shared secret with your viewing key, and tweaks your spending key by it. The result is an address only you can open.
- 03
You scan
Every announcement carries the ephemeral public key and a one-byte view tag. Your viewing key checks the tag first, which skips almost everything cheaply, and only then does the curve work.
- 04
You spend
The private key for that address is your spending key plus the same tweak. Nobody else can compute it, including us.
Disclosure04
API05
Run your own06
- 01
Run it
docker compose up -d --build, with a volume on /data. That is the whole deployment: one Next.js server, no database, no queue, no worker. It answers /api/v1/health only when it can also write where it keeps accounts.
- 02
Deploy the router
node contracts/compile.mjs && DEPLOYER_KEY=0x… node contracts/deploy.mjs. It refuses any chain that is not Robinhood Chain, prints the address and the block, and --dry-run estimates the gas without sending anything.
- 03
Point the app at it
Set NEXT_PUBLIC_SIGNETPAY_ROUTER to the address and ANNOUNCEMENT_FROM_BLOCK to the deployment block, then docker compose up -d --build again. The router address is read by the browser, so it is baked in at build time and a plain restart will not pick it up. Shielding, private payments, payroll batches and the announcement index turn on together.
- 04
Watch it catch up
The index sweeps from that block forward and says how far behind it is while it does. It keeps what it has read on disk, so a restart resumes instead of starting over.
A single instance holds its state in JSON files under the data directory. That is deliberate and it is a limit: on a host with a read-only or per-invocation filesystem, writes are lost, and the status page reports the deployment as degraded rather than letting you find out from a user.