+   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.

Signetpay / DocsKeys · Payments · Disclosure · API ↓

The shape of it01

(Overview)

01↗

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.

02↗

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.

03↗

Stealth addresses

Each payment lands on a fresh address derived from your published meta-address. Two payments to @you share nothing on chain.

04↗

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

(Keys)

  1. 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.

  2. 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.

  3. 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.

  4. 04

    Lock

    Closing the tab is locking. There is nothing to log out of on our side: the keys were never there.

DerivationHMAC-SHA256 over the unlock signature, with the domain strings signetpay/spending/v1 and signetpay/viewing/v1, rejected and re-derived if the result is not a valid secp256k1 scalar.
DeterministicThe same wallet and the same message always give the same keys. Lose the device, sign again, get your notes back.
Ledger keyA third value, signetpay/ledger/v1, encrypts your local ledger before it is stored. We hold the blob; we cannot open it.
Never transmittedNo key material is ever sent to the server. What the server holds is listed, in full, in the Privacy Policy.

Stealth payments03

(Payments)

  1. 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.

  2. 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.

  3. 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.

  4. 04

    You spend

    The private key for that address is your spending key plus the same tweak. Nobody else can compute it, including us.

LinkabilityTwo payments to the same handle land on two unrelated addresses. Nothing on chain ties them to each other or to you.
View tagOne byte, so scanning is a byte comparison in the common case rather than an elliptic-curve operation.
TestedThe repo's unit tests generate twenty payments and assert both that the recipient recognises each one and that the derived key controls the address — a scheme that recognises but cannot spend would lose money silently.
The contractcontracts/SignetpayRouter.sol: 2.7 kB, no owner, no pause, no upgrade path and no custody — tokens go straight from the payer to the stealth address. Its tests run it in an EVM against the calldata this app produces, and one of them proves a payment can be recognised and then spent by its recipient and by nobody else.

Disclosure04

(Disclosure)

Viewing keyYour viewing private key and your spending public key, encoded together with an svk1 prefix. It can recognise your payments. It cannot spend them.
ScopeA key covers every payment you receive and lasts as long as your account. Limiting it to a date range, and an expiry that revokes it on its own, are planned.
Proof of holdingA signed statement that a wallet and its notes hold a token, with an spf1 prefix, checkable by the recipient without giving them the ability to read anything else.
No master keyWe hold no key that reads your account. A legal request reaches what the Privacy Policy lists and no further.

API05

(API)

POST /api/v1/auth/loginAddress, issued timestamp and signature. Sets the session cookie. The signature must be under ten minutes old.
POST /api/v1/auth/keysPublishes your meta-address so people can pay you.
GET /api/v1/resolve/:nameResolves a handle or address to a wallet and a meta-address. Public: it is how a payer finds you.
POST /api/v1/rpcJSON-RPC to Robinhood Chain through an allowlist of read methods plus eth_sendRawTransaction. Capped at twenty-five calls a batch and two hundred and forty a minute per address.
GET /api/v1/wallet/:address/tokensERC-20 balances, read from the chain with batched balanceOf calls.
GET /api/v1/wallet/:address/transfersTransfer history, read from the chain's Transfer log in both directions.
GET /api/v1/prices · /stocks · /yield · /token/supplyMarket and protocol reads, cached. Each says plainly when a source is unavailable rather than estimating.
GET /api/v1/announcementsThe public stealth feed. Everyone gets the same list; only a viewing key can tell which lines are whose, which is why it takes no account.
POST /api/v1/invoicesIssue an invoice link. GET lists yours, /:id is public so the link opens for whoever it was sent to, /:id/cancel withdraws it and /:id/paid records the transaction that settled it.
POST /api/v1/waitlistJoin a product waitlist. It stores the wallet, the product and the moment, and nothing else.
GET /api/v1/statusEvery dependency, checked on the request. The same data the public status page shows.
GET /api/v1/healthFor the container and the load balancer: 200 only when the app can serve a request and write where it keeps accounts.
PUT /api/v1/ledgerStores your encrypted ledger blob with an optimistic version; a stale version answers 409 rather than overwriting.

Run your own06

(Self-host)

  1. 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.

  2. 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.

  3. 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.

  4. 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.

NEXT_PUBLIC_SIGNETPAY_ROUTERThe deployed stealth router. Unset, every page that needs it says so rather than pretending; set, the stealth half of the app is live. Read in the browser, so changing it needs a rebuild, not a restart.
ANNOUNCEMENT_FROM_BLOCKThe block the router was deployed in. Without it the index starts a day back, which is slower and, for a router older than that, incomplete.
RHC_RPC_URLYour own Robinhood Chain endpoints, comma separated. Without it the app uses the public ones and fails over between them.
SIGNETPAY_DATA_DIRWhere accounts, handles, payment requests, waitlists and encrypted ledgers are written. Must be a durable, writable path — the status page says so when it is not.
SESSION_SECRETThe key that signs session cookies. Set it, or the app generates one on first run and keeps it in the data directory.
ANTHROPIC_API_KEYEnables the in-app assistant. Without it the assistant says it is not configured on this deployment.
NEXT_PUBLIC_CARD_TREASURYThe address the card prepay flow funds. Without it the card page says the treasury is not configured instead of inventing one. Read in the browser, so changing it needs a rebuild, not a restart.

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.

Questions07

(FAQ)

Is this a mixer?No. A mixer takes coins in and gives different coins out to break a link. Signetpay is an account: money enters by bridge or transfer and lands as encrypted notes that only your key opens. Nothing is pooled with strangers to launder a trail. Screening at the edge and licensed ramps are what the design calls for and what we build next; neither is in place today.
What can you see?Your wallet address, your handle, the meta-address you publish, the profile you fill in, the payment requests you send or receive, and whether you joined a waitlist. Your balances, positions, memos and payroll are encrypted in your browser with a key we never receive. The Privacy Policy lists all of it, including the parts that are not encrypted.
Is it self-custodial?Yes, in the literal sense: there is no key of yours on our side to take. Signing in proves the wallet; unlocking derives your keys from a signature, in the tab, and they are gone when you close it.
What if I lose the device?Connect the same wallet somewhere else and sign again. The keys are derived from the signature, not stored, so they come back identical and your notes are readable again. Lose the wallet itself and nobody — us included — can recover the account.
How does my accountant check anything?You issue a viewing key: read-only, derived from your own keys. It can recognise the payments you receive and can never spend them. Today it covers all of them and does not expire, so treat sharing one as permanent for that holder. Scoping it to a date range, and expiry, are planned.
Do I need the token?No. Every feature works without it. $SIGNETPAY moves limits and fees by tier, and that is all it gates.
Is all of this live?Most of it. The account, handles, balances, chain reads, invoices and the disclosure tooling work today. The stealth router is written, tested and ready to deploy — one command — and until it is deployed on Robinhood Chain every page that depends on it says so rather than showing a demo. The card is waiting on an issuer, and fiat ramps on a licensed partner.
Can I run my own instance?Yes. It is one Next.js app that boots with no configuration and reads the chain through public endpoints. The variables above are the whole surface.

Security08

(Security)

ReportingWrite to security@signetpay.xyz. The machine-readable version is at /.well-known/security.txt.
Most wantedAnything touching key derivation, the stealth scheme, the session cookie or the RPC proxy. Those are the places where a bug costs money rather than a page.
HeadersEvery response refuses framing, disables sniffing and ships a content security policy; inside the app that policy is nonce-based, so an injected inline script never runs.
What we can seeListed in full in the Privacy Policy, including the parts that are not encrypted. If it is not in that list, we do not have it.
SignetpayLoading (0%)