Live on Base Mainnet
AI agents pay USDC on Base over HTTP 402 and get private VPN access through Sentinel's decentralized node network. One request, one signature, connected. No KYC. No accounts. Everything on-chain.
Only exception: Fedora — its SELinux blocks VPNs and can’t be worked around. macOS, Ubuntu and other mainstream Linux are fine.
Pricing for the x402 Managed Plan. Agents pay upfront for exactly the days they need. USDC on Base, settled automatically by the x402 protocol. On the separate Autonomous path, agents hold P2P and pay nodes at their posted per-GB / per-hour rates.
| Endpoint | Cost |
|---|---|
| /vpn/connect/1day | $0.033 |
| /vpn/connect/7days | $0.233 |
| /vpn/connect/30days | $1.00 |
The x402 path is the focus of this site: one paid HTTP request and the agent is provisioned — zero gas, zero P2P tokens. Separately, a fully autonomous agent can skip x402 entirely and pay nodes directly at their per-gigabyte or per-hour rates. Two distinct paths, same network.
Agent pays USDC via HTTP 402 — live at x402.sentinel.co, from $0.033 for a day to $1.00 for 30 days. We add the agent to our subscription plan, grant gas allowance, and the agent connects with one command. Zero gas on Sentinel.
POST /vpn/connect/30daysGET /nodes for the live list)A separate path that never touches the x402 server. Agent holds P2P tokens, manages its own gas, and pays Sentinel nodes directly at their posted per-GB or per-hour rates. Full autonomy. No intermediary in the payment or the tunnel.
https://x402.sentinel.co with hundreds of active servers in the plan and pricing from $0.033 (1 day) to $1.00 (30 days).
The Autonomous path is fully separate: the agent holds its own P2P and pays nodes directly at per-GB / per-hour rates — it never touches the x402 server.
Both paths use the same Sentinel network and the same WireGuard/V2Ray tunnels.
Live at https://x402.sentinel.co. Agent sends one HTTP request. Payment, settlement, subscription provisioning, and fee granting all happen automatically.
POST /vpn/connect/30days with a Sentinel address in the body. Server returns HTTP 402 Payment Required with a PAYMENT-REQUIRED header containing USDC amount, payTo address, and network (Base).
@x402/fetch reads the 402 response, signs an EIP-3009 transferWithAuthorization for the exact USDC amount, and resends the request with a PAYMENT-SIGNATURE header. Agent's EVM key never leaves the agent.
Our self-hosted facilitator verifies the EIP-3009 signature and settles the USDC transfer on Base. Fully decentralized — no Coinbase, no third party. Payment confirmed in ~2 seconds.
AutomaticServer sends one atomic Sentinel transaction: MsgShareSubscription (adds agent to our plan) + MsgGrantAllowance (fee grant so agent pays zero gas). Agent can now start VPN sessions on any node in the plan, handshaking directly. We never see the tunnel or the traffic.
A separate path for agents that want zero intermediaries. The agent holds its own P2P tokens, picks any node on the network, pays it directly at its posted per-gigabyte or per-hour rate, and manages its own gas. The x402 server plays no part — no operator, no fee-grant, no subscription. One call: connect() from blue-js-sdk/ai-path.
A funded test agent ran this end-to-end with no operator in the loop: it picked a US node, opened on-chain session 45433200, paid 40.19 P2P for a 1 GB V2Ray session, and the SDK confirmed the exit IP 104.252.19.187 through the tunnel — ~67 s start to finish. That 40 P2P is the live network median; the cheapest nodes run ~2 P2P/GB.
The agent market-buys P2P with USDT on MEXC spot by API and withdraws the tokens to its own Sentinel wallet. No human, no UI — the full recipe is below.
MEXC · P2PAgent creates a Sentinel wallet and holds the mnemonic itself — the keys never leave the agent. It pays its own gas, bundled into the session transaction, from the P2P it bought in step zero.
AgentAgent queries the live node list and picks by country, speed, and price — or passes country and lets the SDK auto-select the best one. Every node posts its own per-gigabyte and per-hour rates (median ~40 P2P/GB, from ~2 P2P on the cheapest); the agent pays exactly that, no plan, no tier.
Agent starts the session, pays the node in P2P at its posted rate, then handshakes directly over WireGuard (full system route) or V2Ray (zero-admin SOCKS5 proxy). No middleman in the payment or the tunnel — the x402 server is never contacted. connect() registers cleanup handlers, so a crashed process never strands the tunnel.
No human, no UI. The agent market-buys P2P with USDT on MEXC spot — the P2PUSDT pair is live (maker fee 0%, taker 0.05%, minimum market order 1 USDT) — then withdraws the tokens to its own Sentinel wallet. Three signed HTTP calls, no SDK required. Auth is an X-MEXC-APIKEY header plus an HMAC-SHA256 signature of the query string.
import { createHmac } from 'node:crypto';
const KEY = process.env.MEXC_API_KEY; // dashboard key with Spot Trade + Withdraw scopes
const SECRET = process.env.MEXC_API_SECRET;
async function mexc(method, path, params = {}) {
const qs = new URLSearchParams({ ...params, timestamp: Date.now() }).toString();
const sig = createHmac('sha256', SECRET).update(qs).digest('hex');
const res = await fetch(`https://api.mexc.com${path}?${qs}&signature=${sig}`, {
method, headers: { 'X-MEXC-APIKEY': KEY },
});
return res.json();
}
// 1. Market-buy P2P with USDT — spend exactly 10 USDT (min 1, max 100k per order)
await mexc('POST', '/api/v3/order', {
symbol: 'P2PUSDT', side: 'BUY', type: 'MARKET', quoteOrderQty: '10',
});
// 2. Read the filled P2P balance
const { balances } = await mexc('GET', '/api/v3/account');
const amount = balances.find(b => b.asset === 'P2P').free;
// 3. Withdraw to the agent's own Sentinel wallet (wallet.address = 'sent1...')
const { networkList } = (await mexc('GET', '/api/v3/capital/config/getall'))
.find(c => c.coin === 'P2P');
await mexc('POST', '/api/v3/capital/withdraw', {
coin: 'P2P', netWork: networkList[0].netWork,
address: wallet.address, amount,
});
// P2P lands on-chain in the agent's wallet — gas + direct node payments covered.
// At ~$0.00008/P2P, 10 USDT buys ~125,000 P2P: years of gas, weeks of bandwidth.
One-time human setup: create the MEXC API key with Spot Trade + Withdraw permissions and allowlist the agent's sent1... withdrawal address. From then on the agent is self-funding — it can top up its own P2P whenever the balance runs low.
Let the x402 server handle everything with @x402/fetch, or go fully autonomous with blue-js-sdk — both live now. Both give you private internet access through the same Sentinel network.
npm install @x402/fetch @x402/evm blue-js-sdk viem
// You need ONE thing: an EVM private key with >= $1.00 USDC on Base (chainId 8453).
// No ETH needed — EIP-3009 is gasless from the agent side (facilitator pays Base gas).
// No P2P needed — the operator fee-grants Sentinel gas after provisioning.
//
// How to fund a fresh EVM key with USDC on Base:
// * Coinbase / any CEX with Base withdrawals: withdraw USDC, pick "Base" as network.
// * Canonical bridge: bridge.base.org (ETH L1 → Base, ~10 min).
// * Third-party bridges: Across, Stargate (faster, small fee).
// * Already on Base? Swap ETH → USDC on Uniswap or Aerodrome.
//
// USDC contract on Base: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 (6 decimals, native).
// Verify balance:
const bal = await fetch(`https://api.basescan.org/api?module=account&action=tokenbalance&contractaddress=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913&address=${addr}`);
import { x402Client, wrapFetchWithPayment } from '@x402/fetch';
import { ExactEvmScheme } from '@x402/evm/exact/client';
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { base } from 'viem/chains';
import { setup, createWallet, connect, disconnect, rpcQueryNodesForPlan, createRpcQueryClientWithFallback } from 'blue-js-sdk/ai-path';
// Optional: pre-fetch V2Ray (no admin). connect({ protocol: 'v2ray' }) auto-fetches it anyway.
await setup();
// Create a Sentinel wallet (one-time — save the mnemonic!)
const wallet = await createWallet();
// wallet.address = 'sent1...' wallet.mnemonic = '12 words'
// Set up x402 payment client with your EVM key
const account = privateKeyToAccount(process.env.EVM_KEY);
const viemClient = createWalletClient({
account, chain: base, transport: http('https://mainnet.base.org'),
});
const scheme = new ExactEvmScheme({
address: account.address,
signTypedData: (msg) => viemClient.signTypedData(msg),
});
const client = new x402Client();
client.register('eip155:8453', scheme);
const paidFetch = wrapFetchWithPayment(fetch, client);
// Request VPN access — 402 → auto-sign → facilitator settles → provisioned
const res = await paidFetch('https://x402.sentinel.co/vpn/connect/30days', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sentinelAddr: wallet.address, country: 'DE' }), // country optional — validated BEFORE payment
});
const provision = await res.json();
// {
// provisioned: true,
// subscriptionId: 1192288,
// planId: 42,
// feeGranter: 'sent12e03w...', ← operator pays all gas
// nodeAddress: 'sentnode1...', ← node matching country — or choose from nodes[]
// nodeCountry: 'Germany', ← verified location of nodeAddress
// nodes: ['sentnode1a...', ...], ← full list of plan nodes
// sentinelTxHash: 'F1FE3C...',
// expiresAt: '2026-05-14T...',
// }
// connect(), createWallet(), rpcQueryNodesForPlan(), createRpcQueryClientWithFallback()
// already imported above from 'blue-js-sdk/ai-path'
// Resolve node — server returns one, but fall back to a live plan query if empty
let nodeAddress = provision.nodeAddress;
if (!nodeAddress) {
const rpc = await createRpcQueryClientWithFallback();
const { items } = await rpcQueryNodesForPlan(rpc, provision.planId, { status: 1, limit: 50 });
nodeAddress = items[0]?.address;
}
// Connect using the provisioned subscription + fee grant
// Agent has 0 P2P tokens — operator covers all gas
const vpn = await connect({
mnemonic: wallet.mnemonic, // from Step 1
nodeAddress, // from provision or fallback query
subscriptionId: String(provision.subscriptionId), // from Step 2
feeGranter: provision.feeGranter, // operator pays gas
protocol: 'v2ray', // zero admin, zero manual install (Windows)
});
// All traffic now routes through encrypted P2P tunnel
console.log(`VPN active: ${vpn.ip} via ${vpn.protocol}`);
// { connected: true, ip: '104.234.x.x', protocol: 'v2ray',
// sessionId: '39269345', nodeAddress: 'sentnode1...' }
// Disconnect also uses fee grant — 0 tokens needed
await disconnect();
// Payment (Steps 0-2 above) is identical. macOS & Linux skip the JS connect()
// and use Sentinel's native CLI instead. Unlike Windows, the CLI does NOT bundle a tunnel:
// it shells out to wg-quick, so install wireguard-tools first (apt/brew/pacman). Four commands.
// CLI: https://github.com/sentinel-official/sentinel-dvpncli
// Install once (Go 1.24+):
// go install github.com/sentinel-official/sentinel-dvpncli@latest
// 1. Import the SAME wallet whose sentinelAddr (sent1...) you sent to x402.
// keys add is interactive: prompt 1 = mnemonic, prompt 2 = BIP-39 passphrase.
// printf feeds both (mnemonic line + blank line = default empty passphrase).
printf '%s\n\n' "$AGENT_MNEMONIC" | sentinel-dvpncli keys add agent --keyring.backend test
// 2. Start the session against the subscription x402 shared with you.
// --tx.fee-granter-addr = provision.feeGranter → operator pays the P2P gas (you pay 0).
sentinel-dvpncli tx session-start "$NODE_ADDRESS" \
--subscription-id "$SUBSCRIPTION_ID" \
--tx.fee-granter-addr "$FEE_GRANTER" \
--tx.from-name agent --keyring.backend test \
--rpc.chain-id sentinelhub-2 --output-format json
// 3. Get the session id you just created (session-start does not print it cleanly).
// AGENT_ADDR = the same sent1... you sent to x402. Newest entry is your session.
SESSION_ID=$(sentinel-dvpncli query sessions \
--account-addr "$AGENT_ADDR" --subscription-id "$SUBSCRIPTION_ID" \
--output-format json | jq '.result[-1].id // .response[-1].id')
// 4. Bring the tunnel up (takes the SESSION id, not the subscription id).
// WireGuard may need sudo to create the interface, same as any wg client.
sentinel-dvpncli connect "$SESSION_ID"
// → tunnel up. Tear down with: sentinel-dvpncli tx session-cancel "$SESSION_ID"
//
// Defaults: --rpc.addrs https://rpc.sentinel.co:443, --rpc.chain-id sentinelhub-2.
// Only exception across all platforms: Fedora (SELinux blocks VPN interfaces).
// 1. Agent sends POST /vpn/connect/30days
// → Server returns HTTP 402 + PAYMENT-REQUIRED header
//
// 2. @x402/fetch reads the 402 response:
// { scheme: 'exact', network: 'eip155:8453',
// amount: '1000000', asset: '0x8335...USDC',
// payTo: '0x605C...85B' }
//
// 3. Signs EIP-3009 transferWithAuthorization
// (USDC native, no approve needed)
//
// 4. Resends request with PAYMENT-SIGNATURE header
// → Facilitator verifies + settles USDC on Base
// → Server provisions agent on Sentinel chain
// → Returns { subscriptionId, feeGranter, expiresAt }
//
// 5. Agent calls connect({ mnemonic, nodeAddress: provision.nodeAddress, subscriptionId, feeGranter })
// → SDK validates fee grant on-chain (RPC, ~250ms)
// → Broadcasts MsgStartSession via broadcastWithFeeGrant
// → Handshakes with VPN node (WireGuard/V2Ray)
// → Tunnel established, IP changed
try {
const vpn = await connect({
mnemonic, nodeAddress, subscriptionId, feeGranter,
});
} catch (err) {
if (err.code === 'FEE_GRANT_NOT_FOUND') {
// No grant on-chain — request provisioning from x402 server
} else if (err.code === 'FEE_GRANT_EXPIRED') {
// Grant expired — re-purchase via /vpn/connect/*
} else if (err.code === 'FEE_GRANT_EXHAUSTED') {
// spend_limit too low (<20k udvpn) — re-provision
}
// err.nextAction tells the agent what to do programmatically
// err.details has { granter, grantee, ... } for debugging
}
npm install blue-js-sdk
import { connect, disconnect, getBalance } from 'blue-js-sdk/ai-path';
// Agent already holds P2P (bought on MEXC by API — see Autonomous Path above).
// getBalance → { address, udvpn, p2p, sufficient }
const bal = await getBalance(process.env.SENTINEL_MNEMONIC);
// Pick any Sentinel node and pay it directly in P2P. No x402, no operator.
// protocol: 'v2ray' = zero admin on Windows; the binary auto-downloads (~70% of nodes).
// Omit country to let the SDK pick the best node anywhere; pass nodeAddress to pin one.
const vpn = await connect({
mnemonic: process.env.SENTINEL_MNEMONIC, // agent holds its own keys
country: 'US', // optional — or { nodeAddress: 'sentnode1...' }
protocol: 'v2ray', // 'v2ray' (zero admin) or 'wireguard' (full route, admin once)
gigabytes: 1, // default 1 — pay per-GB; or { preferHourly: true, hours: 24 }
});
// vpn → {
// sessionId: '45433200', // on-chain session, paid by the agent
// protocol: 'v2ray',
// nodeAddress: 'sentnode1qjy6h...',
// ip: '104.252.19.187', // exit IP, verified THROUGH the tunnel by the SDK
// socksPort: 1080, // V2Ray: route traffic through this SOCKS5 port
// }
// ...do work over the tunnel...
await disconnect(); // soft: session preserved on-chain, unused GB stays available
WireGuard captures all system traffic, so your public IP changes the moment it connects. V2Ray is a userspace SOCKS5 proxy — only traffic you point at vpn.socksPort goes through the node. A plain fetch() on the OS default route still shows your real IP, and that is expected, not a failure. The SDK already proves routing for you in vpn.ip (checked through the tunnel). Use vpn.socksPort with a SOCKS agent, or pick protocol: 'wireguard' for transparent system-wide routing.
// The agent controls the full lifecycle — no x402, no operator:
//
// 1. Wallet: Agent creates & holds its Sentinel keys (mnemonic)
// 2. Tokens: Agent holds P2P (bought on MEXC by API — step zero above)
// 3. Gas: Agent pays its own Sentinel gas (bundled into the session TX)
// 4. Node: Agent queries nodes, picks by country / speed / price
// 5. Session: Agent starts & ends sessions, paying the node at its
// posted per-GB or per-hour rate — ~40 P2P buys 1 GB
// at the live median (cheapest nodes ~2 P2P/GB)
// 6. Tunnel: Agent handshakes directly with the node (WireGuard / V2Ray)
//
// connect() registers cleanup handlers automatically — on crash or exit it
// tears the tunnel down so a dropped process never strands your connection.
//
// Zero middlemen. Zero trust required. Full autonomy. Ships in blue-js-sdk today.
const res = await fetch('https://x402.sentinel.co/pricing');
const pricing = await res.json();
// {
// protocol: 'x402', network: 'eip155:8453', asset: 'USDC',
// payTo: '0xCC689D...',
// tiers: {
// '1day': { price: '$0.033', endpoint: '/vpn/connect/1day' },
// '7days': { price: '$0.233', endpoint: '/vpn/connect/7days' },
// '30days':{ price: '$1.00', endpoint: '/vpn/connect/30days' }
// },
// sentinelNetwork: 'sentinel', countries: '70+',
// protocols: ['wireguard', 'v2ray']
// }
// Minimum USDC needed: $0.033 for 1 day tier
Both flows are designed so trust is unnecessary. Your keys, your tunnel, your traffic. The only difference is who manages the gas.
Autonomous: Sentinel mnemonic stays local. Managed: EIP-3009 signed locally — facilitator only receives the signature. In both flows, all private keys remain on the agent.
WireGuard/V2Ray handshake is direct between the agent and the VPN node in both flows. We never see the tunnel credentials, encryption keys, or traffic. Ever.
Managed Plan uses our own facilitator — no Coinbase, no third party. Verifies EIP-3009 signatures and settles USDC on Base. Fully auditable on-chain. Open source.
Autonomous: Agent pays nodes, no middleman at all. Managed: Trust us to provision the subscription, but the tunnel is still direct and we can't see traffic. Agent can switch flows at any time.
USDC payments settled via EIP-3009 transferWithAuthorization — no custom contract, no approve step. Native USDC on Base.
All /vpn/connect/* routes return HTTP 402 until USDC payment is settled. Free endpoints require no payment.
POST /vpn/connect/1day $0.033 USDC
POST /vpn/connect/7days $0.233 USDC
POST /vpn/connect/30days $1.00 USDC
Body: { "sentinelAddr": "sent1...", "country": "DE" } country optional — validated before payment
Without payment → 402 + PAYMENT-REQUIRED header
With payment → 200 + provisioning result
GET /pricing Tiers, network, asset info
GET /nodes Plan nodes + live geo (country, city, protocol, byCountry)
GET /health Server status + uptime
GET /agent/:sentinelAddr Check subscription status
{
"x402Version": 2,
"accepts": [{
"scheme": "exact",
"network": "eip155:8453",
"amount": "33000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0x605C...85B",
"maxTimeoutSeconds": 300
}]
}
Everything in one script. Pay USDC on Base, get provisioned, connect to VPN, verify IP changed, disconnect. Zero P2P tokens needed.
// npm install @x402/fetch @x402/evm blue-js-sdk viem
import { x402Client, wrapFetchWithPayment } from '@x402/fetch';
import { ExactEvmScheme } from '@x402/evm/exact/client';
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { base } from 'viem/chains';
import {
setup, createWallet, connect, disconnect,
rpcQueryNodesForPlan, createRpcQueryClientWithFallback,
} from 'blue-js-sdk/ai-path';
// ── Step 0 (optional): pre-fetch V2Ray (no admin). connect({ protocol: 'v2ray' }) auto-fetches it anyway ──
await setup();
// ── Step 1: Create Sentinel wallet (one-time) ──
const wallet = await createWallet();
// SAVE wallet.mnemonic — this is the agent's VPN identity
// ── Step 2: Set up x402 payment client ──
const account = privateKeyToAccount(process.env.EVM_KEY);
const viemClient = createWalletClient({
account, chain: base, transport: http('https://mainnet.base.org'),
});
const client = new x402Client();
client.register('eip155:8453', new ExactEvmScheme({
address: account.address,
signTypedData: (msg) => viemClient.signTypedData(msg),
}));
const paidFetch = wrapFetchWithPayment(fetch, client);
// ── Step 3: Pay USDC on Base via x402 ──
const res = await paidFetch('https://x402.sentinel.co/vpn/connect/30days', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sentinelAddr: wallet.address, country: 'DE' }), // country optional
});
const provision = await res.json();
// provision.nodeAddress + provision.subscriptionId + provision.feeGranter → all you need
// ── Step 4: Resolve node — use provision.nodeAddress, fall back to live plan query ──
let nodeAddress = provision.nodeAddress;
if (!nodeAddress) {
const rpc = await createRpcQueryClientWithFallback();
const { items } = await rpcQueryNodesForPlan(rpc, provision.planId, { status: 1, limit: 50 });
nodeAddress = items[0]?.address;
}
// ── Step 5: Connect to VPN (0 gas, 0 P2P) ──
// This connect() is the Windows path. On macOS/Linux use the native CLI instead
// (install wireguard-tools first — the CLI shells out to wg-quick, it bundles no tunnel):
// printf '%s\n\n' "$MNEMONIC" | sentinel-dvpncli keys add agent --keyring.backend test
// sentinel-dvpncli tx session-start <nodeAddress> --subscription-id <id> --tx.fee-granter-addr <feeGranter> --tx.from-name agent --keyring.backend test
// sessionId=$(sentinel-dvpncli query sessions --account-addr <sent1...> --subscription-id <id> --output-format json | jq '.result[-1].id')
// sentinel-dvpncli connect <sessionId> (needs wg-quick on PATH: install wireguard-tools + sudo)
const vpn = await connect({
mnemonic: wallet.mnemonic,
nodeAddress,
subscriptionId: String(provision.subscriptionId),
feeGranter: provision.feeGranter,
protocol: 'v2ray', // zero admin, zero manual install (Windows)
});
console.log(`VPN active: ${vpn.ip} via ${vpn.protocol}`);
// All traffic now through encrypted P2P tunnel
// ── Step 6: Do private work ──
const data = await fetch('https://api.example.com/sensitive');
// ── Step 7: Disconnect (also uses fee grant) ──
await disconnect();
{
"provisioned": true,
"sentinelAddr": "sent1abc...",
"days": 30,
"subscriptionId": 1192288, ← pass to connect()
"planId": 42,
"feeGranter": "sent12e03...", ← pass to connect()
"nodeAddress": "sentnode1...", ← node selected (matches country if requested) — pass to connect()
"nodeCountry": "Germany", ← verified location of nodeAddress
"nodes": ["sentnode1a...", "sentnode1b..."], ← full list — agent can choose
"sentinelTxHash": "2C1CFE...",
"expiresAt": "2026-05-14T...",
"operatorAddress": "0xCC689D...",
"instructions": "import { setup, connect } from 'blue-js-sdk/ai-path'; /* setup() is optional on Windows — connect({ protocol: 'v2ray' }) auto-fetches the V2Ray binary (no admin, ~70% of nodes) */ await connect({ mnemonic, nodeAddress, subscriptionId, feeGranter, protocol: 'v2ray' })"
}
When x402 provisions an agent, it creates an AllowedMsgAllowance fee grant on the Sentinel chain. The operator pays gas for the agent's session operations.
MsgStartSessionRequest (connect), MsgCancelSessionRequest (disconnect), MsgUpdateSessionRequest (keep-alive). The agent broadcasts these TXs with feeGranter set — chain deducts gas from operator.
5 P2P (5,000,000 udvpn) per agent — enough for ~25 session operations. If exhausted, the SDK throws FEE_GRANT_EXHAUSTED with nextAction: 'request_fee_grant_renewal'.
Grant expires 24 hours after the purchased VPN period. If expired, the session ends naturally when the subscription allocation expires. No tokens are lost.
Before connecting, the SDK validates the fee grant via RPC (~250ms): checks existence, expiration, spend limit (≥20,000 udvpn), and allowed messages. Fails fast with typed errors if invalid.
The landing page covers the essentials. For production integration, error handling edge cases, and architectural details:
Complete operator-provisioned mode documentation: fee grant pre-check (5-step validation), crash recovery, auto-reconnect dispatch, fee-granted disconnect. The primary reference for x402 integration.
Every technical detail of the connection lifecycle: RPC protobuf queries, LCD failover, broadcastWithFeeGrant internals, credential persistence, session allocation tracking.
Visual decision tree for connection mode selection. Covers direct pay, plan subscription, operator-provisioned (fee-granted), and error recovery paths.
92+ failure patterns with prevention rules. Includes fee grant failures (W4–W7), crash persistence rules, reconnect mode dispatch, and spend limit validation.
All operator-specific variables are in .env. Change these to deploy your own instance with your own wallet, plan, and pricing.
# ─── Base Chain (EVM) ───
# Your EVM wallet address — where USDC payments are settled
OPERATOR_ADDRESS=0xYourWalletAddress
# Base mainnet (eip155:8453) or Sepolia testnet (eip155:84532)
BASE_NETWORK=eip155:8453
# Self-hosted facilitator EVM private key
# This wallet needs ETH on Base for gas (~$0.001 per settlement)
# The facilitator verifies EIP-3009 signatures and settles USDC
FACILITATOR_PRIVATE_KEY=0xYourFacilitatorKey
FACILITATOR_PORT=4021
# ─── Sentinel Chain ───
# Operator mnemonic — this wallet must own the plan and have P2P for gas
# Used to: share subscriptions, create fee grants, manage subscription pool
SENTINEL_OPERATOR_MNEMONIC=your twelve word mnemonic phrase here
# Your Sentinel plan ID — create one at sentinel.co or via CLI
# The plan determines which nodes agents can connect to
SENTINEL_PLAN_ID=42
# RPC and LCD endpoints (defaults work for most setups)
SENTINEL_RPC_URL=https://rpc.sentinel.co:443
SENTINEL_LCD_URL=https://lcd.sentinel.co
# ─── Server ───
PORT=4020
EVM wallet on Base. This is where USDC payments from agents are settled by the facilitator. Must match the payTo field in x402 payment middleware. Fund with ETH for gas if also used as facilitator.
EVM private key for the self-hosted facilitator. This wallet verifies EIP-3009 transferWithAuthorization signatures and settles USDC on-chain. Needs ~0.001 ETH per settlement on Base. Can be the same as OPERATOR_ADDRESS.
12-word BIP39 mnemonic for the Sentinel chain operator wallet. Must own the plan (SENTINEL_PLAN_ID) and hold P2P tokens for gas. Used to create MsgShareSubscription + MsgGrantAllowance for each agent. ~0.01 P2P per provisioning TX.
Your Sentinel plan ID. Determines which VPN nodes agents can access. Create a plan via sentinelcli tx plan create or through the Plan Manager. Link nodes to the plan, then agents connect to those nodes.
// Bytes to allocate per agent — how much data each agent can use
const SHARE_BYTES = 1_000_000_000; // 1 GB per agent
// Fee grant budget per agent — covers ~25 session starts/stops
const FEE_GRANT_SPEND_LIMIT = 5_000_000; // 5 P2P (udvpn)
// Allowed messages for fee grant — what the agent can broadcast
const FEE_GRANT_ALLOWED_MESSAGES = [
'/sentinel.subscription.v3.MsgStartSessionRequest',
'/sentinel.session.v3.MsgCancelSessionRequest',
'/sentinel.session.v3.MsgUpdateSessionRequest',
'/sentinel.node.v3.MsgStartSessionRequest',
];
// Subscription pool: chain limits 8 allocations per subscription.
// Server auto-creates new subscriptions when pool is exhausted.
// At 1000 agents = ~125 subscriptions. Managed automatically.