ATProto wallet attestations and DID-keyed escrow on Monad testnet, with a PWA, an indexer and Solidity contracts. Apache-2.0. mallbucks.malldao.xyz
monad wallet passkeys account-abstraction solidity atproto bluesky

Files
README.md

mallbucks #

Pay anyone by their Bluesky handle, on Monad. Mallbucks is a mobile-first wallet (a PWA) where the handle is the payment address: no address to copy, no seed phrase, no gas token to hold. A person who has no wallet yet can be paid anyway, and the money waits for them.

This is a testnet prototype. Do not use it with real funds.

What it does #

  1. Pay a handle. Search for a person, pick them, type an amount. The app finds their wallet through a signed record they publish in their own ATProto repository (xyz.malldao.wallet.attestation). The record links their DID to their address, so nobody has to trust our database for that link.
  2. Pay someone with no wallet. The payment goes into an escrow contract keyed by a hash of the recipient's DID. When they register a wallet, the server verifies they control that DID and releases the funds.
  3. A wallet with no seed phrase. A new user creates a wallet with a passkey (Face ID, fingerprint or device PIN). The signing key is derived on the device from the passkey's WebAuthn PRF output using Mera. It is the sole owner of an ERC-4337 Kernel smart account, and a paymaster pays gas.
  4. Trust signals. Search results show avatars, how many degrees away someone is in your Bluesky graph (1st, 2nd, 3rd+) and which of your follows follow them.
  5. More than transfers. Gifts ("hongbao") claimable by link, payment requests with end-to-end encrypted memos, recurring subscriptions, a storefront service (in a separate repository), and a rewards API other ATProto apps use to pay people.

How it uses Monad #

  • Payments confirm in seconds (about 5 to 6 seconds end to end in our QA runs), and fees are small enough that we sponsor gas for every user. That is what makes "just pay them" and the no-gas-token wallet workable.
  • The contracts and a server-side event logger run on Monad testnet (chain id 10143). The indexer reads Monad events to keep wallet records, balances and the transfer history in step.
  • Mera passkey keys sign ERC-4337 user operations against Monad through a ZeroDev bundler and paymaster.

Contract addresses (Monad testnet, deployed 2026-10-13) #

Contract Address
Mallbucks token (MBKS, ERC-20, 18 decimals) 0xd52F720Ba9112b3561E098738398b068616840e6
MallbucksEscrow 0xaE5cd78221bE98c1326b720B2320857f223F258c
AUSD (Agora dollar, 6 decimals) used for dollar payments 0xa9012a055bd4e0eDfF8Ce09f960291C09D5322dC

Deploy blocks, owners and the migration from the earlier token are in contracts/deployment-record.md.

Architecture #

ATProto account (DID)                          passkey (device secure element)
  |  OAuth session; PDS holds the signing key     |  WebAuthn PRF output (32 bytes)
  |                                               v
  |                                  BIP-44 key (secp256k1) via Mera
  |                                               |
  |   signed attestation record                   v
  +-------------------------------  ZeroDev Kernel smart account (ERC-4337)
        stored in the user's own PDS repo         |
        + verified by the onboarding server       v
                                     bundler + paymaster -> Monad testnet
  • pwa/ is the app: React 19, Vite, TypeScript, wagmi/viem, permissionless for the smart account, @category-labs/mera for passkey keys, @atproto/oauth-client-browser for sign-in.
  • src/ is the server (TypeScript, Node): the onboarding and registry API, the escrow auto-release, the contract event logger, the rewards API and the indexer that writes records back to user repos.
  • contracts/ is Solidity with Foundry: Mallbucks.sol, MallbucksEscrow.sol, MallbucksSubscription.sol.
  • lexicons/ holds the ATProto record schemas.
  • docs/ has the design: wallet-architecture.md (passkey to on-chain transaction, traced end to end), attack-analysis-attestation-registry.md, crypto-agility.md, rewards-api.md.

Setup #

Requirements: Node 22 or later. Foundry only if you want to run the contract tests.

# server
cp .env.example .env        # fill in MONAD_RPC_URL, contract addresses, PDS settings
npm install
npm run dev                 # tsx src/main.ts

# app
cd pwa
npm install
npm run dev                 # Vite dev server; a passkey needs https or localhost

# tests
npm test                    # server tests (vitest)
cd pwa && npx vitest run    # app tests
cd contracts && forge test  # contract tests (needs forge-std in contracts/lib)

Environment variables are documented in .env.example. The app reads VITE_* overrides for the RPC URL, contract addresses and the ZeroDev project id, all with defaults for the live testnet deployment.

To try it without setting anything up, open the live app, sign in with any Bluesky handle, and choose "Create with Face ID / fingerprint". docs/ has step-by-step notes for the QA runs we did on an iPhone and a laptop.

Pre-existing components and external code #

Everything in this repository was started on 2026-09-14, inside the hackathon window, and the commit history covers the build. It depends on some things that existed before, listed here so they are not mistaken for new work:

  • Aurora-Locus (an ATProto PDS), aurora-prism (relay and appview) and derived-dids (key derivation utilities) are separate earlier projects by the same authors. We run our own PDS on Aurora-Locus. Their code is not in this repository.
  • Mera (@category-labs/mera) provides the passkey PRF ceremony and secp256k1 signing sessions. We wrote the derivation, session storage, encryption and wallet flows around it.
  • ZeroDev provides the bundler and paymaster. permissionless, viem, wagmi, @atproto/api and @atproto/oauth-client-browser are used as libraries. The Solidity contracts build on OpenZeppelin Contracts (ERC-20, Ownable2Step, UUPS, SafeERC20); the contract logic is ours.
  • Bluesky and the ATProto network supply identity, the follow graph and the public API for profiles.

AI use disclosure #

Large parts of this code, its tests and its documentation were written with an AI coding assistant, Claude Code (Anthropic). The authors set the design, reviewed the changes, ran the tests and decided what shipped. Commits that were made with the assistant carry a Co-Authored-By line. The promo video music, the sound effects and the video assembly scripts were also generated with Claude Code, from original compositions and public-domain footage.

Security notes #

  • The server never receives a wallet key, a passkey output or a seed. It stores a public registry (DID to address) and verifies signatures.
  • The attestation message is signed by the user's own wallet key and can be verified from public data.
  • See SECURITY.md and docs/attack-analysis-attestation-registry.md for the threat analysis and what is not yet covered.

Status #

A testnet prototype built for the Metropolis Hackathon. It is deployed, running and tested (hundreds of server and app tests, and contract tests), but it has had no external security audit.

License #

Apache License 2.0. See LICENSE.

Copyright 2026 Alex Wykoff and Qian Li.

Credits #

Built by Alex Wykoff and Qian Li. The commit history shows who wrote what.