Skip to content

Enricrypto/tollgate

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tollgate

Pay-per-use WireGuard VPN gateway for autonomous agents. No subscriptions, no pre-auth — each access is gated by a USDC micropayment verified on Base.

Flow: agent hits /vpn → gets 402 → pays 0.01 USDC on Base Sepolia via Openfort → retries with tx hash → receives WireGuard peer config valid for 1 hour.


Use cases

Tollgate is built for programmatic, pay-per-use VPN access — no accounts, no subscriptions, no human in the loop. The x402 protocol makes it native to any agent or automated system that can send a USDC transaction.

Use case Why x402 fits
Autonomous AI agents An agent hits a geo-restricted API, receives a 402, pays, and retries — entirely without human intervention
CI/CD pipelines Build jobs pay only for the minutes they need VPN access; no standing subscription tied to infrastructure
Web scraping / data collection Each agent session gets a fresh peer IP; pay per crawl run rather than managing a pool of proxies
Multi-agent systems Many agents coordinate independently — each pays for its own session with no shared credentials or API keys to rotate
Privacy-sensitive upstream calls Agents mask the operator's origin IP when calling third-party APIs, without exposing a persistent VPN identity
IoT / embedded devices Low-cost devices that need occasional secure tunneling; per-use micropayments are more economical than monthly plans
Temporary developer access A dev needs VPN access for one hour during an incident — pays $0.01 instead of provisioning a full subscription

How it works

Payment protocol (x402)

Tollgate implements the x402 payment protocol. When an agent hits GET /vpn without a payment header, the server returns a 402 Payment Required with a JSON body describing exactly what to pay, to whom, and on which network. The agent pays, then retries with X-Payment: <txHash>. The server verifies on-chain before provisioning access.

Agent wallet (Openfort)

The agent script uses Openfort backend wallets to send USDC programmatically — no private keys in env vars, no manual signing. Openfort handles key custody, gas sponsorship, and ERC-4337 account abstraction under the hood.

On-chain verification

The server uses Alchemy to fetch the transaction receipt from Base Sepolia and parses the ERC-20 Transfer event logs to verify:

  • Transfer was sent to PAYMENT_RECIPIENT_ADDRESS
  • Amount ≥ PAYMENT_AMOUNT_USDC
  • USDC contract matches USDC_CONTRACT_ADDRESS

Because Openfort uses a bundler/paymaster, there can be a few seconds of indexing lag between when the UserOperation is confirmed and when Alchemy's RPC node returns the receipt. The server retries up to 4 times with 5s intervals before rejecting.

Replay protection

Each tx hash is stored in Redis with a 7-day TTL. Submitting the same hash twice returns a 402.

WireGuard provisioning

On successful payment, the server generates a unique keypair via the wg CLI, allocates an IP from 10.0.0.10–10.0.0.209, adds the peer to the wg0 interface, stores the session in Redis, and returns the client config string.


Network

Currently running on Base Sepolia (testnet) — chainId 84532, USDC 0x036CbD53842c5426634e7929541eC2318f3dCF7e.

Mainnet constants are commented in scripts/agent-sim.ts and src/services/payment.ts — swap them in when a live Openfort key is available.


Stack

  • Node.js (ESM, LTS) · Express · TypeScript · tsx
  • ethers.js v6 · ioredis · pino · express-rate-limit
  • WireGuard (managed via wg CLI, no shell — execFileSync)
  • Openfort (@openfort/openfort-node) — backend wallet for agent payments
  • Base Sepolia (testnet) · USDC 0x036CbD53842c5426634e7929541eC2318f3dCF7e

Quick start (local dev)

Prerequisites

  • Node.js 20+
  • Redis running locally (brew services start redis on macOS)
  • An Alchemy API key (Base Sepolia)
  • A Base wallet address to receive payments
  • An Openfort account with a backend wallet configured

Install

npm install

Configure

cp .env.example .env

Fill in .env. Minimum required to start:

PORT=3002
REDIS_URL=redis://127.0.0.1:6379
ALCHEMY_API_KEY=your_key_here
PAYMENT_RECIPIENT_ADDRESS=0xYourWalletAddress
BASE_CHAIN_ID=84532
USDC_CONTRACT_ADDRESS=0x036CbD53842c5426634e7929541eC2318f3dCF7e

Run

npm start          # production entry point (src/main.ts)
npm run dev        # auto-restarts on file changes
npm run typecheck  # type check without running

Test

npm test           # runs all tests (uses PAYMENT_STUB and WG_STUB — no Alchemy or wg needed)

Agent simulation (end-to-end test)

Simulates an autonomous agent paying USDC via Openfort and receiving a WireGuard config.

Prerequisites

  • Tollgate server running (npm start)
  • An Openfort backend wallet account funded with:

Set up Openfort backend wallet

Create a backend wallet account (one-time setup):

node --input-type=module << 'EOF'
import 'dotenv/config';
import Openfort from '@openfort/openfort-node';
const of = new Openfort(process.env.OPENFORT_SECRET_KEY, { walletSecret: process.env.OPENFORT_WALLET_SECRET });
const account = await of.accounts.evm.backend.create({ chainId: 84532 });
console.log('OPENFORT_ACCOUNT_ID=' + account.id);
console.log('Wallet address:      ' + account.address);
EOF

Fund the printed wallet address via the faucets above, then add the account ID to .env.

Configure

Add to .env:

OPENFORT_SECRET_KEY=sk_...
OPENFORT_WALLET_SECRET=...
OPENFORT_ACCOUNT_ID=acc_...
TOLLGATE_URL=http://your-server:3002

Run

node --import tsx/esm scripts/agent-sim.ts

Expected output:

[1] GET http://your-server:3002/vpn
[1] 402 received — payment required: 0.01 USDC to 0x...

[2] Checking USDC contract registration in Openfort...
[2] USDC already registered: con_...

[3] Sending 0.01 USDC → 0x... via Openfort...
[3] Confirmed — tx: 0x...

[4] GET http://your-server:3002/vpn with X-Payment: 0x...
[4] 200 received — WireGuard config provisioned
    Session ID : <uuid>
    Allowed IP : 10.0.0.x
    Expires in : 3600s

[5] Tollgate access provisioned.
    WireGuard config : /tmp/tollgate-client.conf
    Receipt          : /tmp/tollgate-receipt.json

    Connect with: sudo wg-quick up /tmp/tollgate-client.conf

── Openfort ────────────────────────────────────────────────────────────
    Intent ID      : tin_...
    Account        : acc_...

Connect the tunnel

sudo wg-quick up /tmp/tollgate-client.conf

# Disconnect
sudo wg-quick down /tmp/tollgate-client.conf

Expire a session manually

node --import tsx/esm scripts/expire-test.ts <sessionId>

Production deployment (Ubuntu VPS)

Install dependencies

sudo apt update && sudo apt install wireguard redis-server -y
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
npm install -g pm2

WireGuard server setup

# Generate server keypair
wg genkey | tee /etc/wireguard/server_private.key | wg pubkey > /etc/wireguard/server_public.key

# Create interface config (replace YOUR_PRIVATE_KEY and eth0 if needed)
cat > /etc/wireguard/wg0.conf << EOF
[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = YOUR_PRIVATE_KEY

PostUp   = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
EOF

# Enable IP forwarding
echo "net.ipv4.ip_forward=1" | tee -a /etc/sysctl.conf && sysctl -p

# Start WireGuard
systemctl enable wg-quick@wg0 && systemctl start wg-quick@wg0

Deploy and run

# Copy project to server
scp -r /path/to/tollgate user@your-server:~/tollgate

# On the server
cd ~/tollgate
cp .env.example .env && nano .env   # fill in all values
npm install

pm2 start src/main.ts --name tollgate --cwd ~/tollgate --interpreter tsx
pm2 save
pm2 startup

Firewall

ufw allow 22/tcp
ufw allow 3002/tcp
ufw allow 51820/udp
ufw enable

Production security checklist

Before going live, make sure these are set in your production .env:

Setting Production value Why
STRICT_REPLAY_CHECK true Prevents replay attacks if Redis restarts — fails closed (503) instead of open
PUBLIC_HOST https://your-domain.com Ensures the 402 resource URL is correct behind a reverse proxy
PAYMENT_AMOUNT_USDC your chosen price Default 0.01 — adjust for mainnet economics

Additional hardening in place out of the box:

  • Rate limiting/vpn is limited to 20 requests per IP per minute via express-rate-limit. Tune max in src/server.ts for your expected traffic.
  • No shell execution — all wg CLI calls use execFileSync with explicit argument arrays, eliminating shell injection surface.
  • API key redaction — Alchemy RPC errors are sanitised before being returned to clients; the API key is never exposed in 402 responses.
  • Peer lifecycle safety — if Redis is unavailable when storing a new session, the WireGuard peer is rolled back (wg set … remove) before the 500 is returned, preventing zombie peers.

Architecture

Request flow

flowchart TD
    A([Agent]) --> B[GET /vpn]
    B --> C{Rate limit\nexceeded?}
    C -->|Yes| D[429 Too Many Requests]
    C -->|No| E{X-Payment\nheader?}
    E -->|Missing| F[402 + payment JSON\nx402 middleware]
    E -->|Present txHash| G[payment.ts\nAlchemy → Base Sepolia\nretry up to 4×5s]
    G -->|Invalid| H[402 + reason]
    G -->|Valid| I[replay.ts → Redis\nreplay check + mark used]
    I -->|Duplicate TX| J[402 Already used]
    I -->|Fresh TX| K[wireguard.ts\ngenkey · allocateIP · addPeer]
    K --> L[session.ts → Redis\nTTL = SESSION_TTL_SECONDS]
    L --> M[200 + WireGuard config]
Loading

Security layers

flowchart LR
    subgraph L1[Layer 1 — Network]
        A[UFW Firewall\nports 22 · 3002 · 51820]
    end
    subgraph L2[Layer 2 — Rate Limiting]
        B[express-rate-limit\n20 req / IP / min]
    end
    subgraph L3[Layer 3 — Payment Gate]
        C[x402 middleware\n402 challenge]
    end
    subgraph L4[Layer 4 — On-chain Verification]
        D[Alchemy RPC\nTransfer event · amount · recipient]
    end
    subgraph L5[Layer 5 — Replay Protection]
        E[Redis\nTX hash · 7-day TTL]
    end
    subgraph L6[Layer 6 — Peer Safety]
        F[Rollback on Redis failure\nno zombie peers]
    end
    A --> B --> C --> D --> E --> F
Loading

Environment variables

Variable Description
BASE_CHAIN_ID Chain ID for payment verification. Default 84532 (Base Sepolia). Set to 8453 for mainnet.
USDC_CONTRACT_ADDRESS USDC contract address for the configured chain. Default 0x036CbD... (Base Sepolia).
PAYMENT_RECIPIENT_ADDRESS Wallet address that receives USDC payments.
PAYMENT_AMOUNT_USDC Price per access in USDC. Default 0.01.
SESSION_TTL_SECONDS How long a provisioned WireGuard peer stays active. Default 3600 (1 hour).
STRICT_REPLAY_CHECK true = fail closed if Redis is down (prevents replay attacks). Default false. Set to true in production.
PUBLIC_HOST Set to your public URL when behind a reverse proxy — used in the 402 resource field.

Next steps

flowchart LR
    D1([Day 1 ✓\nCore Gateway]) --> D2[Day 2\nSession Lifecycle]
    D2 --> a[Session expiry daemon]
    D2 --> b[WireGuard peer cleanup]
    D2 --> c[Rate limiting per wallet]
    D2 --> d[IP pool hardening]
    D2 --> e[Status dashboard]
    D2 --> D3[Day 3\nMainnet & Scale]
    D3 --> f[Swap to Base mainnet]
    D3 --> g[Live Openfort key]
    D3 --> h[WireGuard concurrency queue]
    D3 --> i[Reverse proxy + TLS]
Loading

About

Pay-per-use WireGuard VPN gateway for autonomous agents. No subscriptions, no pre-auth — each access is gated by a USDC micropayment verified on Base.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors