
Soapay
Privacy infrastructure for payments on chain. Get paid to a fresh address only you can open.
Demo
About
Soapay is privacy infrastructure for payments on chain. Every payment lands on a fresh address that only you can open, and you can spend it without it ever linking back to you. On a public chain, anyone can see what you earn. A payroll batch on Base shows every recipient and every amount next to each other, readable forever, and your colleagues can see it too. A coworker who knows their own line can trace everyone else's, and at Gitcoin DAO a contributor put names to fifteen colleagues' exact salaries starting from nothing but their own pay address. So companies walk away. Visa calls the lack of privacy a dealbreaker for banks, and Toku says every CFO conversation ends the moment they realise payroll would be public. The fixes that exist (Base Ledgers, Tempo Zones, Toku on Aleo) are private ledgers for enterprises. They are permissioned, gated behind an application for access, so they are not for everyone, and they are not fully private, since every payment goes through the company running them, which sees the payment, keeps the books and, in a ledger, decides what you can withdraw. Everyone else still pays in public. With Soapay, a recipient shares one name, such as alice.soapay.eth, and nothing else. There is nothing to apply for and no account to open, so it is permissionless, and any name that carries the stealth records can be paid with no server of ours in the loop. Every payment to that name lands on a fresh stealth address that only the recipient can open and spend from. A coworker reading the same batch sees a list of never-before-seen addresses and cannot tell which line is theirs. The recipient scans the chain, finds their payments, and spends them without ever touching the wallet everyone already knows. Soapay is not another stealth wallet. A stealth wallet like Fluidkey uses the same ERC-5564 and ERC-6538 standards and also hides your wallet from strangers, but it is built for an individual receiving payments. Its server derives your addresses at name resolution and holds your viewing key, so it sees every payment you receive, and it never touches the batch, which is where a coworker reads your salary. Soapay works on the payer side of the same standards. The sender's browser derives every address and throws the ephemeral key away, only you hold your viewing key, and no server of ours sees anything beyond public chain data. One transaction pays and announces every recipient atomically. Amounts can be split into identical chunks and sorted so per-transaction totals never leak. A consolidation guard and a timing queue stop a spend from linking your addresses. A compliant exit through Privacy Pools, built and proven on a Sepolia fork, lets you cash out. Key rotation is attested by World ID. And everything, addresses, ledger and pool secrets, rebuilds from your seed with the public SDK, with no company in the loop. It works in three steps. First, onboarding happens once, usually from an employer's invite link. The recipient app derives spending and viewing keys from one seed, locks them behind a passkey, registers the stealth meta-address in the canonical ERC-6538 registry through a throwaway registrant, and issues an ENSv2 subname whose stealth text record holds the meta-address. The name has no addr record, so nobody can pay a static address by mistake. Second, payment happens in one transaction. The sender app takes names and amounts, resolves each name once and pins its meta-address, derives a fresh ERC-5564 stealth address for every line, sorts the lines so the order says nothing about who is who, and pays and announces them atomically. Plain EOA employers go through StealthDisperse, our one contract, which holds no funds, keeps no state and has no owner. Smart accounts and 7702 wallets send an EIP-5792 batch with no contract at all, and Safes get a transaction bundle to sign. Large runs are cut into transactions of at most 350 lines and never split by employee, because per-transaction totals would reveal salaries. Third, the recipient finds and spends. The scanner filters announcer events by view tag and reads real balances. On first spend a stealth address delegates to an audited ERC-4337 account through EIP-7702, and a paymaster pays the gas, sponsored on the Base Sepolia demo and taken in USDC by Circle's paymaster on Base mainnet, so the address never holds ETH and the main wallet never appears. A consolidation guard blocks a spend that would link your addresses to an identifiable destination and warns on the rest. Three integrations carry the design. ENSv2 on Sepolia gives per-resource access control, so an employee can rotate only their own stealth record and the issuer can only issue names. World ID Proof of Human sessions attest key rotations, so a stolen key alone cannot redirect pay. And an MCP server gives AI agents the same identities, with ENSIP-26 records and spending guardrails, so agents can be paid and can pay by name. The trust model is simple. The employer is trusted and keeps its own records, so compliance stays where it already lives, and the only adversary is a coworker. The one contract we wrote holds no funds, keeps no state and has no owner, and everything else we call is a canonical, audited contract. Soapay is built for payroll and ready for any payout. Payroll is the first use case because it is where public payments hurt most, but nothing in the rail is specific to salaries. The same one-to-many flow already handles dividends and revenue share, token and equity allocations such as vesting unlocks and investor distributions, vendor and supplier payments, grants and bounties, prizes and airdrops, tips and creator payouts, and agents paying agents by name. The SDK plans all of them through one distribution module with payroll, dividend and grant presets, with vesting as a kind planned the same way, and a CLI runs any of them from a CSV. Soapay is built on Base with USDC and demoed on Base Sepolia with a mock USDC and sponsored gas, with names on ENSv2 Sepolia.
How it's made
Soapay is a pnpm and turbo monorepo in TypeScript: two Vite and React apps (recipient and company), a small Hono API on Node, one Solidity contract in Foundry, an SDK package that holds every piece of protocol logic, an MCP server for agents, and a CLI. The apps import protocol code only from the SDK, so there is no private code path. Payments run on Base (USDC) and are demoed on Base Sepolia. Names live on ENSv2 on Ethereum Sepolia. The public demo deploys from main to Railway through a GitHub Actions workflow. Stealth addresses are ERC-5564 scheme 1, secp256k1 with view tags, through the ScopeLift SDK, viem and the noble and scure crypto libraries. A recipient's spending and viewing keys come from one BIP-39 recovery phrase, saved as a recovery kit. The vault unlocks with a passkey: the WebAuthn PRF extension feeds HKDF, which derives an AES-GCM key, with a passphrase fallback. The meta-address is registered in the canonical ERC-6538 registry through a throwaway registrant, with the API relaying the gas so the main wallet never appears. Unlike a stealth wallet such as Fluidkey, no server of ours derives addresses or holds a viewing key: the sender's browser derives every address and discards the ephemeral key, and only the recipient can scan. StealthDisperse is the only contract we wrote. It pulls USDC from the payer with transferFrom and, in the same transaction, calls the canonical ERC-5564 announcer for every line. It holds no funds, keeps no state and has no owner. Lines must be strictly ascending by stealth address, which rejects duplicates on-chain and makes the order say nothing about the roster. Each line is packed into 64 bytes of calldata (stealth address, amount, view tag and key prefix in one word, the ephemeral key's x-coordinate in the other) to cut Base's L1 data fee. The metadata carries the transfer selector rather than transferFrom, because that selector collides with ERC-721 and a scanner could not tell an amount from a token id, and it appends the payer, because the announcer records the contract as the caller. It has 31 unit, fuzz and gas tests plus 4 fork tests against real Base USDC and the real announcer, and is deployed by CREATE2 on Base Sepolia. Smart accounts and 7702 wallets skip the contract and send an EIP-5792 atomic batch of transfer and announce calls, and Safes get a Transaction Builder export through MultiSendCallOnly. Big runs are sorted globally, cut into transactions of at most 350 lines, and never split by employee, since per-transaction totals would reveal salaries. Amounts are chunked by default into one company-wide denomination with an exact remainder line. Spending is gasless from the stealth address itself. On first spend the address delegates to the audited Simple7702Account through EIP-7702 and sends a userOp through EntryPoint v0.8 and Pimlico's public bundler. On Base mainnet Circle's USDC paymaster takes the gas from the same USDC balance, and the Base Sepolia demo sponsors it, so no ETH ever has to arrive from somewhere linkable. A consolidation guard rebuilds the cluster graph, blocks a spend to an identifiable destination, and warns before a spend that would merge address clusters or recombine chunks from one pay run, and a client-side queue releases spends in random windows so timing does not link them either. We verified on a Base mainnet fork that Base USDC's permit validates for Simple7702Account through ERC-1271, which is what makes the EOA permit path work for delegated accounts. The fork suite starts its own anvil fork and plays the bundler, so it needs no keys. Cashing out goes through Privacy Pools via CCTP, proven on a Sepolia fork and hidden on the testnet build, with each leg's minimum derived from the pool minimum, the bridge fee and the paymaster prefunds. ENSv2 is what removes the trusted gateway. soapay.eth was bought through the real ETHRegistrar on Sepolia and runs its own subname registry. Every employee gets a PermissionedResolver deployed through the VerifiableFactory with its records and roles set in initialize, before the name is registered, so records land atomically. Per-resource access control gives each actor one power: the employee's registrant key holds ROLE_SET_TEXT on the stealth record only, the issuer key holds ROLE_REGISTRAR only, names carry an empty role bitmap so they are non-transferable, and the company keeps ROLE_UNREGISTER. There is no addr record, so a plain wallet cannot pay a static address by mistake. Resolution is standard viem getEnsText through the ENSv2 Universal Resolver, and a Foundry fork test exercises the live ENSv2 contracts end to end. World ID covers the one moment that needs continuity: key rotation, since the stealth record decides where future pay goes. At setup an employee can open a Proof of Human session through IDKit. To rotate, they prove the same session; the API verifies nonce, signal, credential, environment and replay against the Developer Portal, signs an EIP-712 MetaRotation attestation, relays the ERC-6538 re-registration and tops up their Sepolia gas. The payer's app auto-accepts a changed pin only with that attestation. No Orb, no onboarding gate. The MCP server gives agents the same identities. create_agent_identity registers a meta-address and claims a subname with ENSIP-26 agent-context and agent-endpoint records written in the resolver's initialize, and pay, scan, balance and spend cover the flow, behind guardrails: every value move is a dry run and then a confirm of a single-use plan that expires in ten minutes, with per-call and per-day caps, an optional allowlist, pinned meta-addresses, and keys that never leave the process. Live on Base Sepolia, an agent created mcp-agent-7c1e.soapay.eth, received 0.3 USDC through StealthDisperse, found it with scan and spent 0.1 USDC to another name through the bundler and paymaster. Two smaller hacks worth mentioning. The ScopeLift stealth SDK cannot be loaded by plain Node, so Vite bundles it for the apps, Vitest inlines it, esbuild bundles it for the MCP server and the tools run under tsx. And the demo's coworker view rebuilds an entire pay-run batch from the transaction receipt alone, announcements plus USDC transfer logs, so the audience sees exactly what the chain shows a colleague, then flips to the recipient's view where only their viewing key lights up their lines.