@agent-score/commerce 1.8.0 → 2.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 +73 -9
- package/dist/{_response-BMt2y4Or.d.mts → _response-BFYN3b6i.d.mts} +19 -22
- package/dist/{_response-DyJ3mWI3.d.ts → _response-_iPD5AIj.d.ts} +19 -22
- package/dist/challenge/index.d.mts +106 -198
- package/dist/challenge/index.d.ts +106 -198
- package/dist/challenge/index.js +238 -111
- package/dist/challenge/index.js.map +1 -1
- package/dist/challenge/index.mjs +238 -111
- package/dist/challenge/index.mjs.map +1 -1
- package/dist/checkout-BoFwnVsj.d.ts +931 -0
- package/dist/checkout-DRbQ0Fsh.d.mts +931 -0
- package/dist/core.d.mts +2 -2
- package/dist/core.d.ts +2 -2
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +1 -1
- package/dist/core.mjs.map +1 -1
- package/dist/discovery/index.d.mts +453 -51
- package/dist/discovery/index.d.ts +453 -51
- package/dist/discovery/index.js +1092 -58
- package/dist/discovery/index.js.map +1 -1
- package/dist/discovery/index.mjs +1060 -57
- package/dist/discovery/index.mjs.map +1 -1
- package/dist/identity/express.d.mts +3 -3
- package/dist/identity/express.d.ts +3 -3
- package/dist/identity/express.js +30 -19
- package/dist/identity/express.js.map +1 -1
- package/dist/identity/express.mjs +30 -19
- package/dist/identity/express.mjs.map +1 -1
- package/dist/identity/fastify.d.mts +4 -4
- package/dist/identity/fastify.d.ts +4 -4
- package/dist/identity/fastify.js +30 -19
- package/dist/identity/fastify.js.map +1 -1
- package/dist/identity/fastify.mjs +30 -19
- package/dist/identity/fastify.mjs.map +1 -1
- package/dist/identity/hono.d.mts +3 -3
- package/dist/identity/hono.d.ts +3 -3
- package/dist/identity/hono.js +30 -19
- package/dist/identity/hono.js.map +1 -1
- package/dist/identity/hono.mjs +30 -19
- package/dist/identity/hono.mjs.map +1 -1
- package/dist/identity/nextjs.d.mts +6 -7
- package/dist/identity/nextjs.d.ts +6 -7
- package/dist/identity/nextjs.js +30 -19
- package/dist/identity/nextjs.js.map +1 -1
- package/dist/identity/nextjs.mjs +30 -19
- package/dist/identity/nextjs.mjs.map +1 -1
- package/dist/identity/policy.d.mts +41 -4
- package/dist/identity/policy.d.ts +41 -4
- package/dist/identity/policy.js +3662 -18
- package/dist/identity/policy.js.map +1 -1
- package/dist/identity/policy.mjs +3648 -3
- package/dist/identity/policy.mjs.map +1 -1
- package/dist/identity/web.d.mts +3 -3
- package/dist/identity/web.d.ts +3 -3
- package/dist/identity/web.js +30 -19
- package/dist/identity/web.js.map +1 -1
- package/dist/identity/web.mjs +30 -19
- package/dist/identity/web.mjs.map +1 -1
- package/dist/index.d.mts +72 -329
- package/dist/index.d.ts +72 -329
- package/dist/index.js +3651 -373
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3628 -361
- package/dist/index.mjs.map +1 -1
- package/dist/payment/index.d.mts +257 -266
- package/dist/payment/index.d.ts +257 -266
- package/dist/payment/index.js +586 -149
- package/dist/payment/index.js.map +1 -1
- package/dist/payment/index.mjs +573 -148
- package/dist/payment/index.mjs.map +1 -1
- package/dist/{agent_instructions-DiMSGkdm.d.mts → pricing-CQ9DIFaw.d.ts} +109 -56
- package/dist/{agent_instructions-DiMSGkdm.d.ts → pricing-CxzwyiO6.d.mts} +109 -56
- package/dist/rail_spec-XP0wKgJV.d.mts +132 -0
- package/dist/rail_spec-XP0wKgJV.d.ts +132 -0
- package/dist/{signer-CFVQsWjL.d.mts → signer-3FAit11j.d.mts} +27 -1
- package/dist/{signer-CFVQsWjL.d.ts → signer-3FAit11j.d.ts} +27 -1
- package/dist/solana-Cds87OTu.d.mts +67 -0
- package/dist/solana-Cds87OTu.d.ts +67 -0
- package/dist/stripe-multichain/index.d.mts +56 -67
- package/dist/stripe-multichain/index.d.ts +56 -67
- package/dist/stripe-multichain/index.js +68 -42
- package/dist/stripe-multichain/index.js.map +1 -1
- package/dist/stripe-multichain/index.mjs +68 -41
- package/dist/stripe-multichain/index.mjs.map +1 -1
- package/dist/{wwwauthenticate-CU1eNvMQ.d.mts → wwwauthenticate-D_FMnPgU.d.mts} +9 -10
- package/dist/{wwwauthenticate-CU1eNvMQ.d.ts → wwwauthenticate-D_FMnPgU.d.ts} +9 -10
- package/dist/x402_server-hgQzWQwB.d.mts +81 -0
- package/dist/x402_server-hgQzWQwB.d.ts +81 -0
- package/package.json +13 -9
package/dist/index.js
CHANGED
|
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
8
11
|
var __export = (target, all) => {
|
|
9
12
|
for (var name in all)
|
|
10
13
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -15,22 +18,1162 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
18
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
19
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
20
|
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
|
|
33
|
+
// src/identity/ucp.ts
|
|
34
|
+
function ucpSigningKeyFromJWKImpl(jwk) {
|
|
35
|
+
if (!jwk || typeof jwk !== "object") {
|
|
36
|
+
throw new Error(`UCPSigningKey.fromJWK expected a non-null object; got ${typeof jwk}.`);
|
|
37
|
+
}
|
|
38
|
+
if (typeof jwk.kid !== "string" || !jwk.kid) {
|
|
39
|
+
throw new Error("UCPSigningKey.fromJWK: JWK missing required field `kid` (or non-string).");
|
|
40
|
+
}
|
|
41
|
+
if (typeof jwk.kty !== "string" || !jwk.kty) {
|
|
42
|
+
throw new Error("UCPSigningKey.fromJWK: JWK missing required field `kty` (or non-string).");
|
|
43
|
+
}
|
|
44
|
+
if (jwk.kty !== "OKP" && jwk.kty !== "EC" && jwk.kty !== "RSA") {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`UCPSigningKey.fromJWK: kty=${JSON.stringify(jwk.kty)} is not a supported asymmetric key type (expected OKP, EC, or RSA). Symmetric \`oct\` keys are rejected because they cannot publicly verify a JWS in the trust-mode UCP flow.`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
if ((jwk.kty === "EC" || jwk.kty === "OKP") && (typeof jwk.crv !== "string" || !jwk.crv)) {
|
|
50
|
+
throw new Error(`UCPSigningKey.fromJWK: kty=${jwk.kty} requires a non-empty \`crv\` field (e.g., "P-256" for EC, "Ed25519" for OKP).`);
|
|
51
|
+
}
|
|
52
|
+
return jwk;
|
|
53
|
+
}
|
|
54
|
+
function buildUCPProfile(input) {
|
|
55
|
+
for (const [name, bindings] of Object.entries(input.services ?? {})) {
|
|
56
|
+
for (const binding of bindings) {
|
|
57
|
+
if ((binding.transport === "rest" || binding.transport === "mcp" || binding.transport === "a2a") && (binding.endpoint === void 0 || binding.endpoint === null || binding.endpoint === "")) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`buildUCPProfile: service "${name}" transport=${binding.transport} requires \`endpoint\`. Per UCP spec service.json business_schema, rest/mcp/a2a bindings MUST carry an endpoint URL.`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const paymentHandlers = {};
|
|
65
|
+
for (const [name, bindings] of Object.entries(input.payment_handlers ?? {})) {
|
|
66
|
+
paymentHandlers[name] = bindings.map((binding) => {
|
|
67
|
+
if (Array.isArray(binding.available_instruments) && binding.available_instruments.length === 0) {
|
|
68
|
+
const { available_instruments: _drop, ...rest } = binding;
|
|
69
|
+
return rest;
|
|
70
|
+
}
|
|
71
|
+
return binding;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const capabilities = {};
|
|
75
|
+
for (const [name, bindings] of Object.entries(input.capabilities ?? {})) {
|
|
76
|
+
capabilities[name] = [...bindings];
|
|
77
|
+
}
|
|
78
|
+
if (input.agentscore_gate) {
|
|
79
|
+
const gateConfig = { ...input.agentscore_gate };
|
|
80
|
+
const agentscoreBinding = {
|
|
81
|
+
version: AGENTSCORE_CAPABILITY_VERSION,
|
|
82
|
+
spec: input.agentscore_spec_url ?? AGENTSCORE_DEFAULT_SPEC_URL,
|
|
83
|
+
schema: input.agentscore_schema_url ?? AGENTSCORE_DEFAULT_SCHEMA_URL,
|
|
84
|
+
extends: AGENTSCORE_EXTENDS
|
|
85
|
+
};
|
|
86
|
+
if (Object.keys(gateConfig).length > 0) agentscoreBinding.config = gateConfig;
|
|
87
|
+
const existing = capabilities[AGENTSCORE_CAPABILITY_NAME];
|
|
88
|
+
if (existing) existing.push(agentscoreBinding);
|
|
89
|
+
else capabilities[AGENTSCORE_CAPABILITY_NAME] = [agentscoreBinding];
|
|
90
|
+
}
|
|
91
|
+
const ucp = {
|
|
92
|
+
version: input.version ?? DEFAULT_VERSION,
|
|
93
|
+
services: input.services ?? {},
|
|
94
|
+
capabilities,
|
|
95
|
+
payment_handlers: paymentHandlers
|
|
96
|
+
};
|
|
97
|
+
if (input.name !== void 0) ucp.name = input.name;
|
|
98
|
+
if (input.supported_versions !== void 0) ucp.supported_versions = input.supported_versions;
|
|
99
|
+
if (input.ucp_extras) {
|
|
100
|
+
for (const k of Object.keys(input.ucp_extras)) {
|
|
101
|
+
if (RESERVED_UCP_FIELDS.has(k)) {
|
|
102
|
+
throw new Error(`buildUCPProfile: ucp_extras key "${k}" collides with a reserved \`ucp\` field; rejected.`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
Object.assign(ucp, input.ucp_extras);
|
|
106
|
+
}
|
|
107
|
+
const profile = {
|
|
108
|
+
ucp,
|
|
109
|
+
signing_keys: input.signing_keys
|
|
110
|
+
};
|
|
111
|
+
if (input.extras) {
|
|
112
|
+
for (const k of Object.keys(input.extras)) {
|
|
113
|
+
if (RESERVED_TOP_LEVEL.has(k)) {
|
|
114
|
+
throw new Error(`buildUCPProfile: extras key "${k}" collides with a reserved profile field; rejected.`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
Object.assign(profile, input.extras);
|
|
118
|
+
}
|
|
119
|
+
return profile;
|
|
120
|
+
}
|
|
121
|
+
function ucpNetworkName(caip2OrUcp, fallback) {
|
|
122
|
+
if (caip2OrUcp === void 0) return fallback;
|
|
123
|
+
return CAIP2_TO_UCP_NETWORK[caip2OrUcp] ?? caip2OrUcp;
|
|
124
|
+
}
|
|
125
|
+
function staticRecipient(r) {
|
|
126
|
+
return typeof r === "string" && r.length > 0 ? r : void 0;
|
|
127
|
+
}
|
|
128
|
+
function tempoToNetworkEntry(spec) {
|
|
129
|
+
const entry = {
|
|
130
|
+
network: spec.testnet ? "tempo-testnet" : spec.network ?? "tempo-mainnet",
|
|
131
|
+
chain_id: spec.chainId ?? 4217
|
|
132
|
+
};
|
|
133
|
+
const recipient = staticRecipient(spec.recipient);
|
|
134
|
+
if (recipient !== void 0) entry.recipient = recipient;
|
|
135
|
+
return entry;
|
|
136
|
+
}
|
|
137
|
+
function solanaMppToNetworkEntry(spec) {
|
|
138
|
+
const entry = {
|
|
139
|
+
network: ucpNetworkName(spec.network, "solana-mainnet-beta")
|
|
140
|
+
};
|
|
141
|
+
const recipient = staticRecipient(spec.recipient);
|
|
142
|
+
if (recipient !== void 0) entry.recipient = recipient;
|
|
143
|
+
return entry;
|
|
144
|
+
}
|
|
145
|
+
function tempoSessionToNetworkEntry(spec) {
|
|
146
|
+
const entry = {
|
|
147
|
+
network: spec.testnet ? "tempo-testnet" : "tempo-mainnet",
|
|
148
|
+
escrow_contract: spec.escrowContract
|
|
149
|
+
};
|
|
150
|
+
const recipient = staticRecipient(spec.recipient);
|
|
151
|
+
if (recipient !== void 0) entry.recipient = recipient;
|
|
152
|
+
return entry;
|
|
153
|
+
}
|
|
154
|
+
function isTempoRailSpec(s) {
|
|
155
|
+
return !("escrowContract" in s) && !("rpcUrl" in s) && !("tokenProgram" in s);
|
|
156
|
+
}
|
|
157
|
+
function isTempoSessionRailSpec(s) {
|
|
158
|
+
return "escrowContract" in s && "store" in s;
|
|
159
|
+
}
|
|
160
|
+
function mppRailToNetworkEntry(spec) {
|
|
161
|
+
if (isTempoSessionRailSpec(spec)) return tempoSessionToNetworkEntry(spec);
|
|
162
|
+
if ("rpcUrl" in spec || "tokenProgram" in spec || (spec.network?.startsWith("solana:") ?? false)) {
|
|
163
|
+
return solanaMppToNetworkEntry(spec);
|
|
164
|
+
}
|
|
165
|
+
if (isTempoRailSpec(spec)) return tempoToNetworkEntry(spec);
|
|
166
|
+
return tempoToNetworkEntry(spec);
|
|
167
|
+
}
|
|
168
|
+
function mppPaymentHandler({
|
|
169
|
+
networks: networks2
|
|
170
|
+
}) {
|
|
171
|
+
return {
|
|
172
|
+
"sh.agentscore.payment.mpp": [{
|
|
173
|
+
id: "mpp",
|
|
174
|
+
version: HANDLER_VERSION,
|
|
175
|
+
spec: `${SPEC_BASE}/mpp`,
|
|
176
|
+
schema: `${SCHEMA_BASE}/mpp.json`,
|
|
177
|
+
config: { networks: networks2.map(mppRailToNetworkEntry) }
|
|
178
|
+
}]
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function x402RailToNetworkEntry(spec) {
|
|
182
|
+
const entry = {
|
|
183
|
+
network: ucpNetworkName(spec.network, "base-8453")
|
|
184
|
+
};
|
|
185
|
+
const recipient = staticRecipient(spec.recipient);
|
|
186
|
+
if (recipient !== void 0) entry.recipient = recipient;
|
|
187
|
+
return entry;
|
|
188
|
+
}
|
|
189
|
+
function x402PaymentHandler({
|
|
190
|
+
networks: networks2
|
|
191
|
+
}) {
|
|
192
|
+
return {
|
|
193
|
+
"sh.agentscore.payment.x402": [{
|
|
194
|
+
id: "x402",
|
|
195
|
+
version: HANDLER_VERSION,
|
|
196
|
+
spec: `${SPEC_BASE}/x402`,
|
|
197
|
+
schema: `${SCHEMA_BASE}/x402.json`,
|
|
198
|
+
config: { networks: networks2.map(x402RailToNetworkEntry) }
|
|
199
|
+
}]
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function stripeSptPaymentHandler({
|
|
203
|
+
spec
|
|
204
|
+
}) {
|
|
205
|
+
return {
|
|
206
|
+
"sh.agentscore.payment.stripe_spt": [{
|
|
207
|
+
id: "stripe-spt",
|
|
208
|
+
version: HANDLER_VERSION,
|
|
209
|
+
spec: `${SPEC_BASE}/stripe_spt`,
|
|
210
|
+
schema: `${SCHEMA_BASE}/stripe_spt.json`,
|
|
211
|
+
config: { rail: "stripe-spt", profile_id: spec.profileId ?? null }
|
|
212
|
+
}]
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
var UCPSigningKey, DEFAULT_VERSION, AGENTSCORE_CAPABILITY_NAME, AGENTSCORE_CAPABILITY_VERSION, AGENTSCORE_DEFAULT_SPEC_URL, AGENTSCORE_DEFAULT_SCHEMA_URL, AGENTSCORE_EXTENDS, RESERVED_TOP_LEVEL, RESERVED_UCP_FIELDS, AGENTSCORE_UCP_CAPABILITY, HANDLER_VERSION, SPEC_BASE, SCHEMA_BASE, CAIP2_TO_UCP_NETWORK;
|
|
216
|
+
var init_ucp = __esm({
|
|
217
|
+
"src/identity/ucp.ts"() {
|
|
218
|
+
"use strict";
|
|
219
|
+
UCPSigningKey = {
|
|
220
|
+
fromJWK: ucpSigningKeyFromJWKImpl
|
|
221
|
+
};
|
|
222
|
+
DEFAULT_VERSION = "2026-04-08";
|
|
223
|
+
AGENTSCORE_CAPABILITY_NAME = "sh.agentscore.identity";
|
|
224
|
+
AGENTSCORE_CAPABILITY_VERSION = "2026-04-08";
|
|
225
|
+
AGENTSCORE_DEFAULT_SPEC_URL = "https://agentscore.sh/specification/identity";
|
|
226
|
+
AGENTSCORE_DEFAULT_SCHEMA_URL = "https://agentscore.sh/schemas/ucp/sh-agentscore-identity-v1.json";
|
|
227
|
+
AGENTSCORE_EXTENDS = ["dev.ucp.shopping.checkout", "dev.ucp.shopping.cart"];
|
|
228
|
+
RESERVED_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
229
|
+
"ucp",
|
|
230
|
+
"signing_keys",
|
|
231
|
+
"signature",
|
|
232
|
+
"__proto__",
|
|
233
|
+
"constructor",
|
|
234
|
+
"prototype"
|
|
235
|
+
]);
|
|
236
|
+
RESERVED_UCP_FIELDS = /* @__PURE__ */ new Set([
|
|
237
|
+
"version",
|
|
238
|
+
"name",
|
|
239
|
+
"services",
|
|
240
|
+
"capabilities",
|
|
241
|
+
"payment_handlers",
|
|
242
|
+
"supported_versions",
|
|
243
|
+
"__proto__",
|
|
244
|
+
"constructor",
|
|
245
|
+
"prototype"
|
|
246
|
+
]);
|
|
247
|
+
AGENTSCORE_UCP_CAPABILITY = AGENTSCORE_CAPABILITY_NAME;
|
|
248
|
+
HANDLER_VERSION = "2026-04-08";
|
|
249
|
+
SPEC_BASE = "https://agentscore.sh/specification/payment-handlers";
|
|
250
|
+
SCHEMA_BASE = "https://agentscore.sh/schemas/payment-handlers";
|
|
251
|
+
CAIP2_TO_UCP_NETWORK = {
|
|
252
|
+
"eip155:8453": "base-8453",
|
|
253
|
+
"eip155:84532": "base-84532",
|
|
254
|
+
"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": "solana-mainnet-beta",
|
|
255
|
+
"solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1": "solana-devnet"
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// src/identity/ucp-jwks.ts
|
|
261
|
+
async function loadJose() {
|
|
262
|
+
try {
|
|
263
|
+
return await import("jose");
|
|
264
|
+
} catch (err) {
|
|
265
|
+
throw new Error(
|
|
266
|
+
`UCP signing requires the \`jose\` library, which is an optional peer dependency. ${JOSE_INSTALL_HINT}
|
|
267
|
+
Original error: ${err instanceof Error ? err.message : String(err)}`
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function canonicalizeProfile(profile) {
|
|
272
|
+
const stripped = { ...profile };
|
|
273
|
+
delete stripped.signature;
|
|
274
|
+
return stableStringify(stripped);
|
|
275
|
+
}
|
|
276
|
+
function stableStringify(value) {
|
|
277
|
+
if (value === void 0) {
|
|
278
|
+
throw new Error(
|
|
279
|
+
"stableStringify: undefined values are not allowed in canonicalized JSON. Object fields with no value must be omitted."
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
if (typeof value === "function" || typeof value === "symbol") {
|
|
283
|
+
throw new Error(`stableStringify: ${typeof value} values are not allowed in canonicalized JSON.`);
|
|
284
|
+
}
|
|
285
|
+
if (typeof value === "bigint") {
|
|
286
|
+
throw new Error("stableStringify: BigInt values are not allowed; use a decimal string.");
|
|
287
|
+
}
|
|
288
|
+
if (value instanceof Date) {
|
|
289
|
+
throw new Error(
|
|
290
|
+
"stableStringify: Date instances are not allowed; serialize to an ISO string before passing."
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
if (value instanceof Map || value instanceof Set || value instanceof WeakMap || value instanceof WeakSet) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
`stableStringify: ${value.constructor.name} values are not allowed; convert to a plain object/array first.`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
if (ArrayBuffer.isView(value)) {
|
|
299
|
+
throw new Error("stableStringify: typed arrays are not allowed; convert to a plain array first.");
|
|
300
|
+
}
|
|
301
|
+
if (typeof value === "number") {
|
|
302
|
+
if (!Number.isFinite(value)) {
|
|
303
|
+
throw new Error(
|
|
304
|
+
`UCP profile canonicalization rejects non-finite Number ${value}. Use a decimal string for any value that may be NaN/Infinity.`
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
if (!Number.isInteger(value)) {
|
|
308
|
+
throw new Error(
|
|
309
|
+
`UCP profile canonicalization rejects non-integer Number ${value}. Use a decimal string (e.g. "9.99") for monetary or fractional fields to preserve cross-language byte-parity.`
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
if (!Number.isSafeInteger(value)) {
|
|
313
|
+
throw new Error(
|
|
314
|
+
`stableStringify: integer ${value} exceeds Number.MAX_SAFE_INTEGER. For values >2^53, use a decimal string to preserve cross-language byte parity.`
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (typeof value === "string") {
|
|
319
|
+
if (value.includes("\u2028") || value.includes("\u2029")) {
|
|
320
|
+
throw new Error(
|
|
321
|
+
"stableStringify: strings containing U+2028 (LINE SEPARATOR) or U+2029 (PARAGRAPH SEPARATOR) are not allowed; cross-language byte parity requires neither be present (Node JSON.stringify on older V8 escapes them; Python json.dumps with ensure_ascii=False does not)."
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
return JSON.stringify(value);
|
|
325
|
+
}
|
|
326
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
327
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
328
|
+
const obj = value;
|
|
329
|
+
const keys = Object.keys(obj).sort((a, b) => {
|
|
330
|
+
const aPoints = [...a].map((c) => c.codePointAt(0));
|
|
331
|
+
const bPoints = [...b].map((c) => c.codePointAt(0));
|
|
332
|
+
const len = Math.min(aPoints.length, bPoints.length);
|
|
333
|
+
for (let i = 0; i < len; i += 1) {
|
|
334
|
+
if (aPoints[i] !== bPoints[i]) return aPoints[i] - bPoints[i];
|
|
335
|
+
}
|
|
336
|
+
return aPoints.length - bPoints.length;
|
|
337
|
+
});
|
|
338
|
+
for (const k of keys) {
|
|
339
|
+
if (k.includes("\u2028") || k.includes("\u2029")) {
|
|
340
|
+
throw new Error(
|
|
341
|
+
"stableStringify: object keys containing U+2028 (LINE SEPARATOR) or U+2029 (PARAGRAPH SEPARATOR) are not allowed; cross-language byte parity (Node JSON.stringify on older V8 escapes them; Python json.dumps with ensure_ascii=False does not)."
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
const pairs = keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`);
|
|
346
|
+
return `{${pairs.join(",")}}`;
|
|
347
|
+
}
|
|
348
|
+
async function generateUCPSigningKey(opts) {
|
|
349
|
+
const jose = await loadJose();
|
|
350
|
+
const alg = opts.alg ?? "EdDSA";
|
|
351
|
+
const { privateKey, publicKey } = await jose.generateKeyPair(alg, { extractable: true });
|
|
352
|
+
const exportedJwk = await jose.exportJWK(publicKey);
|
|
353
|
+
const publicJWK = {
|
|
354
|
+
kid: opts.kid,
|
|
355
|
+
alg,
|
|
356
|
+
use: "sig",
|
|
357
|
+
...exportedJwk
|
|
358
|
+
};
|
|
359
|
+
return { privateKey, publicJWK };
|
|
360
|
+
}
|
|
361
|
+
async function signUCPProfile(profile, {
|
|
362
|
+
signingKey,
|
|
363
|
+
kid,
|
|
364
|
+
alg = "EdDSA"
|
|
365
|
+
}) {
|
|
366
|
+
const jose = await loadJose();
|
|
367
|
+
if (!ALLOWED_ALGS.includes(alg)) {
|
|
368
|
+
throw new Error(
|
|
369
|
+
`signUCPProfile: alg ${JSON.stringify(alg)} is not in the supported set [${ALLOWED_ALGS.join(", ")}].`
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
if (typeof kid !== "string" || !kid) {
|
|
373
|
+
throw new Error("signUCPProfile: kid must be a non-empty string.");
|
|
374
|
+
}
|
|
375
|
+
const kids = (profile.signing_keys ?? []).map((k) => k.kid);
|
|
376
|
+
if (!kids.includes(kid)) {
|
|
377
|
+
throw new Error(
|
|
378
|
+
`signUCPProfile: kid ${JSON.stringify(kid)} is not present in profile.signing_keys[] (declared kids: ${JSON.stringify(kids)}). Verifiers will not find the key.`
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
const canonicalBody = canonicalizeProfile(profile);
|
|
382
|
+
const payloadBytes = new TextEncoder().encode(canonicalBody);
|
|
383
|
+
const signature = await new jose.CompactSign(payloadBytes).setProtectedHeader({ alg, kid, typ: PROFILE_TYP }).sign(signingKey);
|
|
384
|
+
return { ...profile, signature };
|
|
385
|
+
}
|
|
386
|
+
async function verifyUCPProfile(profile, jwks) {
|
|
387
|
+
if (profile === null || typeof profile !== "object" || Array.isArray(profile)) {
|
|
388
|
+
throw new UCPVerificationError(
|
|
389
|
+
"no_signature",
|
|
390
|
+
`UCP profile must be a JSON object; got ${profile === null ? "null" : Array.isArray(profile) ? "array" : typeof profile}.`
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
const jose = await loadJose();
|
|
394
|
+
if (!jwks || typeof jwks !== "object" || !Array.isArray(jwks.keys)) {
|
|
395
|
+
throw new UCPVerificationError(
|
|
396
|
+
"malformed_jwks",
|
|
397
|
+
`UCP verifier expected JWKS shape { keys: [...] }; got ${jwks === null ? "null" : typeof jwks === "object" ? "object without keys[] array" : typeof jwks}.`
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
const stripped = { ...profile };
|
|
401
|
+
const sig = stripped.signature;
|
|
402
|
+
delete stripped.signature;
|
|
403
|
+
if (typeof sig !== "string" || !sig) {
|
|
404
|
+
throw new UCPVerificationError(
|
|
405
|
+
"no_signature",
|
|
406
|
+
`UCP profile signature must be a non-empty string; got ${sig === void 0 ? "undefined" : typeof sig}.`
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
let header;
|
|
410
|
+
try {
|
|
411
|
+
const protectedB64 = sig.split(".")[0];
|
|
412
|
+
if (!protectedB64) throw new Error("JWS protected header segment is empty.");
|
|
413
|
+
const headerJson = new TextDecoder().decode(jose.base64url.decode(protectedB64));
|
|
414
|
+
const parsed = JSON.parse(headerJson);
|
|
415
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
416
|
+
throw new Error("JWS protected header is not a JSON object.");
|
|
417
|
+
}
|
|
418
|
+
header = parsed;
|
|
419
|
+
} catch (err) {
|
|
420
|
+
throw new UCPVerificationError(
|
|
421
|
+
"malformed_jws",
|
|
422
|
+
`JWS protected header is not valid base64url-encoded JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
if (header.typ !== PROFILE_TYP) {
|
|
426
|
+
throw new UCPVerificationError("wrong_typ", `UCP signature typ must be "${PROFILE_TYP}"; got ${String(header.typ)}.`);
|
|
427
|
+
}
|
|
428
|
+
if (!ALLOWED_ALGS.includes(header.alg)) {
|
|
429
|
+
throw new UCPVerificationError("unsupported_alg", `UCP signing alg must be one of ${ALLOWED_ALGS.join(", ")}; got ${String(header.alg)}.`);
|
|
430
|
+
}
|
|
431
|
+
if (typeof header.kid !== "string" || !header.kid) {
|
|
432
|
+
throw new UCPVerificationError(
|
|
433
|
+
"missing_kid",
|
|
434
|
+
`UCP signature header kid must be a non-empty string; got ${header.kid === void 0 ? "undefined" : typeof header.kid}.`
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
if ("crit" in header) {
|
|
438
|
+
const crit = header.crit;
|
|
439
|
+
if (!Array.isArray(crit) || crit.length === 0 || !crit.every((c) => typeof c === "string")) {
|
|
440
|
+
throw new UCPVerificationError(
|
|
441
|
+
"malformed_jws",
|
|
442
|
+
`JWS protected header crit must be a non-empty array of strings; got ${JSON.stringify(crit)}.`
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
throw new UCPVerificationError(
|
|
446
|
+
"unrecognized_critical_header",
|
|
447
|
+
`JWS protected header advertises unrecognized crit headers: ${JSON.stringify(crit)}.`
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
let signedPayload;
|
|
451
|
+
try {
|
|
452
|
+
const verified = await jose.compactVerify(
|
|
453
|
+
sig,
|
|
454
|
+
async (h) => {
|
|
455
|
+
const kid = h.kid;
|
|
456
|
+
if (typeof kid !== "string" || !kid) {
|
|
457
|
+
throw new UCPVerificationError(
|
|
458
|
+
"missing_kid",
|
|
459
|
+
`UCP signature header kid must be a non-empty string; got ${kid === void 0 ? "undefined" : typeof kid}.`
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
const matches = jwks.keys.filter(
|
|
463
|
+
(k) => k != null && typeof k === "object" && k.kid === kid
|
|
464
|
+
);
|
|
465
|
+
if (matches.length === 0) throw new UCPVerificationError("kid_not_found", `No JWK in JWKS matching kid=${JSON.stringify(kid)}.`);
|
|
466
|
+
if (matches.length > 1) throw new UCPVerificationError("duplicate_kid", `JWKS contains ${matches.length} keys with kid=${JSON.stringify(kid)}; expected exactly one.`);
|
|
467
|
+
const matchedKey = matches[0];
|
|
468
|
+
if (matchedKey.use != null && matchedKey.use !== "sig") {
|
|
469
|
+
throw new UCPVerificationError("unusable_key", `JWK with kid=${kid} has use=${JSON.stringify(matchedKey.use)}; expected "sig".`);
|
|
470
|
+
}
|
|
471
|
+
if (matchedKey.alg != null && matchedKey.alg !== h.alg) {
|
|
472
|
+
throw new UCPVerificationError(
|
|
473
|
+
"unusable_key",
|
|
474
|
+
`JWK alg ${JSON.stringify(matchedKey.alg)} does not match JWS header alg ${JSON.stringify(h.alg)}.`
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
return jose.importJWK(matches[0], h.alg);
|
|
478
|
+
}
|
|
479
|
+
);
|
|
480
|
+
signedPayload = verified.payload;
|
|
481
|
+
} catch (err) {
|
|
482
|
+
if (err instanceof UCPVerificationError) throw err;
|
|
483
|
+
if (err instanceof Error && err.name === "JOSEAlgNotAllowed") {
|
|
484
|
+
throw new UCPVerificationError("unsupported_alg", `UCP signing alg not allowed: ${err.message}`);
|
|
485
|
+
}
|
|
486
|
+
if (err instanceof Error && err.name === "JWSSignatureVerificationFailed") {
|
|
487
|
+
throw new UCPVerificationError("signature_invalid", `UCP signature verification failed: ${err.message}`);
|
|
488
|
+
}
|
|
489
|
+
if (err instanceof Error && err.name === "JWSInvalid") {
|
|
490
|
+
throw new UCPVerificationError("malformed_jws", `Malformed JWS: ${err.message}`);
|
|
491
|
+
}
|
|
492
|
+
if (err instanceof Error && err.name === "JOSENotSupported") {
|
|
493
|
+
throw new UCPVerificationError("unrecognized_critical_header", `UCP signing rejected unrecognized critical header: ${err.message}`);
|
|
494
|
+
}
|
|
495
|
+
throw err;
|
|
496
|
+
}
|
|
497
|
+
let canonicalBody;
|
|
498
|
+
try {
|
|
499
|
+
canonicalBody = canonicalizeProfile(stripped);
|
|
500
|
+
} catch (err) {
|
|
501
|
+
throw new UCPVerificationError(
|
|
502
|
+
"body_mismatch",
|
|
503
|
+
`Failed to canonicalize received profile for verification: ${err instanceof Error ? err.message : String(err)}`
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
const expectedPayload = new TextEncoder().encode(canonicalBody);
|
|
507
|
+
if (!constantTimeEqual(signedPayload, expectedPayload)) {
|
|
508
|
+
throw new UCPVerificationError("body_mismatch", "UCP profile body does not match the signed payload (tampered or non-canonical).");
|
|
509
|
+
}
|
|
510
|
+
return true;
|
|
511
|
+
}
|
|
512
|
+
function constantTimeEqual(a, b) {
|
|
513
|
+
if (a.length !== b.length) return false;
|
|
514
|
+
let diff = 0;
|
|
515
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
516
|
+
diff |= a[i] ^ b[i];
|
|
517
|
+
}
|
|
518
|
+
return diff === 0;
|
|
519
|
+
}
|
|
520
|
+
function buildJWKSResponse(keys) {
|
|
521
|
+
return { keys };
|
|
522
|
+
}
|
|
523
|
+
function readEnvTrimmed(name) {
|
|
524
|
+
const raw = process.env[name];
|
|
525
|
+
if (raw === void 0) return void 0;
|
|
526
|
+
const trimmed = raw.trim();
|
|
527
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
528
|
+
}
|
|
529
|
+
function detectAlgFromJwk(jwk) {
|
|
530
|
+
if (jwk.kty === "OKP" && jwk.crv === "Ed25519") return "EdDSA";
|
|
531
|
+
if (jwk.kty === "EC" && jwk.crv === "P-256") return "ES256";
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
function cacheKey(opts) {
|
|
535
|
+
return `${opts.envJwkVar}|${opts.envKidVar}|${opts.envAlgVar}|${opts.defaultKid}|${opts.defaultAlg}`;
|
|
536
|
+
}
|
|
537
|
+
async function buildEnvSigningKey(opts) {
|
|
538
|
+
const kidDefault = readEnvTrimmed(opts.envKidVar) ?? opts.defaultKid;
|
|
539
|
+
const rawAlg = (readEnvTrimmed(opts.envAlgVar) ?? "").toUpperCase();
|
|
540
|
+
const algFallback = rawAlg === "ES256" ? "ES256" : opts.defaultAlg;
|
|
541
|
+
const envJwk = readEnvTrimmed(opts.envJwkVar);
|
|
542
|
+
if (envJwk) {
|
|
543
|
+
let jwkDict;
|
|
544
|
+
try {
|
|
545
|
+
jwkDict = JSON.parse(envJwk);
|
|
546
|
+
} catch (err) {
|
|
547
|
+
throw new Error(
|
|
548
|
+
`${opts.envJwkVar} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
if (!jwkDict || typeof jwkDict !== "object" || Array.isArray(jwkDict) || Object.keys(jwkDict).length === 0) {
|
|
552
|
+
throw new Error(`${opts.envJwkVar} must be a non-empty JWK object.`);
|
|
553
|
+
}
|
|
554
|
+
const detectedAlg = detectAlgFromJwk(jwkDict);
|
|
555
|
+
if (!detectedAlg) {
|
|
556
|
+
throw new Error(
|
|
557
|
+
`${opts.envJwkVar} has unsupported kty/crv (got kty=${String(jwkDict.kty)} crv=${String(jwkDict.crv)}); expected OKP+Ed25519 or EC+P-256.`
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
const canonicalPrivateJwk = detectedAlg === "EdDSA" ? { kty: jwkDict.kty, crv: jwkDict.crv, x: jwkDict.x, d: jwkDict.d } : { kty: jwkDict.kty, crv: jwkDict.crv, x: jwkDict.x, y: jwkDict.y, d: jwkDict.d };
|
|
561
|
+
const { importJWK } = await import("jose");
|
|
562
|
+
const { createPublicKey } = await import("crypto");
|
|
563
|
+
let privateKey;
|
|
564
|
+
let publicNodeKey;
|
|
565
|
+
try {
|
|
566
|
+
privateKey = await importJWK(
|
|
567
|
+
canonicalPrivateJwk,
|
|
568
|
+
detectedAlg
|
|
569
|
+
);
|
|
570
|
+
publicNodeKey = createPublicKey({ key: canonicalPrivateJwk, format: "jwk" });
|
|
571
|
+
} catch (err) {
|
|
572
|
+
const className = err instanceof Error ? err.constructor.name : typeof err;
|
|
573
|
+
const code = err && typeof err === "object" && "code" in err ? String(err.code) : null;
|
|
574
|
+
const codeSuffix = code ? ` [${code}]` : "";
|
|
575
|
+
throw new Error(
|
|
576
|
+
`${opts.envJwkVar} has malformed key material (${className}${codeSuffix}). Verify the JWK is well-formed and matches the declared kty/crv. Underlying details suppressed to avoid leaking key bytes.`
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
const publicJWK = publicNodeKey.export({ format: "jwk" });
|
|
580
|
+
publicJWK.kid = jwkDict.kid || kidDefault;
|
|
581
|
+
publicJWK.alg = detectedAlg;
|
|
582
|
+
publicJWK.use = "sig";
|
|
583
|
+
return { privateKey, publicJWK };
|
|
584
|
+
}
|
|
585
|
+
return generateUCPSigningKey({ kid: kidDefault, alg: algFallback });
|
|
586
|
+
}
|
|
587
|
+
async function loadUCPSigningKeyFromEnv({
|
|
588
|
+
envJwkVar,
|
|
589
|
+
envKidVar,
|
|
590
|
+
envAlgVar,
|
|
591
|
+
defaultKid,
|
|
592
|
+
defaultAlg
|
|
593
|
+
} = {}) {
|
|
594
|
+
const resolved = {
|
|
595
|
+
...DEFAULT_LOAD_OPTS,
|
|
596
|
+
...envJwkVar !== void 0 && { envJwkVar },
|
|
597
|
+
...envKidVar !== void 0 && { envKidVar },
|
|
598
|
+
...envAlgVar !== void 0 && { envAlgVar },
|
|
599
|
+
...defaultKid !== void 0 && { defaultKid },
|
|
600
|
+
...defaultAlg !== void 0 && { defaultAlg }
|
|
601
|
+
};
|
|
602
|
+
const key = cacheKey(resolved);
|
|
603
|
+
let cached = envLoaderCache.get(key);
|
|
604
|
+
if (cached) return cached;
|
|
605
|
+
cached = buildEnvSigningKey(resolved).catch((err) => {
|
|
606
|
+
envLoaderCache.delete(key);
|
|
607
|
+
throw err;
|
|
608
|
+
});
|
|
609
|
+
envLoaderCache.set(key, cached);
|
|
610
|
+
return cached;
|
|
611
|
+
}
|
|
612
|
+
var JOSE_INSTALL_HINT, ALLOWED_ALGS, PROFILE_TYP, UCPVerificationError, DEFAULT_LOAD_OPTS, envLoaderCache;
|
|
613
|
+
var init_ucp_jwks = __esm({
|
|
614
|
+
"src/identity/ucp-jwks.ts"() {
|
|
615
|
+
"use strict";
|
|
616
|
+
JOSE_INSTALL_HINT = "Install the optional peer dependency: `npm install jose@^6` (or `bun add jose`). Tested against jose v6.x.";
|
|
617
|
+
ALLOWED_ALGS = ["EdDSA", "ES256"];
|
|
618
|
+
PROFILE_TYP = "agentscore-profile+jws";
|
|
619
|
+
UCPVerificationError = class extends Error {
|
|
620
|
+
constructor(code, message) {
|
|
621
|
+
super(message);
|
|
622
|
+
this.code = code;
|
|
623
|
+
this.name = "UCPVerificationError";
|
|
624
|
+
}
|
|
625
|
+
code;
|
|
626
|
+
};
|
|
627
|
+
DEFAULT_LOAD_OPTS = {
|
|
628
|
+
envJwkVar: "UCP_SIGNING_KEY_JWK_PRIVATE",
|
|
629
|
+
envKidVar: "UCP_SIGNING_KEY_KID",
|
|
630
|
+
envAlgVar: "UCP_SIGNING_KEY_ALG",
|
|
631
|
+
defaultKid: "merchant-default",
|
|
632
|
+
defaultAlg: "EdDSA"
|
|
633
|
+
};
|
|
634
|
+
envLoaderCache = /* @__PURE__ */ new Map();
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
// src/payment/usdc.ts
|
|
639
|
+
var USDC;
|
|
640
|
+
var init_usdc = __esm({
|
|
641
|
+
"src/payment/usdc.ts"() {
|
|
642
|
+
"use strict";
|
|
643
|
+
USDC = {
|
|
644
|
+
base: {
|
|
645
|
+
mainnet: { address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", decimals: 6 },
|
|
646
|
+
sepolia: { address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e", decimals: 6 }
|
|
647
|
+
},
|
|
648
|
+
solana: {
|
|
649
|
+
mainnet: { mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", decimals: 6 },
|
|
650
|
+
devnet: { mint: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU", decimals: 6 }
|
|
651
|
+
},
|
|
652
|
+
tempo: {
|
|
653
|
+
mainnet: { address: "0x20C000000000000000000000b9537d11c60E8b50", decimals: 6 },
|
|
654
|
+
testnet: { address: "0x20c0000000000000000000000000000000000000", decimals: 6 }
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
// src/payment/wwwauthenticate.ts
|
|
661
|
+
function paymentRequiredHeader({
|
|
662
|
+
x402Version,
|
|
663
|
+
accepts,
|
|
664
|
+
resource
|
|
665
|
+
}) {
|
|
666
|
+
return Buffer.from(JSON.stringify({ x402Version, accepts, ...resource ? { resource } : {} })).toString("base64");
|
|
667
|
+
}
|
|
668
|
+
var init_wwwauthenticate = __esm({
|
|
669
|
+
"src/payment/wwwauthenticate.ts"() {
|
|
670
|
+
"use strict";
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
// src/payment/networks.ts
|
|
675
|
+
var networks;
|
|
676
|
+
var init_networks = __esm({
|
|
677
|
+
"src/payment/networks.ts"() {
|
|
678
|
+
"use strict";
|
|
679
|
+
networks = {
|
|
680
|
+
base: {
|
|
681
|
+
mainnet: { caip2: "eip155:8453", chainId: 8453 },
|
|
682
|
+
sepolia: { caip2: "eip155:84532", chainId: 84532 }
|
|
683
|
+
},
|
|
684
|
+
solana: {
|
|
685
|
+
mainnet: { caip2: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" },
|
|
686
|
+
devnet: { caip2: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1" }
|
|
687
|
+
},
|
|
688
|
+
tempo: {
|
|
689
|
+
mainnet: { caip2: "eip155:4217", chainId: 4217 },
|
|
690
|
+
testnet: { caip2: "eip155:42431", chainId: 42431 }
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
// src/payment/rails.ts
|
|
697
|
+
function lookupRail(name) {
|
|
698
|
+
return rails[name];
|
|
699
|
+
}
|
|
700
|
+
var rails;
|
|
701
|
+
var init_rails = __esm({
|
|
702
|
+
"src/payment/rails.ts"() {
|
|
703
|
+
"use strict";
|
|
704
|
+
init_networks();
|
|
705
|
+
init_usdc();
|
|
706
|
+
rails = {
|
|
707
|
+
"tempo-mainnet": {
|
|
708
|
+
method: "tempo",
|
|
709
|
+
network: networks.tempo.mainnet.caip2,
|
|
710
|
+
chainId: networks.tempo.mainnet.chainId,
|
|
711
|
+
currency: USDC.tempo.mainnet.address,
|
|
712
|
+
decimals: USDC.tempo.mainnet.decimals,
|
|
713
|
+
asset: USDC.tempo.mainnet.address
|
|
714
|
+
},
|
|
715
|
+
"tempo-testnet": {
|
|
716
|
+
method: "tempo",
|
|
717
|
+
network: networks.tempo.testnet.caip2,
|
|
718
|
+
chainId: networks.tempo.testnet.chainId,
|
|
719
|
+
currency: USDC.tempo.testnet.address,
|
|
720
|
+
decimals: USDC.tempo.testnet.decimals,
|
|
721
|
+
asset: USDC.tempo.testnet.address
|
|
722
|
+
},
|
|
723
|
+
"x402-base-mainnet": {
|
|
724
|
+
method: "x402",
|
|
725
|
+
network: networks.base.mainnet.caip2,
|
|
726
|
+
chainId: networks.base.mainnet.chainId,
|
|
727
|
+
currency: USDC.base.mainnet.address,
|
|
728
|
+
decimals: USDC.base.mainnet.decimals,
|
|
729
|
+
asset: USDC.base.mainnet.address
|
|
730
|
+
},
|
|
731
|
+
"x402-base-sepolia": {
|
|
732
|
+
method: "x402",
|
|
733
|
+
network: networks.base.sepolia.caip2,
|
|
734
|
+
chainId: networks.base.sepolia.chainId,
|
|
735
|
+
currency: USDC.base.sepolia.address,
|
|
736
|
+
decimals: USDC.base.sepolia.decimals,
|
|
737
|
+
asset: USDC.base.sepolia.address
|
|
738
|
+
},
|
|
739
|
+
// Upto rails — pay UP TO a max amount (Permit2-based, vs EIP-3009 for exact). Use for
|
|
740
|
+
// variable-cost APIs where the actual cost depends on output (LLM tokens, bandwidth, etc.).
|
|
741
|
+
// Only available on EVM networks; Solana svm doesn't ship an upto scheme yet.
|
|
742
|
+
"x402-base-mainnet-upto": {
|
|
743
|
+
method: "x402-upto",
|
|
744
|
+
network: networks.base.mainnet.caip2,
|
|
745
|
+
chainId: networks.base.mainnet.chainId,
|
|
746
|
+
currency: USDC.base.mainnet.address,
|
|
747
|
+
decimals: USDC.base.mainnet.decimals,
|
|
748
|
+
asset: USDC.base.mainnet.address
|
|
749
|
+
},
|
|
750
|
+
"x402-base-sepolia-upto": {
|
|
751
|
+
method: "x402-upto",
|
|
752
|
+
network: networks.base.sepolia.caip2,
|
|
753
|
+
chainId: networks.base.sepolia.chainId,
|
|
754
|
+
currency: USDC.base.sepolia.address,
|
|
755
|
+
decimals: USDC.base.sepolia.decimals,
|
|
756
|
+
asset: USDC.base.sepolia.address
|
|
757
|
+
},
|
|
758
|
+
"mpp-solana-mainnet": {
|
|
759
|
+
method: "solana",
|
|
760
|
+
network: networks.solana.mainnet.caip2,
|
|
761
|
+
currency: USDC.solana.mainnet.mint,
|
|
762
|
+
decimals: USDC.solana.mainnet.decimals,
|
|
763
|
+
asset: USDC.solana.mainnet.mint
|
|
764
|
+
},
|
|
765
|
+
"mpp-solana-devnet": {
|
|
766
|
+
method: "solana",
|
|
767
|
+
network: networks.solana.devnet.caip2,
|
|
768
|
+
currency: USDC.solana.devnet.mint,
|
|
769
|
+
decimals: USDC.solana.devnet.decimals,
|
|
770
|
+
asset: USDC.solana.devnet.mint
|
|
771
|
+
},
|
|
772
|
+
"stripe-spt": {
|
|
773
|
+
method: "stripe",
|
|
774
|
+
currency: "usd",
|
|
775
|
+
decimals: 2
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
// src/payment/directive.ts
|
|
782
|
+
function buildPaymentRequestBlob({
|
|
783
|
+
rail,
|
|
784
|
+
amountUsd,
|
|
785
|
+
currency,
|
|
786
|
+
decimals,
|
|
787
|
+
recipient,
|
|
788
|
+
chainId,
|
|
789
|
+
networkId
|
|
790
|
+
}) {
|
|
791
|
+
const railDef = rail ? lookupRail(rail) : void 0;
|
|
792
|
+
const decimalsResolved = decimals ?? railDef?.decimals ?? 6;
|
|
793
|
+
const currencyResolved = currency ?? railDef?.currency ?? "usd";
|
|
794
|
+
const chainIdResolved = chainId ?? railDef?.chainId;
|
|
795
|
+
const amountNum = typeof amountUsd === "string" ? Number(amountUsd) : amountUsd;
|
|
796
|
+
const amountRaw = BigInt(Math.round(amountNum * 10 ** decimalsResolved)).toString();
|
|
797
|
+
const blob = { amount: amountRaw, currency: currencyResolved, decimals: decimalsResolved };
|
|
798
|
+
if (recipient) blob.recipient = recipient;
|
|
799
|
+
const methodDetails = {};
|
|
800
|
+
if (chainIdResolved !== void 0) methodDetails.chainId = chainIdResolved;
|
|
801
|
+
if (networkId) methodDetails.networkId = networkId;
|
|
802
|
+
if (Object.keys(methodDetails).length > 0) blob.methodDetails = methodDetails;
|
|
803
|
+
return Buffer.from(JSON.stringify(blob)).toString("base64url");
|
|
804
|
+
}
|
|
805
|
+
function paymentDirective({
|
|
806
|
+
rail,
|
|
807
|
+
id,
|
|
808
|
+
realm,
|
|
809
|
+
method,
|
|
810
|
+
intent,
|
|
811
|
+
expires,
|
|
812
|
+
request
|
|
813
|
+
}) {
|
|
814
|
+
const railDef = rail ? lookupRail(rail) : void 0;
|
|
815
|
+
const methodResolved = method ?? railDef?.method ?? "unknown";
|
|
816
|
+
const intentResolved = intent ?? "charge";
|
|
817
|
+
const expiresResolved = expires ?? new Date(Date.now() + 5 * 60 * 1e3).toISOString();
|
|
818
|
+
return `Payment id="${id}", realm="${realm}", method="${methodResolved}", intent="${intentResolved}", expires="${expiresResolved}", request="${request}"`;
|
|
819
|
+
}
|
|
820
|
+
var init_directive = __esm({
|
|
821
|
+
"src/payment/directive.ts"() {
|
|
822
|
+
"use strict";
|
|
823
|
+
init_rails();
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
// src/discovery/probe.ts
|
|
828
|
+
var probe_exports = {};
|
|
829
|
+
__export(probe_exports, {
|
|
830
|
+
buildDiscoveryProbeResponse: () => buildDiscoveryProbeResponse,
|
|
831
|
+
isDiscoveryProbeRequest: () => isDiscoveryProbeRequest,
|
|
832
|
+
sampleX402AcceptForNetwork: () => sampleX402AcceptForNetwork
|
|
833
|
+
});
|
|
834
|
+
function sampleX402AcceptForNetwork(caip2, amountAtomic = "1000000") {
|
|
835
|
+
if (caip2 === networks.base.mainnet.caip2) {
|
|
836
|
+
return {
|
|
837
|
+
scheme: "exact",
|
|
838
|
+
network: caip2,
|
|
839
|
+
amount: amountAtomic,
|
|
840
|
+
asset: USDC.base.mainnet.address,
|
|
841
|
+
payTo: ZERO_EVM_PAYTO,
|
|
842
|
+
maxTimeoutSeconds: 300,
|
|
843
|
+
// ``extra.name`` mirrors the on-chain USDC contract's ``name()`` because
|
|
844
|
+
// EIP-712 domain hashes include this string. Wrong name → every signed
|
|
845
|
+
// payload fails facilitator verify with ``invalid_exact_evm_payload_signature``.
|
|
846
|
+
// Base mainnet USDC returns "USD Coin"; base sepolia USDC returns "USDC".
|
|
847
|
+
extra: { name: "USD Coin", version: "2" }
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
if (caip2 === networks.base.sepolia.caip2) {
|
|
851
|
+
return {
|
|
852
|
+
scheme: "exact",
|
|
853
|
+
network: caip2,
|
|
854
|
+
amount: amountAtomic,
|
|
855
|
+
asset: USDC.base.sepolia.address,
|
|
856
|
+
payTo: ZERO_EVM_PAYTO,
|
|
857
|
+
maxTimeoutSeconds: 300,
|
|
858
|
+
extra: { name: "USDC", version: "2" }
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
if (caip2 === networks.solana.mainnet.caip2) {
|
|
862
|
+
return {
|
|
863
|
+
scheme: "exact",
|
|
864
|
+
network: caip2,
|
|
865
|
+
amount: amountAtomic,
|
|
866
|
+
asset: USDC.solana.mainnet.mint,
|
|
867
|
+
payTo: ZERO_SOLANA_PAYTO,
|
|
868
|
+
maxTimeoutSeconds: 300
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
if (caip2 === networks.solana.devnet.caip2) {
|
|
872
|
+
return {
|
|
873
|
+
scheme: "exact",
|
|
874
|
+
network: caip2,
|
|
875
|
+
amount: amountAtomic,
|
|
876
|
+
asset: USDC.solana.devnet.mint,
|
|
877
|
+
payTo: ZERO_SOLANA_PAYTO,
|
|
878
|
+
maxTimeoutSeconds: 300
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
return null;
|
|
882
|
+
}
|
|
883
|
+
function buildDiscoveryProbeResponse(opts) {
|
|
884
|
+
const probeId = `probe_${Date.now()}`;
|
|
885
|
+
const expires = new Date(Date.now() + (opts.ttlSeconds ?? 300) * 1e3).toISOString();
|
|
886
|
+
const request = buildPaymentRequestBlob({
|
|
887
|
+
rail: opts.sampleRail,
|
|
888
|
+
amountUsd: opts.sampleAmountUsd,
|
|
889
|
+
recipient: opts.sampleRecipient
|
|
890
|
+
});
|
|
891
|
+
const directive = paymentDirective({
|
|
892
|
+
rail: opts.sampleRail,
|
|
893
|
+
id: probeId,
|
|
894
|
+
realm: opts.realm,
|
|
895
|
+
intent: opts.intent,
|
|
896
|
+
expires,
|
|
897
|
+
request
|
|
898
|
+
});
|
|
899
|
+
const bodyObj = {
|
|
900
|
+
error: {
|
|
901
|
+
code: "payment_required",
|
|
902
|
+
message: opts.message ?? "This endpoint requires payment. Send a valid request body to receive a full challenge."
|
|
903
|
+
},
|
|
904
|
+
discovery: true,
|
|
905
|
+
...opts.docsUrl ? { docs: opts.docsUrl } : {}
|
|
906
|
+
};
|
|
907
|
+
const headers = {
|
|
908
|
+
"content-type": "application/json",
|
|
909
|
+
"www-authenticate": directive
|
|
910
|
+
};
|
|
911
|
+
if (opts.x402Sample) {
|
|
912
|
+
const x402Version = opts.x402Sample.version ?? 2;
|
|
913
|
+
const sampleAccepts = opts.x402Sample.accepts ?? (opts.x402Sample.networks ?? []).map((n) => sampleX402AcceptForNetwork(n, opts.x402Sample.amountAtomic ?? "1000000")).filter((e) => e !== null);
|
|
914
|
+
headers["payment-required"] = paymentRequiredHeader({
|
|
915
|
+
x402Version,
|
|
916
|
+
accepts: sampleAccepts,
|
|
917
|
+
...opts.x402Sample.resourceUrl ? { resource: { url: opts.x402Sample.resourceUrl, mimeType: "application/json" } } : {}
|
|
918
|
+
});
|
|
919
|
+
bodyObj.x402Version = x402Version;
|
|
920
|
+
const headerJson = JSON.parse(Buffer.from(headers["payment-required"], "base64").toString("utf-8"));
|
|
921
|
+
bodyObj.accepts = headerJson.accepts;
|
|
922
|
+
}
|
|
923
|
+
return {
|
|
924
|
+
status: 402,
|
|
925
|
+
headers,
|
|
926
|
+
body: JSON.stringify(bodyObj)
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
async function isDiscoveryProbeRequest(req) {
|
|
930
|
+
if (req.method !== "POST") return false;
|
|
931
|
+
const auth = req.headers.get("authorization");
|
|
932
|
+
if (auth?.startsWith("Payment ")) return false;
|
|
933
|
+
const body = await req.clone().text();
|
|
934
|
+
return !body || body === "{}";
|
|
935
|
+
}
|
|
936
|
+
var ZERO_EVM_PAYTO, ZERO_SOLANA_PAYTO;
|
|
937
|
+
var init_probe = __esm({
|
|
938
|
+
"src/discovery/probe.ts"() {
|
|
939
|
+
"use strict";
|
|
940
|
+
init_directive();
|
|
941
|
+
init_networks();
|
|
942
|
+
init_usdc();
|
|
943
|
+
init_wwwauthenticate();
|
|
944
|
+
ZERO_EVM_PAYTO = "0x0000000000000000000000000000000000000000";
|
|
945
|
+
ZERO_SOLANA_PAYTO = "11111111111111111111111111111111";
|
|
946
|
+
}
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
// src/discovery/well_known.ts
|
|
950
|
+
var well_known_exports = {};
|
|
951
|
+
__export(well_known_exports, {
|
|
952
|
+
bootstrapUcpSigningKey: () => bootstrapUcpSigningKey,
|
|
953
|
+
buildSignedJwksResponse: () => buildSignedJwksResponse,
|
|
954
|
+
buildSignedUcpResponse: () => buildSignedUcpResponse,
|
|
955
|
+
defaultA2aServices: () => defaultA2aServices,
|
|
956
|
+
signedResponseExpress: () => signedResponseExpress,
|
|
957
|
+
signedResponseFastify: () => signedResponseFastify,
|
|
958
|
+
signedResponseHono: () => signedResponseHono,
|
|
959
|
+
signedResponseNextjs: () => signedResponseNextjs,
|
|
960
|
+
signedResponseWeb: () => signedResponseWeb,
|
|
961
|
+
wellKnownCorsPreflightHeaders: () => wellKnownCorsPreflightHeaders,
|
|
962
|
+
wellKnownPreflightResponse: () => wellKnownPreflightResponse
|
|
963
|
+
});
|
|
964
|
+
function requestId(headers) {
|
|
965
|
+
if (headers === void 0) return void 0;
|
|
966
|
+
if (headers instanceof Headers) return headers.get("x-request-id") ?? void 0;
|
|
967
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
968
|
+
if (k.toLowerCase() === "x-request-id") return v;
|
|
969
|
+
}
|
|
970
|
+
return void 0;
|
|
971
|
+
}
|
|
972
|
+
function attachRequestId(headers, requestHeaders) {
|
|
973
|
+
const rid = requestId(requestHeaders);
|
|
974
|
+
if (rid !== void 0) headers["X-Request-ID"] = rid;
|
|
975
|
+
}
|
|
976
|
+
function isTempoSession(s) {
|
|
977
|
+
return "escrowContract" in s && "store" in s;
|
|
978
|
+
}
|
|
979
|
+
function isStripe(s) {
|
|
980
|
+
return !("recipient" in s);
|
|
981
|
+
}
|
|
982
|
+
function railHasRecipientField(spec) {
|
|
983
|
+
return Object.hasOwn(spec, "recipient");
|
|
984
|
+
}
|
|
985
|
+
function composeHandlers(checkout) {
|
|
986
|
+
const handlers = {};
|
|
987
|
+
const mpp = [];
|
|
988
|
+
const x402 = [];
|
|
989
|
+
const stripe = [];
|
|
990
|
+
for (const spec of Object.values(checkout.rails)) {
|
|
991
|
+
if (isStripe(spec)) {
|
|
992
|
+
stripe.push(spec);
|
|
993
|
+
continue;
|
|
994
|
+
}
|
|
995
|
+
if (isTempoSession(spec)) {
|
|
996
|
+
if (railHasRecipientField(spec)) mpp.push(spec);
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
const network = spec.network ?? "";
|
|
1000
|
+
if (network.startsWith("eip155:") || "mode" in spec) {
|
|
1001
|
+
if (railHasRecipientField(spec)) x402.push(spec);
|
|
1002
|
+
} else if (network.startsWith("solana:") || "rpcUrl" in spec) {
|
|
1003
|
+
if (railHasRecipientField(spec)) mpp.push(spec);
|
|
1004
|
+
} else {
|
|
1005
|
+
if (railHasRecipientField(spec)) mpp.push(spec);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
if (mpp.length > 0) Object.assign(handlers, mppPaymentHandler({ networks: mpp }));
|
|
1009
|
+
if (x402.length > 0) Object.assign(handlers, x402PaymentHandler({ networks: x402 }));
|
|
1010
|
+
for (const spec of stripe) Object.assign(handlers, stripeSptPaymentHandler({ spec }));
|
|
1011
|
+
return handlers;
|
|
1012
|
+
}
|
|
1013
|
+
function misconfiguredResponse(requestHeaders) {
|
|
1014
|
+
const body = {
|
|
1015
|
+
error: {
|
|
1016
|
+
code: "ucp_misconfigured",
|
|
1017
|
+
message: "Merchant has no configured payment handlers."
|
|
1018
|
+
},
|
|
1019
|
+
next_steps: {
|
|
1020
|
+
action: "contact_merchant",
|
|
1021
|
+
user_message: "This merchant is temporarily unable to accept agent payments."
|
|
1022
|
+
},
|
|
1023
|
+
agent_instructions: {
|
|
1024
|
+
action: "contact_merchant",
|
|
1025
|
+
steps: [
|
|
1026
|
+
"Surface a transient error to the user.",
|
|
1027
|
+
"Retry later; the merchant operator will repair the configuration."
|
|
1028
|
+
],
|
|
1029
|
+
user_message: "Merchant temporarily offline for agent payments."
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
const headers = {
|
|
1033
|
+
"Access-Control-Allow-Origin": "*",
|
|
1034
|
+
"Cache-Control": `public, max-age=${UCP_CACHE_SECONDS}`
|
|
1035
|
+
};
|
|
1036
|
+
attachRequestId(headers, requestHeaders);
|
|
1037
|
+
return {
|
|
1038
|
+
body: JSON.stringify(body),
|
|
1039
|
+
mediaType: "application/json",
|
|
1040
|
+
headers,
|
|
1041
|
+
status: 503
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
async function buildSignedUcpResponse(opts) {
|
|
1045
|
+
const {
|
|
1046
|
+
checkout,
|
|
1047
|
+
name,
|
|
1048
|
+
wellKnownUcpUrl,
|
|
1049
|
+
services,
|
|
1050
|
+
requestHeaders,
|
|
1051
|
+
signingKid = "merchant-default",
|
|
1052
|
+
agentscoreGate
|
|
1053
|
+
} = opts;
|
|
1054
|
+
const handlers = composeHandlers(checkout);
|
|
1055
|
+
if (Object.keys(handlers).length === 0) {
|
|
1056
|
+
return misconfiguredResponse(requestHeaders);
|
|
1057
|
+
}
|
|
1058
|
+
const key = await loadUCPSigningKeyFromEnv({ defaultKid: signingKid });
|
|
1059
|
+
const signingKeyEntry = UCPSigningKey.fromJWK(key.publicJWK);
|
|
1060
|
+
const profile = buildUCPProfile({
|
|
1061
|
+
name,
|
|
1062
|
+
supported_versions: { "2026-04-08": wellKnownUcpUrl },
|
|
1063
|
+
agentscore_gate: agentscoreGate,
|
|
1064
|
+
services,
|
|
1065
|
+
payment_handlers: handlers,
|
|
1066
|
+
signing_keys: [signingKeyEntry]
|
|
1067
|
+
});
|
|
1068
|
+
const signed = await signUCPProfile(profile, {
|
|
1069
|
+
signingKey: key.privateKey,
|
|
1070
|
+
kid: key.publicJWK.kid,
|
|
1071
|
+
alg: key.publicJWK.alg ?? "EdDSA"
|
|
1072
|
+
});
|
|
1073
|
+
const headers = {
|
|
1074
|
+
"Cache-Control": `public, max-age=${UCP_CACHE_SECONDS}`,
|
|
1075
|
+
"Access-Control-Allow-Origin": "*"
|
|
1076
|
+
};
|
|
1077
|
+
attachRequestId(headers, requestHeaders);
|
|
1078
|
+
return {
|
|
1079
|
+
body: JSON.stringify(signed),
|
|
1080
|
+
mediaType: "application/json",
|
|
1081
|
+
headers,
|
|
1082
|
+
status: 200
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
async function buildSignedJwksResponse(opts) {
|
|
1086
|
+
const { requestHeaders, signingKid = "merchant-default" } = opts ?? {};
|
|
1087
|
+
const key = await loadUCPSigningKeyFromEnv({ defaultKid: signingKid });
|
|
1088
|
+
const jwks = buildJWKSResponse([UCPSigningKey.fromJWK(key.publicJWK)]);
|
|
1089
|
+
const headers = {
|
|
1090
|
+
"Cache-Control": `public, max-age=${JWKS_CACHE_SECONDS}`,
|
|
1091
|
+
"Access-Control-Allow-Origin": "*"
|
|
1092
|
+
};
|
|
1093
|
+
attachRequestId(headers, requestHeaders);
|
|
1094
|
+
return {
|
|
1095
|
+
body: JSON.stringify(jwks),
|
|
1096
|
+
mediaType: "application/jwk-set+json",
|
|
1097
|
+
headers,
|
|
1098
|
+
status: 200
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
function wellKnownCorsPreflightHeaders(requestHeaders) {
|
|
1102
|
+
const headers = {
|
|
1103
|
+
"Access-Control-Allow-Origin": "*",
|
|
1104
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
1105
|
+
"Access-Control-Max-Age": "86400",
|
|
1106
|
+
Vary: "Access-Control-Request-Headers"
|
|
1107
|
+
};
|
|
1108
|
+
if (requestHeaders === void 0) return headers;
|
|
1109
|
+
const acrh = requestHeaders instanceof Headers ? requestHeaders.get("access-control-request-headers") : Object.entries(requestHeaders).find(([k]) => k.toLowerCase() === "access-control-request-headers")?.[1];
|
|
1110
|
+
if (acrh) headers["Access-Control-Allow-Headers"] = acrh;
|
|
1111
|
+
return headers;
|
|
1112
|
+
}
|
|
1113
|
+
function wellKnownPreflightResponse(requestHeaders) {
|
|
1114
|
+
return new Response(null, {
|
|
1115
|
+
status: 204,
|
|
1116
|
+
headers: wellKnownCorsPreflightHeaders(requestHeaders)
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
function defaultA2aServices(opts) {
|
|
1120
|
+
return {
|
|
1121
|
+
"dev.ucp.shopping": [
|
|
1122
|
+
{
|
|
1123
|
+
version: "2026-04-08",
|
|
1124
|
+
spec: UCP_SHOPPING_SPEC_2026_04_08,
|
|
1125
|
+
transport: "a2a",
|
|
1126
|
+
endpoint: opts.agentCardUrl
|
|
1127
|
+
}
|
|
1128
|
+
]
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
async function bootstrapUcpSigningKey(opts) {
|
|
1132
|
+
const defaultKid = opts?.defaultKid ?? "merchant-default";
|
|
1133
|
+
await loadUCPSigningKeyFromEnv({ defaultKid });
|
|
1134
|
+
}
|
|
1135
|
+
function signedResponseHono(resp) {
|
|
1136
|
+
return new Response(resp.body, {
|
|
1137
|
+
status: resp.status,
|
|
1138
|
+
headers: { ...resp.headers, "Content-Type": resp.mediaType }
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
function signedResponseNextjs(resp) {
|
|
1142
|
+
return signedResponseHono(resp);
|
|
1143
|
+
}
|
|
1144
|
+
function signedResponseWeb(resp) {
|
|
1145
|
+
return signedResponseHono(resp);
|
|
1146
|
+
}
|
|
1147
|
+
function signedResponseExpress(res, resp) {
|
|
1148
|
+
res.status(resp.status);
|
|
1149
|
+
res.set(resp.headers);
|
|
1150
|
+
res.type(resp.mediaType);
|
|
1151
|
+
res.send(resp.body);
|
|
1152
|
+
}
|
|
1153
|
+
function signedResponseFastify(reply, resp) {
|
|
1154
|
+
reply.code(resp.status);
|
|
1155
|
+
for (const [k, v] of Object.entries(resp.headers)) reply.header(k, v);
|
|
1156
|
+
reply.type(resp.mediaType);
|
|
1157
|
+
return reply.send(resp.body);
|
|
1158
|
+
}
|
|
1159
|
+
var UCP_CACHE_SECONDS, JWKS_CACHE_SECONDS, UCP_SHOPPING_SPEC_2026_04_08;
|
|
1160
|
+
var init_well_known = __esm({
|
|
1161
|
+
"src/discovery/well_known.ts"() {
|
|
1162
|
+
"use strict";
|
|
1163
|
+
init_ucp();
|
|
1164
|
+
init_ucp_jwks();
|
|
1165
|
+
UCP_CACHE_SECONDS = 60;
|
|
1166
|
+
JWKS_CACHE_SECONDS = 300;
|
|
1167
|
+
UCP_SHOPPING_SPEC_2026_04_08 = "https://ucp.dev/2026-04-08/specification/overview";
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
29
1170
|
|
|
30
1171
|
// src/index.ts
|
|
31
1172
|
var index_exports = {};
|
|
32
1173
|
__export(index_exports, {
|
|
33
1174
|
AGENTSCORE_UCP_CAPABILITY: () => AGENTSCORE_UCP_CAPABILITY,
|
|
1175
|
+
Checkout: () => Checkout,
|
|
1176
|
+
CheckoutValidationError: () => CheckoutValidationError,
|
|
34
1177
|
FIXABLE_DENIAL_REASONS: () => FIXABLE_DENIAL_REASONS,
|
|
35
1178
|
UCPSigningKey: () => UCPSigningKey,
|
|
36
1179
|
UCPVerificationError: () => UCPVerificationError,
|
|
@@ -38,16 +1181,25 @@ __export(index_exports, {
|
|
|
38
1181
|
buildA2AAgentCard: () => buildA2AAgentCard,
|
|
39
1182
|
buildAgentMemoryHint: () => buildAgentMemoryHint,
|
|
40
1183
|
buildContactSupportNextSteps: () => buildContactSupportNextSteps,
|
|
41
|
-
|
|
1184
|
+
buildGateFromPolicy: () => buildGateFromPolicy,
|
|
42
1185
|
buildJWKSResponse: () => buildJWKSResponse,
|
|
43
1186
|
buildSignerMismatchBody: () => buildSignerMismatchBody,
|
|
44
1187
|
buildUCPProfile: () => buildUCPProfile,
|
|
45
1188
|
denialReasonStatus: () => denialReasonStatus,
|
|
46
1189
|
denialReasonToBody: () => denialReasonToBody,
|
|
47
1190
|
extractPaymentSigner: () => extractPaymentSigner,
|
|
1191
|
+
extractPaymentSignerFromAuth: () => extractPaymentSignerFromAuth,
|
|
1192
|
+
extractSignerForPrecheck: () => extractSignerForPrecheck,
|
|
1193
|
+
formatUsdCents: () => formatUsdCents,
|
|
48
1194
|
generateUCPSigningKey: () => generateUCPSigningKey,
|
|
1195
|
+
getIdentityStatus: () => getIdentityStatus,
|
|
1196
|
+
hashOperatorToken: () => hashOperatorToken,
|
|
49
1197
|
isFixableDenial: () => isFixableDenial,
|
|
1198
|
+
loadSolanaFeePayer: () => loadSolanaFeePayer,
|
|
1199
|
+
loadUCPSigningKeyFromEnv: () => loadUCPSigningKeyFromEnv,
|
|
1200
|
+
makeMppxComposeHook: () => makeMppxComposeHook,
|
|
50
1201
|
mppPaymentHandler: () => mppPaymentHandler,
|
|
1202
|
+
pricingResult: () => pricingResult,
|
|
51
1203
|
readX402PaymentHeader: () => readX402PaymentHeader,
|
|
52
1204
|
runGateWithEnforcement: () => runGateWithEnforcement,
|
|
53
1205
|
shippingCountryAllowed: () => shippingCountryAllowed,
|
|
@@ -55,6 +1207,13 @@ __export(index_exports, {
|
|
|
55
1207
|
signUCPProfile: () => signUCPProfile,
|
|
56
1208
|
stripeSptPaymentHandler: () => stripeSptPaymentHandler,
|
|
57
1209
|
ucpA2AExtension: () => ucpA2AExtension,
|
|
1210
|
+
validateShippingAgainstPolicy: () => validateShippingAgainstPolicy,
|
|
1211
|
+
validationEnvelope: () => validationEnvelope,
|
|
1212
|
+
validationResponseExpress: () => validationResponseExpress,
|
|
1213
|
+
validationResponseFastify: () => validationResponseFastify,
|
|
1214
|
+
validationResponseHono: () => validationResponseHono,
|
|
1215
|
+
validationResponseNextjs: () => validationResponseNextjs,
|
|
1216
|
+
validationResponseWeb: () => validationResponseWeb,
|
|
58
1217
|
verificationAgentInstructions: () => verificationAgentInstructions,
|
|
59
1218
|
verifyUCPProfile: () => verifyUCPProfile,
|
|
60
1219
|
x402PaymentHandler: () => x402PaymentHandler
|
|
@@ -79,13 +1238,16 @@ function denialReasonStatus(reason) {
|
|
|
79
1238
|
if (reason.code === "api_error") return 503;
|
|
80
1239
|
return 403;
|
|
81
1240
|
}
|
|
82
|
-
function buildSignerMismatchBody(
|
|
83
|
-
|
|
1241
|
+
function buildSignerMismatchBody({
|
|
1242
|
+
result,
|
|
1243
|
+
userMessage,
|
|
1244
|
+
learnMoreUrl
|
|
1245
|
+
}) {
|
|
84
1246
|
if (result.kind === "pass") return null;
|
|
85
|
-
const
|
|
1247
|
+
const learnMoreUrlResolved = learnMoreUrl ?? "https://docs.agentscore.sh/guides/agent-identity";
|
|
86
1248
|
if (result.kind === "wallet_signer_mismatch") {
|
|
87
1249
|
const linkedWallets = result.linkedWallets ?? [];
|
|
88
|
-
const
|
|
1250
|
+
const userMessageResolved = userMessage ?? (linkedWallets.length > 0 ? `Sign the payment with one of the wallets linked to this operator: ${linkedWallets.join(", ")}. Then retry.` : "Sign the payment with the same wallet you claimed via X-Wallet-Address, or switch to X-Operator-Token for rail-independent identity.");
|
|
89
1251
|
return {
|
|
90
1252
|
error: {
|
|
91
1253
|
code: "wallet_signer_mismatch",
|
|
@@ -98,8 +1260,8 @@ function buildSignerMismatchBody(input) {
|
|
|
98
1260
|
linked_wallets: linkedWallets,
|
|
99
1261
|
next_steps: {
|
|
100
1262
|
action: "regenerate_payment_from_linked_wallet",
|
|
101
|
-
user_message:
|
|
102
|
-
learn_more_url:
|
|
1263
|
+
user_message: userMessageResolved,
|
|
1264
|
+
learn_more_url: learnMoreUrlResolved
|
|
103
1265
|
}
|
|
104
1266
|
};
|
|
105
1267
|
}
|
|
@@ -110,8 +1272,8 @@ function buildSignerMismatchBody(input) {
|
|
|
110
1272
|
},
|
|
111
1273
|
next_steps: {
|
|
112
1274
|
action: "switch_to_operator_token",
|
|
113
|
-
user_message:
|
|
114
|
-
learn_more_url:
|
|
1275
|
+
user_message: userMessage ?? "Drop the X-Wallet-Address header and retry with X-Operator-Token (works on every payment rail).",
|
|
1276
|
+
learn_more_url: learnMoreUrlResolved
|
|
115
1277
|
}
|
|
116
1278
|
};
|
|
117
1279
|
}
|
|
@@ -119,27 +1281,35 @@ function buildContactSupportNextSteps(supportEmail, message) {
|
|
|
119
1281
|
return {
|
|
120
1282
|
action: "contact_support",
|
|
121
1283
|
support_email: supportEmail,
|
|
122
|
-
user_message: message ?? `If you believe this denial is in error, contact support at ${supportEmail} with your
|
|
1284
|
+
user_message: message ?? `If you believe this denial is in error, contact support at ${supportEmail} with the details of your request.`
|
|
123
1285
|
};
|
|
124
1286
|
}
|
|
125
|
-
function verificationAgentInstructions(
|
|
1287
|
+
function verificationAgentInstructions({
|
|
1288
|
+
userAction,
|
|
1289
|
+
retryStep,
|
|
1290
|
+
extraSteps,
|
|
1291
|
+
pollIntervalSeconds = 5,
|
|
1292
|
+
timeoutSeconds = 3600,
|
|
1293
|
+
orderTtl,
|
|
1294
|
+
extra
|
|
1295
|
+
} = {}) {
|
|
126
1296
|
const baseSteps = [
|
|
127
1297
|
"Present the verify_url directly to the user \u2014 it is a complete, ready-to-open URL with the session token already embedded (e.g. https://agentscore.sh/verify?session=sess_...). Do NOT modify or construct the URL yourself.",
|
|
128
|
-
`Immediately begin polling poll_url every ${
|
|
1298
|
+
`Immediately begin polling poll_url every ${pollIntervalSeconds} seconds with header X-Poll-Secret set to poll_secret. The user will complete verification in their browser while you poll in the background.`,
|
|
129
1299
|
"The user visits the URL, signs in, completes identity verification (photo ID + selfie via Stripe Identity), and closes the tab. They do NOT need to copy or paste anything back to you.",
|
|
130
1300
|
'When your poll returns status "verified", extract operator_token from the response. This is a one-time value \u2014 save it immediately. Subsequent polls return status "consumed" without the token.',
|
|
131
|
-
|
|
1301
|
+
retryStep ?? "Retry the original merchant request with header X-Operator-Token set to the operator_token value."
|
|
132
1302
|
];
|
|
133
1303
|
return {
|
|
134
1304
|
action: "poll_for_credential",
|
|
135
|
-
user_action:
|
|
136
|
-
steps:
|
|
137
|
-
poll_interval_seconds:
|
|
1305
|
+
user_action: userAction ?? "The user must visit verify_url to complete identity verification before this request can proceed",
|
|
1306
|
+
steps: extraSteps ? [...baseSteps, ...extraSteps] : baseSteps,
|
|
1307
|
+
poll_interval_seconds: pollIntervalSeconds,
|
|
138
1308
|
poll_secret_header: "X-Poll-Secret",
|
|
139
1309
|
retry_token_header: "X-Operator-Token",
|
|
140
|
-
timeout_seconds:
|
|
141
|
-
...
|
|
142
|
-
...
|
|
1310
|
+
timeout_seconds: timeoutSeconds,
|
|
1311
|
+
...orderTtl ? { order_ttl: orderTtl } : {},
|
|
1312
|
+
...extra ?? {}
|
|
143
1313
|
};
|
|
144
1314
|
}
|
|
145
1315
|
|
|
@@ -260,7 +1430,72 @@ function denialReasonToBody(reason) {
|
|
|
260
1430
|
return body;
|
|
261
1431
|
}
|
|
262
1432
|
|
|
1433
|
+
// src/address.ts
|
|
1434
|
+
var SOLANA_BASE58_RE = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
1435
|
+
var isSolanaAddress = (address) => SOLANA_BASE58_RE.test(address) && !address.startsWith("0x");
|
|
1436
|
+
var normalizeAddress = (address) => {
|
|
1437
|
+
if (isSolanaAddress(address)) {
|
|
1438
|
+
return address;
|
|
1439
|
+
}
|
|
1440
|
+
return address.toLowerCase();
|
|
1441
|
+
};
|
|
1442
|
+
|
|
1443
|
+
// src/cache.ts
|
|
1444
|
+
var TTLCache = class {
|
|
1445
|
+
constructor(defaultTtlMs, maxSize = 1e4) {
|
|
1446
|
+
this.defaultTtlMs = defaultTtlMs;
|
|
1447
|
+
this.maxSize = maxSize;
|
|
1448
|
+
}
|
|
1449
|
+
defaultTtlMs;
|
|
1450
|
+
store = /* @__PURE__ */ new Map();
|
|
1451
|
+
maxSize;
|
|
1452
|
+
get(key) {
|
|
1453
|
+
const entry = this.store.get(key);
|
|
1454
|
+
if (!entry) return void 0;
|
|
1455
|
+
if (Date.now() > entry.expiresAt) {
|
|
1456
|
+
this.store.delete(key);
|
|
1457
|
+
return void 0;
|
|
1458
|
+
}
|
|
1459
|
+
return entry.value;
|
|
1460
|
+
}
|
|
1461
|
+
set(key, value, ttlMs) {
|
|
1462
|
+
if (this.store.size >= this.maxSize) {
|
|
1463
|
+
this.sweep();
|
|
1464
|
+
}
|
|
1465
|
+
if (this.store.size >= this.maxSize) {
|
|
1466
|
+
this.evictOldest(this.store.size - this.maxSize + 1);
|
|
1467
|
+
}
|
|
1468
|
+
this.store.set(key, {
|
|
1469
|
+
value,
|
|
1470
|
+
expiresAt: Date.now() + (ttlMs ?? this.defaultTtlMs)
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
/** Remove all expired entries. */
|
|
1474
|
+
sweep() {
|
|
1475
|
+
const now = Date.now();
|
|
1476
|
+
for (const [k, v] of this.store) {
|
|
1477
|
+
if (now > v.expiresAt) {
|
|
1478
|
+
this.store.delete(k);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
/** Evict the oldest `count` entries by insertion order. */
|
|
1483
|
+
evictOldest(count) {
|
|
1484
|
+
let removed = 0;
|
|
1485
|
+
for (const key of this.store.keys()) {
|
|
1486
|
+
if (removed >= count) break;
|
|
1487
|
+
this.store.delete(key);
|
|
1488
|
+
removed++;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
};
|
|
1492
|
+
|
|
263
1493
|
// src/core.ts
|
|
1494
|
+
function stripTrailingSlashes(s) {
|
|
1495
|
+
let end = s.length;
|
|
1496
|
+
while (end > 0 && s.charCodeAt(end - 1) === 47) end--;
|
|
1497
|
+
return end === s.length ? s : s.slice(0, end);
|
|
1498
|
+
}
|
|
264
1499
|
var CANONICAL_AGENTSCORE_API = "https://api.agentscore.sh";
|
|
265
1500
|
var WALLET_SIGNER_MISMATCH_INSTRUCTIONS = JSON.stringify({
|
|
266
1501
|
action: "resign_or_switch_to_operator_token",
|
|
@@ -303,6 +1538,317 @@ function buildAgentMemoryHint() {
|
|
|
303
1538
|
persist_in_credential_store: ["operator_token"]
|
|
304
1539
|
};
|
|
305
1540
|
}
|
|
1541
|
+
function createAgentScoreCore(options) {
|
|
1542
|
+
if (!options.apiKey) {
|
|
1543
|
+
throw new Error("AgentScore API key is required. Get one at https://agentscore.sh/sign-up");
|
|
1544
|
+
}
|
|
1545
|
+
const {
|
|
1546
|
+
apiKey,
|
|
1547
|
+
requireKyc,
|
|
1548
|
+
requireSanctionsClear,
|
|
1549
|
+
minAge,
|
|
1550
|
+
blockedJurisdictions,
|
|
1551
|
+
allowedJurisdictions,
|
|
1552
|
+
failOpen = false,
|
|
1553
|
+
cacheSeconds = 300,
|
|
1554
|
+
baseUrl: rawBaseUrl = "https://api.agentscore.sh",
|
|
1555
|
+
chain: gateChain,
|
|
1556
|
+
userAgent,
|
|
1557
|
+
createSessionOnMissing
|
|
1558
|
+
} = options;
|
|
1559
|
+
const baseUrl = stripTrailingSlashes(rawBaseUrl);
|
|
1560
|
+
const agentMemoryHint = buildAgentMemoryHint();
|
|
1561
|
+
const defaultUa = `@agent-score/commerce@${"2.0.0"}`;
|
|
1562
|
+
const userAgentHeader = userAgent ? `${userAgent} (${defaultUa})` : defaultUa;
|
|
1563
|
+
const sdk = new import_sdk.AgentScore({ apiKey, baseUrl, userAgent: userAgentHeader });
|
|
1564
|
+
const sessionSdkCache = /* @__PURE__ */ new Map();
|
|
1565
|
+
function getSessionSdk(sessionApiKey, sessionBaseUrl) {
|
|
1566
|
+
const key = `${sessionApiKey}|${sessionBaseUrl ?? ""}`;
|
|
1567
|
+
let s = sessionSdkCache.get(key);
|
|
1568
|
+
if (!s) {
|
|
1569
|
+
s = new import_sdk.AgentScore({
|
|
1570
|
+
apiKey: sessionApiKey,
|
|
1571
|
+
baseUrl: sessionBaseUrl ?? baseUrl,
|
|
1572
|
+
userAgent: userAgentHeader
|
|
1573
|
+
});
|
|
1574
|
+
sessionSdkCache.set(key, s);
|
|
1575
|
+
}
|
|
1576
|
+
return s;
|
|
1577
|
+
}
|
|
1578
|
+
const cache = new TTLCache(cacheSeconds * 1e3);
|
|
1579
|
+
async function tryMintSessionDenial(ctx) {
|
|
1580
|
+
if (!createSessionOnMissing) return void 0;
|
|
1581
|
+
try {
|
|
1582
|
+
const sessionBody = {};
|
|
1583
|
+
if (createSessionOnMissing.context != null) sessionBody.context = createSessionOnMissing.context;
|
|
1584
|
+
if (createSessionOnMissing.productName != null) sessionBody.product_name = createSessionOnMissing.productName;
|
|
1585
|
+
if (createSessionOnMissing.getSessionOptions && ctx !== void 0) {
|
|
1586
|
+
try {
|
|
1587
|
+
const dynamic = await createSessionOnMissing.getSessionOptions(ctx);
|
|
1588
|
+
if (dynamic?.context != null) sessionBody.context = dynamic.context;
|
|
1589
|
+
if (dynamic?.productName != null) sessionBody.product_name = dynamic.productName;
|
|
1590
|
+
} catch (err) {
|
|
1591
|
+
console.warn("[gate] createSessionOnMissing.getSessionOptions hook failed:", err instanceof Error ? err.message : err);
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
const sessionSdk = getSessionSdk(createSessionOnMissing.apiKey, createSessionOnMissing.baseUrl);
|
|
1595
|
+
const data = await sessionSdk.createSession({
|
|
1596
|
+
...sessionBody.context !== void 0 ? { context: sessionBody.context } : {},
|
|
1597
|
+
...sessionBody.product_name !== void 0 ? { product_name: sessionBody.product_name } : {}
|
|
1598
|
+
});
|
|
1599
|
+
if (typeof data.session_id !== "string" || typeof data.poll_secret !== "string" || typeof data.verify_url !== "string") {
|
|
1600
|
+
console.warn("[gate] /v1/sessions returned 200 without required fields \u2014 falling back to bare denial");
|
|
1601
|
+
return void 0;
|
|
1602
|
+
}
|
|
1603
|
+
let extra;
|
|
1604
|
+
if (createSessionOnMissing.onBeforeSession && ctx !== void 0) {
|
|
1605
|
+
try {
|
|
1606
|
+
const sessionMeta = {
|
|
1607
|
+
session_id: data.session_id,
|
|
1608
|
+
verify_url: data.verify_url,
|
|
1609
|
+
poll_secret: data.poll_secret,
|
|
1610
|
+
poll_url: data.poll_url,
|
|
1611
|
+
expires_at: data.expires_at
|
|
1612
|
+
};
|
|
1613
|
+
const result = await createSessionOnMissing.onBeforeSession(ctx, sessionMeta);
|
|
1614
|
+
if (result && typeof result === "object") extra = result;
|
|
1615
|
+
} catch (err) {
|
|
1616
|
+
console.warn("[gate] createSessionOnMissing.onBeforeSession hook failed:", err instanceof Error ? err.message : err);
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
const apiNextSteps = data.next_steps;
|
|
1620
|
+
return {
|
|
1621
|
+
code: "identity_verification_required",
|
|
1622
|
+
verify_url: data.verify_url,
|
|
1623
|
+
session_id: data.session_id,
|
|
1624
|
+
poll_secret: data.poll_secret,
|
|
1625
|
+
poll_url: data.poll_url,
|
|
1626
|
+
agent_instructions: apiNextSteps ? JSON.stringify(apiNextSteps) : void 0,
|
|
1627
|
+
agent_memory: agentMemoryHint,
|
|
1628
|
+
...extra && { extra }
|
|
1629
|
+
};
|
|
1630
|
+
} catch (err) {
|
|
1631
|
+
console.warn("[gate] createSessionOnMissing path failed \u2014 falling back to bare denial:", err instanceof Error ? err.message : err);
|
|
1632
|
+
return void 0;
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
async function evaluate(identity, ctx, signer) {
|
|
1636
|
+
if (!identity || !identity.address && !identity.operatorToken) {
|
|
1637
|
+
if (failOpen) return { kind: "allow" };
|
|
1638
|
+
const sessionReason = await tryMintSessionDenial(ctx);
|
|
1639
|
+
if (sessionReason) return { kind: "deny", reason: sessionReason };
|
|
1640
|
+
const missingIdentityInstructions = JSON.stringify({
|
|
1641
|
+
action: "probe_identity_then_session",
|
|
1642
|
+
steps: [
|
|
1643
|
+
"If you have a wallet AND this merchant accepts a wallet-signing rail (Tempo MPP or x402 \u2014 see openapi.json x-payment-info): send X-Wallet-Address and retry. If the wallet is already linked to an AgentScore operator (via prior SIWE claim or prior captureWallet), this passes in one round trip. If the wallet is unlinked or the account has no KYC, the 403 will include a verify_url \u2014 share it with the user to claim the wallet + complete KYC, then retry.",
|
|
1644
|
+
"If step 1 is denied or you already have a stored operator_token (valid + not expired): send X-Operator-Token: opc_... and retry.",
|
|
1645
|
+
"If neither applies: retry with NO identity header. Merchants that auto-create verification sessions (most AgentScore merchants do) return verify_url + session_id + poll_secret in the 403 body \u2014 share verify_url with the user, then poll poll_url every 5s with the X-Poll-Secret header until status=verified (the poll returns a one-time operator_token). If the retry returns the same bare 403, this merchant does not support self-service session bootstrapping \u2014 direct the user to https://agentscore.sh/sign-up to create an AgentScore identity and mint an operator_token from their dashboard (https://agentscore.sh/dashboard/verify). The user hands the opc_... to you, and you retry with X-Operator-Token."
|
|
1646
|
+
],
|
|
1647
|
+
user_message: "Try X-Wallet-Address first if you have a wallet and the merchant accepts Tempo/x402; fall back to a stored X-Operator-Token, then to the session/verify flow described in agent_memory.bootstrap."
|
|
1648
|
+
});
|
|
1649
|
+
return {
|
|
1650
|
+
kind: "deny",
|
|
1651
|
+
reason: {
|
|
1652
|
+
code: "missing_identity",
|
|
1653
|
+
agent_instructions: missingIdentityInstructions,
|
|
1654
|
+
agent_memory: agentMemoryHint
|
|
1655
|
+
}
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
const cacheKey2 = identity.operatorToken?.toLowerCase() ?? (identity.address ? normalizeAddress(identity.address) : "");
|
|
1659
|
+
const cached = cache.get(cacheKey2);
|
|
1660
|
+
if (cached) {
|
|
1661
|
+
if (cached.allow) {
|
|
1662
|
+
const cachedRaw = cached.raw;
|
|
1663
|
+
const cachedQuota = cachedRaw?.quota;
|
|
1664
|
+
return {
|
|
1665
|
+
kind: "allow",
|
|
1666
|
+
data: cachedRaw,
|
|
1667
|
+
...cachedQuota !== void 0 && { quota: cachedQuota }
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
if (isFixableDenial(cached.reasons)) {
|
|
1671
|
+
const sessionReason = await tryMintSessionDenial(ctx);
|
|
1672
|
+
if (sessionReason) return { kind: "deny", reason: sessionReason };
|
|
1673
|
+
}
|
|
1674
|
+
return {
|
|
1675
|
+
kind: "deny",
|
|
1676
|
+
reason: {
|
|
1677
|
+
code: "wallet_not_trusted",
|
|
1678
|
+
decision: cached.decision,
|
|
1679
|
+
reasons: cached.reasons,
|
|
1680
|
+
verify_url: cached.raw?.verify_url,
|
|
1681
|
+
data: cached.raw
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
}
|
|
1685
|
+
const policy = {};
|
|
1686
|
+
if (requireKyc != null) policy.require_kyc = requireKyc;
|
|
1687
|
+
if (requireSanctionsClear != null) policy.require_sanctions_clear = requireSanctionsClear;
|
|
1688
|
+
if (minAge != null) policy.min_age = minAge;
|
|
1689
|
+
if (blockedJurisdictions != null) policy.blocked_jurisdictions = blockedJurisdictions;
|
|
1690
|
+
if (allowedJurisdictions != null) policy.allowed_jurisdictions = allowedJurisdictions;
|
|
1691
|
+
let data;
|
|
1692
|
+
try {
|
|
1693
|
+
const opts = {
|
|
1694
|
+
chain: gateChain,
|
|
1695
|
+
...Object.keys(policy).length > 0 ? { policy } : {},
|
|
1696
|
+
// Pre-extracted payment signer (by the adapter middleware). When present, the API
|
|
1697
|
+
// composes BOTH signer_match (wallet-binding) and signer_sanctions (OFAC SDN wallet
|
|
1698
|
+
// check) verdicts on the response in one round trip. Under
|
|
1699
|
+
// policy.require_sanctions_clear, a signer_sanctions hit flips decision -> deny inline.
|
|
1700
|
+
...signer && { signer: { address: signer.address, network: signer.network } }
|
|
1701
|
+
};
|
|
1702
|
+
const result = identity.address ? await sdk.assess(identity.address, { ...opts, operatorToken: identity.operatorToken }) : await sdk.assess(null, { ...opts, operatorToken: identity.operatorToken });
|
|
1703
|
+
data = result;
|
|
1704
|
+
} catch (err) {
|
|
1705
|
+
if (err instanceof import_sdk.PaymentRequiredError) {
|
|
1706
|
+
if (failOpen) return { kind: "allow" };
|
|
1707
|
+
return { kind: "deny", reason: { code: "payment_required" } };
|
|
1708
|
+
}
|
|
1709
|
+
if (err instanceof import_sdk.TokenExpiredError) {
|
|
1710
|
+
return {
|
|
1711
|
+
kind: "deny",
|
|
1712
|
+
reason: {
|
|
1713
|
+
code: "token_expired",
|
|
1714
|
+
data: err.details,
|
|
1715
|
+
...err.verifyUrl ? { verify_url: err.verifyUrl } : {},
|
|
1716
|
+
...err.sessionId ? { session_id: err.sessionId } : {},
|
|
1717
|
+
...err.pollSecret ? { poll_secret: err.pollSecret } : {},
|
|
1718
|
+
...err.pollUrl ? { poll_url: err.pollUrl } : {},
|
|
1719
|
+
...err.nextSteps ? { agent_instructions: JSON.stringify(err.nextSteps) } : {},
|
|
1720
|
+
...err.agentMemory ? { agent_memory: err.agentMemory } : {}
|
|
1721
|
+
}
|
|
1722
|
+
};
|
|
1723
|
+
}
|
|
1724
|
+
if (err instanceof import_sdk.InvalidCredentialError) {
|
|
1725
|
+
return {
|
|
1726
|
+
kind: "deny",
|
|
1727
|
+
reason: {
|
|
1728
|
+
code: "invalid_credential",
|
|
1729
|
+
agent_instructions: INVALID_CREDENTIAL_INSTRUCTIONS,
|
|
1730
|
+
agent_memory: agentMemoryHint
|
|
1731
|
+
}
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
if (err instanceof import_sdk.QuotaExceededError) {
|
|
1735
|
+
console.warn("[gate] /v1/assess returned 429 quota_exceeded");
|
|
1736
|
+
if (failOpen) return { kind: "allow", degraded: true, infraReason: "quota_exceeded" };
|
|
1737
|
+
return {
|
|
1738
|
+
kind: "deny",
|
|
1739
|
+
reason: { code: "api_error", agent_instructions: QUOTA_EXCEEDED_INSTRUCTIONS }
|
|
1740
|
+
};
|
|
1741
|
+
}
|
|
1742
|
+
if (err instanceof import_sdk.TimeoutError) {
|
|
1743
|
+
console.warn("[gate] /v1/assess timed out:", err.message);
|
|
1744
|
+
if (failOpen) return { kind: "allow", degraded: true, infraReason: "network_timeout" };
|
|
1745
|
+
return { kind: "deny", reason: { code: "api_error" } };
|
|
1746
|
+
}
|
|
1747
|
+
const status = err?.status;
|
|
1748
|
+
const errName = err instanceof Error ? err.name : "";
|
|
1749
|
+
if (status === 429) {
|
|
1750
|
+
console.warn("[gate] /v1/assess returned 429 (untyped \u2014 defensive)");
|
|
1751
|
+
if (failOpen) return { kind: "allow", degraded: true, infraReason: "quota_exceeded" };
|
|
1752
|
+
return {
|
|
1753
|
+
kind: "deny",
|
|
1754
|
+
reason: { code: "api_error", agent_instructions: QUOTA_EXCEEDED_INSTRUCTIONS }
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
if (errName === "TimeoutError" || errName === "AbortError") {
|
|
1758
|
+
console.warn("[gate] /v1/assess timed out (by Error.name):", err instanceof Error ? err.message : err);
|
|
1759
|
+
if (failOpen) return { kind: "allow", degraded: true, infraReason: "network_timeout" };
|
|
1760
|
+
return { kind: "deny", reason: { code: "api_error" } };
|
|
1761
|
+
}
|
|
1762
|
+
const errCode = err?.code;
|
|
1763
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1764
|
+
const detail = errCode ? `${errCode}: ${msg}` : msg;
|
|
1765
|
+
console.warn(`[gate] /v1/assess call failed \u2014 surfacing as api_error: ${detail}`);
|
|
1766
|
+
if (failOpen) return { kind: "allow", degraded: true, infraReason: "api_error" };
|
|
1767
|
+
return { kind: "deny", reason: { code: "api_error" } };
|
|
1768
|
+
}
|
|
1769
|
+
const decision = data.decision;
|
|
1770
|
+
const decisionReasons = data.decision_reasons ?? [];
|
|
1771
|
+
const allow = decision === "allow" || decision == null;
|
|
1772
|
+
cache.set(cacheKey2, { allow, decision: decision ?? void 0, reasons: decisionReasons, raw: data });
|
|
1773
|
+
if (allow) {
|
|
1774
|
+
const quota = data.quota;
|
|
1775
|
+
return {
|
|
1776
|
+
kind: "allow",
|
|
1777
|
+
data,
|
|
1778
|
+
...quota !== void 0 && { quota }
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
if (isFixableDenial(decisionReasons)) {
|
|
1782
|
+
const sessionReason = await tryMintSessionDenial(ctx);
|
|
1783
|
+
if (sessionReason) return { kind: "deny", reason: sessionReason };
|
|
1784
|
+
}
|
|
1785
|
+
return {
|
|
1786
|
+
kind: "deny",
|
|
1787
|
+
reason: {
|
|
1788
|
+
code: "wallet_not_trusted",
|
|
1789
|
+
decision: decision ?? void 0,
|
|
1790
|
+
reasons: decisionReasons,
|
|
1791
|
+
verify_url: data.verify_url,
|
|
1792
|
+
data
|
|
1793
|
+
}
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
async function captureWallet(options2) {
|
|
1797
|
+
try {
|
|
1798
|
+
await sdk.associateWallet({
|
|
1799
|
+
operatorToken: options2.operatorToken,
|
|
1800
|
+
walletAddress: options2.walletAddress,
|
|
1801
|
+
network: options2.network,
|
|
1802
|
+
...options2.idempotencyKey ? { idempotencyKey: options2.idempotencyKey } : {}
|
|
1803
|
+
});
|
|
1804
|
+
} catch (err) {
|
|
1805
|
+
console.warn("[agentscore-commerce] captureWallet failed:", err instanceof Error ? err.message : err);
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
function projectSignerMatch(sm, claimedNorm, signerNorm) {
|
|
1809
|
+
const kind = sm.kind;
|
|
1810
|
+
if (kind === "pass") {
|
|
1811
|
+
return {
|
|
1812
|
+
kind: "pass",
|
|
1813
|
+
claimedOperator: sm.claimed_operator ?? null,
|
|
1814
|
+
signerOperator: sm.signer_operator ?? null
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1817
|
+
if (kind === "wallet_auth_requires_wallet_signing") {
|
|
1818
|
+
return {
|
|
1819
|
+
kind: "wallet_auth_requires_wallet_signing",
|
|
1820
|
+
claimedWallet: sm.claimed_wallet ?? claimedNorm,
|
|
1821
|
+
agentInstructions: sm.agent_instructions ?? WALLET_AUTH_REQUIRES_WALLET_SIGNING_INSTRUCTIONS
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
const linked = sm.linked_wallets;
|
|
1825
|
+
return {
|
|
1826
|
+
kind: "wallet_signer_mismatch",
|
|
1827
|
+
claimedOperator: sm.claimed_operator ?? null,
|
|
1828
|
+
actualSignerOperator: sm.signer_operator ?? null,
|
|
1829
|
+
expectedSigner: sm.expected_signer ?? claimedNorm,
|
|
1830
|
+
actualSigner: sm.actual_signer ?? signerNorm,
|
|
1831
|
+
linkedWallets: Array.isArray(linked) ? linked.filter((w) => typeof w === "string") : [],
|
|
1832
|
+
agentInstructions: sm.agent_instructions ?? WALLET_SIGNER_MISMATCH_INSTRUCTIONS
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1835
|
+
function getSignerVerdict(claimedAddress) {
|
|
1836
|
+
const claimedNorm = normalizeAddress(claimedAddress);
|
|
1837
|
+
const cached = cache.get(claimedNorm);
|
|
1838
|
+
if (!cached) return void 0;
|
|
1839
|
+
const raw = cached.raw;
|
|
1840
|
+
if (!raw) return void 0;
|
|
1841
|
+
const rawMatch = raw.signer_match;
|
|
1842
|
+
const rawSanctions = raw.signer_sanctions;
|
|
1843
|
+
if (!rawMatch && !rawSanctions) return void 0;
|
|
1844
|
+
const signerNorm = rawMatch?.actual_signer ?? claimedNorm;
|
|
1845
|
+
return {
|
|
1846
|
+
signer_match: rawMatch ? projectSignerMatch(rawMatch, claimedNorm, signerNorm) : null,
|
|
1847
|
+
signer_sanctions: rawSanctions ?? null
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1850
|
+
return { evaluate, captureWallet, getSignerVerdict };
|
|
1851
|
+
}
|
|
306
1852
|
|
|
307
1853
|
// src/signer.ts
|
|
308
1854
|
var TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
@@ -377,9 +1923,33 @@ async function extractPaymentSigner(request, x402PaymentHeader) {
|
|
|
377
1923
|
}
|
|
378
1924
|
return null;
|
|
379
1925
|
}
|
|
1926
|
+
async function extractPaymentSignerFromAuth(authHeader, x402PaymentHeader) {
|
|
1927
|
+
const request = new Request("http://internal.gate/", {
|
|
1928
|
+
headers: authHeader ? { authorization: authHeader } : {}
|
|
1929
|
+
});
|
|
1930
|
+
return extractPaymentSigner(request, x402PaymentHeader);
|
|
1931
|
+
}
|
|
380
1932
|
function readX402PaymentHeader(request) {
|
|
381
1933
|
return request.headers.get("payment-signature") ?? request.headers.get("x-payment") ?? void 0;
|
|
382
1934
|
}
|
|
1935
|
+
function lowerHeaders(headers) {
|
|
1936
|
+
const out = {};
|
|
1937
|
+
for (const [k, v] of Object.entries(headers)) out[k.toLowerCase()] = v;
|
|
1938
|
+
return out;
|
|
1939
|
+
}
|
|
1940
|
+
async function extractSignerForPrecheck(headers) {
|
|
1941
|
+
const lower = lowerHeaders(headers);
|
|
1942
|
+
const x402 = lower["payment-signature"] ?? lower["x-payment"];
|
|
1943
|
+
if (x402) {
|
|
1944
|
+
const signer = await extractPaymentSignerFromAuth(void 0, x402);
|
|
1945
|
+
if (signer !== null) return signer;
|
|
1946
|
+
}
|
|
1947
|
+
const authorization = lower["authorization"];
|
|
1948
|
+
if (authorization && authorization.toLowerCase().startsWith("payment ")) {
|
|
1949
|
+
return await extractPaymentSignerFromAuth(authorization);
|
|
1950
|
+
}
|
|
1951
|
+
return null;
|
|
1952
|
+
}
|
|
383
1953
|
|
|
384
1954
|
// src/identity/a2a.ts
|
|
385
1955
|
var PROTOCOL_VERSION = "1.0";
|
|
@@ -435,434 +2005,2062 @@ function buildA2AAgentCard(input) {
|
|
|
435
2005
|
return card;
|
|
436
2006
|
}
|
|
437
2007
|
|
|
438
|
-
// src/
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
2008
|
+
// src/index.ts
|
|
2009
|
+
init_ucp();
|
|
2010
|
+
init_ucp_jwks();
|
|
2011
|
+
|
|
2012
|
+
// src/checkout.ts
|
|
2013
|
+
var import_node_crypto = require("crypto");
|
|
2014
|
+
|
|
2015
|
+
// src/payment/rail_spec.ts
|
|
2016
|
+
init_usdc();
|
|
2017
|
+
async function resolveRecipient(r) {
|
|
2018
|
+
if (typeof r === "string") return r;
|
|
2019
|
+
return Promise.resolve(r());
|
|
2020
|
+
}
|
|
2021
|
+
var RAIL_SPEC_DEFAULTS = {
|
|
2022
|
+
tempo: {
|
|
2023
|
+
network: "tempo-mainnet",
|
|
2024
|
+
chainId: 4217,
|
|
2025
|
+
token: USDC.tempo.mainnet.address,
|
|
2026
|
+
symbol: "USDC.e",
|
|
2027
|
+
decimals: 6,
|
|
2028
|
+
testnet: false,
|
|
2029
|
+
recommend: "both"
|
|
2030
|
+
},
|
|
2031
|
+
x402Base: {
|
|
2032
|
+
network: "eip155:8453",
|
|
2033
|
+
chainId: 8453,
|
|
2034
|
+
token: USDC.base.mainnet.address,
|
|
2035
|
+
symbol: "USDC",
|
|
2036
|
+
decimals: 6,
|
|
2037
|
+
mode: "exact"
|
|
2038
|
+
},
|
|
2039
|
+
solanaMpp: {
|
|
2040
|
+
network: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
|
2041
|
+
token: USDC.solana.mainnet.mint,
|
|
2042
|
+
symbol: "USDC",
|
|
2043
|
+
decimals: 6
|
|
2044
|
+
},
|
|
2045
|
+
stripe: {
|
|
2046
|
+
rails: ["card", "link", "shared_payment_token"]
|
|
2047
|
+
},
|
|
2048
|
+
tempoSession: {
|
|
2049
|
+
currency: USDC.tempo.mainnet.address,
|
|
2050
|
+
testnet: false
|
|
442
2051
|
}
|
|
443
|
-
|
|
444
|
-
|
|
2052
|
+
};
|
|
2053
|
+
|
|
2054
|
+
// src/challenge/accepted_methods.ts
|
|
2055
|
+
async function buildAcceptedMethods({
|
|
2056
|
+
tempo,
|
|
2057
|
+
x402_base,
|
|
2058
|
+
solana_mpp,
|
|
2059
|
+
stripe
|
|
2060
|
+
}) {
|
|
2061
|
+
const out = [];
|
|
2062
|
+
if (tempo) {
|
|
2063
|
+
out.push({
|
|
2064
|
+
method: "tempo/charge",
|
|
2065
|
+
network: tempo.network ?? RAIL_SPEC_DEFAULTS.tempo.network,
|
|
2066
|
+
chain_id: tempo.chainId ?? RAIL_SPEC_DEFAULTS.tempo.chainId,
|
|
2067
|
+
token: tempo.token ?? RAIL_SPEC_DEFAULTS.tempo.token,
|
|
2068
|
+
symbol: tempo.symbol ?? RAIL_SPEC_DEFAULTS.tempo.symbol,
|
|
2069
|
+
decimals: tempo.decimals ?? RAIL_SPEC_DEFAULTS.tempo.decimals,
|
|
2070
|
+
pay_to: await resolveRecipient(tempo.recipient)
|
|
2071
|
+
});
|
|
445
2072
|
}
|
|
446
|
-
if (
|
|
447
|
-
|
|
2073
|
+
if (x402_base) {
|
|
2074
|
+
out.push({
|
|
2075
|
+
method: "x402/exact",
|
|
2076
|
+
network: x402_base.network ?? RAIL_SPEC_DEFAULTS.x402Base.network,
|
|
2077
|
+
chain_id: x402_base.chainId ?? RAIL_SPEC_DEFAULTS.x402Base.chainId,
|
|
2078
|
+
token: x402_base.token ?? RAIL_SPEC_DEFAULTS.x402Base.token,
|
|
2079
|
+
symbol: x402_base.symbol ?? RAIL_SPEC_DEFAULTS.x402Base.symbol,
|
|
2080
|
+
decimals: x402_base.decimals ?? RAIL_SPEC_DEFAULTS.x402Base.decimals,
|
|
2081
|
+
pay_to: await resolveRecipient(x402_base.recipient)
|
|
2082
|
+
});
|
|
448
2083
|
}
|
|
449
|
-
if (
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
2084
|
+
if (solana_mpp) {
|
|
2085
|
+
out.push({
|
|
2086
|
+
method: "solana/charge",
|
|
2087
|
+
network: solana_mpp.network ?? RAIL_SPEC_DEFAULTS.solanaMpp.network,
|
|
2088
|
+
token: solana_mpp.token ?? RAIL_SPEC_DEFAULTS.solanaMpp.token,
|
|
2089
|
+
symbol: solana_mpp.symbol ?? RAIL_SPEC_DEFAULTS.solanaMpp.symbol,
|
|
2090
|
+
decimals: solana_mpp.decimals ?? RAIL_SPEC_DEFAULTS.solanaMpp.decimals,
|
|
2091
|
+
pay_to: await resolveRecipient(solana_mpp.recipient)
|
|
2092
|
+
});
|
|
453
2093
|
}
|
|
454
|
-
if (
|
|
455
|
-
|
|
2094
|
+
if (stripe) {
|
|
2095
|
+
out.push({
|
|
2096
|
+
method: "stripe/charge",
|
|
2097
|
+
rails: stripe.rails ?? [...RAIL_SPEC_DEFAULTS.stripe.rails],
|
|
2098
|
+
profile_id: stripe.profileId ?? null
|
|
2099
|
+
});
|
|
456
2100
|
}
|
|
457
|
-
return
|
|
2101
|
+
return out;
|
|
458
2102
|
}
|
|
459
|
-
|
|
460
|
-
|
|
2103
|
+
|
|
2104
|
+
// src/challenge/agent_instructions.ts
|
|
2105
|
+
var TEMPO_WARNING = "Do NOT use `tempo wallet transfer`. That moves USDC on-chain without completing the protocol handshake, so the request will not complete. Use `tempo request` instead.";
|
|
2106
|
+
var X402_WARNING = "Do NOT send USDC manually to the deposit addresses. Use `agentscore-pay pay` so the credential is signed and submitted; otherwise the request will not complete even though the deposit lands.";
|
|
2107
|
+
var TEMPO_TOOL = "`tempo request` for Tempo USDC";
|
|
2108
|
+
var AGENTSCORE_PAY_TOOL = "`agentscore-pay` \u2014 Base + Solana + Tempo from one CLI";
|
|
2109
|
+
var DEFAULT_WALLET_COMPATIBILITY = "Any client that can produce a valid MPP credential (Authorization: Payment) or x402 X-Payment header. Use the CLI commands above; sign-it-yourself is also fine.";
|
|
2110
|
+
function defaultRecommendedTools(howToPay) {
|
|
2111
|
+
const tools = [];
|
|
2112
|
+
if (howToPay.tempo) tools.push(TEMPO_TOOL);
|
|
2113
|
+
if (howToPay.tempo || howToPay.x402_base || howToPay.solana_mpp) tools.push(AGENTSCORE_PAY_TOOL);
|
|
2114
|
+
return tools;
|
|
2115
|
+
}
|
|
2116
|
+
function defaultWarnings(howToPay) {
|
|
2117
|
+
const w = [];
|
|
2118
|
+
if (howToPay.tempo) w.push(TEMPO_WARNING);
|
|
2119
|
+
if (howToPay.x402_base) w.push(X402_WARNING);
|
|
2120
|
+
return w;
|
|
2121
|
+
}
|
|
2122
|
+
var RAIL_CLIENTS = {
|
|
2123
|
+
tempo_mpp: ["agentscore-pay", "tempo request", "x402-proxy"],
|
|
2124
|
+
x402_base: ["agentscore-pay", "x402-proxy", "purl (omit --network flag)"],
|
|
2125
|
+
solana_mpp: ["agentscore-pay"],
|
|
2126
|
+
stripe: ["link-cli"]
|
|
461
2127
|
};
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
"
|
|
470
|
-
"
|
|
471
|
-
"
|
|
472
|
-
"
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
2128
|
+
function compatibleClientsByRails(rails2) {
|
|
2129
|
+
const out = {};
|
|
2130
|
+
for (const r of rails2) out[r] = [...RAIL_CLIENTS[r]];
|
|
2131
|
+
return Object.keys(out).length === 0 ? void 0 : out;
|
|
2132
|
+
}
|
|
2133
|
+
function defaultCompatibleClients(howToPay) {
|
|
2134
|
+
const rails2 = [];
|
|
2135
|
+
if (howToPay.tempo) rails2.push("tempo_mpp");
|
|
2136
|
+
if (howToPay.x402_base) rails2.push("x402_base");
|
|
2137
|
+
if (howToPay.solana_mpp) rails2.push("solana_mpp");
|
|
2138
|
+
if (howToPay.stripe) rails2.push("stripe");
|
|
2139
|
+
return compatibleClientsByRails(rails2);
|
|
2140
|
+
}
|
|
2141
|
+
function buildAgentInstructions({
|
|
2142
|
+
howToPay,
|
|
2143
|
+
recommendedTools,
|
|
2144
|
+
walletCompatibility,
|
|
2145
|
+
timeoutSeconds,
|
|
2146
|
+
warnings,
|
|
2147
|
+
extraWarnings,
|
|
2148
|
+
recommended,
|
|
2149
|
+
compatibleClients,
|
|
2150
|
+
extra
|
|
2151
|
+
}) {
|
|
2152
|
+
const compatibleClientsOut = compatibleClients ?? defaultCompatibleClients(howToPay);
|
|
2153
|
+
return {
|
|
2154
|
+
how_to_pay: howToPay,
|
|
2155
|
+
recommended_tools: recommendedTools ?? defaultRecommendedTools(howToPay),
|
|
2156
|
+
wallet_compatibility: walletCompatibility ?? DEFAULT_WALLET_COMPATIBILITY,
|
|
2157
|
+
timeout_seconds: timeoutSeconds ?? 300,
|
|
2158
|
+
warnings: warnings ?? [...defaultWarnings(howToPay), ...extraWarnings ?? []],
|
|
2159
|
+
...recommended ? { recommended } : {},
|
|
2160
|
+
...compatibleClientsOut ? { compatible_clients: compatibleClientsOut } : {},
|
|
2161
|
+
...extra ?? {}
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
// src/challenge/agent_memory.ts
|
|
2166
|
+
function firstEncounterAgentMemory({
|
|
2167
|
+
firstEncounter
|
|
2168
|
+
}) {
|
|
2169
|
+
if (!firstEncounter) return void 0;
|
|
2170
|
+
return buildAgentMemoryHint();
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
// src/challenge/body.ts
|
|
2174
|
+
function build402Body({
|
|
2175
|
+
acceptedMethods,
|
|
2176
|
+
agentInstructions,
|
|
2177
|
+
identityMetadata,
|
|
2178
|
+
agentMemory,
|
|
2179
|
+
pricing,
|
|
2180
|
+
amountUsd,
|
|
2181
|
+
currency,
|
|
2182
|
+
orderId,
|
|
2183
|
+
product,
|
|
2184
|
+
retryBody,
|
|
2185
|
+
recommended,
|
|
2186
|
+
x402,
|
|
2187
|
+
extra
|
|
2188
|
+
}) {
|
|
2189
|
+
const body = {
|
|
2190
|
+
payment_required: true,
|
|
2191
|
+
accepted_methods: acceptedMethods
|
|
2192
|
+
};
|
|
2193
|
+
if (x402) {
|
|
2194
|
+
body.x402Version = x402.version ?? 2;
|
|
2195
|
+
body.accepts = x402.accepts;
|
|
2196
|
+
if (x402.extensions !== void 0 && Object.keys(x402.extensions).length > 0) {
|
|
2197
|
+
body.extensions = x402.extensions;
|
|
495
2198
|
}
|
|
496
2199
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
2200
|
+
if (amountUsd !== void 0) body.amount_usd = amountUsd;
|
|
2201
|
+
if (currency) body.currency = currency;
|
|
2202
|
+
if (pricing) body.pricing = pricing;
|
|
2203
|
+
if (orderId !== void 0) body.order_id = orderId;
|
|
2204
|
+
if (product) body.product = product;
|
|
2205
|
+
if (recommended) body.recommended = recommended;
|
|
2206
|
+
if (retryBody !== void 0) body.retry_body = retryBody;
|
|
2207
|
+
if (identityMetadata) {
|
|
2208
|
+
Object.assign(body, identityMetadata);
|
|
506
2209
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
2210
|
+
if (agentInstructions) body.agent_instructions = agentInstructions;
|
|
2211
|
+
if (agentMemory !== void 0) body.agent_memory = agentMemory;
|
|
2212
|
+
if (extra) Object.assign(body, extra);
|
|
2213
|
+
return body;
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
// src/challenge/how_to_pay.ts
|
|
2217
|
+
var TEMPO_SETUP = [
|
|
2218
|
+
"curl -fsSL https://tempo.xyz/install | bash",
|
|
2219
|
+
"tempo wallet login",
|
|
2220
|
+
"tempo wallet whoami",
|
|
2221
|
+
"tempo wallet fund # if balance is zero"
|
|
2222
|
+
];
|
|
2223
|
+
var PAY_SETUP_BASE = [
|
|
2224
|
+
"npm install -g @agent-score/pay # or: brew install agentscore/tap/agentscore-pay",
|
|
2225
|
+
"agentscore-pay wallet create --chain base",
|
|
2226
|
+
"agentscore-pay balance --chain base # fund the printed address with USDC on Base"
|
|
2227
|
+
];
|
|
2228
|
+
var PAY_SETUP_SOLANA = [
|
|
2229
|
+
"npm install -g @agent-score/pay # or: brew install agentscore/tap/agentscore-pay",
|
|
2230
|
+
"agentscore-pay wallet create --chain solana",
|
|
2231
|
+
"agentscore-pay balance --chain solana # fund the printed address with USDC on Solana"
|
|
2232
|
+
];
|
|
2233
|
+
function buildHowToPay({
|
|
2234
|
+
url,
|
|
2235
|
+
retryBodyJson,
|
|
2236
|
+
totalUsd,
|
|
2237
|
+
rails: rails2,
|
|
2238
|
+
opTokenPlaceholder,
|
|
2239
|
+
maxSpend
|
|
2240
|
+
}) {
|
|
2241
|
+
const totalNum = typeof totalUsd === "string" ? Number(totalUsd) : totalUsd;
|
|
2242
|
+
const maxSpendStr = String(maxSpend ?? (Math.ceil(totalNum) + 1).toFixed(2));
|
|
2243
|
+
const opToken = opTokenPlaceholder ?? "<your_opc_token>";
|
|
2244
|
+
const block = {};
|
|
2245
|
+
if (rails2.tempo) {
|
|
2246
|
+
const networkName = rails2.tempo.testnet ? "tempo-testnet" : rails2.tempo.network ?? RAIL_SPEC_DEFAULTS.tempo.network;
|
|
2247
|
+
const chainId = rails2.tempo.chainId ?? RAIL_SPEC_DEFAULTS.tempo.chainId;
|
|
2248
|
+
const recommend = rails2.tempo.recommend ?? RAIL_SPEC_DEFAULTS.tempo.recommend;
|
|
2249
|
+
const tempoCommand = `tempo request -X POST -H 'X-Operator-Token: ${opToken}' -H 'Content-Type: application/json' --json '${retryBodyJson}' --max-spend ${maxSpendStr} ${url}`;
|
|
2250
|
+
const payCommand = `agentscore-pay pay POST ${url} --chain tempo -H 'X-Operator-Token: ${opToken}' -H 'Content-Type: application/json' -d '${retryBodyJson}' --max-spend ${maxSpendStr}`;
|
|
2251
|
+
block.tempo = {
|
|
2252
|
+
setup: TEMPO_SETUP,
|
|
2253
|
+
prerequisite: `Run \`tempo wallet whoami\` and confirm USDC.e balance on ${networkName} (chain ${chainId}) is at least $${maxSpendStr}. If the tempo CLI is not installed, run the setup commands above first.`,
|
|
2254
|
+
command: recommend === "agentscore-pay" ? payCommand : tempoCommand,
|
|
2255
|
+
...recommend === "both" ? { alternative_command: payCommand } : recommend === "agentscore-pay" ? { alternative_command: tempoCommand } : {},
|
|
2256
|
+
what_it_does: `Pays via Tempo USDC on ${networkName}.`
|
|
2257
|
+
};
|
|
510
2258
|
}
|
|
511
|
-
if (
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
2259
|
+
if (rails2.x402_base) {
|
|
2260
|
+
const network = rails2.x402_base.network ?? RAIL_SPEC_DEFAULTS.x402Base.network;
|
|
2261
|
+
block.x402_base = {
|
|
2262
|
+
setup: PAY_SETUP_BASE,
|
|
2263
|
+
prerequisite: `Run \`agentscore-pay balance --chain base\` and confirm USDC balance on Base (${network}) is at least $${maxSpendStr}. If the CLI is not installed, run the setup commands above first.`,
|
|
2264
|
+
command: `agentscore-pay pay POST ${url} --chain base -H 'X-Operator-Token: ${opToken}' -H 'Content-Type: application/json' -d '${retryBodyJson}' --max-spend ${maxSpendStr}`,
|
|
2265
|
+
what_it_does: "Pays via USDC on Base."
|
|
518
2266
|
};
|
|
519
|
-
if (Object.keys(gateConfig).length > 0) agentscoreBinding.config = gateConfig;
|
|
520
|
-
const existing = capabilities[AGENTSCORE_CAPABILITY_NAME];
|
|
521
|
-
if (existing) existing.push(agentscoreBinding);
|
|
522
|
-
else capabilities[AGENTSCORE_CAPABILITY_NAME] = [agentscoreBinding];
|
|
523
2267
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
if (input.ucp_extras) {
|
|
533
|
-
for (const k of Object.keys(input.ucp_extras)) {
|
|
534
|
-
if (RESERVED_UCP_FIELDS.has(k)) {
|
|
535
|
-
throw new Error(`buildUCPProfile: ucp_extras key "${k}" collides with a reserved \`ucp\` field; rejected.`);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
Object.assign(ucp, input.ucp_extras);
|
|
2268
|
+
if (rails2.solana_mpp) {
|
|
2269
|
+
const network = rails2.solana_mpp.network ?? RAIL_SPEC_DEFAULTS.solanaMpp.network;
|
|
2270
|
+
block.solana_mpp = {
|
|
2271
|
+
setup: PAY_SETUP_SOLANA,
|
|
2272
|
+
prerequisite: `Run \`agentscore-pay balance --chain solana\` and confirm USDC balance on Solana (${network}) is at least $${maxSpendStr}. If the CLI is not installed, run the setup commands above first.`,
|
|
2273
|
+
command: `agentscore-pay pay POST ${url} --chain solana -H 'X-Operator-Token: ${opToken}' -H 'Content-Type: application/json' -d '${retryBodyJson}' --max-spend ${maxSpendStr}`,
|
|
2274
|
+
what_it_does: "Pays via USDC on Solana."
|
|
2275
|
+
};
|
|
539
2276
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
2277
|
+
if (rails2.stripe) {
|
|
2278
|
+
const stripeCfg = rails2.stripe;
|
|
2279
|
+
const amountCents = Math.round(totalNum * 100);
|
|
2280
|
+
const linkCliBlocked = amountCents > 5e4;
|
|
2281
|
+
const productName = stripeCfg.productName ?? "this purchase";
|
|
2282
|
+
const sptContext = `Purchasing "${productName}" via the agent commerce API. The user authorized this purchase through their AI agent for $${totalNum}; charge to be settled via shared payment token over the Machine Payments Protocol.`;
|
|
2283
|
+
const stripe = {
|
|
2284
|
+
prerequisite: "Either your own Stripe account with Shared Payment Token acceptance, OR a Stripe Link wallet (any user with link.com).",
|
|
2285
|
+
instructions: "Mint a SharedPaymentToken scoped to the profile_id advertised in accepted_methods, then submit via Authorization: Payment MPP header with method=stripe/charge."
|
|
2286
|
+
};
|
|
2287
|
+
if (stripeCfg.profileId && !linkCliBlocked) {
|
|
2288
|
+
stripe.setup_link_cli = [
|
|
2289
|
+
"npm install -g @stripe/link-cli # or use npx -y @stripe/link-cli for one-shot",
|
|
2290
|
+
"link-cli auth login # one-time, opens your Link wallet",
|
|
2291
|
+
"link-cli payment-methods list --output-json # copy a csmrpd_... id"
|
|
2292
|
+
];
|
|
2293
|
+
stripe.command_link_cli = [
|
|
2294
|
+
`SPEND_ID=$(link-cli spend-request create --payment-method-id <csmrpd_id_from_payment_methods_list> --credential-type shared_payment_token --network-id ${stripeCfg.profileId} --amount ${amountCents} --context "${sptContext}" --request-approval --output-json | jq -r .id)`,
|
|
2295
|
+
`link-cli mpp pay ${url} --spend-request-id $SPEND_ID --method POST --data '${retryBodyJson}' --header 'X-Operator-Token: ${opToken}' --output-json`
|
|
2296
|
+
];
|
|
2297
|
+
stripe.what_it_does_link_cli = "Mints a one-time-use SharedPaymentToken scoped to this purchase (user approves in Link wallet), then submits it as the payment credential.";
|
|
2298
|
+
} else if (linkCliBlocked) {
|
|
2299
|
+
stripe.note = `link-cli SPT path not available for this purchase \u2014 Stripe link-cli caps spend requests at $500.00 ($50000 cents); your total is $${totalNum}. Use your own Stripe account with the SharedPaymentToken API instead.`;
|
|
549
2300
|
}
|
|
550
|
-
|
|
2301
|
+
block.stripe = stripe;
|
|
551
2302
|
}
|
|
552
|
-
return
|
|
553
|
-
}
|
|
554
|
-
var AGENTSCORE_UCP_CAPABILITY = AGENTSCORE_CAPABILITY_NAME;
|
|
555
|
-
var HANDLER_VERSION = "2026-04-08";
|
|
556
|
-
var SPEC_BASE = "https://agentscore.sh/specification/payment-handlers";
|
|
557
|
-
var SCHEMA_BASE = "https://agentscore.sh/schemas/payment-handlers";
|
|
558
|
-
function mppPaymentHandler(input) {
|
|
559
|
-
return {
|
|
560
|
-
"sh.agentscore.payment.mpp": [{
|
|
561
|
-
id: "mpp",
|
|
562
|
-
version: HANDLER_VERSION,
|
|
563
|
-
spec: `${SPEC_BASE}/mpp`,
|
|
564
|
-
schema: `${SCHEMA_BASE}/mpp.json`,
|
|
565
|
-
config: { networks: input.networks }
|
|
566
|
-
}]
|
|
567
|
-
};
|
|
2303
|
+
return block;
|
|
568
2304
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
};
|
|
2305
|
+
|
|
2306
|
+
// src/challenge/identity.ts
|
|
2307
|
+
function buildIdentityMetadata({
|
|
2308
|
+
mode,
|
|
2309
|
+
wallet,
|
|
2310
|
+
signerMatchResult,
|
|
2311
|
+
linkedWallets,
|
|
2312
|
+
signerConstraint
|
|
2313
|
+
}) {
|
|
2314
|
+
const block = { identity_mode: mode };
|
|
2315
|
+
if (mode !== "wallet") return block;
|
|
2316
|
+
if (wallet) {
|
|
2317
|
+
block.required_signer = signerMatchResult?.expectedSigner ?? wallet;
|
|
2318
|
+
}
|
|
2319
|
+
if (linkedWallets && linkedWallets.length > 0) {
|
|
2320
|
+
block.linked_wallets = linkedWallets;
|
|
2321
|
+
}
|
|
2322
|
+
block.signer_constraint = signerConstraint ?? "Payment must be signed with the claimed wallet OR any same-operator linked wallet listed in linked_wallets.";
|
|
2323
|
+
return block;
|
|
579
2324
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
2325
|
+
|
|
2326
|
+
// src/challenge/pricing.ts
|
|
2327
|
+
function buildPricingBlock({
|
|
2328
|
+
subtotalCents,
|
|
2329
|
+
taxCents = 0,
|
|
2330
|
+
shippingCents,
|
|
2331
|
+
discountCents,
|
|
2332
|
+
totalCents,
|
|
2333
|
+
taxRate,
|
|
2334
|
+
taxState,
|
|
2335
|
+
currency
|
|
2336
|
+
}) {
|
|
2337
|
+
const shipping = shippingCents ?? 0;
|
|
2338
|
+
const discount = discountCents ?? 0;
|
|
2339
|
+
const total = totalCents ?? Math.max(0, subtotalCents + taxCents + shipping - discount);
|
|
2340
|
+
const block = {
|
|
2341
|
+
subtotal: formatCents(subtotalCents),
|
|
2342
|
+
tax: formatCents(taxCents),
|
|
2343
|
+
total: formatCents(total)
|
|
589
2344
|
};
|
|
2345
|
+
if (shippingCents !== void 0) block.shipping = formatCents(shipping);
|
|
2346
|
+
if (discountCents !== void 0) block.discount = formatCents(discount);
|
|
2347
|
+
if (taxRate !== void 0) block.tax_rate = taxRate;
|
|
2348
|
+
if (taxState !== void 0) block.tax_state = taxState;
|
|
2349
|
+
if (currency !== void 0) block.currency = currency;
|
|
2350
|
+
return block;
|
|
2351
|
+
}
|
|
2352
|
+
function formatCents(cents) {
|
|
2353
|
+
return (cents / 100).toFixed(2);
|
|
590
2354
|
}
|
|
591
2355
|
|
|
592
|
-
// src/
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
2356
|
+
// src/challenge/respond_402.ts
|
|
2357
|
+
init_wwwauthenticate();
|
|
2358
|
+
function respond402({
|
|
2359
|
+
mppxChallengeHeaders,
|
|
2360
|
+
body,
|
|
2361
|
+
x402
|
|
2362
|
+
}) {
|
|
2363
|
+
const headers = {};
|
|
2364
|
+
for (const [k, v] of Object.entries(mppxChallengeHeaders)) {
|
|
2365
|
+
headers[k.toLowerCase()] = v;
|
|
601
2366
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
try {
|
|
606
|
-
return await import("jose");
|
|
607
|
-
} catch (err) {
|
|
608
|
-
throw new Error(
|
|
609
|
-
`UCP signing requires the \`jose\` library, which is an optional peer dependency. ${JOSE_INSTALL_HINT}
|
|
610
|
-
Original error: ${err instanceof Error ? err.message : String(err)}`
|
|
611
|
-
);
|
|
2367
|
+
headers["content-type"] = "application/json";
|
|
2368
|
+
if (x402) {
|
|
2369
|
+
headers["payment-required"] = paymentRequiredHeader(x402);
|
|
612
2370
|
}
|
|
2371
|
+
return { body, headers, status: 402 };
|
|
613
2372
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
2373
|
+
|
|
2374
|
+
// src/challenge/validation_error.ts
|
|
2375
|
+
function buildValidationError({
|
|
2376
|
+
code,
|
|
2377
|
+
message,
|
|
2378
|
+
requiredFields,
|
|
2379
|
+
exampleBody,
|
|
2380
|
+
nextSteps,
|
|
2381
|
+
extra
|
|
2382
|
+
}) {
|
|
2383
|
+
const body = {
|
|
2384
|
+
error: { code, message }
|
|
2385
|
+
};
|
|
2386
|
+
if (requiredFields) body.required_fields = requiredFields;
|
|
2387
|
+
if (exampleBody !== void 0) body.example_body = exampleBody;
|
|
2388
|
+
if (nextSteps) body.next_steps = nextSteps;
|
|
2389
|
+
if (extra) Object.assign(body, extra);
|
|
2390
|
+
return body;
|
|
618
2391
|
}
|
|
619
|
-
|
|
620
|
-
|
|
2392
|
+
|
|
2393
|
+
// src/stripe-multichain/mppx_stripe.ts
|
|
2394
|
+
async function createMppxStripe({
|
|
2395
|
+
profileId,
|
|
2396
|
+
secretKey,
|
|
2397
|
+
paymentMethodTypes
|
|
2398
|
+
}) {
|
|
2399
|
+
const moduleName = "mppx/server";
|
|
2400
|
+
const mppx = await import(moduleName).catch(() => null);
|
|
2401
|
+
if (!mppx?.stripe?.charge) {
|
|
621
2402
|
throw new Error(
|
|
622
|
-
"
|
|
2403
|
+
"mppx not installed \u2014 install with `npm install mppx` to use createMppxStripe."
|
|
623
2404
|
);
|
|
624
2405
|
}
|
|
625
|
-
|
|
626
|
-
|
|
2406
|
+
return mppx.stripe.charge({
|
|
2407
|
+
networkId: profileId,
|
|
2408
|
+
paymentMethodTypes: paymentMethodTypes ?? ["card", "link"],
|
|
2409
|
+
secretKey
|
|
2410
|
+
});
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
// src/payment/mppx_server.ts
|
|
2414
|
+
init_networks();
|
|
2415
|
+
init_usdc();
|
|
2416
|
+
function isStripeRailSpec(s) {
|
|
2417
|
+
return !("recipient" in s);
|
|
2418
|
+
}
|
|
2419
|
+
function isTempoSessionRailSpec2(s) {
|
|
2420
|
+
return "escrowContract" in s && "store" in s;
|
|
2421
|
+
}
|
|
2422
|
+
function isSolanaMppRailSpec(s) {
|
|
2423
|
+
if (!("recipient" in s)) return false;
|
|
2424
|
+
if ("escrowContract" in s) return false;
|
|
2425
|
+
if ("rpcUrl" in s || "tokenProgram" in s) return true;
|
|
2426
|
+
return s.network?.startsWith("solana:") ?? false;
|
|
2427
|
+
}
|
|
2428
|
+
function solanaNetworkFromCAIP2(caip2) {
|
|
2429
|
+
if (caip2 === networks.solana.devnet.caip2) return "devnet";
|
|
2430
|
+
return "mainnet-beta";
|
|
2431
|
+
}
|
|
2432
|
+
function solanaDefaultRpcUrl(network) {
|
|
2433
|
+
if (network === "mainnet-beta") return "https://api.mainnet-beta.solana.com";
|
|
2434
|
+
if (network === "devnet") return "https://api.devnet.solana.com";
|
|
2435
|
+
return "http://localhost:8899";
|
|
2436
|
+
}
|
|
2437
|
+
async function createMppxServer({
|
|
2438
|
+
rails: rails2,
|
|
2439
|
+
methods: extraMethods,
|
|
2440
|
+
secretKey
|
|
2441
|
+
}) {
|
|
2442
|
+
const mppx = await dynamicImport("mppx/server");
|
|
2443
|
+
if (!mppx?.Mppx?.create) {
|
|
2444
|
+
throw new Error("mppx not installed \u2014 `npm install mppx` to use createMppxServer.");
|
|
627
2445
|
}
|
|
628
|
-
|
|
629
|
-
|
|
2446
|
+
const methods = [...extraMethods ?? []];
|
|
2447
|
+
for (const [name, spec] of Object.entries(rails2 ?? {})) {
|
|
2448
|
+
if (isStripeRailSpec(spec)) {
|
|
2449
|
+
methods.push(await registerStripe(spec));
|
|
2450
|
+
continue;
|
|
2451
|
+
}
|
|
2452
|
+
if (isTempoSessionRailSpec2(spec)) {
|
|
2453
|
+
methods.push(await registerTempoSession(mppx, spec));
|
|
2454
|
+
continue;
|
|
2455
|
+
}
|
|
2456
|
+
if (isSolanaMppRailSpec(spec)) {
|
|
2457
|
+
methods.push(await registerSolana(spec));
|
|
2458
|
+
continue;
|
|
2459
|
+
}
|
|
2460
|
+
methods.push(registerTempo(mppx, spec, name));
|
|
630
2461
|
}
|
|
631
|
-
|
|
2462
|
+
return mppx.Mppx.create({ methods, secretKey });
|
|
2463
|
+
}
|
|
2464
|
+
function registerTempo(mppx, spec, _name) {
|
|
2465
|
+
if (!mppx.tempo?.charge) {
|
|
2466
|
+
throw new Error("mppx.tempo.charge not available \u2014 check installed mppx version.");
|
|
2467
|
+
}
|
|
2468
|
+
const defaultCurrency = spec.testnet ? USDC.tempo.testnet.address : USDC.tempo.mainnet.address;
|
|
2469
|
+
if (typeof spec.recipient !== "string") {
|
|
2470
|
+
throw new TypeError(
|
|
2471
|
+
"createMppxServer: TempoRailSpec requires a string recipient (per-order factories not supported here)."
|
|
2472
|
+
);
|
|
2473
|
+
}
|
|
2474
|
+
return mppx.tempo.charge({
|
|
2475
|
+
currency: spec.token ?? defaultCurrency,
|
|
2476
|
+
recipient: spec.recipient,
|
|
2477
|
+
testnet: spec.testnet ?? false
|
|
2478
|
+
});
|
|
2479
|
+
}
|
|
2480
|
+
async function registerTempoSession(mppx, spec) {
|
|
2481
|
+
if (!mppx.tempo?.session) {
|
|
632
2482
|
throw new Error(
|
|
633
|
-
"
|
|
2483
|
+
"mppx.tempo.session not available \u2014 your mppx version may not support sessions yet. Upgrade with `npm install mppx@latest`."
|
|
634
2484
|
);
|
|
635
2485
|
}
|
|
636
|
-
|
|
2486
|
+
const defaultCurrency = spec.testnet ? USDC.tempo.testnet.address : USDC.tempo.mainnet.address;
|
|
2487
|
+
return mppx.tempo.session({
|
|
2488
|
+
currency: spec.currency ?? defaultCurrency,
|
|
2489
|
+
recipient: await resolveRecipient(spec.recipient),
|
|
2490
|
+
escrowContract: spec.escrowContract,
|
|
2491
|
+
store: spec.store,
|
|
2492
|
+
testnet: spec.testnet ?? false,
|
|
2493
|
+
...spec.chains ? { chains: spec.chains } : {}
|
|
2494
|
+
});
|
|
2495
|
+
}
|
|
2496
|
+
async function registerSolana(spec) {
|
|
2497
|
+
const solanaMpp = await dynamicImport("@solana/mpp/server");
|
|
2498
|
+
if (!solanaMpp?.charge) {
|
|
637
2499
|
throw new Error(
|
|
638
|
-
|
|
2500
|
+
"@solana/mpp not installed \u2014 `npm install @solana/mpp @solana/kit` to use the solana rail."
|
|
639
2501
|
);
|
|
640
2502
|
}
|
|
641
|
-
|
|
642
|
-
|
|
2503
|
+
const network = solanaNetworkFromCAIP2(spec.network);
|
|
2504
|
+
const defaultMint = network === "mainnet-beta" ? USDC.solana.mainnet.mint : USDC.solana.devnet.mint;
|
|
2505
|
+
const defaultDecimals = network === "mainnet-beta" ? USDC.solana.mainnet.decimals : USDC.solana.devnet.decimals;
|
|
2506
|
+
if (typeof spec.recipient !== "string") {
|
|
2507
|
+
throw new TypeError(
|
|
2508
|
+
"createMppxServer: SolanaMppRailSpec requires a string recipient (per-order factories not supported here)."
|
|
2509
|
+
);
|
|
643
2510
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
2511
|
+
const baseMethod = solanaMpp.charge({
|
|
2512
|
+
recipient: spec.recipient,
|
|
2513
|
+
currency: spec.token ?? defaultMint,
|
|
2514
|
+
decimals: spec.decimals ?? defaultDecimals,
|
|
2515
|
+
network,
|
|
2516
|
+
...spec.rpcUrl ? { rpcUrl: spec.rpcUrl } : {},
|
|
2517
|
+
...spec.signer ? { signer: spec.signer } : {},
|
|
2518
|
+
...spec.tokenProgram ? { tokenProgram: spec.tokenProgram } : {}
|
|
2519
|
+
});
|
|
2520
|
+
return wrapSolanaChargeWithFinalizedBlockhash(baseMethod, spec.rpcUrl ?? solanaDefaultRpcUrl(network));
|
|
2521
|
+
}
|
|
2522
|
+
async function registerStripe(spec) {
|
|
2523
|
+
if (!spec.profileId || !spec.secretKey) {
|
|
2524
|
+
throw new Error(
|
|
2525
|
+
"createMppxServer: StripeRailSpec requires both profileId and secretKey."
|
|
2526
|
+
);
|
|
2527
|
+
}
|
|
2528
|
+
return createMppxStripe({
|
|
2529
|
+
profileId: spec.profileId,
|
|
2530
|
+
secretKey: spec.secretKey,
|
|
2531
|
+
paymentMethodTypes: spec.paymentMethodTypes
|
|
2532
|
+
});
|
|
2533
|
+
}
|
|
2534
|
+
async function dynamicImport(moduleName) {
|
|
2535
|
+
try {
|
|
2536
|
+
return await import(moduleName);
|
|
2537
|
+
} catch {
|
|
2538
|
+
return null;
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
function wrapSolanaChargeWithFinalizedBlockhash(baseMethod, rpcUrl) {
|
|
2542
|
+
return {
|
|
2543
|
+
...baseMethod,
|
|
2544
|
+
async request(args) {
|
|
2545
|
+
const orig = await baseMethod.request(args);
|
|
2546
|
+
if (args.credential || !orig || typeof orig !== "object") return orig;
|
|
2547
|
+
try {
|
|
2548
|
+
const res = await fetch(rpcUrl, {
|
|
2549
|
+
method: "POST",
|
|
2550
|
+
headers: { "Content-Type": "application/json" },
|
|
2551
|
+
body: JSON.stringify({
|
|
2552
|
+
id: 1,
|
|
2553
|
+
jsonrpc: "2.0",
|
|
2554
|
+
method: "getLatestBlockhash",
|
|
2555
|
+
params: [{ commitment: "finalized" }]
|
|
2556
|
+
})
|
|
2557
|
+
});
|
|
2558
|
+
const data = await res.json();
|
|
2559
|
+
const finalized = data?.result?.value?.blockhash;
|
|
2560
|
+
if (finalized) {
|
|
2561
|
+
return {
|
|
2562
|
+
...orig,
|
|
2563
|
+
methodDetails: { ...orig.methodDetails ?? {}, recentBlockhash: finalized }
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2566
|
+
} catch {
|
|
2567
|
+
}
|
|
2568
|
+
return orig;
|
|
659
2569
|
}
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
|
|
2573
|
+
// src/payment/x402_server.ts
|
|
2574
|
+
init_networks();
|
|
2575
|
+
|
|
2576
|
+
// src/payment/x402.ts
|
|
2577
|
+
function registerX402SchemesV1V2(server, network, scheme) {
|
|
2578
|
+
server.register(network, scheme);
|
|
2579
|
+
if (typeof server.registerV1 === "function") {
|
|
2580
|
+
server.registerV1(network, scheme);
|
|
660
2581
|
}
|
|
661
|
-
|
|
662
|
-
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
// src/payment/x402_server.ts
|
|
2585
|
+
async function createX402Server(opts = {}) {
|
|
2586
|
+
const x402Core = await dynamicImport2("@x402/core/server") ?? null;
|
|
2587
|
+
if (!x402Core) {
|
|
2588
|
+
throw new Error(
|
|
2589
|
+
"@x402/core not installed \u2014 `npm install @x402/core` to use createX402Server."
|
|
2590
|
+
);
|
|
2591
|
+
}
|
|
2592
|
+
let facilitator;
|
|
2593
|
+
const facilitatorChoice = opts.facilitator ?? (process.env.CDP_API_KEY_ID && process.env.CDP_API_KEY_SECRET ? "coinbase" : "http");
|
|
2594
|
+
if (facilitatorChoice === "coinbase") {
|
|
2595
|
+
const cb = await dynamicImport2("@coinbase/x402");
|
|
2596
|
+
if (!cb?.facilitator) {
|
|
663
2597
|
throw new Error(
|
|
664
|
-
|
|
2598
|
+
'@coinbase/x402 not installed \u2014 `npm install @coinbase/x402` for facilitator: "coinbase".'
|
|
665
2599
|
);
|
|
666
2600
|
}
|
|
667
|
-
|
|
2601
|
+
facilitator = new x402Core.HTTPFacilitatorClient(cb.facilitator);
|
|
2602
|
+
} else if (facilitatorChoice === "http") {
|
|
2603
|
+
facilitator = new x402Core.HTTPFacilitatorClient();
|
|
2604
|
+
} else {
|
|
2605
|
+
facilitator = facilitatorChoice;
|
|
668
2606
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
const
|
|
673
|
-
const
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
if (
|
|
2607
|
+
const server = new x402Core.x402ResourceServer(facilitator);
|
|
2608
|
+
let evmExactModule = null;
|
|
2609
|
+
let evmUptoModule = null;
|
|
2610
|
+
for (const rail of opts.rails ?? []) {
|
|
2611
|
+
const isUpto = rail.endsWith("-upto");
|
|
2612
|
+
if (rail.startsWith("x402-base")) {
|
|
2613
|
+
const baseRail = isUpto ? rail.slice(0, -5) : rail;
|
|
2614
|
+
const network = baseRail === "x402-base-mainnet" ? networks.base.mainnet.caip2 : networks.base.sepolia.caip2;
|
|
2615
|
+
if (isUpto) {
|
|
2616
|
+
evmUptoModule ??= await dynamicImport2("@x402/evm/upto/server");
|
|
2617
|
+
if (!evmUptoModule?.UptoEvmScheme) {
|
|
2618
|
+
throw new Error("@x402/evm not installed \u2014 `npm install @x402/evm` for x402 base upto rails.");
|
|
2619
|
+
}
|
|
2620
|
+
registerX402SchemesV1V2(server, network, new evmUptoModule.UptoEvmScheme());
|
|
2621
|
+
} else {
|
|
2622
|
+
evmExactModule ??= await dynamicImport2("@x402/evm/exact/server");
|
|
2623
|
+
if (!evmExactModule?.ExactEvmScheme) {
|
|
2624
|
+
throw new Error("@x402/evm not installed \u2014 `npm install @x402/evm` for x402 base rails.");
|
|
2625
|
+
}
|
|
2626
|
+
registerX402SchemesV1V2(server, network, new evmExactModule.ExactEvmScheme());
|
|
2627
|
+
}
|
|
678
2628
|
}
|
|
679
|
-
|
|
680
|
-
})
|
|
681
|
-
|
|
682
|
-
|
|
2629
|
+
}
|
|
2630
|
+
for (const { network, scheme } of opts.schemes ?? []) {
|
|
2631
|
+
registerX402SchemesV1V2(server, network, scheme);
|
|
2632
|
+
}
|
|
2633
|
+
if (opts.bazaar) {
|
|
2634
|
+
const bazaar = await dynamicImport2("@x402/extensions/bazaar");
|
|
2635
|
+
if (!bazaar?.bazaarResourceServerExtension) {
|
|
683
2636
|
throw new Error(
|
|
684
|
-
"
|
|
2637
|
+
"@x402/extensions not installed \u2014 `npm install @x402/extensions` for bazaar discovery."
|
|
685
2638
|
);
|
|
686
2639
|
}
|
|
2640
|
+
server.registerExtension(bazaar.bazaarResourceServerExtension);
|
|
687
2641
|
}
|
|
688
|
-
|
|
689
|
-
|
|
2642
|
+
if (opts.initialize !== false) {
|
|
2643
|
+
await server.initialize();
|
|
2644
|
+
}
|
|
2645
|
+
return server;
|
|
690
2646
|
}
|
|
691
|
-
async function
|
|
692
|
-
const
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
2647
|
+
async function buildX402AcceptsFor402(server, opts) {
|
|
2648
|
+
const requirements = await server.buildPaymentRequirements(
|
|
2649
|
+
{
|
|
2650
|
+
scheme: opts.scheme ?? "exact",
|
|
2651
|
+
network: opts.network,
|
|
2652
|
+
price: opts.price,
|
|
2653
|
+
payTo: opts.payTo,
|
|
2654
|
+
maxTimeoutSeconds: opts.maxTimeoutSeconds ?? 300
|
|
2655
|
+
},
|
|
2656
|
+
opts.extensions
|
|
2657
|
+
);
|
|
2658
|
+
return Array.isArray(requirements) ? requirements : [];
|
|
2659
|
+
}
|
|
2660
|
+
async function dynamicImport2(moduleName) {
|
|
2661
|
+
try {
|
|
2662
|
+
return await import(moduleName);
|
|
2663
|
+
} catch {
|
|
2664
|
+
return null;
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
// src/payment/lazy.ts
|
|
2669
|
+
function x402RailName(spec) {
|
|
2670
|
+
const network = spec.network ?? "eip155:8453";
|
|
2671
|
+
if (network === "eip155:8453") return "x402-base-mainnet";
|
|
2672
|
+
if (network === "eip155:84532") return "x402-base-sepolia";
|
|
2673
|
+
throw new Error(
|
|
2674
|
+
`lazyX402Server: unsupported X402BaseRailSpec.network=${JSON.stringify(network)}`
|
|
2675
|
+
);
|
|
2676
|
+
}
|
|
2677
|
+
function lazyX402Server(opts) {
|
|
2678
|
+
const { spec, cdpApiKeyId, cdpApiKeySecret } = opts;
|
|
2679
|
+
const railName = x402RailName(spec);
|
|
2680
|
+
const useCdp = Boolean(cdpApiKeyId && cdpApiKeySecret);
|
|
2681
|
+
const facilitator = useCdp ? "coinbase" : "http";
|
|
2682
|
+
let cached;
|
|
2683
|
+
let pending;
|
|
2684
|
+
return async () => {
|
|
2685
|
+
if (cached !== void 0) return cached;
|
|
2686
|
+
if (pending !== void 0) return pending;
|
|
2687
|
+
pending = (async () => {
|
|
2688
|
+
const server = await createX402Server({ facilitator, rails: [railName] });
|
|
2689
|
+
cached = server;
|
|
2690
|
+
pending = void 0;
|
|
2691
|
+
return server;
|
|
2692
|
+
})();
|
|
2693
|
+
return pending;
|
|
701
2694
|
};
|
|
702
|
-
return { privateKey, publicJWK };
|
|
703
2695
|
}
|
|
704
|
-
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
2696
|
+
function lazyMppxServer(opts) {
|
|
2697
|
+
const { rails: rails2, secretKey } = opts;
|
|
2698
|
+
let cached;
|
|
2699
|
+
let pending;
|
|
2700
|
+
return async () => {
|
|
2701
|
+
if (cached !== void 0) return cached;
|
|
2702
|
+
if (pending !== void 0) return pending;
|
|
2703
|
+
pending = (async () => {
|
|
2704
|
+
const server = await createMppxServer({ secretKey, rails: rails2 });
|
|
2705
|
+
cached = server;
|
|
2706
|
+
pending = void 0;
|
|
2707
|
+
return server;
|
|
2708
|
+
})();
|
|
2709
|
+
return pending;
|
|
2710
|
+
};
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
// src/payment/x402_settle.ts
|
|
2714
|
+
function classifyX402SettleResult(result) {
|
|
2715
|
+
if (result.success) return null;
|
|
2716
|
+
switch (result.phase) {
|
|
2717
|
+
case "no_requirements":
|
|
2718
|
+
return {
|
|
2719
|
+
status: 500,
|
|
2720
|
+
code: "payment_internal_error",
|
|
2721
|
+
message: "Failed to build x402 payment requirements for this configuration",
|
|
2722
|
+
nextSteps: {
|
|
2723
|
+
action: "contact_support",
|
|
2724
|
+
user_message: "The merchant could not produce a payment challenge for this request. Try again later or contact support."
|
|
2725
|
+
}
|
|
2726
|
+
};
|
|
2727
|
+
case "verify_failed":
|
|
2728
|
+
return {
|
|
2729
|
+
status: 400,
|
|
2730
|
+
code: "payment_proof_invalid",
|
|
2731
|
+
message: "Payment credential failed verification; regenerate from a fresh 402 challenge",
|
|
2732
|
+
nextSteps: {
|
|
2733
|
+
action: "regenerate_payment_credential",
|
|
2734
|
+
user_message: "The payment credential was rejected at verify time. Discard it, fetch a fresh 402 challenge, and re-sign."
|
|
2735
|
+
}
|
|
2736
|
+
};
|
|
2737
|
+
case "facilitator_error":
|
|
2738
|
+
return {
|
|
2739
|
+
status: 503,
|
|
2740
|
+
code: "payment_provider_unavailable",
|
|
2741
|
+
message: "Payment provider could not process this network configuration",
|
|
2742
|
+
nextSteps: {
|
|
2743
|
+
action: "try_different_rail",
|
|
2744
|
+
user_message: "This rail is currently unavailable. Pick a different rail from the 402 challenge and retry."
|
|
2745
|
+
}
|
|
2746
|
+
};
|
|
2747
|
+
case "settle_failed":
|
|
2748
|
+
return {
|
|
2749
|
+
status: 503,
|
|
2750
|
+
code: "payment_provider_unavailable",
|
|
2751
|
+
message: "Payment credential verified but on-chain settlement failed",
|
|
2752
|
+
nextSteps: {
|
|
2753
|
+
action: "retry_or_swap_method",
|
|
2754
|
+
retry_after_seconds: 10,
|
|
2755
|
+
user_message: "Transient settlement error. Retry in a few seconds, or pick a different rail from the 402 challenge."
|
|
2756
|
+
}
|
|
2757
|
+
};
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
async function processX402Settle({
|
|
2761
|
+
x402Server,
|
|
2762
|
+
payload,
|
|
2763
|
+
resourceConfig,
|
|
2764
|
+
resourceMeta,
|
|
2765
|
+
extension,
|
|
2766
|
+
transportContext
|
|
2767
|
+
}) {
|
|
2768
|
+
const server = x402Server;
|
|
2769
|
+
let builtRequirements;
|
|
2770
|
+
try {
|
|
2771
|
+
builtRequirements = await server.buildPaymentRequirements(resourceConfig);
|
|
2772
|
+
} catch (err) {
|
|
2773
|
+
console.warn("[x402_settle] build_requirements failed:", err instanceof Error ? err.message : err);
|
|
2774
|
+
return { success: false, phase: "facilitator_error", step: "build_requirements", error: err };
|
|
2775
|
+
}
|
|
2776
|
+
const matchedRequirement = builtRequirements[0];
|
|
2777
|
+
if (!matchedRequirement) {
|
|
2778
|
+
return { success: false, phase: "no_requirements", reason: "x402Server.buildPaymentRequirements returned empty" };
|
|
2779
|
+
}
|
|
2780
|
+
const resolvedTransportContext = transportContext ?? (() => {
|
|
2781
|
+
const path = new URL(resourceMeta.url).pathname;
|
|
2782
|
+
return { method: "POST", adapter: { getPath: () => path }, routePattern: path };
|
|
2783
|
+
})();
|
|
2784
|
+
let enrichedExt;
|
|
2785
|
+
try {
|
|
2786
|
+
enrichedExt = extension !== void 0 ? server.enrichExtensions(extension, resolvedTransportContext) : void 0;
|
|
2787
|
+
} catch (err) {
|
|
2788
|
+
console.warn("[x402_settle] enrich_extensions failed:", err instanceof Error ? err.message : err);
|
|
2789
|
+
return { success: false, phase: "facilitator_error", step: "enrich_extensions", error: err };
|
|
2790
|
+
}
|
|
2791
|
+
let verifyResult;
|
|
2792
|
+
try {
|
|
2793
|
+
verifyResult = await server.verifyPayment(
|
|
2794
|
+
payload,
|
|
2795
|
+
matchedRequirement,
|
|
2796
|
+
enrichedExt,
|
|
2797
|
+
resolvedTransportContext
|
|
710
2798
|
);
|
|
2799
|
+
} catch (err) {
|
|
2800
|
+
console.warn("[x402_settle] verify_payment failed:", err instanceof Error ? err.message : err);
|
|
2801
|
+
return { success: false, phase: "facilitator_error", step: "verify_payment", error: err };
|
|
711
2802
|
}
|
|
712
|
-
|
|
713
|
-
|
|
2803
|
+
const verifyOk = verifyResult.isValid === true || verifyResult.success === true;
|
|
2804
|
+
if (!verifyOk) {
|
|
2805
|
+
return { success: false, phase: "verify_failed", verifyResult };
|
|
714
2806
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
2807
|
+
try {
|
|
2808
|
+
const settleResult = await server.settlePayment(
|
|
2809
|
+
payload,
|
|
2810
|
+
matchedRequirement,
|
|
2811
|
+
enrichedExt,
|
|
2812
|
+
resolvedTransportContext
|
|
719
2813
|
);
|
|
2814
|
+
const paymentResponseHeader = settleResult ? Buffer.from(JSON.stringify(settleResult)).toString("base64") : void 0;
|
|
2815
|
+
return {
|
|
2816
|
+
success: true,
|
|
2817
|
+
matchedRequirement,
|
|
2818
|
+
settleResult,
|
|
2819
|
+
paymentResponseHeader,
|
|
2820
|
+
verifyResult
|
|
2821
|
+
};
|
|
2822
|
+
} catch (err) {
|
|
2823
|
+
return { success: false, phase: "settle_failed", error: err, matchedRequirement };
|
|
720
2824
|
}
|
|
721
|
-
const canonicalBody = canonicalizeProfile(profile);
|
|
722
|
-
const payloadBytes = new TextEncoder().encode(canonicalBody);
|
|
723
|
-
const signature = await new jose.CompactSign(payloadBytes).setProtectedHeader({ alg, kid: opts.kid, typ: PROFILE_TYP }).sign(opts.signingKey);
|
|
724
|
-
return { ...profile, signature };
|
|
725
2825
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
2826
|
+
|
|
2827
|
+
// src/payment/x402_validation.ts
|
|
2828
|
+
init_networks();
|
|
2829
|
+
var X402_SUPPORTED_BASE_NETWORKS = /* @__PURE__ */ new Set([
|
|
2830
|
+
networks.base.mainnet.caip2,
|
|
2831
|
+
networks.base.sepolia.caip2
|
|
2832
|
+
]);
|
|
2833
|
+
var EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
2834
|
+
var REGENERATE_WARNING = "Use `agentscore-pay pay --chain base` (or `tempo request` for Tempo USDC) so the credential is signed and submitted via the protocol handshake. Do NOT use `tempo wallet transfer` \u2014 that sends USDC on-chain but does not complete the handshake.";
|
|
2835
|
+
function regenerateBody(message, userMessage) {
|
|
2836
|
+
return {
|
|
2837
|
+
error: { code: "payment_proof_invalid", message },
|
|
2838
|
+
next_steps: {
|
|
2839
|
+
action: "regenerate_payment_credential",
|
|
2840
|
+
user_message: userMessage,
|
|
2841
|
+
warning: REGENERATE_WARNING
|
|
2842
|
+
}
|
|
2843
|
+
};
|
|
2844
|
+
}
|
|
2845
|
+
async function verifyX402Request({
|
|
2846
|
+
request,
|
|
2847
|
+
isCachedAddress,
|
|
2848
|
+
acceptedNetwork
|
|
2849
|
+
}) {
|
|
2850
|
+
const headerValue = request.headers.get("payment-signature") ?? request.headers.get("x-payment");
|
|
2851
|
+
if (!headerValue) {
|
|
2852
|
+
return {
|
|
2853
|
+
ok: false,
|
|
2854
|
+
status: 400,
|
|
2855
|
+
body: regenerateBody(
|
|
2856
|
+
"X-Payment header missing",
|
|
2857
|
+
"No X-Payment header was sent. Generate the credential from the 402 challenge and resubmit on the same endpoint."
|
|
2858
|
+
)
|
|
2859
|
+
};
|
|
732
2860
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
2861
|
+
let payload;
|
|
2862
|
+
try {
|
|
2863
|
+
payload = JSON.parse(Buffer.from(headerValue, "base64").toString());
|
|
2864
|
+
} catch {
|
|
2865
|
+
return {
|
|
2866
|
+
ok: false,
|
|
2867
|
+
status: 400,
|
|
2868
|
+
body: regenerateBody(
|
|
2869
|
+
"X-Payment header is not valid base64 JSON",
|
|
2870
|
+
"The payment credential could not be decoded. Reconstruct the credential from the 402 challenge and retry."
|
|
2871
|
+
)
|
|
2872
|
+
};
|
|
2873
|
+
}
|
|
2874
|
+
const signedNetwork = payload.accepted?.network;
|
|
2875
|
+
const signedPayTo = payload.accepted?.payTo;
|
|
2876
|
+
if (!signedNetwork || signedNetwork !== acceptedNetwork) {
|
|
2877
|
+
if (signedNetwork && signedNetwork.toLowerCase().startsWith("solana:")) {
|
|
2878
|
+
return {
|
|
2879
|
+
ok: false,
|
|
2880
|
+
status: 400,
|
|
2881
|
+
body: regenerateBody(
|
|
2882
|
+
`x402 on ${signedNetwork} is not accepted; Solana payments must use the \`solana/charge\` rail advertised in the 402 challenge. This server accepts x402 on ${acceptedNetwork} only.`,
|
|
2883
|
+
"Solana payments are not accepted over x402 at this merchant. Pick the `solana/charge` rail from the 402 challenge and re-sign."
|
|
2884
|
+
)
|
|
2885
|
+
};
|
|
2886
|
+
}
|
|
2887
|
+
return {
|
|
2888
|
+
ok: false,
|
|
2889
|
+
status: 400,
|
|
2890
|
+
body: regenerateBody(
|
|
2891
|
+
`Unsupported x402 network ${signedNetwork ?? "<missing>"}; this server accepts ${acceptedNetwork}.`,
|
|
2892
|
+
"The credential signed for an unsupported network. Pick the accepted network from the 402 challenge and re-sign."
|
|
2893
|
+
)
|
|
2894
|
+
};
|
|
2895
|
+
}
|
|
2896
|
+
const addressShapeOk = typeof signedPayTo === "string" && EVM_ADDRESS_RE.test(signedPayTo);
|
|
2897
|
+
if (!signedPayTo || !addressShapeOk) {
|
|
2898
|
+
return {
|
|
2899
|
+
ok: false,
|
|
2900
|
+
status: 400,
|
|
2901
|
+
body: regenerateBody(
|
|
2902
|
+
`Payment payload missing or malformed accepted.payTo address for network ${signedNetwork}`,
|
|
2903
|
+
"The credential payload is missing or malformed payTo for the signed network. Reconstruct the credential from the 402 challenge."
|
|
2904
|
+
)
|
|
2905
|
+
};
|
|
2906
|
+
}
|
|
2907
|
+
if (!await isCachedAddress(signedPayTo)) {
|
|
2908
|
+
return {
|
|
2909
|
+
ok: false,
|
|
2910
|
+
status: 400,
|
|
2911
|
+
body: regenerateBody(
|
|
2912
|
+
"payTo address not found in cache or expired. Request a fresh 402 challenge and retry.",
|
|
2913
|
+
"The deposit address is unknown or expired on this server. Request a fresh 402 challenge and re-sign against the new payTo."
|
|
2914
|
+
)
|
|
2915
|
+
};
|
|
2916
|
+
}
|
|
2917
|
+
return { ok: true, payload, signedNetwork, signedPayTo };
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
// src/payment/zero-settle.ts
|
|
2921
|
+
var EVM_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
2922
|
+
var SOLANA_BASE58_RE2 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
2923
|
+
var NULL_RESULT = {
|
|
2924
|
+
signerAddress: null,
|
|
2925
|
+
signerNetwork: null,
|
|
2926
|
+
txHash: null
|
|
2927
|
+
};
|
|
2928
|
+
function zeroAmountCarveOut({
|
|
2929
|
+
rail,
|
|
2930
|
+
payload,
|
|
2931
|
+
authorizationHeader
|
|
2932
|
+
}) {
|
|
2933
|
+
if (rail === "x402-base") {
|
|
2934
|
+
return x402SignerFromPayload(payload);
|
|
2935
|
+
}
|
|
2936
|
+
if (rail === "tempo" || rail === "solana") {
|
|
2937
|
+
return mppSignerFromAuth(authorizationHeader);
|
|
2938
|
+
}
|
|
2939
|
+
return NULL_RESULT;
|
|
2940
|
+
}
|
|
2941
|
+
function x402SignerFromPayload(payload) {
|
|
2942
|
+
if (!payload || typeof payload !== "object") return NULL_RESULT;
|
|
2943
|
+
const inner = payload.payload;
|
|
2944
|
+
if (!inner || typeof inner !== "object") return NULL_RESULT;
|
|
2945
|
+
const authorization = inner.authorization;
|
|
2946
|
+
if (!authorization || typeof authorization !== "object") return NULL_RESULT;
|
|
2947
|
+
const fromAddr = authorization.from;
|
|
2948
|
+
if (typeof fromAddr !== "string" || !EVM_RE.test(fromAddr)) return NULL_RESULT;
|
|
2949
|
+
return {
|
|
2950
|
+
signerAddress: fromAddr.toLowerCase(),
|
|
2951
|
+
signerNetwork: "evm",
|
|
2952
|
+
txHash: null
|
|
2953
|
+
};
|
|
2954
|
+
}
|
|
2955
|
+
function mppSignerFromAuth(authorizationHeader) {
|
|
2956
|
+
if (typeof authorizationHeader !== "string") return NULL_RESULT;
|
|
2957
|
+
if (!authorizationHeader.toLowerCase().startsWith("payment ")) return NULL_RESULT;
|
|
2958
|
+
const token = authorizationHeader.slice("payment ".length).trim();
|
|
2959
|
+
if (!token) return NULL_RESULT;
|
|
2960
|
+
let credential;
|
|
2961
|
+
try {
|
|
2962
|
+
credential = JSON.parse(atob(token));
|
|
2963
|
+
} catch {
|
|
2964
|
+
return NULL_RESULT;
|
|
2965
|
+
}
|
|
2966
|
+
if (!credential || typeof credential !== "object") return NULL_RESULT;
|
|
2967
|
+
let source = credential.source;
|
|
2968
|
+
if (typeof source !== "string") {
|
|
2969
|
+
const challenge = credential.challenge;
|
|
2970
|
+
if (challenge && typeof challenge === "object") {
|
|
2971
|
+
source = challenge.source;
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
if (typeof source !== "string") return NULL_RESULT;
|
|
2975
|
+
const parts = source.split(":");
|
|
2976
|
+
if (parts.length < 4 || parts[0] !== "did" || parts[1] !== "pkh") return NULL_RESULT;
|
|
2977
|
+
const family = parts[2];
|
|
2978
|
+
const addr = parts[parts.length - 1] ?? "";
|
|
2979
|
+
if (family === "eip155" && EVM_RE.test(addr)) {
|
|
2980
|
+
return { signerAddress: addr.toLowerCase(), signerNetwork: "evm", txHash: null };
|
|
2981
|
+
}
|
|
2982
|
+
if (family === "solana" && SOLANA_BASE58_RE2.test(addr)) {
|
|
2983
|
+
return { signerAddress: addr, signerNetwork: "solana", txHash: null };
|
|
2984
|
+
}
|
|
2985
|
+
return NULL_RESULT;
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
// src/checkout.ts
|
|
2989
|
+
function pricingResult(opts) {
|
|
2990
|
+
const currency = opts.currency ?? "USD";
|
|
2991
|
+
if (opts.subtotalCents !== void 0) {
|
|
2992
|
+
const totalCents = Math.max(
|
|
2993
|
+
0,
|
|
2994
|
+
opts.subtotalCents + (opts.taxCents ?? 0) + (opts.shippingCents ?? 0) - (opts.discountCents ?? 0)
|
|
738
2995
|
);
|
|
2996
|
+
const derivedAmount = opts.amountUsd ?? totalCents / 100;
|
|
2997
|
+
const block = buildPricingBlock({
|
|
2998
|
+
subtotalCents: opts.subtotalCents,
|
|
2999
|
+
taxCents: opts.taxCents ?? 0,
|
|
3000
|
+
...opts.shippingCents !== void 0 && { shippingCents: opts.shippingCents },
|
|
3001
|
+
...opts.discountCents !== void 0 && { discountCents: opts.discountCents },
|
|
3002
|
+
...opts.taxRate !== void 0 && { taxRate: opts.taxRate },
|
|
3003
|
+
...opts.taxState !== void 0 && { taxState: opts.taxState },
|
|
3004
|
+
currency
|
|
3005
|
+
});
|
|
3006
|
+
return {
|
|
3007
|
+
amountUsd: derivedAmount,
|
|
3008
|
+
currency,
|
|
3009
|
+
block,
|
|
3010
|
+
...opts.product !== void 0 && { product: opts.product },
|
|
3011
|
+
...opts.bodyExtras !== void 0 && { bodyExtras: opts.bodyExtras }
|
|
3012
|
+
};
|
|
3013
|
+
}
|
|
3014
|
+
if (opts.amountUsd === void 0) {
|
|
3015
|
+
throw new Error("pricingResult requires either `subtotalCents` or `amountUsd`.");
|
|
3016
|
+
}
|
|
3017
|
+
return {
|
|
3018
|
+
amountUsd: opts.amountUsd,
|
|
3019
|
+
currency,
|
|
3020
|
+
...opts.product !== void 0 && { product: opts.product },
|
|
3021
|
+
...opts.bodyExtras !== void 0 && { bodyExtras: opts.bodyExtras }
|
|
3022
|
+
};
|
|
3023
|
+
}
|
|
3024
|
+
function getIdentityStatus(ctx) {
|
|
3025
|
+
const assess = ctx.request.assess;
|
|
3026
|
+
if (assess === null || assess === void 0) return "anonymous";
|
|
3027
|
+
const decision = assess.decision;
|
|
3028
|
+
if (decision === "allow") return "verified";
|
|
3029
|
+
return "unverified";
|
|
3030
|
+
}
|
|
3031
|
+
var CheckoutValidationError = class extends Error {
|
|
3032
|
+
code;
|
|
3033
|
+
action;
|
|
3034
|
+
status;
|
|
3035
|
+
extra;
|
|
3036
|
+
constructor(opts) {
|
|
3037
|
+
super(opts.message);
|
|
3038
|
+
this.name = "CheckoutValidationError";
|
|
3039
|
+
this.code = opts.code;
|
|
3040
|
+
this.action = opts.action ?? "fix_request";
|
|
3041
|
+
this.status = opts.status ?? 400;
|
|
3042
|
+
this.extra = opts.extra;
|
|
3043
|
+
}
|
|
3044
|
+
};
|
|
3045
|
+
function lowerHeaders2(headers) {
|
|
3046
|
+
const out = {};
|
|
3047
|
+
for (const [k, v] of Object.entries(headers)) out[k.toLowerCase()] = v;
|
|
3048
|
+
return out;
|
|
3049
|
+
}
|
|
3050
|
+
function hasX402Header(headers) {
|
|
3051
|
+
const h = lowerHeaders2(headers);
|
|
3052
|
+
return Boolean(h["payment-signature"] ?? h["x-payment"]);
|
|
3053
|
+
}
|
|
3054
|
+
function hasMppxHeader(headers) {
|
|
3055
|
+
const h = lowerHeaders2(headers);
|
|
3056
|
+
return (h["authorization"] ?? "").startsWith("Payment ");
|
|
3057
|
+
}
|
|
3058
|
+
function resolveIdentityMetadata(ctx) {
|
|
3059
|
+
const h = lowerHeaders2(ctx.request.headers);
|
|
3060
|
+
const wallet = h["x-wallet-address"];
|
|
3061
|
+
if (!wallet) return void 0;
|
|
3062
|
+
let linkedWallets;
|
|
3063
|
+
const assess = ctx.request.assess;
|
|
3064
|
+
if (assess && typeof assess === "object") {
|
|
3065
|
+
const identity = assess["identity"];
|
|
3066
|
+
if (identity && typeof identity === "object") {
|
|
3067
|
+
const lw = identity["linked_wallets"];
|
|
3068
|
+
if (Array.isArray(lw) && lw.every((x) => typeof x === "string")) {
|
|
3069
|
+
linkedWallets = lw;
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
return buildIdentityMetadata({
|
|
3074
|
+
mode: "wallet",
|
|
3075
|
+
wallet,
|
|
3076
|
+
...linkedWallets !== void 0 ? { linkedWallets } : {}
|
|
3077
|
+
});
|
|
3078
|
+
}
|
|
3079
|
+
function isStripeRailSpec2(s) {
|
|
3080
|
+
return !("recipient" in s);
|
|
3081
|
+
}
|
|
3082
|
+
function isTempoSessionRailSpec3(s) {
|
|
3083
|
+
return "escrowContract" in s && "store" in s;
|
|
3084
|
+
}
|
|
3085
|
+
function specRailKey(spec) {
|
|
3086
|
+
if (isStripeRailSpec2(spec)) return "stripe";
|
|
3087
|
+
if (isTempoSessionRailSpec3(spec)) return "tempo_mpp";
|
|
3088
|
+
const network = spec.network ?? "";
|
|
3089
|
+
if (network.startsWith("eip155:")) return "x402_base";
|
|
3090
|
+
if (network.startsWith("solana:") || "rpcUrl" in spec) return "solana_mpp";
|
|
3091
|
+
return "tempo_mpp";
|
|
3092
|
+
}
|
|
3093
|
+
function specMethodName(spec) {
|
|
3094
|
+
if (isStripeRailSpec2(spec)) return "stripe/spt";
|
|
3095
|
+
if (isTempoSessionRailSpec3(spec)) return "tempo/charge";
|
|
3096
|
+
const network = spec.network ?? "";
|
|
3097
|
+
if (network.startsWith("eip155:")) return "x402/exact (base)";
|
|
3098
|
+
if (network.startsWith("solana:") || "rpcUrl" in spec) return "solana/charge";
|
|
3099
|
+
return "tempo/charge";
|
|
3100
|
+
}
|
|
3101
|
+
function makeMppxComposeHook(opts) {
|
|
3102
|
+
return async (ctx) => {
|
|
3103
|
+
if (ctx.pricing === null) return { status: 402 };
|
|
3104
|
+
const mpp = await opts.serverGetter();
|
|
3105
|
+
const lower = lowerHeaders2(ctx.request.headers);
|
|
3106
|
+
const authorization = lower["authorization"];
|
|
3107
|
+
const amountStr = ctx.pricing.amountUsd.toFixed(2);
|
|
3108
|
+
let result;
|
|
3109
|
+
try {
|
|
3110
|
+
result = await mpp.charge({ authorization, amount: amountStr });
|
|
3111
|
+
} catch {
|
|
3112
|
+
return { status: 402 };
|
|
3113
|
+
}
|
|
3114
|
+
if (!Array.isArray(result)) {
|
|
3115
|
+
const challenge = result;
|
|
3116
|
+
const realm = mpp.realm ?? "";
|
|
3117
|
+
const headers = typeof challenge.toWwwAuthenticate === "function" ? { "www-authenticate": challenge.toWwwAuthenticate(realm) } : {};
|
|
3118
|
+
return { status: 402, headers };
|
|
3119
|
+
}
|
|
3120
|
+
const [credential, receipt] = result;
|
|
3121
|
+
const txHash = receipt.reference ?? receipt.transaction ?? null;
|
|
3122
|
+
let signerAddress = null;
|
|
3123
|
+
let signerNetwork = null;
|
|
3124
|
+
const source = credential.source;
|
|
3125
|
+
if (typeof source === "string") {
|
|
3126
|
+
const parts = source.split(":");
|
|
3127
|
+
if (parts.length >= 4 && parts[0] === "did" && parts[1] === "pkh") {
|
|
3128
|
+
const family = parts[2];
|
|
3129
|
+
const addr = parts[parts.length - 1] ?? null;
|
|
3130
|
+
if (family === "eip155" && addr !== null) {
|
|
3131
|
+
signerAddress = addr.toLowerCase();
|
|
3132
|
+
signerNetwork = "evm";
|
|
3133
|
+
} else if (family === "solana" && addr !== null) {
|
|
3134
|
+
signerAddress = addr;
|
|
3135
|
+
signerNetwork = "solana";
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
return {
|
|
3140
|
+
status: 200,
|
|
3141
|
+
txHash,
|
|
3142
|
+
signerAddress,
|
|
3143
|
+
signerNetwork,
|
|
3144
|
+
raw: { credential, receipt }
|
|
3145
|
+
};
|
|
3146
|
+
};
|
|
3147
|
+
}
|
|
3148
|
+
function applyRecipientOverrides(rails2, overrides) {
|
|
3149
|
+
const out = {};
|
|
3150
|
+
for (const [key, spec] of Object.entries(rails2)) {
|
|
3151
|
+
if (isStripeRailSpec2(spec)) {
|
|
3152
|
+
out[key] = spec;
|
|
3153
|
+
continue;
|
|
3154
|
+
}
|
|
3155
|
+
const override = overrides[key];
|
|
3156
|
+
const finalRecipient = override ?? spec.recipient;
|
|
3157
|
+
if (finalRecipient === "" || finalRecipient === void 0) continue;
|
|
3158
|
+
out[key] = override !== void 0 ? { ...spec, recipient: override } : spec;
|
|
3159
|
+
}
|
|
3160
|
+
return out;
|
|
3161
|
+
}
|
|
3162
|
+
function pickRail(rails2, key) {
|
|
3163
|
+
const spec = rails2[key];
|
|
3164
|
+
return spec === void 0 ? void 0 : spec;
|
|
3165
|
+
}
|
|
3166
|
+
var Checkout = class {
|
|
3167
|
+
rails;
|
|
3168
|
+
url;
|
|
3169
|
+
merchantName;
|
|
3170
|
+
computePricing;
|
|
3171
|
+
preValidate;
|
|
3172
|
+
x402Server;
|
|
3173
|
+
composeMppx;
|
|
3174
|
+
mintRecipients;
|
|
3175
|
+
mintReferenceId;
|
|
3176
|
+
onSettled;
|
|
3177
|
+
isCachedAddress;
|
|
3178
|
+
zeroSettleCarveOut;
|
|
3179
|
+
gate;
|
|
3180
|
+
discoveryExtensions;
|
|
3181
|
+
discoveryProbe;
|
|
3182
|
+
_x402ServerGetter;
|
|
3183
|
+
constructor(opts) {
|
|
3184
|
+
const x402Server = opts.x402Server;
|
|
3185
|
+
let x402ServerGetter;
|
|
3186
|
+
if (x402Server === void 0) {
|
|
3187
|
+
const baseSpec = Object.values(opts.rails).find(
|
|
3188
|
+
(s) => !isTempoSessionRailSpec3(s) && !isStripeRailSpec2(s) && "recipient" in s && (s.network ?? "").startsWith("eip155:")
|
|
3189
|
+
);
|
|
3190
|
+
if (baseSpec !== void 0) {
|
|
3191
|
+
x402ServerGetter = lazyX402Server({
|
|
3192
|
+
spec: baseSpec,
|
|
3193
|
+
cdpApiKeyId: opts.cdpApiKeyId,
|
|
3194
|
+
cdpApiKeySecret: opts.cdpApiKeySecret
|
|
3195
|
+
});
|
|
3196
|
+
}
|
|
3197
|
+
} else {
|
|
3198
|
+
const baseSpec = opts.rails["x402_base"];
|
|
3199
|
+
if (baseSpec === void 0 || !("recipient" in baseSpec)) {
|
|
3200
|
+
throw new Error(
|
|
3201
|
+
"Checkout: x402Server requires an X402BaseRailSpec in rails['x402_base'] (the rail's `network` field supplies the CAIP-2)."
|
|
3202
|
+
);
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
let composeMppx = opts.composeMppx;
|
|
3206
|
+
if (composeMppx === void 0 && opts.mppxSecretKey !== void 0) {
|
|
3207
|
+
const mppRails = {};
|
|
3208
|
+
for (const [k, v] of Object.entries(opts.rails)) {
|
|
3209
|
+
if (isStripeRailSpec2(v) || isTempoSessionRailSpec3(v) || "recipient" in v) {
|
|
3210
|
+
mppRails[k] = v;
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
const getter = lazyMppxServer({
|
|
3214
|
+
rails: mppRails,
|
|
3215
|
+
secretKey: opts.mppxSecretKey
|
|
3216
|
+
});
|
|
3217
|
+
composeMppx = makeMppxComposeHook({ serverGetter: getter });
|
|
3218
|
+
}
|
|
3219
|
+
this.rails = opts.rails;
|
|
3220
|
+
this.url = opts.url;
|
|
3221
|
+
this.merchantName = opts.gate?.merchantName;
|
|
3222
|
+
this.computePricing = opts.computePricing;
|
|
3223
|
+
this.preValidate = opts.preValidate;
|
|
3224
|
+
this.x402Server = x402Server;
|
|
3225
|
+
this._x402ServerGetter = x402ServerGetter;
|
|
3226
|
+
this.composeMppx = composeMppx;
|
|
3227
|
+
this.mintRecipients = opts.mintRecipients;
|
|
3228
|
+
this.mintReferenceId = opts.mintReferenceId;
|
|
3229
|
+
this.onSettled = opts.onSettled;
|
|
3230
|
+
this.isCachedAddress = opts.isCachedAddress;
|
|
3231
|
+
this.zeroSettleCarveOut = opts.zeroSettleCarveOut ?? false;
|
|
3232
|
+
this.gate = opts.gate;
|
|
3233
|
+
this.discoveryExtensions = opts.discoveryExtensions;
|
|
3234
|
+
this.discoveryProbe = opts.discoveryProbe;
|
|
739
3235
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
)
|
|
3236
|
+
/** Canonical `RailKey` list derived from the configured rails dict. Each
|
|
3237
|
+
* `*RailSpec` type maps to one `RailKey` (Tempo and TempoSession both fold
|
|
3238
|
+
* to `"tempo_mpp"`). Dedupes so listing is per protocol, not per recipient.
|
|
3239
|
+
* Use in `.well-known/mpp.json`, skill.md / llms.txt discovery responses. */
|
|
3240
|
+
get acceptedRails() {
|
|
3241
|
+
const out = [];
|
|
3242
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3243
|
+
for (const spec of Object.values(this.rails)) {
|
|
3244
|
+
const key = specRailKey(spec);
|
|
3245
|
+
if (seen.has(key)) continue;
|
|
3246
|
+
seen.add(key);
|
|
3247
|
+
out.push(key);
|
|
3248
|
+
}
|
|
3249
|
+
return out;
|
|
748
3250
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
const
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
|
|
3251
|
+
/** Protocol-shaped method-name list (`"tempo/charge"`, `"x402/exact (base)"`).
|
|
3252
|
+
* Suitable for the `methods: [...]` array of `.well-known/mpp.json`. */
|
|
3253
|
+
get acceptedMethodNames() {
|
|
3254
|
+
const out = [];
|
|
3255
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3256
|
+
for (const spec of Object.values(this.rails)) {
|
|
3257
|
+
const name = specMethodName(spec);
|
|
3258
|
+
if (seen.has(name)) continue;
|
|
3259
|
+
seen.add(name);
|
|
3260
|
+
out.push(name);
|
|
757
3261
|
}
|
|
758
|
-
|
|
759
|
-
} catch (err) {
|
|
760
|
-
throw new UCPVerificationError(
|
|
761
|
-
"malformed_jws",
|
|
762
|
-
`JWS protected header is not valid base64url-encoded JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
763
|
-
);
|
|
3262
|
+
return out;
|
|
764
3263
|
}
|
|
765
|
-
|
|
766
|
-
|
|
3264
|
+
/** Resolve the x402 server, awaiting the lazy getter on first use. */
|
|
3265
|
+
async getX402Server() {
|
|
3266
|
+
if (this.x402Server !== void 0) return this.x402Server;
|
|
3267
|
+
if (this._x402ServerGetter === void 0) return void 0;
|
|
3268
|
+
this.x402Server = await this._x402ServerGetter();
|
|
3269
|
+
return this.x402Server;
|
|
767
3270
|
}
|
|
768
|
-
|
|
769
|
-
|
|
3271
|
+
x402ServerAvailable() {
|
|
3272
|
+
return this.x402Server !== void 0 || this._x402ServerGetter !== void 0;
|
|
770
3273
|
}
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
3274
|
+
/** Return the rails-dict key for the X402BaseRailSpec entry. Defaults to
|
|
3275
|
+
* `"x402_base"` when no match found. */
|
|
3276
|
+
x402RailKey() {
|
|
3277
|
+
for (const [k, v] of Object.entries(this.rails)) {
|
|
3278
|
+
if (!isStripeRailSpec2(v) && !isTempoSessionRailSpec3(v) && (v.network ?? "").startsWith("eip155:")) {
|
|
3279
|
+
return k;
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
return "x402_base";
|
|
776
3283
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
`JWS protected header crit must be a non-empty array of strings; got ${JSON.stringify(crit)}.`
|
|
783
|
-
);
|
|
3284
|
+
/** Return the rails-dict key for the primary MPP rail. */
|
|
3285
|
+
mppRailKey() {
|
|
3286
|
+
for (const [k, v] of Object.entries(this.rails)) {
|
|
3287
|
+
const network = v.network ?? "";
|
|
3288
|
+
if (!isStripeRailSpec2(v) && !network.startsWith("eip155:")) return k;
|
|
784
3289
|
}
|
|
785
|
-
|
|
786
|
-
"unrecognized_critical_header",
|
|
787
|
-
`JWS protected header advertises unrecognized crit headers: ${JSON.stringify(crit)}.`
|
|
788
|
-
);
|
|
3290
|
+
return "tempo";
|
|
789
3291
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
3292
|
+
/** CAIP-2 read from `rails['x402_base'].network` (or its default).
|
|
3293
|
+
* Defined only when an `X402BaseRailSpec` is present in rails AND a server
|
|
3294
|
+
* is configured (explicit or auto-derived); otherwise `null`. */
|
|
3295
|
+
get x402BaseNetwork() {
|
|
3296
|
+
if (!this.x402ServerAvailable()) return null;
|
|
3297
|
+
for (const spec of Object.values(this.rails)) {
|
|
3298
|
+
if (!isStripeRailSpec2(spec) && !isTempoSessionRailSpec3(spec) && (spec.network ?? "").startsWith("eip155:")) {
|
|
3299
|
+
return spec.network ?? "eip155:8453";
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
return null;
|
|
3303
|
+
}
|
|
3304
|
+
async handle(request) {
|
|
3305
|
+
const referenceId = await this.mintRefId(request);
|
|
3306
|
+
const ctx = {
|
|
3307
|
+
request,
|
|
3308
|
+
referenceId,
|
|
3309
|
+
pricing: null,
|
|
3310
|
+
recipients: {},
|
|
3311
|
+
state: {}
|
|
3312
|
+
};
|
|
3313
|
+
if (this.discoveryProbe !== void 0 && request.method === "POST") {
|
|
3314
|
+
const auth = request.headers["authorization"] ?? request.headers["Authorization"];
|
|
3315
|
+
const isProbe = !auth?.startsWith("Payment ") && !hasX402Header(request.headers) && !hasMppxHeader(request.headers) && (request.body === void 0 || request.body === null || typeof request.body === "object" && Object.keys(request.body).length === 0);
|
|
3316
|
+
if (isProbe) {
|
|
3317
|
+
const { buildDiscoveryProbeResponse: buildDiscoveryProbeResponse2 } = await Promise.resolve().then(() => (init_probe(), probe_exports));
|
|
3318
|
+
const cfg = this.discoveryProbe;
|
|
3319
|
+
const probe = buildDiscoveryProbeResponse2({
|
|
3320
|
+
realm: cfg.realm,
|
|
3321
|
+
sampleRail: cfg.sampleRail,
|
|
3322
|
+
sampleAmountUsd: cfg.sampleAmountUsd,
|
|
3323
|
+
sampleRecipient: cfg.sampleRecipient,
|
|
3324
|
+
...cfg.intent !== void 0 && { intent: cfg.intent },
|
|
3325
|
+
...cfg.ttlSeconds !== void 0 && { ttlSeconds: cfg.ttlSeconds },
|
|
3326
|
+
...cfg.docsUrl !== void 0 && { docsUrl: cfg.docsUrl },
|
|
3327
|
+
...cfg.message !== void 0 && { message: cfg.message },
|
|
3328
|
+
...cfg.x402Sample !== void 0 && { x402Sample: cfg.x402Sample }
|
|
3329
|
+
});
|
|
3330
|
+
return {
|
|
3331
|
+
status: probe.status,
|
|
3332
|
+
body: JSON.parse(probe.body),
|
|
3333
|
+
headers: probe.headers,
|
|
3334
|
+
referenceId: ctx.referenceId,
|
|
3335
|
+
settled: false
|
|
3336
|
+
};
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
if (this.preValidate !== void 0) {
|
|
3340
|
+
try {
|
|
3341
|
+
const state = await this.preValidate(ctx);
|
|
3342
|
+
if (state && typeof state === "object") Object.assign(ctx.state, state);
|
|
3343
|
+
} catch (err) {
|
|
3344
|
+
if (err instanceof CheckoutValidationError) {
|
|
3345
|
+
return this.validationErrorResult(ctx, err);
|
|
801
3346
|
}
|
|
802
|
-
|
|
803
|
-
|
|
3347
|
+
throw err;
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
const hasPaymentHeader = hasX402Header(request.headers) || hasMppxHeader(request.headers);
|
|
3351
|
+
if (this.gate !== void 0 && hasPaymentHeader) {
|
|
3352
|
+
const denial = await this.runGate(ctx);
|
|
3353
|
+
if (denial !== null) {
|
|
3354
|
+
return {
|
|
3355
|
+
status: denial.status,
|
|
3356
|
+
body: denial.body,
|
|
3357
|
+
headers: { ...denial.headers ?? {} },
|
|
3358
|
+
referenceId: ctx.referenceId,
|
|
3359
|
+
settled: false
|
|
3360
|
+
};
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
ctx.pricing = await this.computePricing(ctx);
|
|
3364
|
+
await this.resolveRecipientsForCtx(ctx);
|
|
3365
|
+
const x402ServerOk = this.x402ServerAvailable() && this.x402BaseNetwork !== null;
|
|
3366
|
+
if (hasX402Header(request.headers) && x402ServerOk) {
|
|
3367
|
+
const zero = await this.handleZeroSettle(ctx, "x402-base");
|
|
3368
|
+
if (zero !== null) return zero;
|
|
3369
|
+
return await this.handleX402(ctx);
|
|
3370
|
+
}
|
|
3371
|
+
if (hasMppxHeader(request.headers) && this.composeMppx !== void 0) {
|
|
3372
|
+
const zero = await this.handleZeroSettle(ctx, "tempo");
|
|
3373
|
+
if (zero !== null) return zero;
|
|
3374
|
+
return await this.handleMppx(ctx);
|
|
3375
|
+
}
|
|
3376
|
+
await this.resolveRecipientsForCtx(ctx);
|
|
3377
|
+
let mppxHeaders = {};
|
|
3378
|
+
if (this.composeMppx !== void 0) {
|
|
3379
|
+
try {
|
|
3380
|
+
const preComposed = await this.composeMppx(ctx);
|
|
3381
|
+
if (preComposed.status === 402) {
|
|
3382
|
+
mppxHeaders = { ...preComposed.headers ?? {} };
|
|
3383
|
+
}
|
|
3384
|
+
} catch {
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
return await this.emit402(ctx, mppxHeaders);
|
|
3388
|
+
}
|
|
3389
|
+
validationErrorResult(ctx, err) {
|
|
3390
|
+
const body = buildValidationError({
|
|
3391
|
+
code: err.code,
|
|
3392
|
+
message: err.message,
|
|
3393
|
+
nextSteps: { action: err.action, user_message: err.message },
|
|
3394
|
+
extra: err.extra
|
|
3395
|
+
});
|
|
3396
|
+
return {
|
|
3397
|
+
status: err.status,
|
|
3398
|
+
body,
|
|
3399
|
+
headers: {},
|
|
3400
|
+
referenceId: ctx.referenceId,
|
|
3401
|
+
settled: false
|
|
3402
|
+
};
|
|
3403
|
+
}
|
|
3404
|
+
async runGate(ctx) {
|
|
3405
|
+
const gate = this.gate;
|
|
3406
|
+
if (gate === void 0) return null;
|
|
3407
|
+
if (gate.runGate !== void 0) {
|
|
3408
|
+
const result = await gate.runGate(ctx);
|
|
3409
|
+
if (result === void 0 || result === null) return null;
|
|
3410
|
+
if (typeof result !== "object" || typeof result.status !== "number") {
|
|
3411
|
+
throw new TypeError(
|
|
3412
|
+
"gate.runGate must return null/undefined (allow) or an object { status, body, headers? } (deny)"
|
|
804
3413
|
);
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
3414
|
+
}
|
|
3415
|
+
return result;
|
|
3416
|
+
}
|
|
3417
|
+
if (gate.apiKey === void 0) return null;
|
|
3418
|
+
let policyOverride;
|
|
3419
|
+
if (gate.perRequestPolicy !== void 0) {
|
|
3420
|
+
policyOverride = await gate.perRequestPolicy(ctx);
|
|
3421
|
+
if (policyOverride === null) return null;
|
|
3422
|
+
}
|
|
3423
|
+
const coreOpts = {
|
|
3424
|
+
apiKey: gate.apiKey,
|
|
3425
|
+
...gate.baseUrl !== void 0 && { baseUrl: gate.baseUrl },
|
|
3426
|
+
...gate.userAgent !== void 0 && { userAgent: gate.userAgent },
|
|
3427
|
+
...gate.requireKyc !== void 0 && { requireKyc: gate.requireKyc },
|
|
3428
|
+
...gate.requireSanctionsClear !== void 0 && { requireSanctionsClear: gate.requireSanctionsClear },
|
|
3429
|
+
...gate.minAge !== void 0 && { minAge: gate.minAge },
|
|
3430
|
+
...gate.blockedJurisdictions !== void 0 && { blockedJurisdictions: gate.blockedJurisdictions },
|
|
3431
|
+
...gate.allowedJurisdictions !== void 0 && { allowedJurisdictions: gate.allowedJurisdictions },
|
|
3432
|
+
...gate.failOpen !== void 0 && { failOpen: gate.failOpen },
|
|
3433
|
+
...gate.cacheSeconds !== void 0 && { cacheSeconds: gate.cacheSeconds },
|
|
3434
|
+
...gate.chain !== void 0 && { chain: gate.chain },
|
|
3435
|
+
...gate.createSessionOnMissing !== void 0 && {
|
|
3436
|
+
createSessionOnMissing: gate.createSessionOnMissing
|
|
3437
|
+
},
|
|
3438
|
+
...policyOverride ?? {}
|
|
3439
|
+
};
|
|
3440
|
+
const core = createAgentScoreCore(coreOpts);
|
|
3441
|
+
const headers = lowerHeaders2(ctx.request.headers);
|
|
3442
|
+
const walletAddress = headers["x-wallet-address"];
|
|
3443
|
+
const operatorToken = headers["x-operator-token"];
|
|
3444
|
+
const identity = walletAddress !== void 0 || operatorToken !== void 0 ? {
|
|
3445
|
+
...walletAddress !== void 0 && { address: walletAddress },
|
|
3446
|
+
...operatorToken !== void 0 && { operatorToken }
|
|
3447
|
+
} : void 0;
|
|
3448
|
+
const x402Header = headers["payment-signature"] ?? headers["x-payment"];
|
|
3449
|
+
const signer = await extractPaymentSignerFromAuth(headers["authorization"], x402Header);
|
|
3450
|
+
const outcome = await core.evaluate(identity, ctx, signer);
|
|
3451
|
+
if (outcome.kind === "allow") {
|
|
3452
|
+
if (operatorToken !== void 0) {
|
|
3453
|
+
const opToken = operatorToken;
|
|
3454
|
+
ctx.captureWallet = async (opts) => {
|
|
3455
|
+
await core.captureWallet({
|
|
3456
|
+
operatorToken: opToken,
|
|
3457
|
+
walletAddress: opts.walletAddress,
|
|
3458
|
+
network: opts.network,
|
|
3459
|
+
...opts.idempotencyKey !== void 0 && { idempotencyKey: opts.idempotencyKey }
|
|
3460
|
+
});
|
|
3461
|
+
};
|
|
3462
|
+
}
|
|
3463
|
+
if (walletAddress !== void 0) {
|
|
3464
|
+
const verdict = core.getSignerVerdict(walletAddress);
|
|
3465
|
+
const sm = verdict?.signer_match;
|
|
3466
|
+
if (sm && sm.kind !== "pass") {
|
|
3467
|
+
const reason2 = sm.kind === "wallet_auth_requires_wallet_signing" ? {
|
|
3468
|
+
code: "wallet_auth_requires_wallet_signing",
|
|
3469
|
+
expected_signer: sm.claimedWallet,
|
|
3470
|
+
agent_instructions: sm.agentInstructions
|
|
3471
|
+
} : {
|
|
3472
|
+
code: "wallet_signer_mismatch",
|
|
3473
|
+
...sm.claimedOperator !== null && { claimed_operator: sm.claimedOperator },
|
|
3474
|
+
actual_signer_operator: sm.actualSignerOperator,
|
|
3475
|
+
expected_signer: sm.expectedSigner,
|
|
3476
|
+
actual_signer: sm.actualSigner,
|
|
3477
|
+
...sm.linkedWallets.length > 0 && { linked_wallets: sm.linkedWallets },
|
|
3478
|
+
agent_instructions: sm.agentInstructions
|
|
3479
|
+
};
|
|
3480
|
+
if (gate.onDenied !== void 0) {
|
|
3481
|
+
const custom = await gate.onDenied(ctx, reason2);
|
|
3482
|
+
if (custom !== null) return custom;
|
|
3483
|
+
}
|
|
3484
|
+
const body2 = denialReasonToBody(reason2);
|
|
3485
|
+
return { status: 403, body: body2 };
|
|
810
3486
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
3487
|
+
}
|
|
3488
|
+
return null;
|
|
3489
|
+
}
|
|
3490
|
+
const reason = outcome.reason;
|
|
3491
|
+
if (gate.onDenied !== void 0) {
|
|
3492
|
+
const custom = await gate.onDenied(ctx, reason);
|
|
3493
|
+
if (custom !== null) return custom;
|
|
3494
|
+
}
|
|
3495
|
+
const body = denialReasonToBody(reason);
|
|
3496
|
+
const status = reason.code === "token_expired" || reason.code === "invalid_credential" ? 401 : reason.code === "api_error" ? 503 : 403;
|
|
3497
|
+
return { status, body };
|
|
3498
|
+
}
|
|
3499
|
+
async handleZeroSettle(ctx, rail) {
|
|
3500
|
+
if (!this.zeroSettleCarveOut || ctx.pricing === null) return null;
|
|
3501
|
+
const cents = Math.round(ctx.pricing.amountUsd * 100);
|
|
3502
|
+
if (cents !== 0) return null;
|
|
3503
|
+
const headers = lowerHeaders2(ctx.request.headers);
|
|
3504
|
+
let zero;
|
|
3505
|
+
if (rail === "x402-base") {
|
|
3506
|
+
const x402Header = headers["payment-signature"] ?? headers["x-payment"];
|
|
3507
|
+
let payload = null;
|
|
3508
|
+
if (typeof x402Header === "string" && x402Header.length > 0) {
|
|
3509
|
+
try {
|
|
3510
|
+
payload = JSON.parse(atob(x402Header));
|
|
3511
|
+
} catch {
|
|
3512
|
+
payload = null;
|
|
816
3513
|
}
|
|
817
|
-
return jose.importJWK(matches[0], h.alg);
|
|
818
3514
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
if (err instanceof UCPVerificationError) throw err;
|
|
823
|
-
if (err instanceof Error && err.name === "JOSEAlgNotAllowed") {
|
|
824
|
-
throw new UCPVerificationError("unsupported_alg", `UCP signing alg not allowed: ${err.message}`);
|
|
3515
|
+
zero = zeroAmountCarveOut({ rail, payload });
|
|
3516
|
+
} else {
|
|
3517
|
+
zero = zeroAmountCarveOut({ rail, authorizationHeader: headers["authorization"] });
|
|
825
3518
|
}
|
|
826
|
-
|
|
827
|
-
|
|
3519
|
+
const railKey = rail === "x402-base" ? this.x402RailKey() : this.mppRailKey();
|
|
3520
|
+
const outcome = {
|
|
3521
|
+
rail: rail === "x402-base" ? "x402" : "mpp",
|
|
3522
|
+
paymentResponseHeader: null,
|
|
3523
|
+
raw: zero,
|
|
3524
|
+
txHash: null,
|
|
3525
|
+
signerAddress: zero.signerAddress,
|
|
3526
|
+
signerNetwork: zero.signerNetwork,
|
|
3527
|
+
railKey
|
|
3528
|
+
};
|
|
3529
|
+
return await this.buildSuccess(ctx, outcome);
|
|
3530
|
+
}
|
|
3531
|
+
async mintRefId(request) {
|
|
3532
|
+
if (this.mintReferenceId === void 0) return (0, import_node_crypto.randomUUID)();
|
|
3533
|
+
const seedCtx = { request, referenceId: "", pricing: null, recipients: {}, state: {} };
|
|
3534
|
+
return await this.mintReferenceId(seedCtx);
|
|
3535
|
+
}
|
|
3536
|
+
async resolveRecipientsForCtx(ctx) {
|
|
3537
|
+
if (this.mintRecipients === void 0) return ctx.recipients;
|
|
3538
|
+
if (Object.keys(ctx.recipients).length > 0) return ctx.recipients;
|
|
3539
|
+
ctx.recipients = { ...await this.mintRecipients(ctx) };
|
|
3540
|
+
return ctx.recipients;
|
|
3541
|
+
}
|
|
3542
|
+
async asyncIsCachedAddress(address) {
|
|
3543
|
+
if (this.isCachedAddress === void 0) return true;
|
|
3544
|
+
return Promise.resolve(this.isCachedAddress(address));
|
|
3545
|
+
}
|
|
3546
|
+
async handleX402(ctx) {
|
|
3547
|
+
const x402Server = await this.getX402Server();
|
|
3548
|
+
if (ctx.pricing === null || this.x402BaseNetwork === null || x402Server === void 0) {
|
|
3549
|
+
throw new Error("Checkout.handleX402: missing pricing or x402 rail config");
|
|
828
3550
|
}
|
|
829
|
-
|
|
830
|
-
|
|
3551
|
+
const fakeRequest = new Request(ctx.request.url, {
|
|
3552
|
+
method: ctx.request.method,
|
|
3553
|
+
headers: ctx.request.headers
|
|
3554
|
+
});
|
|
3555
|
+
const verified = await verifyX402Request({
|
|
3556
|
+
request: fakeRequest,
|
|
3557
|
+
isCachedAddress: this.asyncIsCachedAddress.bind(this),
|
|
3558
|
+
acceptedNetwork: this.x402BaseNetwork
|
|
3559
|
+
});
|
|
3560
|
+
if (!verified.ok) {
|
|
3561
|
+
return {
|
|
3562
|
+
status: verified.status,
|
|
3563
|
+
body: verified.body,
|
|
3564
|
+
headers: {},
|
|
3565
|
+
referenceId: ctx.referenceId,
|
|
3566
|
+
settled: false,
|
|
3567
|
+
settlePhase: "verify_failed"
|
|
3568
|
+
};
|
|
831
3569
|
}
|
|
832
|
-
|
|
833
|
-
|
|
3570
|
+
const settle = await processX402Settle({
|
|
3571
|
+
x402Server,
|
|
3572
|
+
payload: verified.payload,
|
|
3573
|
+
resourceConfig: {
|
|
3574
|
+
scheme: "exact",
|
|
3575
|
+
network: verified.signedNetwork,
|
|
3576
|
+
price: `$${ctx.pricing.amountUsd.toFixed(2)}`,
|
|
3577
|
+
payTo: verified.signedPayTo,
|
|
3578
|
+
maxTimeoutSeconds: 300
|
|
3579
|
+
},
|
|
3580
|
+
resourceMeta: {
|
|
3581
|
+
url: ctx.request.url,
|
|
3582
|
+
description: "Agent purchase via x402",
|
|
3583
|
+
mimeType: "application/json"
|
|
3584
|
+
}
|
|
3585
|
+
});
|
|
3586
|
+
if (!settle.success) {
|
|
3587
|
+
const classified = classifyX402SettleResult(settle);
|
|
3588
|
+
const responseHeaders = classified !== null && classified.status >= 500 ? { "Cache-Control": "no-store" } : {};
|
|
3589
|
+
if (classified !== null) {
|
|
3590
|
+
return {
|
|
3591
|
+
status: classified.status,
|
|
3592
|
+
body: {
|
|
3593
|
+
error: { code: classified.code, message: classified.message },
|
|
3594
|
+
next_steps: classified.nextSteps
|
|
3595
|
+
},
|
|
3596
|
+
headers: responseHeaders,
|
|
3597
|
+
referenceId: ctx.referenceId,
|
|
3598
|
+
settled: false,
|
|
3599
|
+
settlePhase: settle.phase ?? "settle_failed"
|
|
3600
|
+
};
|
|
3601
|
+
}
|
|
3602
|
+
return {
|
|
3603
|
+
status: 400,
|
|
3604
|
+
body: buildValidationError({
|
|
3605
|
+
code: "payment_proof_invalid",
|
|
3606
|
+
message: `Payment failed during settlement (phase: ${settle.phase ?? "unknown"}).`,
|
|
3607
|
+
nextSteps: { action: "regenerate_payment_credential" },
|
|
3608
|
+
extra: { phase: settle.phase }
|
|
3609
|
+
}),
|
|
3610
|
+
headers: {},
|
|
3611
|
+
referenceId: ctx.referenceId,
|
|
3612
|
+
settled: false,
|
|
3613
|
+
settlePhase: settle.phase ?? "settle_failed"
|
|
3614
|
+
};
|
|
834
3615
|
}
|
|
835
|
-
|
|
3616
|
+
const settleRes = settle.settleResult ?? {};
|
|
3617
|
+
const verifiedFrom = verified.payload.payload?.authorization?.from ?? null;
|
|
3618
|
+
const signerAddress = verifiedFrom !== null ? verifiedFrom.toLowerCase() : settleRes.payer ?? null;
|
|
3619
|
+
const outcome = {
|
|
3620
|
+
rail: "x402",
|
|
3621
|
+
paymentResponseHeader: settle.paymentResponseHeader ?? null,
|
|
3622
|
+
raw: settle,
|
|
3623
|
+
txHash: settleRes.transaction ?? null,
|
|
3624
|
+
signerAddress,
|
|
3625
|
+
signerNetwork: signerAddress !== null ? "evm" : null,
|
|
3626
|
+
railKey: this.x402RailKey()
|
|
3627
|
+
};
|
|
3628
|
+
return await this.buildSuccess(ctx, outcome);
|
|
836
3629
|
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
3630
|
+
async handleMppx(ctx) {
|
|
3631
|
+
if (this.composeMppx === void 0) {
|
|
3632
|
+
throw new Error("Checkout.handleMppx: composeMppx hook not configured");
|
|
3633
|
+
}
|
|
3634
|
+
const composed = await this.composeMppx(ctx);
|
|
3635
|
+
if (composed.status === 200) {
|
|
3636
|
+
const outcome = {
|
|
3637
|
+
rail: "mpp",
|
|
3638
|
+
paymentResponseHeader: composed.paymentResponseHeader ?? null,
|
|
3639
|
+
raw: composed.raw,
|
|
3640
|
+
txHash: composed.txHash ?? null,
|
|
3641
|
+
signerAddress: composed.signerAddress ?? null,
|
|
3642
|
+
signerNetwork: composed.signerNetwork ?? null,
|
|
3643
|
+
railKey: composed.railKey ?? this.mppRailKey()
|
|
3644
|
+
};
|
|
3645
|
+
return await this.buildSuccess(ctx, outcome);
|
|
3646
|
+
}
|
|
3647
|
+
return {
|
|
3648
|
+
status: 400,
|
|
3649
|
+
body: buildValidationError({
|
|
3650
|
+
code: "payment_proof_invalid",
|
|
3651
|
+
message: "MPP credential rejected; regenerate from a fresh 402 challenge.",
|
|
3652
|
+
nextSteps: { action: "regenerate_payment_credential" }
|
|
3653
|
+
}),
|
|
3654
|
+
headers: { ...composed.headers ?? {} },
|
|
3655
|
+
referenceId: ctx.referenceId,
|
|
3656
|
+
settled: false,
|
|
3657
|
+
settlePhase: "verify_failed"
|
|
3658
|
+
};
|
|
845
3659
|
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
3660
|
+
async emit402(ctx, mppxHeaders = {}) {
|
|
3661
|
+
if (ctx.pricing === null) {
|
|
3662
|
+
throw new Error("Checkout.emit402: pricing not computed");
|
|
3663
|
+
}
|
|
3664
|
+
await this.resolveRecipientsForCtx(ctx);
|
|
3665
|
+
const emitRails = applyRecipientOverrides(this.rails, ctx.recipients);
|
|
3666
|
+
const accepted = await buildAcceptedMethods({
|
|
3667
|
+
tempo: pickRail(emitRails, "tempo"),
|
|
3668
|
+
x402_base: pickRail(emitRails, "x402_base"),
|
|
3669
|
+
solana_mpp: pickRail(emitRails, "solana_mpp"),
|
|
3670
|
+
stripe: pickRail(emitRails, "stripe")
|
|
3671
|
+
});
|
|
3672
|
+
const howToPayRails = {};
|
|
3673
|
+
for (const [k, v] of Object.entries(emitRails)) {
|
|
3674
|
+
if (!isTempoSessionRailSpec3(v)) {
|
|
3675
|
+
howToPayRails[k] = v;
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
const howToPay = buildHowToPay({
|
|
3679
|
+
url: this.url,
|
|
3680
|
+
retryBodyJson: JSON.stringify(ctx.request.body),
|
|
3681
|
+
totalUsd: ctx.pricing.amountUsd.toFixed(2),
|
|
3682
|
+
rails: howToPayRails
|
|
3683
|
+
});
|
|
3684
|
+
const pricingBlock = ctx.pricing.block ?? buildPricingBlock({
|
|
3685
|
+
subtotalCents: Math.round(ctx.pricing.amountUsd * 100),
|
|
3686
|
+
currency: ctx.pricing.currency ?? "USD"
|
|
3687
|
+
});
|
|
3688
|
+
let x402Accepts = [];
|
|
3689
|
+
let x402Resource;
|
|
3690
|
+
const baseNetwork = this.x402BaseNetwork;
|
|
3691
|
+
const x402Server = await this.getX402Server();
|
|
3692
|
+
if (x402Server !== void 0 && baseNetwork !== null) {
|
|
3693
|
+
const baseSpec = emitRails["x402_base"];
|
|
3694
|
+
if (baseSpec !== void 0) {
|
|
3695
|
+
const recipient = await resolveRecipientValue(baseSpec.recipient);
|
|
3696
|
+
try {
|
|
3697
|
+
x402Accepts = await buildX402AcceptsFor402(x402Server, {
|
|
3698
|
+
network: baseNetwork,
|
|
3699
|
+
price: `$${ctx.pricing.amountUsd.toFixed(2)}`,
|
|
3700
|
+
payTo: recipient,
|
|
3701
|
+
maxTimeoutSeconds: 300
|
|
3702
|
+
});
|
|
3703
|
+
x402Resource = { url: ctx.request.url, mimeType: "application/json" };
|
|
3704
|
+
} catch {
|
|
3705
|
+
x402Accepts = [];
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
const identityMetadata = resolveIdentityMetadata(ctx);
|
|
3710
|
+
const body = build402Body({
|
|
3711
|
+
acceptedMethods: accepted,
|
|
3712
|
+
agentInstructions: buildAgentInstructions({ howToPay }),
|
|
3713
|
+
...identityMetadata !== void 0 ? { identityMetadata } : {},
|
|
3714
|
+
pricing: pricingBlock,
|
|
3715
|
+
amountUsd: ctx.pricing.amountUsd.toFixed(2),
|
|
3716
|
+
retryBody: ctx.request.body,
|
|
3717
|
+
agentMemory: firstEncounterAgentMemory({ firstEncounter: true }),
|
|
3718
|
+
...ctx.pricing.product ? { product: ctx.pricing.product } : {},
|
|
3719
|
+
...ctx.pricing.bodyExtras ? { extra: ctx.pricing.bodyExtras } : {},
|
|
3720
|
+
...x402Accepts.length > 0 ? {
|
|
3721
|
+
x402: {
|
|
3722
|
+
accepts: x402Accepts,
|
|
3723
|
+
...this.discoveryExtensions !== void 0 && Object.keys(this.discoveryExtensions).length > 0 ? { extensions: this.discoveryExtensions } : {}
|
|
3724
|
+
}
|
|
3725
|
+
} : {}
|
|
3726
|
+
});
|
|
3727
|
+
let x402Block;
|
|
3728
|
+
if (x402Accepts.length > 0) {
|
|
3729
|
+
x402Block = {
|
|
3730
|
+
x402Version: 2,
|
|
3731
|
+
accepts: x402Accepts,
|
|
3732
|
+
...x402Resource ? { resource: x402Resource } : {}
|
|
3733
|
+
};
|
|
3734
|
+
}
|
|
3735
|
+
const respond = respond402({
|
|
3736
|
+
mppxChallengeHeaders: mppxHeaders,
|
|
3737
|
+
body,
|
|
3738
|
+
x402: x402Block
|
|
3739
|
+
});
|
|
3740
|
+
return {
|
|
3741
|
+
status: respond.status,
|
|
3742
|
+
body: respond.body,
|
|
3743
|
+
headers: respond.headers,
|
|
3744
|
+
referenceId: ctx.referenceId,
|
|
3745
|
+
settled: false
|
|
3746
|
+
};
|
|
849
3747
|
}
|
|
850
|
-
|
|
3748
|
+
async buildSuccess(ctx, outcome) {
|
|
3749
|
+
let customBody = null;
|
|
3750
|
+
if (this.onSettled !== void 0) {
|
|
3751
|
+
const result = await this.onSettled(ctx, outcome);
|
|
3752
|
+
if (result !== null && typeof result === "object") customBody = result;
|
|
3753
|
+
}
|
|
3754
|
+
const body = customBody ?? { ok: true };
|
|
3755
|
+
if (!("reference_id" in body)) body.reference_id = ctx.referenceId;
|
|
3756
|
+
const headers = {};
|
|
3757
|
+
if (outcome.paymentResponseHeader) {
|
|
3758
|
+
headers["payment-response"] = outcome.paymentResponseHeader;
|
|
3759
|
+
}
|
|
3760
|
+
return {
|
|
3761
|
+
status: 200,
|
|
3762
|
+
body,
|
|
3763
|
+
headers,
|
|
3764
|
+
referenceId: ctx.referenceId,
|
|
3765
|
+
settled: true
|
|
3766
|
+
};
|
|
3767
|
+
}
|
|
3768
|
+
};
|
|
3769
|
+
async function resolveRecipientValue(r) {
|
|
3770
|
+
return await resolveRecipient(r);
|
|
851
3771
|
}
|
|
852
|
-
function
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
3772
|
+
function validationEnvelope(opts) {
|
|
3773
|
+
const action = opts.action ?? "fix_request";
|
|
3774
|
+
return buildValidationError({
|
|
3775
|
+
code: opts.code,
|
|
3776
|
+
message: opts.message,
|
|
3777
|
+
nextSteps: { action, user_message: opts.message },
|
|
3778
|
+
extra: opts.extra
|
|
3779
|
+
});
|
|
3780
|
+
}
|
|
3781
|
+
function validationResponseHono(input) {
|
|
3782
|
+
const status = input.status ?? 400;
|
|
3783
|
+
const body = validationEnvelope(input);
|
|
3784
|
+
return new Response(JSON.stringify(body), {
|
|
3785
|
+
status,
|
|
3786
|
+
headers: { "Content-Type": "application/json" }
|
|
3787
|
+
});
|
|
3788
|
+
}
|
|
3789
|
+
function validationResponseExpress(res, input) {
|
|
3790
|
+
const status = input.status ?? 400;
|
|
3791
|
+
const body = validationEnvelope(input);
|
|
3792
|
+
res.status(status);
|
|
3793
|
+
res.json(body);
|
|
3794
|
+
}
|
|
3795
|
+
function validationResponseFastify(reply, input) {
|
|
3796
|
+
const status = input.status ?? 400;
|
|
3797
|
+
const body = validationEnvelope(input);
|
|
3798
|
+
reply.code(status);
|
|
3799
|
+
return reply.send(body);
|
|
3800
|
+
}
|
|
3801
|
+
function validationResponseNextjs(input) {
|
|
3802
|
+
return validationResponseHono(input);
|
|
3803
|
+
}
|
|
3804
|
+
function validationResponseWeb(input) {
|
|
3805
|
+
return validationResponseHono(input);
|
|
3806
|
+
}
|
|
3807
|
+
function invalidBodyEnvelope() {
|
|
3808
|
+
return validationEnvelope({
|
|
3809
|
+
code: "invalid_body",
|
|
3810
|
+
message: "Request body must be valid JSON."
|
|
3811
|
+
});
|
|
3812
|
+
}
|
|
3813
|
+
function stripContentType(headers) {
|
|
3814
|
+
const out = {};
|
|
3815
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
3816
|
+
if (k.toLowerCase() !== "content-type") out[k] = v;
|
|
857
3817
|
}
|
|
858
|
-
return
|
|
3818
|
+
return out;
|
|
859
3819
|
}
|
|
860
|
-
function
|
|
861
|
-
return {
|
|
3820
|
+
function headersToRecord(h) {
|
|
3821
|
+
if (h === void 0) return {};
|
|
3822
|
+
if (h instanceof Headers) {
|
|
3823
|
+
const out = {};
|
|
3824
|
+
h.forEach((v, k) => {
|
|
3825
|
+
out[k] = v;
|
|
3826
|
+
});
|
|
3827
|
+
return out;
|
|
3828
|
+
}
|
|
3829
|
+
return { ...h };
|
|
862
3830
|
}
|
|
3831
|
+
Checkout.prototype.handleHono = async function(c, body) {
|
|
3832
|
+
let parsedBody;
|
|
3833
|
+
if (body !== void 0) {
|
|
3834
|
+
parsedBody = body;
|
|
3835
|
+
} else {
|
|
3836
|
+
try {
|
|
3837
|
+
parsedBody = await c.req.json();
|
|
3838
|
+
} catch {
|
|
3839
|
+
return new Response(JSON.stringify(invalidBodyEnvelope()), {
|
|
3840
|
+
status: 400,
|
|
3841
|
+
headers: { "Content-Type": "application/json" }
|
|
3842
|
+
});
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3845
|
+
const rawHeaders = c.req.header();
|
|
3846
|
+
const headers = headersToRecord(rawHeaders);
|
|
3847
|
+
const result = await this.handle({
|
|
3848
|
+
method: c.req.method,
|
|
3849
|
+
url: c.req.url,
|
|
3850
|
+
headers,
|
|
3851
|
+
body: parsedBody,
|
|
3852
|
+
assess: null,
|
|
3853
|
+
raw: c.req.raw ?? c
|
|
3854
|
+
});
|
|
3855
|
+
return new Response(JSON.stringify(result.body), {
|
|
3856
|
+
status: result.status,
|
|
3857
|
+
headers: { "Content-Type": "application/json", ...stripContentType(result.headers) }
|
|
3858
|
+
});
|
|
3859
|
+
};
|
|
3860
|
+
Checkout.prototype.handleExpress = async function(req, res, body) {
|
|
3861
|
+
const parsedBody = body ?? (typeof req.body === "object" && req.body !== null ? req.body : null);
|
|
3862
|
+
if (parsedBody === null) {
|
|
3863
|
+
res.status(400);
|
|
3864
|
+
res.json(invalidBodyEnvelope());
|
|
3865
|
+
return;
|
|
3866
|
+
}
|
|
3867
|
+
const headers = {};
|
|
3868
|
+
for (const [k, v] of Object.entries(req.headers)) {
|
|
3869
|
+
if (typeof v === "string") headers[k] = v;
|
|
3870
|
+
else if (Array.isArray(v) && v[0] !== void 0) headers[k] = v[0];
|
|
3871
|
+
}
|
|
3872
|
+
const url = req.originalUrl ?? req.url ?? "/";
|
|
3873
|
+
const result = await this.handle({
|
|
3874
|
+
method: req.method,
|
|
3875
|
+
url,
|
|
3876
|
+
headers,
|
|
3877
|
+
body: parsedBody,
|
|
3878
|
+
assess: null,
|
|
3879
|
+
raw: req
|
|
3880
|
+
});
|
|
3881
|
+
for (const [k, v] of Object.entries(stripContentType(result.headers))) res.setHeader(k, v);
|
|
3882
|
+
res.status(result.status);
|
|
3883
|
+
res.json(result.body);
|
|
3884
|
+
};
|
|
3885
|
+
Checkout.prototype.handleFastify = async function(request, reply, body) {
|
|
3886
|
+
const parsedBody = body ?? (typeof request.body === "object" && request.body !== null ? request.body : null);
|
|
3887
|
+
if (parsedBody === null) {
|
|
3888
|
+
reply.code(400);
|
|
3889
|
+
return reply.send(invalidBodyEnvelope());
|
|
3890
|
+
}
|
|
3891
|
+
const headers = {};
|
|
3892
|
+
for (const [k, v] of Object.entries(request.headers)) {
|
|
3893
|
+
if (typeof v === "string") headers[k] = v;
|
|
3894
|
+
else if (Array.isArray(v) && v[0] !== void 0) headers[k] = v[0];
|
|
3895
|
+
}
|
|
3896
|
+
const result = await this.handle({
|
|
3897
|
+
method: request.method,
|
|
3898
|
+
url: request.url,
|
|
3899
|
+
headers,
|
|
3900
|
+
body: parsedBody,
|
|
3901
|
+
assess: null,
|
|
3902
|
+
raw: request
|
|
3903
|
+
});
|
|
3904
|
+
for (const [k, v] of Object.entries(stripContentType(result.headers))) reply.header(k, v);
|
|
3905
|
+
reply.code(result.status);
|
|
3906
|
+
return reply.send(result.body);
|
|
3907
|
+
};
|
|
3908
|
+
Checkout.prototype.handleNextjs = async function(request, body) {
|
|
3909
|
+
let parsedBody;
|
|
3910
|
+
if (body !== void 0) {
|
|
3911
|
+
parsedBody = body;
|
|
3912
|
+
} else {
|
|
3913
|
+
try {
|
|
3914
|
+
parsedBody = await request.json();
|
|
3915
|
+
} catch {
|
|
3916
|
+
return new Response(JSON.stringify(invalidBodyEnvelope()), {
|
|
3917
|
+
status: 400,
|
|
3918
|
+
headers: { "Content-Type": "application/json" }
|
|
3919
|
+
});
|
|
3920
|
+
}
|
|
3921
|
+
}
|
|
3922
|
+
const headers = {};
|
|
3923
|
+
request.headers.forEach((v, k) => {
|
|
3924
|
+
headers[k] = v;
|
|
3925
|
+
});
|
|
3926
|
+
const result = await this.handle({
|
|
3927
|
+
method: request.method,
|
|
3928
|
+
url: request.url,
|
|
3929
|
+
headers,
|
|
3930
|
+
body: parsedBody,
|
|
3931
|
+
assess: null,
|
|
3932
|
+
raw: request
|
|
3933
|
+
});
|
|
3934
|
+
return new Response(JSON.stringify(result.body), {
|
|
3935
|
+
status: result.status,
|
|
3936
|
+
headers: { "Content-Type": "application/json", ...stripContentType(result.headers) }
|
|
3937
|
+
});
|
|
3938
|
+
};
|
|
3939
|
+
Checkout.prototype.handleWeb = Checkout.prototype.handleNextjs;
|
|
3940
|
+
async function _ucpSignedResp(checkout, reqHeaders, opts) {
|
|
3941
|
+
const { buildSignedUcpResponse: buildSignedUcpResponse2 } = await Promise.resolve().then(() => (init_well_known(), well_known_exports));
|
|
3942
|
+
return await buildSignedUcpResponse2({
|
|
3943
|
+
checkout,
|
|
3944
|
+
name: opts.name,
|
|
3945
|
+
wellKnownUcpUrl: opts.wellKnownUcpUrl,
|
|
3946
|
+
services: opts.services,
|
|
3947
|
+
requestHeaders: reqHeaders,
|
|
3948
|
+
...opts.signingKid !== void 0 && { signingKid: opts.signingKid },
|
|
3949
|
+
...opts.agentscoreGate !== void 0 && {
|
|
3950
|
+
agentscoreGate: opts.agentscoreGate
|
|
3951
|
+
}
|
|
3952
|
+
});
|
|
3953
|
+
}
|
|
3954
|
+
async function _jwksSignedResp(reqHeaders, opts) {
|
|
3955
|
+
const { buildSignedJwksResponse: buildSignedJwksResponse2 } = await Promise.resolve().then(() => (init_well_known(), well_known_exports));
|
|
3956
|
+
return await buildSignedJwksResponse2({
|
|
3957
|
+
requestHeaders: reqHeaders,
|
|
3958
|
+
...opts.signingKid !== void 0 && { signingKid: opts.signingKid }
|
|
3959
|
+
});
|
|
3960
|
+
}
|
|
3961
|
+
function _preflightResp(reqHeaders) {
|
|
3962
|
+
return new Response(null, {
|
|
3963
|
+
status: 204,
|
|
3964
|
+
headers: _preflightHeaders(reqHeaders)
|
|
3965
|
+
});
|
|
3966
|
+
}
|
|
3967
|
+
function _preflightHeaders(reqHeaders) {
|
|
3968
|
+
const headers = {
|
|
3969
|
+
"Access-Control-Allow-Origin": "*",
|
|
3970
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
3971
|
+
"Access-Control-Max-Age": "86400",
|
|
3972
|
+
Vary: "Access-Control-Request-Headers"
|
|
3973
|
+
};
|
|
3974
|
+
const acrh = reqHeaders.get("access-control-request-headers");
|
|
3975
|
+
if (acrh) headers["Access-Control-Allow-Headers"] = acrh;
|
|
3976
|
+
return headers;
|
|
3977
|
+
}
|
|
3978
|
+
Checkout.prototype.mountUcpRoutesHono = function(app, opts) {
|
|
3979
|
+
const ucpPath = opts.ucpPath ?? "/.well-known/ucp";
|
|
3980
|
+
const jwksPath = opts.jwksPath ?? "/.well-known/jwks.json";
|
|
3981
|
+
const checkout = this;
|
|
3982
|
+
app.get(ucpPath, async (c) => {
|
|
3983
|
+
const resp = await _ucpSignedResp(checkout, c.req.raw.headers, opts);
|
|
3984
|
+
return new Response(resp.body, {
|
|
3985
|
+
status: resp.status,
|
|
3986
|
+
headers: { ...resp.headers, "Content-Type": resp.mediaType }
|
|
3987
|
+
});
|
|
3988
|
+
});
|
|
3989
|
+
app.get(jwksPath, async (c) => {
|
|
3990
|
+
const resp = await _jwksSignedResp(c.req.raw.headers, opts);
|
|
3991
|
+
return new Response(resp.body, {
|
|
3992
|
+
status: resp.status,
|
|
3993
|
+
headers: { ...resp.headers, "Content-Type": resp.mediaType }
|
|
3994
|
+
});
|
|
3995
|
+
});
|
|
3996
|
+
app.options(ucpPath, (c) => _preflightResp(c.req.raw.headers));
|
|
3997
|
+
app.options(jwksPath, (c) => _preflightResp(c.req.raw.headers));
|
|
3998
|
+
};
|
|
3999
|
+
function _headersFromExpressLike(raw) {
|
|
4000
|
+
const out = new Headers();
|
|
4001
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
4002
|
+
if (v === void 0) continue;
|
|
4003
|
+
out.set(k, Array.isArray(v) ? v.join(",") : v);
|
|
4004
|
+
}
|
|
4005
|
+
return out;
|
|
4006
|
+
}
|
|
4007
|
+
Checkout.prototype.mountUcpRoutesExpress = function(app, opts) {
|
|
4008
|
+
const ucpPath = opts.ucpPath ?? "/.well-known/ucp";
|
|
4009
|
+
const jwksPath = opts.jwksPath ?? "/.well-known/jwks.json";
|
|
4010
|
+
const checkout = this;
|
|
4011
|
+
app.get(ucpPath, async (req, res) => {
|
|
4012
|
+
const resp = await _ucpSignedResp(checkout, _headersFromExpressLike(req.headers), opts);
|
|
4013
|
+
res.status(resp.status);
|
|
4014
|
+
res.set(resp.headers);
|
|
4015
|
+
res.type(resp.mediaType);
|
|
4016
|
+
res.send(resp.body);
|
|
4017
|
+
});
|
|
4018
|
+
app.get(jwksPath, async (req, res) => {
|
|
4019
|
+
const resp = await _jwksSignedResp(_headersFromExpressLike(req.headers), opts);
|
|
4020
|
+
res.status(resp.status);
|
|
4021
|
+
res.set(resp.headers);
|
|
4022
|
+
res.type(resp.mediaType);
|
|
4023
|
+
res.send(resp.body);
|
|
4024
|
+
});
|
|
4025
|
+
const preflight = (req, res) => {
|
|
4026
|
+
const reqHeaders = _headersFromExpressLike(req.headers);
|
|
4027
|
+
res.status(204);
|
|
4028
|
+
res.set(_preflightHeaders(reqHeaders));
|
|
4029
|
+
res.send("");
|
|
4030
|
+
};
|
|
4031
|
+
app.options(ucpPath, preflight);
|
|
4032
|
+
app.options(jwksPath, preflight);
|
|
4033
|
+
};
|
|
4034
|
+
Checkout.prototype.mountUcpRoutesFastify = function(app, opts) {
|
|
4035
|
+
const ucpPath = opts.ucpPath ?? "/.well-known/ucp";
|
|
4036
|
+
const jwksPath = opts.jwksPath ?? "/.well-known/jwks.json";
|
|
4037
|
+
const checkout = this;
|
|
4038
|
+
app.get(ucpPath, async (request, reply) => {
|
|
4039
|
+
const resp = await _ucpSignedResp(checkout, _headersFromExpressLike(request.headers), opts);
|
|
4040
|
+
reply.code(resp.status);
|
|
4041
|
+
for (const [k, v] of Object.entries(resp.headers)) reply.header(k, v);
|
|
4042
|
+
reply.type(resp.mediaType);
|
|
4043
|
+
return reply.send(resp.body);
|
|
4044
|
+
});
|
|
4045
|
+
app.get(jwksPath, async (request, reply) => {
|
|
4046
|
+
const resp = await _jwksSignedResp(_headersFromExpressLike(request.headers), opts);
|
|
4047
|
+
reply.code(resp.status);
|
|
4048
|
+
for (const [k, v] of Object.entries(resp.headers)) reply.header(k, v);
|
|
4049
|
+
reply.type(resp.mediaType);
|
|
4050
|
+
return reply.send(resp.body);
|
|
4051
|
+
});
|
|
4052
|
+
const preflight = (request, reply) => {
|
|
4053
|
+
const reqHeaders = _headersFromExpressLike(request.headers);
|
|
4054
|
+
reply.code(204);
|
|
4055
|
+
for (const [k, v] of Object.entries(_preflightHeaders(reqHeaders))) reply.header(k, v);
|
|
4056
|
+
return reply.send("");
|
|
4057
|
+
};
|
|
4058
|
+
app.options(ucpPath, preflight);
|
|
4059
|
+
app.options(jwksPath, preflight);
|
|
4060
|
+
};
|
|
863
4061
|
|
|
864
4062
|
// src/identity/policy.ts
|
|
865
|
-
function
|
|
4063
|
+
function buildGateFromPolicy(policy, base) {
|
|
866
4064
|
if (!policy || !policy.enforcement) return null;
|
|
867
4065
|
return {
|
|
868
4066
|
apiKey: base.apiKey,
|
|
@@ -907,9 +4105,73 @@ function shippingStateAllowed(state, country, policy) {
|
|
|
907
4105
|
const allowed = new Set(policy.allowedShippingStates.map((s) => s.toUpperCase()));
|
|
908
4106
|
return allowed.has(state.toUpperCase());
|
|
909
4107
|
}
|
|
4108
|
+
function validateShippingAgainstPolicy(opts) {
|
|
4109
|
+
const code = opts.errorCode ?? "unsupported_jurisdiction";
|
|
4110
|
+
const action = opts.errorAction ?? "change_shipping_state";
|
|
4111
|
+
const item = opts.productName ? `'${opts.productName}'` : "this item";
|
|
4112
|
+
if (!shippingCountryAllowed(opts.country, opts.policy)) {
|
|
4113
|
+
throw new CheckoutValidationError({
|
|
4114
|
+
code,
|
|
4115
|
+
message: opts.countryMessage ?? `We can't ship ${item} to ${opts.country.toUpperCase() || "<unset>"}.`,
|
|
4116
|
+
action
|
|
4117
|
+
});
|
|
4118
|
+
}
|
|
4119
|
+
if (!shippingStateAllowed(opts.state, opts.country, opts.policy)) {
|
|
4120
|
+
throw new CheckoutValidationError({
|
|
4121
|
+
code,
|
|
4122
|
+
message: opts.stateMessage ?? `We can't ship ${item} to ${opts.state.toUpperCase() || "<unset>"}.`,
|
|
4123
|
+
action
|
|
4124
|
+
});
|
|
4125
|
+
}
|
|
4126
|
+
}
|
|
4127
|
+
|
|
4128
|
+
// src/identity/tokens.ts
|
|
4129
|
+
var import_node_crypto2 = require("crypto");
|
|
4130
|
+
function hashOperatorToken(plaintext) {
|
|
4131
|
+
return (0, import_node_crypto2.createHash)("sha256").update(plaintext, "utf8").digest("hex");
|
|
4132
|
+
}
|
|
4133
|
+
|
|
4134
|
+
// src/payment/index.ts
|
|
4135
|
+
init_directive();
|
|
4136
|
+
init_networks();
|
|
4137
|
+
init_usdc();
|
|
4138
|
+
init_rails();
|
|
4139
|
+
init_wwwauthenticate();
|
|
4140
|
+
|
|
4141
|
+
// src/payment/headers.ts
|
|
4142
|
+
init_directive();
|
|
4143
|
+
init_wwwauthenticate();
|
|
4144
|
+
|
|
4145
|
+
// src/payment/amounts.ts
|
|
4146
|
+
function formatUsdCents(cents) {
|
|
4147
|
+
return (cents / 100).toFixed(2);
|
|
4148
|
+
}
|
|
4149
|
+
|
|
4150
|
+
// src/payment/solana.ts
|
|
4151
|
+
async function loadSolanaFeePayer(opts) {
|
|
4152
|
+
const raw = opts.privateKey;
|
|
4153
|
+
if (!raw) return void 0;
|
|
4154
|
+
const moduleName = "@solana/kit";
|
|
4155
|
+
const kit = await import(moduleName).catch(() => null);
|
|
4156
|
+
if (!kit?.createKeyPairSignerFromPrivateKeyBytes || !kit.getBase58Codec) {
|
|
4157
|
+
throw new Error(
|
|
4158
|
+
"@solana/kit not installed \u2014 `npm install @solana/kit` for loadSolanaFeePayer."
|
|
4159
|
+
);
|
|
4160
|
+
}
|
|
4161
|
+
let bytes;
|
|
4162
|
+
if (/^[0-9a-fA-F]{128}$/.test(raw)) {
|
|
4163
|
+
bytes = new Uint8Array(raw.match(/.{2}/g).map((h) => parseInt(h, 16))).slice(0, 32);
|
|
4164
|
+
} else {
|
|
4165
|
+
const decoded = new Uint8Array(kit.getBase58Codec().encode(raw));
|
|
4166
|
+
bytes = decoded.length === 64 ? decoded.slice(0, 32) : decoded;
|
|
4167
|
+
}
|
|
4168
|
+
return kit.createKeyPairSignerFromPrivateKeyBytes(bytes);
|
|
4169
|
+
}
|
|
910
4170
|
// Annotate the CommonJS export names for ESM import in node:
|
|
911
4171
|
0 && (module.exports = {
|
|
912
4172
|
AGENTSCORE_UCP_CAPABILITY,
|
|
4173
|
+
Checkout,
|
|
4174
|
+
CheckoutValidationError,
|
|
913
4175
|
FIXABLE_DENIAL_REASONS,
|
|
914
4176
|
UCPSigningKey,
|
|
915
4177
|
UCPVerificationError,
|
|
@@ -917,16 +4179,25 @@ function shippingStateAllowed(state, country, policy) {
|
|
|
917
4179
|
buildA2AAgentCard,
|
|
918
4180
|
buildAgentMemoryHint,
|
|
919
4181
|
buildContactSupportNextSteps,
|
|
920
|
-
|
|
4182
|
+
buildGateFromPolicy,
|
|
921
4183
|
buildJWKSResponse,
|
|
922
4184
|
buildSignerMismatchBody,
|
|
923
4185
|
buildUCPProfile,
|
|
924
4186
|
denialReasonStatus,
|
|
925
4187
|
denialReasonToBody,
|
|
926
4188
|
extractPaymentSigner,
|
|
4189
|
+
extractPaymentSignerFromAuth,
|
|
4190
|
+
extractSignerForPrecheck,
|
|
4191
|
+
formatUsdCents,
|
|
927
4192
|
generateUCPSigningKey,
|
|
4193
|
+
getIdentityStatus,
|
|
4194
|
+
hashOperatorToken,
|
|
928
4195
|
isFixableDenial,
|
|
4196
|
+
loadSolanaFeePayer,
|
|
4197
|
+
loadUCPSigningKeyFromEnv,
|
|
4198
|
+
makeMppxComposeHook,
|
|
929
4199
|
mppPaymentHandler,
|
|
4200
|
+
pricingResult,
|
|
930
4201
|
readX402PaymentHeader,
|
|
931
4202
|
runGateWithEnforcement,
|
|
932
4203
|
shippingCountryAllowed,
|
|
@@ -934,6 +4205,13 @@ function shippingStateAllowed(state, country, policy) {
|
|
|
934
4205
|
signUCPProfile,
|
|
935
4206
|
stripeSptPaymentHandler,
|
|
936
4207
|
ucpA2AExtension,
|
|
4208
|
+
validateShippingAgainstPolicy,
|
|
4209
|
+
validationEnvelope,
|
|
4210
|
+
validationResponseExpress,
|
|
4211
|
+
validationResponseFastify,
|
|
4212
|
+
validationResponseHono,
|
|
4213
|
+
validationResponseNextjs,
|
|
4214
|
+
validationResponseWeb,
|
|
937
4215
|
verificationAgentInstructions,
|
|
938
4216
|
verifyUCPProfile,
|
|
939
4217
|
x402PaymentHandler
|