@agentcash/router 1.10.0 → 1.10.1
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 +9 -3
- package/dist/index.cjs +41 -6
- package/dist/index.js +41 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
<a href="#install"><img alt="next.js" src="https://img.shields.io/badge/Next.js-App%20Router-111"></a>
|
|
19
19
|
</p>
|
|
20
20
|
|
|
21
|
+
<p align="center">
|
|
22
|
+
<a href="https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FMerit-Systems%2Fagentcash-router%2Ftree%2Fmain%2Fexamples%2Fvercel-deploy&project-name=agentcash-fortune-demo&repository-name=agentcash-fortune-demo&demo-title=AgentCash%20Router%20Fortune%20Demo&demo-description=Pay-per-call%20fortune%20API%20on%20x402%20and%20MPP&demo-url=https%3A%2F%2Fagentcash.dev&env=EVM_PAYEE_ADDRESS%2CCDP_API_KEY_ID%2CCDP_API_KEY_SECRET&envDescription=Wallet%20that%20receives%20payments%20%2B%20Coinbase%20Developer%20Platform%20API%20keys%20for%20the%20default%20x402%20facilitator.%20Create%20keys%20in%20the%20generous%20CDP%20free%20tier%3A%20https%3A%2F%2Fportal.cdp.coinbase.com%2Fprojects%2Fapi-keys&envLink=https%3A%2F%2Fgithub.com%2FMerit-Systems%2Fagentcash-router%2Fblob%2Fmain%2Fexamples%2Fvercel-deploy%2FREADME.md%23environment-variables">
|
|
23
|
+
<img alt="Deploy with Vercel" src="https://vercel.com/button">
|
|
24
|
+
</a>
|
|
25
|
+
</p>
|
|
26
|
+
|
|
21
27
|
---
|
|
22
28
|
|
|
23
29
|
## Install
|
|
@@ -36,7 +42,7 @@ The recommended entry point reads its config from `process.env`. A copy-paste `.
|
|
|
36
42
|
| Var | Required | Purpose |
|
|
37
43
|
|-----|----------|---------|
|
|
38
44
|
| `EVM_PAYEE_ADDRESS` | yes | EVM address that receives x402 and MPP payments (`0x…`, 20 bytes). Canonicalized to lowercase. The zero address is rejected. |
|
|
39
|
-
| `CDP_API_KEY_ID`, `CDP_API_KEY_SECRET` | yes (EVM) | Coinbase Developer Platform credentials for the default EVM facilitator. Create
|
|
45
|
+
| `CDP_API_KEY_ID`, `CDP_API_KEY_SECRET` | yes (EVM) | Coinbase Developer Platform credentials for the default EVM facilitator. Create API keys in the generous CDP free tier at https://portal.cdp.coinbase.com/projects/api-keys. T3 / `@t3-oss/env-nextjs` users must declare these in their env schema. |
|
|
40
46
|
|
|
41
47
|
### Solana
|
|
42
48
|
|
|
@@ -59,7 +65,8 @@ The recommended entry point reads its config from `process.env`. A copy-paste `.
|
|
|
59
65
|
|
|
60
66
|
| Var | Required | Purpose |
|
|
61
67
|
|-----|----------|---------|
|
|
62
|
-
| `BASE_URL` | yes | Origin URL (`https://api.example.com`). Load-bearing: used as the 402 realm, OpenAPI server URL, and MPP memo prefix. Must match the public domain. |
|
|
68
|
+
| `BASE_URL` | yes outside Vercel | Origin URL (`https://api.example.com`). Load-bearing: used as the 402 realm, OpenAPI server URL, and MPP memo prefix. Must match the public domain. On Vercel, `createRouterFromEnv` auto-derives this from `VERCEL_PROJECT_PRODUCTION_URL`, then `VERCEL_URL`. |
|
|
69
|
+
| `VERCEL_PROJECT_PRODUCTION_URL`, `VERCEL_URL` | no | Vercel system env vars used as fallbacks when `BASE_URL` is unset. Do not set these manually; Vercel provides them during builds and runtime. |
|
|
63
70
|
| `KV_REST_API_URL`, `KV_REST_API_TOKEN` | no | Upstash / Vercel KV. Backs SIWX nonce, SIWX entitlement, and MPP replay. In-memory fallback is unsafe in serverless production. Providing a Kv Store is highly recommended. |
|
|
64
71
|
|
|
65
72
|
## Quick start
|
|
@@ -306,4 +313,3 @@ const loggingPlugin: RouterPlugin = {
|
|
|
306
313
|
},
|
|
307
314
|
};
|
|
308
315
|
```
|
|
309
|
-
|
package/dist/index.cjs
CHANGED
|
@@ -1938,8 +1938,15 @@ function tagBareDecimalAsDollars(amount) {
|
|
|
1938
1938
|
var import_types2 = require("@x402/core/types");
|
|
1939
1939
|
async function verifyX402Payment(opts) {
|
|
1940
1940
|
const { server, request, price, accepts, report } = opts;
|
|
1941
|
-
const
|
|
1942
|
-
if (
|
|
1941
|
+
const payment = await readPaymentPayload(request);
|
|
1942
|
+
if (payment.kind === "none") return null;
|
|
1943
|
+
if (payment.kind === "malformed") {
|
|
1944
|
+
return invalidPaymentVerification({
|
|
1945
|
+
reason: "malformed_payment_header",
|
|
1946
|
+
message: `X-PAYMENT header could not be decoded: ${payment.message}`
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1949
|
+
const payload = payment.payload;
|
|
1943
1950
|
const requirements = await buildExpectedRequirements(server, request, price, accepts, report);
|
|
1944
1951
|
const matching = findVerifiableRequirements(server, requirements, payload);
|
|
1945
1952
|
const accepted = payload.x402Version === 2 ? payload.accepted : void 0;
|
|
@@ -2000,9 +2007,16 @@ function matchesStableFields(requirement, accepted) {
|
|
|
2000
2007
|
}
|
|
2001
2008
|
async function readPaymentPayload(request) {
|
|
2002
2009
|
const paymentHeader = request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY);
|
|
2003
|
-
if (!paymentHeader) return
|
|
2010
|
+
if (!paymentHeader) return { kind: "none" };
|
|
2004
2011
|
const { decodePaymentSignatureHeader } = await import("@x402/core/http");
|
|
2005
|
-
|
|
2012
|
+
try {
|
|
2013
|
+
return { kind: "ok", payload: decodePaymentSignatureHeader(paymentHeader) };
|
|
2014
|
+
} catch (err) {
|
|
2015
|
+
return {
|
|
2016
|
+
kind: "malformed",
|
|
2017
|
+
message: err instanceof Error ? err.message : String(err)
|
|
2018
|
+
};
|
|
2019
|
+
}
|
|
2006
2020
|
}
|
|
2007
2021
|
function invalidPaymentVerification(failure) {
|
|
2008
2022
|
return {
|
|
@@ -3445,6 +3459,7 @@ ${issues}`
|
|
|
3445
3459
|
}
|
|
3446
3460
|
|
|
3447
3461
|
// src/builder.ts
|
|
3462
|
+
var MAX_X402_DESCRIPTION_LENGTH = 400;
|
|
3448
3463
|
var RouteBuilder = class _RouteBuilder {
|
|
3449
3464
|
#s;
|
|
3450
3465
|
constructor(key, registry, deps, defaults) {
|
|
@@ -3961,6 +3976,11 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3961
3976
|
`route '${this.#s.key}': .stream() requires .metered() \u2014 static/free/upto routes can't meter per-chunk billing.`
|
|
3962
3977
|
);
|
|
3963
3978
|
}
|
|
3979
|
+
if (this.#s.description !== void 0 && this.#s.description.length > MAX_X402_DESCRIPTION_LENGTH && this.#s.pricing !== void 0 && this.#s.protocols.includes("x402")) {
|
|
3980
|
+
throw new Error(
|
|
3981
|
+
`route '${this.#s.key}': .description() is ${this.#s.description.length} chars; must be \u2264 ${MAX_X402_DESCRIPTION_LENGTH} chars \u2014 the CDP x402 facilitator rejects payments whose 402 challenge resource.description exceeds ~500 chars.`
|
|
3982
|
+
);
|
|
3983
|
+
}
|
|
3964
3984
|
validateExamples(
|
|
3965
3985
|
this.#s.key,
|
|
3966
3986
|
this.#s.bodySchema,
|
|
@@ -4416,6 +4436,8 @@ var envShape = {
|
|
|
4416
4436
|
params: { code: "invalid_base_url" },
|
|
4417
4437
|
message: "BASE_URL must be a valid URL \u2014 the public origin used as the 402 realm, OpenAPI server URL, and MPP memo prefix. Must match the public domain."
|
|
4418
4438
|
}).optional(),
|
|
4439
|
+
VERCEL_PROJECT_PRODUCTION_URL: import_zod.z.string().optional(),
|
|
4440
|
+
VERCEL_URL: import_zod.z.string().optional(),
|
|
4419
4441
|
EVM_PAYEE_ADDRESS: import_zod.z.string().refine(isEvmAddress, {
|
|
4420
4442
|
params: { code: "invalid_x402_payee", ...x402 },
|
|
4421
4443
|
message: "EVM_PAYEE_ADDRESS must be a 0x-prefixed 20-byte EVM address \u2014 the wallet that receives x402 and MPP payments."
|
|
@@ -4460,7 +4482,7 @@ var EnvInputSchema = import_zod.z.object(envShape).passthrough().superRefine((en
|
|
|
4460
4482
|
addIssue(
|
|
4461
4483
|
ctx,
|
|
4462
4484
|
{ code: "missing_base_url" },
|
|
4463
|
-
"BASE_URL is required \u2014 the public origin used as the 402 realm, OpenAPI server URL, and MPP memo prefix. Set it to your production domain.",
|
|
4485
|
+
"BASE_URL is required \u2014 the public origin used as the 402 realm, OpenAPI server URL, and MPP memo prefix. Set it to your production domain. On Vercel, the router auto-derives it from VERCEL_PROJECT_PRODUCTION_URL or VERCEL_URL.",
|
|
4464
4486
|
["BASE_URL"]
|
|
4465
4487
|
);
|
|
4466
4488
|
}
|
|
@@ -4541,6 +4563,19 @@ function collectKvWarnings(env, kvStoreOptionProvided) {
|
|
|
4541
4563
|
}
|
|
4542
4564
|
return [];
|
|
4543
4565
|
}
|
|
4566
|
+
function withHttps(host) {
|
|
4567
|
+
if (!host) return void 0;
|
|
4568
|
+
return host.startsWith("http://") || host.startsWith("https://") ? host : `https://${host}`;
|
|
4569
|
+
}
|
|
4570
|
+
function deriveBaseUrlEnv(env) {
|
|
4571
|
+
if (env.BASE_URL) return env;
|
|
4572
|
+
const vercelBaseUrl = withHttps(env.VERCEL_PROJECT_PRODUCTION_URL) ?? withHttps(env.VERCEL_URL);
|
|
4573
|
+
if (!vercelBaseUrl) return env;
|
|
4574
|
+
return {
|
|
4575
|
+
...env,
|
|
4576
|
+
BASE_URL: vercelBaseUrl
|
|
4577
|
+
};
|
|
4578
|
+
}
|
|
4544
4579
|
function getConfiguredX402Accepts2(config) {
|
|
4545
4580
|
if (config.x402?.accepts?.length) return [...config.x402.accepts];
|
|
4546
4581
|
return [
|
|
@@ -4709,7 +4744,7 @@ function translateZodIssues(error) {
|
|
|
4709
4744
|
}
|
|
4710
4745
|
function routerConfigFromEnv(options) {
|
|
4711
4746
|
const rawEnv = options.env ?? process.env;
|
|
4712
|
-
const env = trimAll(rawEnv);
|
|
4747
|
+
const env = deriveBaseUrlEnv(trimAll(rawEnv));
|
|
4713
4748
|
const optionIssues = [];
|
|
4714
4749
|
if (!options.title?.trim()) {
|
|
4715
4750
|
optionIssues.push({
|
package/dist/index.js
CHANGED
|
@@ -1897,8 +1897,15 @@ function tagBareDecimalAsDollars(amount) {
|
|
|
1897
1897
|
import { VerifyError } from "@x402/core/types";
|
|
1898
1898
|
async function verifyX402Payment(opts) {
|
|
1899
1899
|
const { server, request, price, accepts, report } = opts;
|
|
1900
|
-
const
|
|
1901
|
-
if (
|
|
1900
|
+
const payment = await readPaymentPayload(request);
|
|
1901
|
+
if (payment.kind === "none") return null;
|
|
1902
|
+
if (payment.kind === "malformed") {
|
|
1903
|
+
return invalidPaymentVerification({
|
|
1904
|
+
reason: "malformed_payment_header",
|
|
1905
|
+
message: `X-PAYMENT header could not be decoded: ${payment.message}`
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
const payload = payment.payload;
|
|
1902
1909
|
const requirements = await buildExpectedRequirements(server, request, price, accepts, report);
|
|
1903
1910
|
const matching = findVerifiableRequirements(server, requirements, payload);
|
|
1904
1911
|
const accepted = payload.x402Version === 2 ? payload.accepted : void 0;
|
|
@@ -1959,9 +1966,16 @@ function matchesStableFields(requirement, accepted) {
|
|
|
1959
1966
|
}
|
|
1960
1967
|
async function readPaymentPayload(request) {
|
|
1961
1968
|
const paymentHeader = request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY);
|
|
1962
|
-
if (!paymentHeader) return
|
|
1969
|
+
if (!paymentHeader) return { kind: "none" };
|
|
1963
1970
|
const { decodePaymentSignatureHeader } = await import("@x402/core/http");
|
|
1964
|
-
|
|
1971
|
+
try {
|
|
1972
|
+
return { kind: "ok", payload: decodePaymentSignatureHeader(paymentHeader) };
|
|
1973
|
+
} catch (err) {
|
|
1974
|
+
return {
|
|
1975
|
+
kind: "malformed",
|
|
1976
|
+
message: err instanceof Error ? err.message : String(err)
|
|
1977
|
+
};
|
|
1978
|
+
}
|
|
1965
1979
|
}
|
|
1966
1980
|
function invalidPaymentVerification(failure) {
|
|
1967
1981
|
return {
|
|
@@ -3404,6 +3418,7 @@ ${issues}`
|
|
|
3404
3418
|
}
|
|
3405
3419
|
|
|
3406
3420
|
// src/builder.ts
|
|
3421
|
+
var MAX_X402_DESCRIPTION_LENGTH = 400;
|
|
3407
3422
|
var RouteBuilder = class _RouteBuilder {
|
|
3408
3423
|
#s;
|
|
3409
3424
|
constructor(key, registry, deps, defaults) {
|
|
@@ -3920,6 +3935,11 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3920
3935
|
`route '${this.#s.key}': .stream() requires .metered() \u2014 static/free/upto routes can't meter per-chunk billing.`
|
|
3921
3936
|
);
|
|
3922
3937
|
}
|
|
3938
|
+
if (this.#s.description !== void 0 && this.#s.description.length > MAX_X402_DESCRIPTION_LENGTH && this.#s.pricing !== void 0 && this.#s.protocols.includes("x402")) {
|
|
3939
|
+
throw new Error(
|
|
3940
|
+
`route '${this.#s.key}': .description() is ${this.#s.description.length} chars; must be \u2264 ${MAX_X402_DESCRIPTION_LENGTH} chars \u2014 the CDP x402 facilitator rejects payments whose 402 challenge resource.description exceeds ~500 chars.`
|
|
3941
|
+
);
|
|
3942
|
+
}
|
|
3923
3943
|
validateExamples(
|
|
3924
3944
|
this.#s.key,
|
|
3925
3945
|
this.#s.bodySchema,
|
|
@@ -4375,6 +4395,8 @@ var envShape = {
|
|
|
4375
4395
|
params: { code: "invalid_base_url" },
|
|
4376
4396
|
message: "BASE_URL must be a valid URL \u2014 the public origin used as the 402 realm, OpenAPI server URL, and MPP memo prefix. Must match the public domain."
|
|
4377
4397
|
}).optional(),
|
|
4398
|
+
VERCEL_PROJECT_PRODUCTION_URL: z.string().optional(),
|
|
4399
|
+
VERCEL_URL: z.string().optional(),
|
|
4378
4400
|
EVM_PAYEE_ADDRESS: z.string().refine(isEvmAddress, {
|
|
4379
4401
|
params: { code: "invalid_x402_payee", ...x402 },
|
|
4380
4402
|
message: "EVM_PAYEE_ADDRESS must be a 0x-prefixed 20-byte EVM address \u2014 the wallet that receives x402 and MPP payments."
|
|
@@ -4419,7 +4441,7 @@ var EnvInputSchema = z.object(envShape).passthrough().superRefine((env, ctx) =>
|
|
|
4419
4441
|
addIssue(
|
|
4420
4442
|
ctx,
|
|
4421
4443
|
{ code: "missing_base_url" },
|
|
4422
|
-
"BASE_URL is required \u2014 the public origin used as the 402 realm, OpenAPI server URL, and MPP memo prefix. Set it to your production domain.",
|
|
4444
|
+
"BASE_URL is required \u2014 the public origin used as the 402 realm, OpenAPI server URL, and MPP memo prefix. Set it to your production domain. On Vercel, the router auto-derives it from VERCEL_PROJECT_PRODUCTION_URL or VERCEL_URL.",
|
|
4423
4445
|
["BASE_URL"]
|
|
4424
4446
|
);
|
|
4425
4447
|
}
|
|
@@ -4500,6 +4522,19 @@ function collectKvWarnings(env, kvStoreOptionProvided) {
|
|
|
4500
4522
|
}
|
|
4501
4523
|
return [];
|
|
4502
4524
|
}
|
|
4525
|
+
function withHttps(host) {
|
|
4526
|
+
if (!host) return void 0;
|
|
4527
|
+
return host.startsWith("http://") || host.startsWith("https://") ? host : `https://${host}`;
|
|
4528
|
+
}
|
|
4529
|
+
function deriveBaseUrlEnv(env) {
|
|
4530
|
+
if (env.BASE_URL) return env;
|
|
4531
|
+
const vercelBaseUrl = withHttps(env.VERCEL_PROJECT_PRODUCTION_URL) ?? withHttps(env.VERCEL_URL);
|
|
4532
|
+
if (!vercelBaseUrl) return env;
|
|
4533
|
+
return {
|
|
4534
|
+
...env,
|
|
4535
|
+
BASE_URL: vercelBaseUrl
|
|
4536
|
+
};
|
|
4537
|
+
}
|
|
4503
4538
|
function getConfiguredX402Accepts2(config) {
|
|
4504
4539
|
if (config.x402?.accepts?.length) return [...config.x402.accepts];
|
|
4505
4540
|
return [
|
|
@@ -4668,7 +4703,7 @@ function translateZodIssues(error) {
|
|
|
4668
4703
|
}
|
|
4669
4704
|
function routerConfigFromEnv(options) {
|
|
4670
4705
|
const rawEnv = options.env ?? process.env;
|
|
4671
|
-
const env = trimAll(rawEnv);
|
|
4706
|
+
const env = deriveBaseUrlEnv(trimAll(rawEnv));
|
|
4672
4707
|
const optionIssues = [];
|
|
4673
4708
|
if (!options.title?.trim()) {
|
|
4674
4709
|
optionIssues.push({
|