Search projects, hackathons, sponsors and tech

    ← All projects

    Demo

    About

    The problem it solves On-chain payments leak everything: balances, counterparties, and amounts are public forever. Even "private" tools usually still link sender → recipient through a wallet address. SpaceNox solves three real problems at once: 1. Confidential balances and transfers Wrap regular USDC into cUSDC and your balance is stored as an encrypted handle on-chain. When you send to someone, you submit an RSA-encrypted intent where the recipient and amount are never visible on the chain. A TEE gateway decrypts the intent inside a secure enclave and settles it in batched anonymity sets, so even an observer indexing every block can't tell who got paid or how much. Use it for: payroll, contractor payments, treasury operations, donations, anything where amounts and counterparties shouldn't be public. 2. Receiving without revealing your wallet Hand out one ENS name (e.g. alice.eth). Every sender resolves it to a fresh one-time stealth address. Funds arrive there, you sweep each one through cUSDC and confidentially route the balance to your main wallet so your real address never appears next to any payment you receive. Use it for: public donation links, invoices, hackathon prizes, freelancing, refunds or any "send me money here" scenario where you don't want to doxx your main wallet. 3. No central point of failure Stealth registrations (Fluidkey keys, nonce counters) live in Swarm, not on a hosted gateway. Encrypted balance state is mirrored to Swarm by the ingestor. Decryption keys live only inside the TEE gateway and never in the browser, never in a database. Net effect: users get bank-grade privacy on stablecoin payments and ENS-based identity, without trusting a company to hold their keys, their balances, or their stealth registry. Existing flows like "wrap → transfer → unwrap" or "share my ENS to get paid" just work but the chain doesn't reveal who, how much, or to whom.

    How it's made

    Challenges we ran into Challenges I ran into 1. Hiding the recipient on-chain, not just the amount Most "confidential token" implementations encrypt amounts but still emit Transfer(from, to, ...), which leaks the counterparty. I had to redesign the transfer flow as a two-step intent protocol: the sender submits an opaque RSA-encrypted blob (submitConfidentialIntent), and the gateway later batches multiple intents together and calls executeConfidentialIntentBatch with a 5-address anonymity set per intent. The contract verifies an EIP-712 input proof for the encrypted recipient index into that set so the chain only sees a list of candidates, not which one actually got paid. 2. Frontend submitting intents but balances not moving After wiring the encrypted-intent flow, transfers looked successful on the UI but cUSDC balances didn't change. Took a while to realize submitConfidentialIntent only queues the intent executeConfidentialIntentBatch is gateway-only and is what actually moves balances. Solution: added /api/intent/settle that loads the runner RSA key on the server, decrypts the intent, builds proofs, and broadcasts the batch tx. The UI now goes submitting → settling → done instead of silently leaving funds stuck. Built a same-origin /api/rpc proxy so the frontend never talks directly to upstream. Added withRpcRetry exponential backoff for server-side calls. Tagged every RPC error with source + rpcHost so I could actually tell which RPC was breaking. Built an "unwrap recovery" panel that lists stuck UnwrapRequested events and re-runs claimUnwrap on demand so users never lose USDC to a transient gateway failure. 3. Vercel monorepo deletes frontend tsconfig First Vercel deploy failed with module-not-found for files that obviously existed. Turned out my root .vercelignore had unanchored entries like tsconfig.json and contracts/ that recursively matched into frontend/. Anchoring everything with a leading / (e.g. /contracts/) and adding a frontend/vercel.json with explicit build/install commands fixed it. 4. Two @scure/bip32 versions in the dependency tree Adding @fluidkey/stealth-account-kit to the frontend pulled in its own pinned @scure/bip32, which conflicted with the one viem uses for HDKey. TypeScript refused to pass the node between them ("separate declarations of a private property privKey"). Runtime was fine, so I cast viewingPrivateKeyNode as never at the boundary and pinned the chain id to a number (Fluidkey rejected bigint). 5. Replacing the Sneaky gateway with Swarm The original ENS stealth example relied on a hosted Deno gateway holding the user's viewing keys. I didn't want that dependency. Rebuilt the whole flow on top of @armsves/swarm-kv (the same package the ingestor uses): registration writes Fluidkey material to Swarm, resolve serializes nonce increments per ENS name, and status reads the issued count back. The catch was concurrency, two parallel resolves on the same name would have collided on the nonce row. Solved with a per-name promise queue on the server so each resolve is a strict read-modify-write. 6. RSA private key in a serverless runtime Tried loading rsa_priv.pem via fs.readFileSync in the frontend's API routes which works locally, breaks under Turbopack's NFT trace and isn't portable to Vercel. Switched to RUNNER_RSA_PRIVATE_KEY_PEM as an env var and made the loader strict (no filesystem fallback), so prod and dev follow the same code path.