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.

📋  This documentation is a work in progress. Sections marked [FILL] need to be completed with your actual configuration values, RPC endpoints, and contract addresses.

Setup Guide

Prerequisites and initial configuration to get NullAgent running locally or in a Conway microVM.

Prerequisites

RequirementVersionNotes
Node.js≥ 18.xRequired for viem and x402 packages
npm≥ 9.xOr pnpm / yarn
MetaMaskLatestFor wallet authentication in the UI
Base RPCAlchemy (free tier) or public Base RPC
ETH on Base≥ 0.01Minimum 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
⚠  [FILL] Replace the GitHub URL above with your actual repository URL once the project is public.

Quick Start

Deploy your first agent in under 5 minutes using the web deploy wizard or CLI.

1
Connect your wallet
Open the deploy wizard at nullagent.xyz and click Deploy Agent. Connect MetaMask and sign the authentication message. No email, no account — wallet-only auth.
2
Configure trading parameters
Select your trading pair (ETH/BRETT or ETH/DEGEN), slippage tolerance, and minimum conviction threshold. Privacy layer is set to L1 (stealth wallets) by default — this cannot be changed until Railgun activates in Q2.
3
Fund your vault
Enter your deposit amount. The vault auto-splits: 60% to execution pool, 30% to gas reserve, 10% to Conway infra (swapped to USDC). Minimum deposit: 0.01 ETH. Recommended: 0.1 ETH for meaningful operation.
4
Agent launches
A Conway Firecracker microVM spins up, installs dependencies, generates your stealth wallet pool, starts the watcher, and exposes a public x402 signal endpoint. The agent is now fully autonomous.
ℹ  The deploy wizard simulates the Conway VM provisioning process in demo mode. Real VM deployment requires a Conway API key — contact Conway.tech to get access.

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
VariableRequiredDescription
PORTNoBackend HTTP port. Defaults to 3001.
ALCHEMY_RPCYesBase mainnet RPC URL. Free at alchemy.com.
PAYMENT_ADDRESSYesWallet address to receive x402 signal sale proceeds.
CONWAY_API_KEYNoEnables real VM deployment. Without it, deploy wizard simulates.
RAILGUN_ENABLEDNoSet to true when Railgun activates in Q2.
FRONTEND_URLYesYour 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.

L1
Active Now
Stealth Wallets
New ephemeral address per operation. Key destroyed after use.
L2
Q2 Activation
Railgun Shield
Shielded UTXO pool breaks the funding wallet → stealth wallet link.
L3
Q3 Activation
ZK Decision Proof
Prove agent acted without revealing wallet, timestamp, or logic.

Why this matters

On Base today, every trading bot runs from a fixed wallet. Every trade is permanently recorded on-chain: the wallet address, the timestamp, the pair, the amount — all of it public, forever. Within days, a competitor can identify your strategy and copy or front-run it.

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

At trade execution time, NullAgent calls 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

StatusDescription
readyGenerated and funded. Waiting to be assigned to a trade.
activeCurrently being used for an in-progress transaction.
confirmedTrade confirmed on-chain. Key still technically in memory.
retiredExecution complete. Key destroyed. Address recorded in DB.
usedHistorical 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

Without Railgun, a sophisticated observer can still trace: your funding wallet → gas distribution wallet → stealth wallet. L1 breaks the stealth wallet link but not the funding origin. Railgun inserts a shielded UTXO pool in between — ETH enters the shield contract from your funding wallet, and exits to an unlinked stealth pool address. The link is broken cryptographically.
// 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

Once Railgun L2 is activated, add the Base mainnet contract addresses here.
ContractAddressNetwork
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

The agent generates a Semaphore group membership proof before each execution. The proof attests: "an agent that is a member of group X executed a trade at conviction > threshold Y". The group merkle root is registered on-chain via ERC-8004. The proof can be verified by any observer without learning which specific agent or wallet was involved.
// 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

ℹ  When running in demo mode (no backend), the frontend falls back to static demo data. The API is fully implemented in backend/src/routes/index.ts.
GET /api/health Server health check
Response
{ "ok": true, "ts": 1718234567, "version": "0.1.0" }
GET /api/stats Aggregate agent statistics
Response
{ "wallets": 12847, "executions": 3291, "signals": 891, "ethProtected": 847.3 }
GET /api/wallets List stealth wallet pool
Response — array of StealthWallet
[{ "id": 1, "address": "0x4f3a...", "status": "ready", "created_at": 1718234567 }]
Wallet status values
ready · funded and available  |  active · in-flight transaction  |  used · trade completed  |  retired · key destroyed
POST /api/wallets/pool Generate new stealth wallets
Request Body
{ "count": 5 }
Response
{ "generated": 5, "wallets": [ ... ] }
GET /api/trades List recent trade history
Response — array of Trade
[{
  "id": 1, "wallet": "0x4f3a...", "stealth_address": "0x4f3a...",
  "pair": "ETH/BRETT", "direction": "BUY", "amount_eth": "0.420",
  "privacy_layer": "L1", "status": "confirmed", "created_at": 1718234567
}]
GET /api/signal Buy a trading signal · x402 protected
x402 Flow
Without a payment header, this endpoint returns HTTP 402. The caller must pay $0.001 USDC on Base, then retry with the payment signature in the header.
Without payment header — HTTP 402
HTTP/1.1 402 Payment Required
X-Payment-Required: { "scheme": "exact", "network": "base", "amount": "1000", "token": "USDC" }
With payment header — HTTP 200
GET /api/signal
X-Payment: [payment-signature]

{ "pair": "ETH/BRETT", "direction": "BUY", "conviction": 0.87, "price_usdc": "0.001" }
GET /api/vault Current vault balances
{ "totalEth": 5.23, "execPool": "3.138", "gasReserve": "1.569", "infraUsdc": "0.523" }
POST /api/auth/verify Verify MetaMask signature
Request Body
{ "address": "0x4f3a...", "signature": "0x...", "message": "Sign this message..." }
Response
{ "authenticated": true, "address": "0x4f3a...", "shortAddress": "0x4f3a...9b2c" }
POST /api/deploy Deploy agent to Conway VM
Request Body
{ "pair": "ETH/BRETT", "slippage": "0.5", "conviction": "65", "depositEth": "0.1" }
Response
{ "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

SERVERsnapshotFull state on connect: wallets, trades, vault, stats, logs
SERVERwallet_addedNew stealth wallet generated and added to pool
SERVERwallet_countsUpdated wallet pool counts by status
SERVERtrade_addedNew trade execution started
SERVERtrade_confirmedTrade confirmed on-chain with tx hash
SERVERvault_updatedVault balance changed
SERVERsignal_addedNew signal available in x402 market
SERVERstatsUpdated aggregate stats (wallets, executions, signals, ethProtected)
SERVERlogAgent log line for the live terminal

Client → Server events

CLIENTgenerate_walletRequest generation of a new stealth wallet
CLIENTget_statsRequest latest aggregate stats
CLIENTpingKeepalive ping

Authentication

NullAgent uses MetaMask personal_sign for wallet-based authentication. No JWT, no email, no OAuth — your wallet is your identity.

1
Request accounts
Call eth_requestAccounts via window.ethereum. User approves wallet connection in MetaMask.
2
Switch to Base mainnet
Call wallet_switchEthereumChain with chainId 0x2105 (8453). If not added, call wallet_addEthereumChain.
3
Sign auth message
Call personal_sign with a timestamped nonce message. The signature proves ownership of the address without exposing the private key.
4
Verify signature
POST the address, signature, and message to /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

x402 extends the HTTP standard. When a client requests a protected resource without payment, the server responds with 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.
Selling signals
NullAgent's backend uses @x402/express middleware on GET /api/signal. Any agent that requests a signal and can pay $0.001 USDC gets the signal automatically.
Paying for compute
The agent uses @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

Update the table below with your actual signal pricing and categories once you go live.
Signal TypePrice (USDC)EndpointStatus
ETH/BRETT BUY signal$0.001/api/signalLive
ETH/DEGEN BUY signal$0.001/api/signalLive
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

Each agent runs in an isolated microVM with its own Linux environment. The agent has no access to other VMs, no shared filesystem, and no shared network namespace. When the agent needs to renew compute credits, it pays automatically via x402 — the vault's USDC pool handles this without any human intervention.

[FILL] — Conway API endpoints

Fill in the actual Conway API URLs once you have API access.
OperationConway EndpointNotes
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
ℹ  Conway's public API is not yet available. Contact Conway.tech directly for API access. Without a key, the deploy wizard simulates VM creation.

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

ERC-8004 defines three core registries: Identity (maps agent address to operator), Reputation (accumulates verifiable execution history), and Validation (allows other agents to attest to an agent's behavior). NullAgent uses the Identity registry to prove it is a legitimate agent without revealing the operator's primary wallet.

[FILL] — Contract addresses

ContractAddress on BaseStatus
AgentRegistry (Identity)[FILL]Pending deployment
ReputationRegistry[FILL]Pending deployment
ValidationRegistry[FILL]Pending deployment
ℹ  NullAgent is built on the open-source 0xgasless/agent-sdk which provides ERC-8004 and x402 compliant infrastructure for any EVM agent.

Vault Architecture

The vault manages all ETH deposited by the operator and splits it automatically into three purpose-specific pools.

Split configuration

PoolAllocationPurposeAsset
Execution Pool60%ETH used for actual trades through stealth walletsETH
Gas Reserve30%Funds each stealth wallet with gas before every tradeETH
Conway Infra10%Swapped to USDC → used for x402 compute credit paymentsETH → USDC

[FILL] — Vault contract address

Once the vault contract is deployed on Base mainnet, add the address here.
⚠  Vault contract address: [FILL — not yet deployed]. Currently vault logic runs off-chain in the backend.

Deploy on Conway

The recommended production deployment runs the agent inside a Conway Firecracker microVM.

1
Get Conway API key
Contact Conway.tech to get API access. Set CONWAY_API_KEY in your backend .env.
2
Push backend to GitHub
Push the backend/ folder to a GitHub repo. Conway will pull from it when provisioning the VM.
3
Use the deploy wizard
Go to nullagent.xyz, connect wallet, configure, fund, and click Launch. With a valid Conway key, this creates a real microVM.

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
✓  For a fully free stack with zero cost: use Railway for the backend (hobby free tier) + Vercel for the frontend (hobby free tier). Domain management via your existing GoDaddy account.