Speak your money. One sentence becomes a payment: no chains to pick, no gas to pay, no seed phrase.
🔗 Landing page: relay-ashen-zeta.vercel.app
🔗 Live app: relay-ashen-zeta.vercel.app/app
🎬 90 second demo: youtu.be/Lsi5LygN5V0
📜 Contract (Arbitrum Sepolia): 0x8DD23aBBA62f10306805F0B2C8BF8459d1C3974e
🐙 Repo: github.com/Chibey-max/relay
Built for the UXmaxx Hackathon: Universal Accounts Track.
| Track | Prize | How Relay qualifies |
|---|---|---|
| Universal Accounts Track (main) | $2,500 / $2,000 / $1,500 | Particle UA SDK in EIP-7702 mode is the spine: unified balance reveal + real cross-chain transfer. Meets all three hard requirements: UA in 7702 mode, ≥1 cross-chain value operation, functional deployed demo. |
| Arbitrum "Road to Open House" bounty | $2,000 (track-agnostic) | Runs primarily on Arbitrum; chain-abstracted UX (embedded wallet, gas abstraction via ZeroDev, invisible bridging via UA) with a contract deployed and verified on Arbitrum Sepolia. |
| Magic Labs bonus | $500 (track-agnostic, single competitive winner) | Email login → real embedded wallet. No seed phrase, no MetaMask, no extension. |
Realistic ceiling if 1st on the main track: $5,000 ($2,500 + $2,000 + $500), from one coherent submission, no bolted-on integrations.
Note: the hackathon's ZeroDev subtrack ($500 × 4 winners, judged on Smart Routing Address usage) is nested under the General Track, which is mutually exclusive with the Universal Accounts Track. You submit to one main track, not both. Relay targets Universal Accounts, so it isn't eligible for that subtrack, and doesn't claim to be. ZeroDev's gas sponsorship + on-chain spend-policy enforcement are real and live (see
lib/zerodev.ts,contracts/src/RelayPolicy.sol). They support the Arbitrum bounty's "gas abstraction" criterion, not a separate ZeroDev prize claim.
Relay is a natural-language payment agent. You type a sentence and Relay parses it, shows your balance unified across every chain you hold funds on, sponsors the policy-check gas via ZeroDev, and settles on Arbitrum from funds already available there. Cross-chain sourcing for the settlement step itself is not yet live, see the FAQ and Status section below for the honest current state. The entire interaction still stays a single sentence.
The hero of the product isn't the sentence, it's the unified balance reveal: money scattered across Base, Arbitrum, and Optimism, shown as one spendable number. That reveal is real and confirmed working. Your existing EOA is upgraded in place via EIP-7702: no new address, no migration, no smart-account deployment.
| Path | What it shows | Wallet needed |
|---|---|---|
| Landing page (relay-ashen-zeta.vercel.app) | The pitch: real verified sends, the balance-reveal moment, the deployed contract, one click through to the app | No |
| Demo mode (/app?mode=demo) | The full flow on a pre-seeded test signer, no login, clearly badged as demo, never presented as a real send | No |
| Live mode (/app) | Email login via Magic, a real Universal Account balance, a real sponsored send settling on Arbitrum | Just an email |
Every demo result is visibly labelled. Every live result links to a real Arbiscan transaction.
"send 5 USDC to 0x…"
│
▼
Groq intent parser lib/groq.ts → { action, amount, token, recipient }
│
▼
Magic email login lib/magic.ts → user's EOA (no seed phrase)
│
▼
Particle Universal Account lib/particle.ts → one balance across chains (7702 mode)
│ ↳ getPrimaryAssets(): the reveal
▼
Transfer to Arbitrum lib/particle.ts → createTransferTransaction()
│ ↳ requires the token already on Arbitrum
▼
ZeroDev paymaster lib/zerodev.ts → gasless UserOp for local actions
│
▼
RelayPolicy.sol contracts/ → on-chain spend limits (Arbitrum Sepolia)
│
▼
Real result + Arbiscan link
Single Next.js 14 App Router repo: all logic in app/api/* routes, one Vercel deploy. No separate backend.
/: the marketing landing page. Decorative and informational only, no SDK imports, no/api/*calls. Real, verified transaction hashes and the contract address shown here are genuine, not placeholders (components/marketing/realSends.ts)./app: the real, wallet-connected product described above. Supports?mode=demoto force the sandboxed demo flow without a wallet./api/*: server routes for intent parsing, balance, and execution, shared by both the demo and live flows in/app.
Next.js 14 (App Router) · TypeScript · Tailwind CSS · Framer Motion · Solidity + Foundry (contracts/) · Particle Universal Accounts SDK · Magic SDK · ZeroDev SDK · Groq (natural-language parsing) · Vercel
ESLint (strict: no unused vars/imports, no any) and Prettier are wired in; see docs/Context.md for the engineering conventions this repo follows.
git clone https://github.com/Chibey-max/relay.git
cd relay
cp .env.example .env.local # demo mode works with zero keys
npm install
npm run dev # http://localhost:3000Demo mode (NEXT_PUBLIC_RELAY_MODE=demo) runs the full flow on a pre-seeded test signer: no login required, safe to demo anywhere. Every demo result is clearly badged as demo, never presented as a real transaction.
Live mode (NEXT_PUBLIC_RELAY_MODE=live) is what's running in production. To run it yourself, add real keys:
- Particle: dashboard.particle.network →
PROJECT_ID,CLIENT_KEY,APP_ID - Magic: dashboard.magic.link → publishable key, Email OTP enabled
- ZeroDev: dashboard.zerodev.app → Arbitrum Sepolia, gas sponsorship policy
- Groq: console.groq.com → API key
Other scripts: npm run build, npm run lint, npm run typecheck, npm run format / npm run format:check.
RelayPolicy.sol enforces per-recipient spend limits so the agent can only move value within bounds the user set, even though the UX is a single sentence. Every limit is per-recipient, read from storage, never a hardcoded global constant. Since recipients arrive as arbitrary addresses typed in a sentence (they can't be pre-whitelisted one by one), a recipient seen for the first time is auto-enrolled under an owner-configurable default (defaultPerTxMaxWei / defaultDailyMaxWei) rather than rejected outright. configureRecipient still lets the owner set a tighter or looser limit for any specific address afterward. checkAndRecord is called for real on every live transfer (via a ZeroDev-sponsored UserOp) and reverts (blocking the transfer) if the policy is violated.
Deployed at 0x8DD23aBBA62f10306805F0B2C8BF8459d1C3974e on Arbitrum Sepolia: owner is the demo deployer key, agent is the ZeroDev Kernel account for RELAY_AGENT_PRIVATE_KEY, defaultPerTxMaxWei/defaultDailyMaxWei are 100 / 500 token-units (18-decimal convention). Confirmed on-chain via direct readContract calls at deploy time.
Verified on Arbiscan: the full source is readable directly on the explorer, see the contract link above.
To redeploy again later (e.g. different default limits, different agent):
# 1. Get the Kernel account address for your RELAY_AGENT_PRIVATE_KEY.
# This is NOT the raw EOA address; msg.sender inside checkAndRecord is
# the smart account that executes the UserOp, not the EOA that signs it.
npm run agent:address
# 2. Deploy with that address as AGENT_ADDRESS. Needs a separate funded
# deployer key (Arbitrum Sepolia ETH from a faucet). This is a plain
# contract deployment, not a sponsored UserOp, so it isn't gas-free.
cd contracts
DEPLOYER_PRIVATE_KEY=0x... \
AGENT_ADDRESS=0x... \
DEFAULT_PER_TX_MAX_WEI=100000000000000000000 \
DEFAULT_DAILY_MAX_WEI=500000000000000000000 \
forge script script/Deploy.s.sol \
--rpc-url $ARB_SEPOLIA_RPC --broadcast --verify \
--etherscan-api-key $ARBISCAN_KEY
# 3. Update NEXT_PUBLIC_RELAY_POLICY_ADDRESS in .env.local (and the link
# above) to the new address printed by the script.DEFAULT_PER_TX_MAX_WEI / DEFAULT_DAILY_MAX_WEI follow an 18-decimal ("ether-style") convention regardless of the token actually transferred. The policy contract never moves tokens itself, it only bounds a number, so 100000000000000000000 means "100 token-units per transaction." Change them (or call setDefaultLimits post-deploy) to whatever bound you want new recipients to start under.
Tests: cd contracts && forge test (9/9 passing, contracts/test/RelayPolicy.t.sol).
- Arbitrum Sepolia ETH: any Sepolia faucet → bridge.arbitrum.io (testnet mode)
- Base Sepolia ETH: Base network faucets
- Optimism Sepolia ETH: Optimism faucets
- Test USDC (Arbitrum Sepolia): faucet.circle.com → Arbitrum Sepolia
Six rules, derived from prior hackathon wins and losses, govern this build:
- Every partner SDK makes a real network call: not instantiated, called.
- Demo/live mode is visible in the UI: nothing hidden from judges.
- No claim in this README isn't true in the code: demo results are badged, never presented as real.
- Verifiable testnet address, not localhost: see contract link above.
- Features mapped to prize tracks inside the app itself: see the Integration Status panel on the live site.
- Honest about what isn't live: graceful fallbacks, clearly labelled.
This project was built with AI-assisted development (Claude) alongside manual engineering, consistent with the principle above: honest about what's actually true rather than a cleaner story than reality.
Particle Universal Accounts was mid-migration to V2 during the hackathon window. If a balance fetch ever surfaces a migration notice, the app states it honestly rather than faking a number. This is intentional, not a bug.
RelayPolicy redeployed with real checkAndRecord enforcement (auto-enrollment under owner-set default limits, blocking on violation), covered by contracts/test/RelayPolicy.t.sol (9/9 passing) and confirmed live on-chain. Verified on Arbiscan, source code is publicly readable on the contract page linked above.
Two real, verified sends are shown throughout the app and the landing page, not fixture data: a 5 USDC transfer and an EIP-7702-authorized 0.00005 ETH transfer, both on Arbitrum One mainnet, both linked to Arbiscan.
Cross-chain sourcing was live-tested on 2026-07-20: a send was attempted while funds sat on Base but not Arbitrum. Balance aggregation across chains worked correctly (visible in the unified balance display), but the actual settlement transaction failed with -32653 Insufficient primary token balance, confirming the requested token must already be present on Arbitrum for a send to complete. This is an honest current limitation, not a bug: the unified balance view is real and live; automatic cross-chain sourcing for settlement is not yet implemented. Confirmed via a real failed transaction on Arbitrum mainnet, error captured directly from Particle's SDK.

