NullAgent Documentation
NullAgent is an autonomous trading agent on Base that executes operations without leaving a traceable on-chain fingerprint. This documentation covers setup, architecture, API reference, and integration guides.
Setup Guide
Prerequisites and initial configuration to get NullAgent running locally or in a Conway microVM.
Prerequisites
| Requirement | Version | Notes |
|---|---|---|
| Node.js | ≥ 18.x | Required for viem and x402 packages |
| npm | ≥ 9.x | Or pnpm / yarn |
| MetaMask | Latest | For wallet authentication in the UI |
| Base RPC | — | Alchemy (free tier) or public Base RPC |
| ETH on Base | ≥ 0.01 | Minimum deposit to fund the vault |
Installation
# Clone the repository git clone https://github.com/0xgasless/nullagent cd nullagent # Install frontend dependencies cd frontend && npm install # Install backend dependencies cd ../backend && npm install
Quick Start
Deploy your first agent in under 5 minutes using the web deploy wizard or CLI.
Environment Variables
All configuration is done through environment variables. Never commit secrets to version control.
Backend .env
# Server PORT=3001 NODE_ENV=production FRONTEND_URL=https://nullagent.xyz # [FILL] your domain # Base RPC — get free key at alchemy.com ALCHEMY_RPC=https://base-mainnet.g.alchemy.com/v2/[FILL_YOUR_KEY] # Wallet that receives x402 signal payments PAYMENT_ADDRESS=[FILL_YOUR_WALLET_ADDRESS] # Conway API (optional — enables real VM deploy) CONWAY_API_KEY=[FILL_IF_AVAILABLE] # Privacy layer flags RAILGUN_ENABLED=false ZK_ENABLED=false
Frontend .env.local
# Backend URL (Railway, Render, or localhost) NEXT_PUBLIC_API_URL=https://api.nullagent.xyz # [FILL] your backend URL NEXT_PUBLIC_WS_URL=wss://api.nullagent.xyz/ws # [FILL] WebSocket URL
| Variable | Required | Description |
|---|---|---|
| PORT | No | Backend HTTP port. Defaults to 3001. |
| ALCHEMY_RPC | Yes | Base mainnet RPC URL. Free at alchemy.com. |
| PAYMENT_ADDRESS | Yes | Wallet address to receive x402 signal sale proceeds. |
| CONWAY_API_KEY | No | Enables real VM deployment. Without it, deploy wizard simulates. |
| RAILGUN_ENABLED | No | Set to true when Railgun activates in Q2. |
| FRONTEND_URL | Yes | Your frontend domain for CORS configuration. |
How Privacy Works
NullAgent implements a three-layer privacy model. Each layer independently reduces traceability — together they make it cryptographically impossible to link agent activity to an operator identity.
Why this matters
NullAgent breaks this model at three levels. L1 eliminates the fixed wallet. L2 breaks the funding link. L3 enables verifiable execution without any on-chain disclosure.
L1 · Stealth Wallets Active
Every trade operation uses a freshly generated EVM wallet. The private key exists only in memory and is destroyed immediately after execution.
How it works
viem.generatePrivateKey() to create a cryptographically random 32-byte private key. This key is derived into an EVM account, used to sign and submit exactly one transaction, then discarded — never written to disk, never logged, never stored in the database. The wallet address is recorded in SQLite as a historical record, but the key is gone forever.// src/privacy/stealth.ts import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts' export function generateStealthWallet() { // 32 bytes of cryptographic randomness const privateKey = generatePrivateKey() const account = privateKeyToAccount(privateKey) // Key lives only in this closure scope // Function returns only the address and a signing capability // Private key is never exposed outside this function return { address: account.address, sign: account.signTransaction, retire: () => { /* key goes out of scope */ } } }
Wallet lifecycle states
| Status | Description |
|---|---|
| ready | Generated and funded. Waiting to be assigned to a trade. |
| active | Currently being used for an in-progress transaction. |
| confirmed | Trade confirmed on-chain. Key still technically in memory. |
| retired | Execution complete. Key destroyed. Address recorded in DB. |
| used | Historical record only. Address exists on-chain, key is gone. |
L2 · Railgun Shield Q2 2025
Railgun's shielded UTXO pool on Base breaks the on-chain link between your funding wallet and the stealth wallet execution pool. Currently loaded as a module, awaiting Q2 activation.
RAILGUN_ENABLED=false — This module is integrated into the codebase and ready to activate. The flag will be set to true when Railgun's Base mainnet integration is confirmed stable in Q2.What Railgun adds
// Current state: module loaded, flag disabled // src/privacy/railgun.ts import { RailgunWallet } from '@railgun-community/wallet' export async function shieldAndRoute(amount: bigint, recipient: string) { if (!process.env.RAILGUN_ENABLED) { // L1 only — direct transfer to stealth pool return directTransfer(amount, recipient) } // L2 active: shield → UTXO pool → unshield to stealth return railgunShield(amount, recipient) }
[FILL] — Railgun contract addresses
| Contract | Address | Network |
|---|---|---|
| RailgunSmartWallet | [FILL WHEN ACTIVE] | Base mainnet |
| RelayAdapt | [FILL WHEN ACTIVE] | Base mainnet |
L3 · ZK Decision Proof Q3 2025
Semaphore-based zero-knowledge proofs that allow an agent to prove it executed a trade — without revealing which wallet, which timestamp, or what the decision logic was.
ZK_ENABLED=false — Semaphore integration is planned for Q3. The architecture below describes the intended implementation.Intended architecture
// Planned — src/privacy/zkproof.ts import { generateProof } from '@semaphore-protocol/core' export async function proveExecution(signal: ExecutionSignal) { const proof = await generateProof( identity, // agent's Semaphore identity group, // NullAgent operator group hashSignal(signal), group.root // merkle root for on-chain verification ) return proof }
REST Endpoints
All endpoints are served from the backend at the configured NEXT_PUBLIC_API_URL. Base path: /api
backend/src/routes/index.ts.{ "ok": true, "ts": 1718234567, "version": "0.1.0" }{ "wallets": 12847, "executions": 3291, "signals": 891, "ethProtected": 847.3 }[{ "id": 1, "address": "0x4f3a...", "status": "ready", "created_at": 1718234567 }]ready · funded and available |
active · in-flight transaction |
used · trade completed |
retired · key destroyed
{ "count": 5 }{ "generated": 5, "wallets": [ ... ] }[{
"id": 1, "wallet": "0x4f3a...", "stealth_address": "0x4f3a...",
"pair": "ETH/BRETT", "direction": "BUY", "amount_eth": "0.420",
"privacy_layer": "L1", "status": "confirmed", "created_at": 1718234567
}]HTTP/1.1 402 Payment Required
X-Payment-Required: { "scheme": "exact", "network": "base", "amount": "1000", "token": "USDC" }GET /api/signal
X-Payment: [payment-signature]
{ "pair": "ETH/BRETT", "direction": "BUY", "conviction": 0.87, "price_usdc": "0.001" }{ "totalEth": 5.23, "execPool": "3.138", "gasReserve": "1.569", "infraUsdc": "0.523" }{ "address": "0x4f3a...", "signature": "0x...", "message": "Sign this message..." }{ "authenticated": true, "address": "0x4f3a...", "shortAddress": "0x4f3a...9b2c" }{ "pair": "ETH/BRETT", "slippage": "0.5", "conviction": "65", "depositEth": "0.1" }{ "agentId": "null-0047", "status": "operational", "vmId": "vm_abc123", "endpoint": "https://null-0047.conway.app" }WebSocket Events
Connect to wss://api.nullagent.xyz/ws for real-time agent state. On connect, you receive a full snapshot. Subsequent events are incremental updates.
Connecting
const ws = new WebSocket('wss://api.nullagent.xyz/ws') ws.onmessage = (event) => { const { type, data } = JSON.parse(event.data) switch(type) { case 'snapshot': handleSnapshot(data); break case 'trade_added': handleNewTrade(data); break case 'wallet_added': handleNewWallet(data); break } }
Server → Client events
Client → Server events
Authentication
NullAgent uses MetaMask personal_sign for wallet-based authentication. No JWT, no email, no OAuth — your wallet is your identity.
eth_requestAccounts via window.ethereum. User approves wallet connection in MetaMask.wallet_switchEthereumChain with chainId 0x2105 (8453). If not added, call wallet_addEthereumChain.personal_sign with a timestamped nonce message. The signature proves ownership of the address without exposing the private key./api/auth/verify. The backend recovers the signer address and confirms it matches.// Complete MetaMask auth flow const eth = window.ethereum const [address] = await eth.request({ method: 'eth_requestAccounts' }) // Switch to Base (chainId 8453) await eth.request({ method: 'wallet_switchEthereumChain', params: [{ chainId: '0x2105' }] }) // Sign auth message const msg = `Sign to authenticate with NullAgent:\nnullagent-${address}-${Date.now()}` const sig = await eth.request({ method: 'personal_sign', params: [msg, address] })
x402 Protocol
NullAgent implements x402 for autonomous HTTP micropayments. The agent sells trading signals at $0.001 USDC per request — no API key, no account, no human in the loop.
How x402 works
HTTP 402 Payment Required and a machine-readable price specification. The client's x402 library automatically pays the specified amount on-chain and retries the request with a X-Payment signature header. The server verifies payment and returns the resource.@x402/express middleware on GET /api/signal. Any agent that requests a signal and can pay $0.001 USDC gets the signal automatically.@x402/fetch to pay Conway compute credits automatically when the vault's USDC balance is sufficient. No manual top-up needed.// Backend: protect an endpoint with x402 import { paymentMiddleware } from '@x402/express' app.use('GET /signal', paymentMiddleware({ network: 'base', asset: 'USDC', amount: '1000', // $0.001 USDC (6 decimals) payTo: process.env.PAYMENT_ADDRESS })) // Client: buy a signal automatically import { fetchWithPayment } from '@x402/fetch' const signal = await fetchWithPayment('https://api.nullagent.xyz/api/signal', { wallet: stealthWallet // pays automatically from stealth wallet USDC })
[FILL] — Signal pricing
| Signal Type | Price (USDC) | Endpoint | Status |
|---|---|---|---|
| ETH/BRETT BUY signal | $0.001 | /api/signal | Live |
| ETH/DEGEN BUY signal | $0.001 | /api/signal | Live |
| High conviction bundle | [FILL] | [FILL] | Planned |
| Subscription feed | [FILL] | [FILL] | Planned |
Conway Compute
Conway.tech runs NullAgent in isolated Firecracker microVMs — the same hypervisor used by AWS Lambda. Each agent gets its own Linux process, private SQLite database, and a public SSL endpoint.
What Conway provides
[FILL] — Conway API endpoints
| Operation | Conway Endpoint | Notes |
|---|---|---|
| Create VM | [FILL] | Returns vmId and endpoint URL |
| Check status | [FILL] | Returns running/stopped/error |
| Expose port | [FILL] | Creates public SSL endpoint |
| Top up credits | [FILL] | x402 autopay target |
ERC-8004 Identity
NullAgent registers on-chain agent identity using ERC-8004 — the emerging standard for autonomous agent registries on EVM chains.
What ERC-8004 provides
[FILL] — Contract addresses
| Contract | Address on Base | Status |
|---|---|---|
| AgentRegistry (Identity) | [FILL] | Pending deployment |
| ReputationRegistry | [FILL] | Pending deployment |
| ValidationRegistry | [FILL] | Pending deployment |
Vault Architecture
The vault manages all ETH deposited by the operator and splits it automatically into three purpose-specific pools.
Split configuration
| Pool | Allocation | Purpose | Asset |
|---|---|---|---|
| Execution Pool | 60% | ETH used for actual trades through stealth wallets | ETH |
| Gas Reserve | 30% | Funds each stealth wallet with gas before every trade | ETH |
| Conway Infra | 10% | Swapped to USDC → used for x402 compute credit payments | ETH → USDC |
[FILL] — Vault contract address
Deploy on Conway
The recommended production deployment runs the agent inside a Conway Firecracker microVM.
backend/ folder to a GitHub repo. Conway will pull from it when provisioning the VM.Self-Hosted
Run the backend on any Node.js host: Railway, Render, Fly.io, or your own VPS.
Deploy backend to Railway (recommended, free tier)
# 1. Push to GitHub git push origin main # 2. Go to railway.app → New Project → Deploy from GitHub # 3. Set Root Directory: backend # 4. Add environment variables (see .env.example) # 5. Railway assigns a URL — set as NEXT_PUBLIC_API_URL in Vercel
Deploy frontend to Vercel (free)
# 1. Import GitHub repo at vercel.com # 2. Set Root Directory: frontend # 3. Set environment variables: NEXT_PUBLIC_API_URL=https://your-backend.railway.app NEXT_PUBLIC_WS_URL=wss://your-backend.railway.app/ws