@agenttoll/sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # AgentToll SDK
2
+
3
+ One-line micropayment integration for AI agents.
4
+
5
+ ## Installation
6
+
7
+ ### From npm (recommended)
8
+ ```bash
9
+ npm install @agenttoll/sdk
10
+ ```
11
+
12
+ ### From GitHub (if npm is unavailable)
13
+ ```bash
14
+ npm install github:agenttoll/sdk
15
+ ```
16
+
17
+ ### Direct download
18
+ Copy [tollbooth.js](./tollbooth.js) into your project.
19
+
20
+ ## Usage
21
+
22
+ ### Express
23
+ ```javascript
24
+ import tollbooth from '@agenttoll/sdk';
25
+ // or if installed locally:
26
+ // import tollbooth from './tollbooth.js';
27
+
28
+ app.use(tollbooth('pk_live_xxx'));
29
+ ```
30
+
31
+ ### Cloudflare Workers
32
+ ```javascript
33
+ import { tollgate } from '@agenttoll/sdk/edge';
34
+
35
+ export default tollgate('pk_live_xxx', { amount: 0.005 });
36
+ ```
37
+
38
+ ### Agent Tool (for AI agents)
39
+ ```javascript
40
+ import { payTollTool } from '@agenttoll/sdk/tool';
41
+
42
+ // Add to your agent's tools
43
+ tools: [payTollTool]
44
+ ```
45
+
46
+ ## API Reference
47
+
48
+ See [https://agenttoll.io/docs](https://agenttoll.io/docs) for full documentation.
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "pay_toll",
3
+ "description": "Pay a micropayment toll to access a paywalled resource. Use this when you encounter an HTTP 402 Payment Required response. The tool handles the payment via Solana USDC and returns an access token for the resource.",
4
+ "strict": true,
5
+ "parameters": {
6
+ "type": "object",
7
+ "required": ["url", "amount"],
8
+ "properties": {
9
+ "url": {
10
+ "type": "string",
11
+ "description": "The URL of the resource that requires payment (from the 402 response)"
12
+ },
13
+ "amount": {
14
+ "type": "number",
15
+ "description": "The payment amount in USDC (from the 402 response x402 headers or body)"
16
+ },
17
+ "max_amount": {
18
+ "type": "number",
19
+ "description": "Maximum amount you're willing to pay. If the requested amount exceeds this, the tool will not proceed. Default: 0.05 USDC"
20
+ },
21
+ "reason": {
22
+ "type": "string",
23
+ "description": "Why accessing this resource is valuable for your current task (helps with logging and human oversight)"
24
+ }
25
+ },
26
+ "additionalProperties": false
27
+ }
28
+ }
@@ -0,0 +1,181 @@
1
+ /**
2
+ * AgentToll Tool: pay_toll
3
+ *
4
+ * This tool allows AgentToll/OpenClaw agents to autonomously pay
5
+ * micropayment tolls when they hit 402 Payment Required responses.
6
+ *
7
+ * Installation:
8
+ * Add this tool to your AgentToll's tool configuration
9
+ *
10
+ * Usage in agent prompts:
11
+ * "If you encounter a 402 Payment Required error on a useful resource,
12
+ * use the pay_toll tool to pay up to 0.01 USDC if the content is
13
+ * valuable for the current task."
14
+ */
15
+
16
+ import { Keypair, Connection, PublicKey, Transaction } from '@solana/web3.js';
17
+ import {
18
+ getAssociatedTokenAddress,
19
+ createTransferInstruction,
20
+ TOKEN_PROGRAM_ID
21
+ } from '@solana/spl-token';
22
+
23
+ const TOLL_API = 'https://toll.agenttoll.io';
24
+ const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'); // Mainnet USDC
25
+
26
+ /**
27
+ * AgentToll-compatible tool implementation
28
+ */
29
+ export async function pay_toll(params, context) {
30
+ const { url, amount, max_amount = 0.05, reason } = params;
31
+ const { wallet_keypair, solana_rpc } = context;
32
+
33
+ // Safety check: don't overpay
34
+ if (amount > max_amount) {
35
+ return {
36
+ success: false,
37
+ error: `Requested amount ${amount} USDC exceeds max_amount ${max_amount} USDC`,
38
+ suggestion: 'Increase max_amount if this resource is valuable, or skip this resource',
39
+ };
40
+ }
41
+
42
+ try {
43
+ // 1. Get payment details from toll service
44
+ const quoteRes = await fetch(`${TOLL_API}/api/pay/quote?resource=${encodeURIComponent(url)}&amount=${amount}`);
45
+ const quote = await quoteRes.json();
46
+
47
+ // 2. Create and send Solana USDC transfer
48
+ const connection = new Connection(solana_rpc || 'https://api.mainnet-beta.solana.com');
49
+ const payer = Keypair.fromSecretKey(wallet_keypair);
50
+
51
+ const payerAta = await getAssociatedTokenAddress(USDC_MINT, payer.publicKey);
52
+ const receiverAta = await getAssociatedTokenAddress(USDC_MINT, new PublicKey(quote.receiver_wallet));
53
+
54
+ // Amount in USDC (6 decimals)
55
+ const transferAmount = Math.floor(amount * 1_000_000);
56
+
57
+ const transaction = new Transaction().add(
58
+ createTransferInstruction(
59
+ payerAta,
60
+ receiverAta,
61
+ payer.publicKey,
62
+ transferAmount,
63
+ [],
64
+ TOKEN_PROGRAM_ID
65
+ )
66
+ );
67
+
68
+ const signature = await connection.sendTransaction(transaction, [payer]);
69
+ await connection.confirmTransaction(signature, 'confirmed');
70
+
71
+ // 3. Submit payment proof to toll service
72
+ const payRes = await fetch(`${TOLL_API}/api/pay`, {
73
+ method: 'POST',
74
+ headers: { 'Content-Type': 'application/json' },
75
+ body: JSON.stringify({
76
+ publisher: quote.publisher || 'default',
77
+ amount: amount,
78
+ resource: url,
79
+ tx_signature: signature,
80
+ agent_id: context.agent_id || 'agenttoll',
81
+ }),
82
+ });
83
+
84
+ const payResult = await payRes.json();
85
+
86
+ if (!payResult.success) {
87
+ return {
88
+ success: false,
89
+ error: payResult.error,
90
+ tx_signature: signature,
91
+ };
92
+ }
93
+
94
+ // 4. Return token for agent to use
95
+ return {
96
+ success: true,
97
+ token: payResult.token,
98
+ expires_in: payResult.expires_in,
99
+ amount_paid: amount,
100
+ tx_signature: signature,
101
+ retry_headers: {
102
+ 'Authorization': `Bearer ${payResult.token}`,
103
+ },
104
+ instructions: `Payment successful. Retry the original request to ${url} with the Authorization header set to: Bearer ${payResult.token}`,
105
+ };
106
+
107
+ } catch (error) {
108
+ return {
109
+ success: false,
110
+ error: error.message,
111
+ suggestion: 'Check wallet balance or try again later',
112
+ };
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Parse 402 response to extract payment details
118
+ * Helper for agents to understand 402 responses
119
+ */
120
+ export function parse402Response(response) {
121
+ // Try x402 headers first
122
+ const headers = response.headers || {};
123
+ if (headers['x-402-amount']) {
124
+ return {
125
+ amount: parseFloat(headers['x-402-amount']),
126
+ currency: headers['x-402-currency'] || 'USDC',
127
+ pay_url: headers['x-402-pay-url'],
128
+ network: headers['x-402-network'] || 'solana',
129
+ };
130
+ }
131
+
132
+ // Try body
133
+ const body = typeof response.body === 'string' ? JSON.parse(response.body) : response.body;
134
+ if (body?.x402) {
135
+ return {
136
+ amount: body.x402.amount,
137
+ currency: body.x402.currency,
138
+ network: body.x402.network,
139
+ pay_url: body.payment?.pay_url,
140
+ };
141
+ }
142
+
143
+ if (body?.payment) {
144
+ return {
145
+ amount: body.payment.amount,
146
+ currency: body.payment.currency,
147
+ network: body.payment.network,
148
+ pay_url: body.payment.pay_url,
149
+ };
150
+ }
151
+
152
+ return null;
153
+ }
154
+
155
+ /**
156
+ * AgentToll system prompt addition for toll handling
157
+ */
158
+ export const TOLL_SYSTEM_PROMPT = `
159
+ ## Toll Payment Capability
160
+
161
+ You have access to the pay_toll tool for handling paywalled content. When you encounter an HTTP 402 Payment Required response:
162
+
163
+ 1. Check the x402 headers or response body for payment details (amount, currency, network)
164
+ 2. Evaluate if the resource is worth paying for based on your current task
165
+ 3. If valuable and within your budget (default max 0.01 USDC), use pay_toll to pay
166
+ 4. Retry the original request with the returned Authorization token
167
+
168
+ Example 402 handling:
169
+ - Receive 402 with amount: 0.005 USDC
170
+ - Decide: "This API has data I need for my research task"
171
+ - Call: pay_toll(url="...", amount=0.005, reason="Need this data for research")
172
+ - Retry request with: Authorization: Bearer <returned_token>
173
+
174
+ Budget guidelines:
175
+ - Small data requests: up to 0.005 USDC
176
+ - API calls: up to 0.01 USDC
177
+ - Premium content: up to 0.05 USDC (ask human first if possible)
178
+ - Never exceed 0.1 USDC without explicit human approval
179
+ `;
180
+
181
+ export default { pay_toll, parse402Response, TOLL_SYSTEM_PROMPT };
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@agenttoll/sdk",
3
+ "version": "1.0.0",
4
+ "description": "One-line micropayment integration for AI agents - Express, Cloudflare Workers, Vercel Edge",
5
+ "main": "tollbooth.js",
6
+ "exports": {
7
+ ".": "./tollbooth.js",
8
+ "./edge": "./tollbooth-edge.js",
9
+ "./next": "./tollbooth-edge.js",
10
+ "./tool": "./agenttoll-tool.js"
11
+ },
12
+ "type": "module",
13
+ "keywords": [
14
+ "agenttoll",
15
+ "openclaw",
16
+ "ai-agents",
17
+ "micropayments",
18
+ "x402",
19
+ "solana",
20
+ "base",
21
+ "tollbooth",
22
+ "express-middleware",
23
+ "cloudflare-workers"
24
+ ],
25
+ "author": "AgentToll",
26
+ "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/agenttoll/sdk"
30
+ },
31
+ "homepage": "https://agenttoll.io",
32
+ "engines": {
33
+ "node": ">=18.0.0"
34
+ },
35
+ "files": [
36
+ "tollbooth.js",
37
+ "tollbooth-edge.js",
38
+ "agenttoll-tool.js",
39
+ "agenttoll-tool-schema.json"
40
+ ]
41
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * AgentToll - Edge Runtime SDK
3
+ * For Cloudflare Workers, Vercel Edge, Deno Deploy
4
+ *
5
+ * @example
6
+ * import { tollgate } from '@agenttoll/sdk/edge'
7
+ * export default tollgate('your-api-key')
8
+ */
9
+
10
+ const TOLL_API_BASE = 'https://toll.agenttoll.io';
11
+
12
+ const AGENT_PATTERNS = [
13
+ /agenttoll/i, /openclaw/i, /clawd/i, /openai-agent/i,
14
+ /anthropic-agent/i, /autogpt/i, /agentgpt/i, /babyagi/i,
15
+ ];
16
+
17
+ function isAgent(request) {
18
+ const ua = request.headers.get('user-agent') || '';
19
+ const agentHeader = request.headers.get('x-agent-type') || request.headers.get('x-agenttoll-id') || '';
20
+ if (AGENT_PATTERNS.some(p => p.test(ua))) return true;
21
+ if (agentHeader) return true;
22
+ if (request.headers.get('x-402-capable') === 'true') return true;
23
+ return false;
24
+ }
25
+
26
+ async function verifyToken(token, apiKey) {
27
+ try {
28
+ const res = await fetch(`${TOLL_API_BASE}/api/verify`, {
29
+ method: 'POST',
30
+ headers: { 'Content-Type': 'application/json', 'X-Publisher-Key': apiKey },
31
+ body: JSON.stringify({ token }),
32
+ });
33
+ return (await res.json()).valid === true;
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+
39
+ function build402(request, apiKey, amount) {
40
+ const url = new URL(request.url);
41
+ return {
42
+ status: 402,
43
+ message: 'Payment Required',
44
+ agent_instructions: `Pay ${amount} USDC to access. Use returned token in Authorization header.`,
45
+ payment: {
46
+ amount,
47
+ currency: 'USDC',
48
+ network: 'solana',
49
+ pay_url: `${TOLL_API_BASE}/pay?publisher=${apiKey}&amount=${amount}&resource=${encodeURIComponent(url.href)}`,
50
+ api_endpoint: `${TOLL_API_BASE}/api/pay`,
51
+ },
52
+ x402: { version: 1, amount, currency: 'USDC', network: 'solana-mainnet' },
53
+ retry: { method: request.method, url: url.href, headers: { 'Authorization': 'Bearer <token>' } },
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Edge-compatible tollgate wrapper
59
+ * Wraps your handler and gates it behind payment
60
+ *
61
+ * @example
62
+ * // Cloudflare Worker
63
+ * import { tollgate } from '@agenttoll/sdk/edge'
64
+ *
65
+ * export default tollgate('pk_live_xxx', {
66
+ * amount: 0.005,
67
+ * freeForHumans: true,
68
+ * handler: async (request) => {
69
+ * return new Response('Premium content!')
70
+ * }
71
+ * })
72
+ */
73
+ export function tollgate(apiKey, options = {}) {
74
+ const amount = options.amount || 0.005;
75
+ const freeForHumans = options.freeForHumans ?? false;
76
+ const handler = options.handler;
77
+
78
+ return {
79
+ async fetch(request, env, ctx) {
80
+ const agent = isAgent(request);
81
+
82
+ // Free for humans if configured
83
+ if (freeForHumans && !agent) {
84
+ return handler ? handler(request, env, ctx) : new Response('OK');
85
+ }
86
+
87
+ // Check payment token
88
+ const auth = request.headers.get('authorization') || '';
89
+ const token = auth.replace(/^Bearer\s+/i, '');
90
+
91
+ if (token && await verifyToken(token, apiKey)) {
92
+ return handler ? handler(request, env, ctx) : new Response('OK');
93
+ }
94
+
95
+ // Return 402
96
+ const paymentInfo = build402(request, apiKey, amount);
97
+ return new Response(JSON.stringify(paymentInfo), {
98
+ status: 402,
99
+ headers: {
100
+ 'Content-Type': 'application/json',
101
+ 'X-402-Version': '1',
102
+ 'X-402-Amount': amount.toString(),
103
+ 'X-402-Currency': 'USDC',
104
+ 'X-402-Pay-URL': paymentInfo.payment.pay_url,
105
+ },
106
+ });
107
+ }
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Middleware for edge frameworks (Hono, itty-router, etc.)
113
+ */
114
+ export function tollMiddleware(apiKey, options = {}) {
115
+ const amount = options.amount || 0.005;
116
+ const freeForHumans = options.freeForHumans ?? false;
117
+
118
+ return async (request, next) => {
119
+ const agent = isAgent(request);
120
+ if (freeForHumans && !agent) return next();
121
+
122
+ const auth = request.headers.get('authorization') || '';
123
+ const token = auth.replace(/^Bearer\s+/i, '');
124
+ if (token && await verifyToken(token, apiKey)) return next();
125
+
126
+ const paymentInfo = build402(request, apiKey, amount);
127
+ return new Response(JSON.stringify(paymentInfo), {
128
+ status: 402,
129
+ headers: { 'Content-Type': 'application/json', 'X-402-Version': '1' },
130
+ });
131
+ };
132
+ }
133
+
134
+ export { isAgent };
package/tollbooth.js ADDED
@@ -0,0 +1,292 @@
1
+ /**
2
+ * AgentToll SDK
3
+ * One-line integration for AI agent micropayments
4
+ *
5
+ * Usage (Express):
6
+ * app.use(require('@agenttoll/sdk')('your-api-key'))
7
+ *
8
+ * Usage (Cloudflare Worker):
9
+ * import { tollgate } from '@agenttoll/sdk/edge'
10
+ * export default tollgate('your-api-key', { amount: 0.005 })
11
+ */
12
+
13
+ const TOLL_API_BASE = process.env.TOLL_API_URL || 'https://toll.agenttoll.io';
14
+
15
+ // Agent detection patterns
16
+ const AGENT_PATTERNS = [
17
+ // Platform-specific
18
+ /agenttoll/i,
19
+ /openclaw/i,
20
+ /clawd/i,
21
+ // Major AI providers
22
+ /claude/i,
23
+ /anthropic/i,
24
+ /openai/i,
25
+ /gpt-4/i,
26
+ /chatgpt/i,
27
+ /gemini/i,
28
+ /google-ai/i,
29
+ // Agent frameworks
30
+ /langchain/i,
31
+ /autogpt/i,
32
+ /agentgpt/i,
33
+ /babyagi/i,
34
+ /crewai/i,
35
+ /superagent/i,
36
+ // Generic patterns
37
+ /ai-agent/i,
38
+ /bot\//i,
39
+ /agent\//i,
40
+ ];
41
+
42
+ /**
43
+ * Detect if request is from an AgentToll/AI agent
44
+ */
45
+ function isAgent(req) {
46
+ const ua = req.headers['user-agent'] || '';
47
+ const agentHeader = req.headers['x-agent-type'] || req.headers['x-agenttoll-id'] || '';
48
+
49
+ // Check User-Agent
50
+ if (AGENT_PATTERNS.some(pattern => pattern.test(ua))) return true;
51
+
52
+ // Check custom agent headers
53
+ if (agentHeader) return true;
54
+
55
+ // Check x402 capability header (agents advertise payment capability)
56
+ if (req.headers['x-402-capable'] === 'true') return true;
57
+
58
+ return false;
59
+ }
60
+
61
+ /**
62
+ * Verify payment token
63
+ */
64
+ async function verifyToken(token, apiKey) {
65
+ try {
66
+ const res = await fetch(`${TOLL_API_BASE}/api/verify`, {
67
+ method: 'POST',
68
+ headers: {
69
+ 'Content-Type': 'application/json',
70
+ 'X-Publisher-Key': apiKey,
71
+ },
72
+ body: JSON.stringify({ token }),
73
+ });
74
+ const data = await res.json();
75
+ return data.valid === true;
76
+ } catch {
77
+ return false;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Build 402 Payment Required response for agents
83
+ * Includes onboarding info for non-x402 capable agents
84
+ */
85
+ function build402Response(req, options) {
86
+ const { apiKey, amount, resource } = options;
87
+ const requestedUrl = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
88
+ const isX402Capable = req.headers['x-402-capable'] === 'true';
89
+
90
+ const paymentInfo = {
91
+ status: 402,
92
+ message: 'Payment Required',
93
+ agent_instructions: `To access this resource, pay ${amount} USDC via the payment URL below. After payment, retry with the returned token in Authorization header.`,
94
+ payment: {
95
+ amount: amount,
96
+ currency: 'USDC',
97
+ network: 'solana',
98
+ pay_url: `${TOLL_API_BASE}/pay?publisher=${apiKey}&amount=${amount}&resource=${encodeURIComponent(requestedUrl)}`,
99
+ api_endpoint: `${TOLL_API_BASE}/api/pay`,
100
+ },
101
+ x402: {
102
+ version: 1,
103
+ amount: amount,
104
+ currency: 'USDC',
105
+ receiver: options.walletAddress || 'pending',
106
+ network: 'solana-mainnet',
107
+ description: `Access to ${resource || requestedUrl}`,
108
+ },
109
+ retry: {
110
+ method: req.method,
111
+ url: requestedUrl,
112
+ headers: {
113
+ 'Authorization': 'Bearer <token_from_payment>',
114
+ },
115
+ },
116
+ };
117
+
118
+ // Add onboarding info for non-x402 capable agents
119
+ if (!isX402Capable) {
120
+ paymentInfo.x402_onboarding = {
121
+ message: "Your agent doesn't appear to have x402 payment capability configured. Here's how to enable it:",
122
+ what_is_x402: "x402 is a protocol that allows AI agents to make micropayments to access premium content and APIs autonomously.",
123
+ setup_steps: [
124
+ "1. Add the 'pay_toll' tool to your agent's tool configuration",
125
+ "2. Configure a Solana wallet with USDC for payments",
126
+ "3. Set budget limits for autonomous spending",
127
+ "4. Add 'x-402-capable: true' header to advertise capability"
128
+ ],
129
+ tool_schema: {
130
+ name: "pay_toll",
131
+ description: "Pay a micropayment toll to access a paywalled resource. Use when you encounter HTTP 402 Payment Required responses.",
132
+ parameters: {
133
+ type: "object",
134
+ required: ["pay_url", "amount"],
135
+ properties: {
136
+ pay_url: { type: "string", description: "The payment URL from the 402 response" },
137
+ amount: { type: "number", description: "Payment amount in USDC" },
138
+ max_amount: { type: "number", description: "Maximum you're willing to pay (safety limit)" }
139
+ }
140
+ }
141
+ },
142
+ example_tool_call: {
143
+ tool: "pay_toll",
144
+ arguments: {
145
+ pay_url: paymentInfo.payment.pay_url,
146
+ amount: amount,
147
+ max_amount: 0.05
148
+ }
149
+ },
150
+ documentation: `${TOLL_API_BASE}/docs#agent-setup`,
151
+ sdk_install: "npm install @agenttoll/sdk",
152
+ sample_integration: `${TOLL_API_BASE}/docs#pay-toll-tool`,
153
+ operator_dashboard: `${TOLL_API_BASE}/dashboard`,
154
+ support: "support@agenttoll.io"
155
+ };
156
+ }
157
+
158
+ return paymentInfo;
159
+ }
160
+
161
+ /**
162
+ * Express/Connect middleware factory
163
+ *
164
+ * @param {string} apiKey - Your publisher API key from toll.agenttoll.io
165
+ * @param {object} options - Configuration options
166
+ * @returns {function} Express middleware
167
+ *
168
+ * @example
169
+ * // One line integration
170
+ * app.use(tollbooth('pk_live_xxx'));
171
+ *
172
+ * @example
173
+ * // With options
174
+ * app.use(tollbooth('pk_live_xxx', {
175
+ * amount: 0.01,
176
+ * paths: ['/api/premium/*'],
177
+ * freeForHumans: true
178
+ * }));
179
+ */
180
+ function tollbooth(apiKey, options = {}) {
181
+ const config = {
182
+ amount: options.amount || 0.005,
183
+ paths: options.paths || ['*'],
184
+ freeForHumans: options.freeForHumans ?? false,
185
+ walletAddress: options.walletAddress || null,
186
+ onPayment: options.onPayment || null,
187
+ bypassHeader: options.bypassHeader || null,
188
+ };
189
+
190
+ return async function tollboothMiddleware(req, res, next) {
191
+ // Check bypass header (for internal services)
192
+ if (config.bypassHeader && req.headers[config.bypassHeader.toLowerCase()]) {
193
+ return next();
194
+ }
195
+
196
+ // Check if path should be tolled
197
+ const shouldToll = config.paths.some(pattern => {
198
+ if (pattern === '*') return true;
199
+ const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
200
+ return regex.test(req.path);
201
+ });
202
+
203
+ if (!shouldToll) return next();
204
+
205
+ // Detect if agent
206
+ const agentRequest = isAgent(req);
207
+
208
+ // If free for humans and not an agent, pass through
209
+ if (config.freeForHumans && !agentRequest) {
210
+ return next();
211
+ }
212
+
213
+ // Check for existing payment token
214
+ const authHeader = req.headers['authorization'] || '';
215
+ const token = authHeader.replace(/^Bearer\s+/i, '');
216
+
217
+ if (token) {
218
+ const valid = await verifyToken(token, apiKey);
219
+ if (valid) {
220
+ // Track successful access
221
+ req.tollPaid = true;
222
+ req.tollAgent = agentRequest;
223
+ return next();
224
+ }
225
+ }
226
+
227
+ // No valid token - return 402 Payment Required
228
+ const paymentInfo = build402Response(req, {
229
+ apiKey,
230
+ amount: config.amount,
231
+ resource: req.path,
232
+ walletAddress: config.walletAddress,
233
+ });
234
+
235
+ // Check if agent advertises x402 capability
236
+ const isX402Capable = req.headers['x-402-capable'] === 'true';
237
+
238
+ // Set x402 headers for agent parsing
239
+ res.setHeader('X-402-Version', '1');
240
+ res.setHeader('X-402-Amount', config.amount.toString());
241
+ res.setHeader('X-402-Currency', 'USDC');
242
+ res.setHeader('X-402-Pay-URL', paymentInfo.payment.pay_url);
243
+ res.setHeader('X-402-Network', 'solana');
244
+ res.setHeader('Content-Type', 'application/json');
245
+
246
+ // Add onboarding hint for non-capable agents
247
+ if (!isX402Capable) {
248
+ res.setHeader('X-402-Onboarding', 'true');
249
+ res.setHeader('X-402-Setup-URL', `${TOLL_API_BASE}/docs#agent-setup`);
250
+ }
251
+
252
+ return res.status(402).json(paymentInfo);
253
+ };
254
+ }
255
+
256
+ /**
257
+ * Protect specific routes (alternative API)
258
+ *
259
+ * @example
260
+ * app.get('/premium', tollbooth.protect('pk_xxx', { amount: 0.01 }), handler)
261
+ */
262
+ tollbooth.protect = function(apiKey, options = {}) {
263
+ return tollbooth(apiKey, { ...options, paths: ['*'] });
264
+ };
265
+
266
+ /**
267
+ * Agent-only tollbooth (humans pass free)
268
+ *
269
+ * @example
270
+ * app.use(tollbooth.agentsOnly('pk_xxx'))
271
+ */
272
+ tollbooth.agentsOnly = function(apiKey, options = {}) {
273
+ return tollbooth(apiKey, { ...options, freeForHumans: true });
274
+ };
275
+
276
+ /**
277
+ * Check if request has valid toll payment
278
+ */
279
+ tollbooth.hasPaid = function(req) {
280
+ return req.tollPaid === true;
281
+ };
282
+
283
+ /**
284
+ * Check if request is from an agent
285
+ */
286
+ tollbooth.isAgent = isAgent;
287
+
288
+ // Export for different module systems
289
+ module.exports = tollbooth;
290
+ module.exports.default = tollbooth;
291
+ module.exports.tollbooth = tollbooth;
292
+ module.exports.isAgent = isAgent;