@agentcash/router 0.3.0 → 0.4.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 CHANGED
@@ -141,11 +141,28 @@ The fluent builder ensures compile-time safety:
141
141
 
142
142
  ### Pricing Modes
143
143
 
144
- **Static**: `router.route('search').paid('0.02')`
144
+ **Static** - Fixed price for all requests:
145
+ ```typescript
146
+ router.route('search').paid('0.02')
147
+ ```
148
+
149
+ **Dynamic** - Calculate price based on request body:
150
+ ```typescript
151
+ router.route('gen')
152
+ .paid((body) => calculateCost(body.imageSize, body.quality))
153
+ .body(imageGenSchema)
154
+ .handler(async ({ body }) => generate(body));
155
+ ```
145
156
 
146
- **Dynamic**: `router.route('gen').paid((body) => calculateCost(body), { maxPrice: '5.00' }).body(schema)`
157
+ **Dynamic with safety net** - Cap at maxPrice if calculation exceeds, fallback to maxPrice on errors:
158
+ ```typescript
159
+ router.route('compute')
160
+ .paid((body) => calculateExpensiveOperation(body), { maxPrice: '10.00' })
161
+ .body(computeSchema)
162
+ .handler(async ({ body }) => compute(body));
163
+ ```
147
164
 
148
- **Tiered**:
165
+ **Tiered** - Price based on a specific field value:
149
166
  ```typescript
150
167
  router.route('upload').paid({
151
168
  field: 'tier',
@@ -153,7 +170,38 @@ router.route('upload').paid({
153
170
  '10mb': { price: '0.02', label: '10 MB' },
154
171
  '100mb': { price: '0.20', label: '100 MB' },
155
172
  },
156
- }).body(schema)
173
+ }).body(uploadSchema)
174
+ ```
175
+
176
+ #### maxPrice Semantics (v0.3.1+)
177
+
178
+ `maxPrice` is **optional** for dynamic pricing and acts as a safety net:
179
+
180
+ 1. **Capping**: If `calculateCost(body)` returns `"15.00"` but `maxPrice: "10.00"`, the client is charged `$10.00` (capped) and a warning alert fires.
181
+
182
+ 2. **Fallback**: If `calculateCost(body)` throws an error and `maxPrice` is set, the route falls back to `maxPrice` (degraded mode) and an alert fires. Without `maxPrice`, the route returns 500.
183
+
184
+ 3. **Trust mode**: No `maxPrice` means full trust in your pricing function (no cap, no fallback).
185
+
186
+ **Best practices:**
187
+ - ✅ Always set `maxPrice` for production routes (safety net)
188
+ - ✅ Use `maxPrice` for routes with external dependencies (pricing APIs)
189
+ - ✅ Monitor alerts for capping events (indicates pricing bug)
190
+ - ⚠️ Skip `maxPrice` only for well-tested, unbounded pricing (e.g., per-GB storage)
191
+
192
+ **Example with safety net:**
193
+ ```typescript
194
+ router.route('ai-gen')
195
+ .paid(async (body) => {
196
+ // External pricing API (can fail)
197
+ const res = await fetch('https://pricing.example.com/calculate', {
198
+ method: 'POST',
199
+ body: JSON.stringify(body),
200
+ });
201
+ return res.json().price;
202
+ }, { maxPrice: '5.00' }) // Fallback if API is down
203
+ .body(genSchema)
204
+ .handler(async ({ body }) => generate(body));
157
205
  ```
158
206
 
159
207
  ### Dual Protocol (x402 + MPP)
package/dist/index.cjs CHANGED
@@ -120,7 +120,7 @@ var RouteRegistry = class {
120
120
  };
121
121
 
122
122
  // src/orchestrate.ts
123
- var import_server2 = require("next/server");
123
+ var import_server3 = require("next/server");
124
124
 
125
125
  // src/plugin.ts
126
126
  function createDefaultContext(meta) {
@@ -354,70 +354,113 @@ async function settleX402Payment(server, payload, requirements) {
354
354
  }
355
355
 
356
356
  // src/protocols/mpp.ts
357
- var mpayLoaded = false;
358
- var Challenge;
359
- var Credential;
360
- var Receipt;
361
- var tempo;
362
- async function ensureMpay() {
363
- if (mpayLoaded) return;
364
- try {
365
- const mpay = await import("mpay");
366
- Challenge = mpay.Challenge;
367
- Credential = mpay.Credential;
368
- Receipt = mpay.Receipt;
369
- const mpayServer = await import("mpay/server");
370
- tempo = mpayServer.tempo;
371
- mpayLoaded = true;
372
- } catch {
373
- throw new Error("mpay package is required for MPP protocol support. Install it: pnpm add mpay");
357
+ var import_mpay = require("mpay");
358
+ var import_server2 = require("mpay/server");
359
+ var import_viem = require("viem");
360
+ var import_chains = require("viem/chains");
361
+ function buildGetClient(rpcUrl) {
362
+ const url = rpcUrl ?? process.env.TEMPO_RPC_URL;
363
+ if (!url) return {};
364
+ return {
365
+ getClient: () => (0, import_viem.createClient)({ chain: import_chains.tempo, transport: (0, import_viem.http)(url) })
366
+ };
367
+ }
368
+ function toStandardRequest(request) {
369
+ if (request.constructor.name === "Request") {
370
+ return request;
374
371
  }
372
+ return new Request(request.url, {
373
+ method: request.method,
374
+ headers: request.headers,
375
+ body: request.body,
376
+ // @ts-expect-error - Request.duplex is required for streaming bodies but not in types yet
377
+ duplex: "half"
378
+ });
375
379
  }
380
+ var DEFAULT_DECIMALS = 6;
376
381
  async function buildMPPChallenge(routeEntry, request, mppConfig, price) {
377
- await ensureMpay();
378
- const methodIntent = tempo.charge({
379
- amount: price,
380
- currency: mppConfig.currency,
381
- recipient: mppConfig.recipient ?? ""
382
+ const standardRequest = toStandardRequest(request);
383
+ const currency = mppConfig.currency;
384
+ const recipient = mppConfig.recipient ?? "";
385
+ const methodIntent = import_server2.tempo.charge({
386
+ currency,
387
+ recipient
382
388
  });
383
- const challenge = Challenge.fromIntent(methodIntent, {
389
+ const challenge = import_mpay.Challenge.fromIntent(methodIntent, {
384
390
  secretKey: mppConfig.secretKey,
385
- realm: new URL(request.url).origin,
386
- request
391
+ realm: new URL(standardRequest.url).origin,
392
+ request: {
393
+ amount: price,
394
+ currency,
395
+ recipient,
396
+ decimals: DEFAULT_DECIMALS
397
+ }
387
398
  });
388
- return Challenge.serialize(challenge);
399
+ return import_mpay.Challenge.serialize(challenge);
389
400
  }
390
401
  async function verifyMPPCredential(request, _routeEntry, mppConfig, price) {
391
- await ensureMpay();
392
- const credential = Credential.fromRequest(request);
393
- if (!credential) return null;
394
- const isValid = Challenge.verify(credential.challenge, { secretKey: mppConfig.secretKey });
395
- if (!isValid) {
396
- return { valid: false, payer: null };
397
- }
398
- const chargeConfig = {
399
- amount: price,
400
- currency: mppConfig.currency,
401
- recipient: mppConfig.recipient ?? ""
402
- };
403
- const verifyResult = await tempo.charge(chargeConfig).verify(credential);
404
- if (!verifyResult?.valid) {
405
- return { valid: false, payer: null };
402
+ const standardRequest = toStandardRequest(request);
403
+ const currency = mppConfig.currency;
404
+ const recipient = mppConfig.recipient ?? "";
405
+ try {
406
+ const authHeader = standardRequest.headers.get("Authorization");
407
+ if (!authHeader) {
408
+ console.error("[MPP] No Authorization header found");
409
+ return null;
410
+ }
411
+ const credential = import_mpay.Credential.fromRequest(standardRequest);
412
+ if (!credential?.challenge) {
413
+ console.error("[MPP] Invalid credential structure");
414
+ return null;
415
+ }
416
+ const isValid = import_mpay.Challenge.verify(credential.challenge, { secretKey: mppConfig.secretKey });
417
+ if (!isValid) {
418
+ console.error("[MPP] Challenge HMAC verification failed");
419
+ return { valid: false, payer: null };
420
+ }
421
+ const methodIntent = import_server2.tempo.charge({
422
+ currency,
423
+ recipient,
424
+ ...buildGetClient(mppConfig.rpcUrl)
425
+ });
426
+ const paymentRequest = {
427
+ amount: price,
428
+ currency,
429
+ recipient,
430
+ decimals: DEFAULT_DECIMALS
431
+ };
432
+ const resolvedRequest = methodIntent.request ? await methodIntent.request({ credential, request: paymentRequest }) : paymentRequest;
433
+ const receipt = await methodIntent.verify({
434
+ credential,
435
+ request: resolvedRequest
436
+ });
437
+ if (!receipt || receipt.status !== "success") {
438
+ console.error("[MPP] Tempo verification failed:", receipt);
439
+ return { valid: false, payer: null };
440
+ }
441
+ const payer = receipt.reference ?? "";
442
+ return {
443
+ valid: true,
444
+ payer,
445
+ txHash: receipt.reference
446
+ };
447
+ } catch (error) {
448
+ console.error("[MPP] Credential verification error:", {
449
+ message: error instanceof Error ? error.message : String(error),
450
+ stack: error instanceof Error ? error.stack : void 0,
451
+ errorType: error?.constructor?.name
452
+ });
453
+ return null;
406
454
  }
407
- return {
408
- valid: true,
409
- payer: verifyResult.payer
410
- };
411
455
  }
412
- async function buildMPPReceipt(reference) {
413
- await ensureMpay();
414
- const receipt = Receipt.from({
456
+ function buildMPPReceipt(reference) {
457
+ const receipt = import_mpay.Receipt.from({
415
458
  method: "tempo",
416
459
  status: "success",
417
460
  reference,
418
461
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
419
462
  });
420
- return Receipt.serialize(receipt);
463
+ return import_mpay.Receipt.serialize(receipt);
421
464
  }
422
465
 
423
466
  // src/auth/siwx.ts
@@ -495,7 +538,7 @@ function createRequestHandler(routeEntry, handler, deps) {
495
538
  firePluginResponse(deps, pluginCtx, meta, response);
496
539
  }
497
540
  function fail(status, message, meta, pluginCtx) {
498
- const response = import_server2.NextResponse.json({ success: false, error: message }, { status });
541
+ const response = import_server3.NextResponse.json({ success: false, error: message }, { status });
499
542
  firePluginResponse(deps, pluginCtx, meta, response);
500
543
  return response;
501
544
  }
@@ -523,7 +566,7 @@ function createRequestHandler(routeEntry, handler, deps) {
523
566
  if (routeEntry.authMode === "unprotected") {
524
567
  return handleAuth(null, void 0);
525
568
  }
526
- let account = void 0;
569
+ let account;
527
570
  if (routeEntry.authMode === "apiKey" || routeEntry.apiKeyResolver) {
528
571
  if (!routeEntry.apiKeyResolver) {
529
572
  return fail(401, "API key resolver not configured", meta, pluginCtx);
@@ -538,6 +581,16 @@ function createRequestHandler(routeEntry, handler, deps) {
538
581
  }
539
582
  }
540
583
  const protocol = detectProtocol(request);
584
+ let earlyBodyData;
585
+ if (!protocol && typeof routeEntry.pricing === "function" && routeEntry.bodySchema) {
586
+ const requestForPricing = request.clone();
587
+ const earlyBodyResult = await parseBody(requestForPricing, routeEntry);
588
+ if (!earlyBodyResult.ok) {
589
+ firePluginResponse(deps, pluginCtx, meta, earlyBodyResult.response);
590
+ return earlyBodyResult.response;
591
+ }
592
+ earlyBodyData = earlyBodyResult.data;
593
+ }
541
594
  if (routeEntry.authMode === "siwx") {
542
595
  if (!request.headers.get("SIGN-IN-WITH-X")) {
543
596
  const url = new URL(request.url);
@@ -585,7 +638,7 @@ function createRequestHandler(routeEntry, handler, deps) {
585
638
  route: routeEntry.key
586
639
  });
587
640
  }
588
- const response = new import_server2.NextResponse(JSON.stringify(paymentRequired), {
641
+ const response = new import_server3.NextResponse(JSON.stringify(paymentRequired), {
589
642
  status: 402,
590
643
  headers: { "Content-Type": "application/json" }
591
644
  });
@@ -606,7 +659,7 @@ function createRequestHandler(routeEntry, handler, deps) {
606
659
  return handleAuth(siwx.wallet, void 0);
607
660
  }
608
661
  if (!protocol || protocol === "siwx") {
609
- return await build402(request, routeEntry, deps, meta, pluginCtx);
662
+ return await build402(request, routeEntry, deps, meta, pluginCtx, earlyBodyData);
610
663
  }
611
664
  const body = await parseBody(request, routeEntry);
612
665
  if (!body.ok) {
@@ -717,7 +770,7 @@ async function parseBody(request, routeEntry) {
717
770
  if (result.success) return { ok: true, data: result.data };
718
771
  return {
719
772
  ok: false,
720
- response: import_server2.NextResponse.json(
773
+ response: import_server3.NextResponse.json(
721
774
  { success: false, error: result.error, issues: result.issues },
722
775
  { status: 400 }
723
776
  )
@@ -744,10 +797,60 @@ function parseQuery(request, routeEntry) {
744
797
  const result = routeEntry.querySchema.safeParse(params);
745
798
  return result.success ? result.data : params;
746
799
  }
747
- async function build402(request, routeEntry, deps, meta, pluginCtx) {
748
- const response = new import_server2.NextResponse(null, { status: 402 });
800
+ async function resolveDynamicPrice(bodyData, routeEntry, deps, pluginCtx, meta) {
801
+ try {
802
+ let price = await resolvePrice(routeEntry.pricing, bodyData);
803
+ if (routeEntry.maxPrice) {
804
+ const calculated = parseFloat(price);
805
+ const max = parseFloat(routeEntry.maxPrice);
806
+ if (calculated > max) {
807
+ firePluginHook(deps.plugin, "onAlert", pluginCtx, {
808
+ level: "warn",
809
+ message: `Price ${price} exceeds maxPrice ${routeEntry.maxPrice}, capping`,
810
+ route: routeEntry.key,
811
+ meta: { calculated: price, maxPrice: routeEntry.maxPrice, body: bodyData }
812
+ });
813
+ price = routeEntry.maxPrice;
814
+ }
815
+ }
816
+ return { price };
817
+ } catch (err) {
818
+ firePluginHook(deps.plugin, "onAlert", pluginCtx, {
819
+ level: "error",
820
+ message: `Pricing function failed: ${err instanceof Error ? err.message : String(err)}`,
821
+ route: routeEntry.key,
822
+ meta: { error: err instanceof Error ? err.stack : String(err), body: bodyData }
823
+ });
824
+ if (routeEntry.maxPrice) {
825
+ firePluginHook(deps.plugin, "onAlert", pluginCtx, {
826
+ level: "warn",
827
+ message: `Using maxPrice ${routeEntry.maxPrice} as fallback after pricing error`,
828
+ route: routeEntry.key
829
+ });
830
+ return { price: routeEntry.maxPrice };
831
+ } else {
832
+ const errorResponse = import_server3.NextResponse.json(
833
+ { success: false, error: "Price calculation failed" },
834
+ { status: 500 }
835
+ );
836
+ firePluginResponse(deps, pluginCtx, meta, errorResponse);
837
+ return { error: errorResponse };
838
+ }
839
+ }
840
+ }
841
+ async function build402(request, routeEntry, deps, meta, pluginCtx, bodyData) {
842
+ const response = new import_server3.NextResponse(null, {
843
+ status: 402,
844
+ headers: {
845
+ "Content-Type": "application/json"
846
+ }
847
+ });
749
848
  let challengePrice;
750
- if (routeEntry.maxPrice) {
849
+ if (bodyData !== void 0 && typeof routeEntry.pricing === "function") {
850
+ const result = await resolveDynamicPrice(bodyData, routeEntry, deps, pluginCtx, meta);
851
+ if ("error" in result) return result.error;
852
+ challengePrice = result.price;
853
+ } else if (routeEntry.maxPrice) {
751
854
  challengePrice = routeEntry.maxPrice;
752
855
  } else if (routeEntry.pricing) {
753
856
  try {
@@ -908,9 +1011,6 @@ var RouteBuilder = class {
908
1011
  next._pricing = pricing;
909
1012
  if (options?.protocols) next._protocols = options.protocols;
910
1013
  if (options?.maxPrice) next._maxPrice = options.maxPrice;
911
- if (typeof pricing === "function" && !options?.maxPrice) {
912
- throw new Error(`route '${this._key}': dynamic pricing requires maxPrice option`);
913
- }
914
1014
  if (typeof pricing === "object" && "tiers" in pricing) {
915
1015
  for (const [tierKey, tierConfig] of Object.entries(pricing.tiers)) {
916
1016
  if (!tierKey) {
@@ -1035,7 +1135,7 @@ var MemoryNonceStore = class {
1035
1135
  };
1036
1136
 
1037
1137
  // src/discovery/well-known.ts
1038
- var import_server3 = require("next/server");
1138
+ var import_server4 = require("next/server");
1039
1139
  function createWellKnownHandler(registry, baseUrl, pricesKeys, options = {}) {
1040
1140
  let validated = false;
1041
1141
  return async (_request) => {
@@ -1073,7 +1173,7 @@ function createWellKnownHandler(registry, baseUrl, pricesKeys, options = {}) {
1073
1173
  if (instructions) {
1074
1174
  body.instructions = instructions;
1075
1175
  }
1076
- return import_server3.NextResponse.json(body, {
1176
+ return import_server4.NextResponse.json(body, {
1077
1177
  headers: {
1078
1178
  "Access-Control-Allow-Origin": "*",
1079
1179
  "Access-Control-Allow-Methods": "GET",
@@ -1084,12 +1184,12 @@ function createWellKnownHandler(registry, baseUrl, pricesKeys, options = {}) {
1084
1184
  }
1085
1185
 
1086
1186
  // src/discovery/openapi.ts
1087
- var import_server4 = require("next/server");
1187
+ var import_server5 = require("next/server");
1088
1188
  function createOpenAPIHandler(registry, baseUrl, pricesKeys, options) {
1089
1189
  let cached = null;
1090
1190
  let validated = false;
1091
1191
  return async (_request) => {
1092
- if (cached) return import_server4.NextResponse.json(cached);
1192
+ if (cached) return import_server5.NextResponse.json(cached);
1093
1193
  if (!validated && pricesKeys) {
1094
1194
  registry.validate(pricesKeys);
1095
1195
  validated = true;
@@ -1116,7 +1216,7 @@ function createOpenAPIHandler(registry, baseUrl, pricesKeys, options) {
1116
1216
  tags: Array.from(tagSet).sort().map((name) => ({ name })),
1117
1217
  paths
1118
1218
  });
1119
- return import_server4.NextResponse.json(cached);
1219
+ return import_server5.NextResponse.json(cached);
1120
1220
  };
1121
1221
  }
1122
1222
  function deriveTag(routeKey) {
package/dist/index.d.cts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
2
  import { ZodType } from 'zod';
3
+ import { PaymentRequirements, PaymentRequired, SettleResponse } from '@x402/core/types';
3
4
 
4
5
  interface NonceStore {
5
6
  check(nonce: string): Promise<boolean>;
@@ -82,6 +83,7 @@ interface AlertEvent {
82
83
  meta?: Record<string, unknown>;
83
84
  }
84
85
  type AlertFn = (level: AlertLevel, message: string, meta?: Record<string, unknown>) => void;
86
+
85
87
  interface X402Server {
86
88
  initialize(): Promise<void>;
87
89
  buildPaymentRequirementsFromOptions(options: Array<{
@@ -91,18 +93,18 @@ interface X402Server {
91
93
  payTo: string;
92
94
  }>, context: {
93
95
  request: Request;
94
- }): Promise<unknown[]>;
95
- createPaymentRequiredResponse(requirements: unknown[], resource: {
96
+ }): Promise<PaymentRequirements[]>;
97
+ createPaymentRequiredResponse(requirements: PaymentRequirements[], resource: {
96
98
  url: string;
97
99
  method: string;
98
100
  description?: string;
99
- }, error: string | null, extensions?: Record<string, unknown>): Promise<unknown>;
100
- findMatchingRequirements(requirements: unknown[], payload: unknown): unknown;
101
- verifyPayment(payload: unknown, requirements: unknown): Promise<{
101
+ }, error: string | null, extensions?: Record<string, unknown>): Promise<PaymentRequired>;
102
+ findMatchingRequirements(requirements: PaymentRequirements[], payload: unknown): PaymentRequirements;
103
+ verifyPayment(payload: unknown, requirements: PaymentRequirements): Promise<{
102
104
  isValid: boolean;
103
105
  payer?: string;
104
106
  }>;
105
- settlePayment(payload: unknown, requirements: unknown): Promise<unknown>;
107
+ settlePayment(payload: unknown, requirements: PaymentRequirements): Promise<SettleResponse>;
106
108
  }
107
109
  type ProtocolType = 'x402' | 'mpp';
108
110
  type AuthMode = 'paid' | 'siwx' | 'apiKey' | 'unprotected';
@@ -183,6 +185,8 @@ interface RouterConfig {
183
185
  secretKey: string;
184
186
  currency: string;
185
187
  recipient?: string;
188
+ /** Tempo RPC URL for on-chain verification. Falls back to TEMPO_RPC_URL env var. */
189
+ rpcUrl?: string;
186
190
  };
187
191
  /**
188
192
  * Payment protocols to accept on auto-priced routes (those using the `prices` config).
@@ -239,6 +243,7 @@ interface OrchestrateDeps {
239
243
  secretKey: string;
240
244
  currency: string;
241
245
  recipient?: string;
246
+ rpcUrl?: string;
242
247
  };
243
248
  }
244
249
 
@@ -265,7 +270,7 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, HasAuth extend
265
270
  private fork;
266
271
  paid(pricing: string, options?: PaidOptions): RouteBuilder<TBody, TQuery, True, False, HasBody>;
267
272
  paid<TBodyIn>(pricing: (body: TBodyIn) => string | Promise<string>, options?: PaidOptions & {
268
- maxPrice: string;
273
+ maxPrice?: string;
269
274
  }): RouteBuilder<TBody, TQuery, True, True, HasBody>;
270
275
  paid(pricing: {
271
276
  field: string;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
2
  import { ZodType } from 'zod';
3
+ import { PaymentRequirements, PaymentRequired, SettleResponse } from '@x402/core/types';
3
4
 
4
5
  interface NonceStore {
5
6
  check(nonce: string): Promise<boolean>;
@@ -82,6 +83,7 @@ interface AlertEvent {
82
83
  meta?: Record<string, unknown>;
83
84
  }
84
85
  type AlertFn = (level: AlertLevel, message: string, meta?: Record<string, unknown>) => void;
86
+
85
87
  interface X402Server {
86
88
  initialize(): Promise<void>;
87
89
  buildPaymentRequirementsFromOptions(options: Array<{
@@ -91,18 +93,18 @@ interface X402Server {
91
93
  payTo: string;
92
94
  }>, context: {
93
95
  request: Request;
94
- }): Promise<unknown[]>;
95
- createPaymentRequiredResponse(requirements: unknown[], resource: {
96
+ }): Promise<PaymentRequirements[]>;
97
+ createPaymentRequiredResponse(requirements: PaymentRequirements[], resource: {
96
98
  url: string;
97
99
  method: string;
98
100
  description?: string;
99
- }, error: string | null, extensions?: Record<string, unknown>): Promise<unknown>;
100
- findMatchingRequirements(requirements: unknown[], payload: unknown): unknown;
101
- verifyPayment(payload: unknown, requirements: unknown): Promise<{
101
+ }, error: string | null, extensions?: Record<string, unknown>): Promise<PaymentRequired>;
102
+ findMatchingRequirements(requirements: PaymentRequirements[], payload: unknown): PaymentRequirements;
103
+ verifyPayment(payload: unknown, requirements: PaymentRequirements): Promise<{
102
104
  isValid: boolean;
103
105
  payer?: string;
104
106
  }>;
105
- settlePayment(payload: unknown, requirements: unknown): Promise<unknown>;
107
+ settlePayment(payload: unknown, requirements: PaymentRequirements): Promise<SettleResponse>;
106
108
  }
107
109
  type ProtocolType = 'x402' | 'mpp';
108
110
  type AuthMode = 'paid' | 'siwx' | 'apiKey' | 'unprotected';
@@ -183,6 +185,8 @@ interface RouterConfig {
183
185
  secretKey: string;
184
186
  currency: string;
185
187
  recipient?: string;
188
+ /** Tempo RPC URL for on-chain verification. Falls back to TEMPO_RPC_URL env var. */
189
+ rpcUrl?: string;
186
190
  };
187
191
  /**
188
192
  * Payment protocols to accept on auto-priced routes (those using the `prices` config).
@@ -239,6 +243,7 @@ interface OrchestrateDeps {
239
243
  secretKey: string;
240
244
  currency: string;
241
245
  recipient?: string;
246
+ rpcUrl?: string;
242
247
  };
243
248
  }
244
249
 
@@ -265,7 +270,7 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, HasAuth extend
265
270
  private fork;
266
271
  paid(pricing: string, options?: PaidOptions): RouteBuilder<TBody, TQuery, True, False, HasBody>;
267
272
  paid<TBodyIn>(pricing: (body: TBodyIn) => string | Promise<string>, options?: PaidOptions & {
268
- maxPrice: string;
273
+ maxPrice?: string;
269
274
  }): RouteBuilder<TBody, TQuery, True, True, HasBody>;
270
275
  paid(pricing: {
271
276
  field: string;