@devlab.group/agent-commerce 0.1.0-alpha.0 → 0.2.0-beta.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 +41 -22
- package/dist/{chunk-CZ6ZDAYD.js → chunk-ASK67X56.js} +9 -7
- package/dist/chunk-ASK67X56.js.map +1 -0
- package/dist/chunk-MQMKQGAB.js +199 -0
- package/dist/chunk-MQMKQGAB.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +253 -49
- package/dist/cli/index.js.map +1 -1
- package/dist/guardrails-Di_yI_DW.d.ts +25 -0
- package/dist/index.d.ts +36 -18
- package/dist/index.js +74 -33
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +2 -2
- package/dist/mcp.js.map +1 -1
- package/dist/{protocol-adapter-C0goMgLK.d.ts → protocol-adapter-CtB0sOnV.d.ts} +2 -3
- package/dist/{runtime-LCk2TzGN.d.ts → runtime-Bie-9FAM.d.ts} +15 -3
- package/dist/x402.d.ts +69 -17
- package/dist/x402.js +265 -216
- package/dist/x402.js.map +1 -1
- package/package.json +13 -7
- package/dist/chunk-CZ6ZDAYD.js.map +0 -1
package/dist/x402.js
CHANGED
|
@@ -1,11 +1,49 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assertPayToIsNotDevAddress, resolveX402Deployment, assertDevKeyIsLocalOnly, describeDeploymentMode } from './chunk-MQMKQGAB.js';
|
|
2
|
+
export { SUPPORTED_NETWORK_IDS } from './chunk-MQMKQGAB.js';
|
|
3
|
+
import { CommerceError, systemClock, NOOP_LOGGER, PACKAGE_VERSION } from './chunk-ASK67X56.js';
|
|
4
|
+
import { PaymentRequirementsV2Schema, PaymentPayloadV2Schema } from '@x402/core/schemas';
|
|
2
5
|
import { getAddress, toHex, isAddress, createWalletClient, http, publicActions, createPublicClient, HttpRequestError, TimeoutError, WaitForTransactionReceiptTimeoutError, encodeAbiParameters, keccak256, ContractFunctionRevertedError, ContractFunctionExecutionError } from 'viem';
|
|
3
6
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
7
|
+
import { x402Facilitator } from '@x402/core/facilitator';
|
|
8
|
+
import { HTTPFacilitatorClient, FacilitatorResponseError } from '@x402/core/http';
|
|
9
|
+
import { isEIP3009Payload, toFacilitatorEvmSigner } from '@x402/evm';
|
|
10
|
+
import { registerExactEvmScheme } from '@x402/evm/exact/facilitator';
|
|
8
11
|
|
|
12
|
+
var LOCAL_CHAIN_ID = 84532;
|
|
13
|
+
function chainIdFromCaip2(network) {
|
|
14
|
+
const match = /^eip155:(\d+)$/.exec(network);
|
|
15
|
+
if (!match?.[1]) return void 0;
|
|
16
|
+
const chainId = Number(match[1]);
|
|
17
|
+
return Number.isSafeInteger(chainId) && chainId > 0 ? chainId : void 0;
|
|
18
|
+
}
|
|
19
|
+
function buildLocalChain(rpcUrl) {
|
|
20
|
+
return {
|
|
21
|
+
id: LOCAL_CHAIN_ID,
|
|
22
|
+
name: "agent-commerce-local",
|
|
23
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
24
|
+
rpcUrls: {
|
|
25
|
+
default: { http: [rpcUrl] }
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function createLocalPublicClient(rpcUrl, timeoutMs) {
|
|
30
|
+
return createPublicClient({
|
|
31
|
+
chain: buildLocalChain(rpcUrl),
|
|
32
|
+
transport: http(rpcUrl, { timeout: timeoutMs } )
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function createLocalFacilitatorClient(rpcUrl, signerPrivateKey) {
|
|
36
|
+
const chain = buildLocalChain(rpcUrl);
|
|
37
|
+
const account = privateKeyToAccount(signerPrivateKey);
|
|
38
|
+
return createWalletClient({
|
|
39
|
+
account,
|
|
40
|
+
chain,
|
|
41
|
+
transport: http(rpcUrl)
|
|
42
|
+
}).extend(publicActions);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/payments/x402/client.ts
|
|
46
|
+
var X402_VERSION = 2;
|
|
9
47
|
var TRANSFER_WITH_AUTHORIZATION_TYPES = {
|
|
10
48
|
TransferWithAuthorization: [
|
|
11
49
|
{ name: "from", type: "address" },
|
|
@@ -16,23 +54,27 @@ var TRANSFER_WITH_AUTHORIZATION_TYPES = {
|
|
|
16
54
|
{ name: "nonce", type: "bytes32" }
|
|
17
55
|
]
|
|
18
56
|
};
|
|
19
|
-
var DEFAULT_VALID_AFTER_SKEW_SECONDS = 600;
|
|
20
57
|
async function createPaymentProof(options) {
|
|
21
|
-
const requirements =
|
|
58
|
+
const requirements = PaymentRequirementsV2Schema.parse(options.accepts);
|
|
22
59
|
if (!requirements.extra || typeof requirements.extra["name"] !== "string" || typeof requirements.extra["version"] !== "string") {
|
|
23
60
|
throw new Error(
|
|
24
61
|
"createPaymentProof: payment requirement is missing extra.name/extra.version \u2014 cannot build the EIP-712 domain"
|
|
25
62
|
);
|
|
26
63
|
}
|
|
64
|
+
const chainId = chainIdFromCaip2(requirements.network);
|
|
65
|
+
if (chainId === void 0) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`createPaymentProof: network "${requirements.network}" is not a CAIP-2 eip155 identifier \u2014 cannot determine the chain id to sign against`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
27
70
|
const account = privateKeyToAccount(options.buyerPrivateKey);
|
|
28
71
|
void options.rpcUrl;
|
|
29
|
-
const chainId = getNetworkId(requirements.network);
|
|
30
72
|
const nonce = options.overrides?.nonce ?? randomNonce();
|
|
31
73
|
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
32
|
-
const validAfter = options.overrides?.validAfter ??
|
|
74
|
+
const validAfter = options.overrides?.validAfter ?? 0;
|
|
33
75
|
const validBefore = options.overrides?.validBefore ?? nowSeconds + requirements.maxTimeoutSeconds;
|
|
34
76
|
const to = getAddress(options.overrides?.payTo ?? requirements.payTo);
|
|
35
|
-
const value = BigInt(options.overrides?.value ?? requirements.
|
|
77
|
+
const value = BigInt(options.overrides?.value ?? requirements.amount);
|
|
36
78
|
const signature = await account.signTypedData({
|
|
37
79
|
domain: {
|
|
38
80
|
name: requirements.extra["name"],
|
|
@@ -52,9 +94,8 @@ async function createPaymentProof(options) {
|
|
|
52
94
|
}
|
|
53
95
|
});
|
|
54
96
|
const payload = {
|
|
55
|
-
x402Version:
|
|
56
|
-
|
|
57
|
-
network: requirements.network,
|
|
97
|
+
x402Version: X402_VERSION,
|
|
98
|
+
accepted: requirements,
|
|
58
99
|
payload: {
|
|
59
100
|
signature,
|
|
60
101
|
authorization: {
|
|
@@ -67,7 +108,7 @@ async function createPaymentProof(options) {
|
|
|
67
108
|
}
|
|
68
109
|
}
|
|
69
110
|
};
|
|
70
|
-
return
|
|
111
|
+
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
|
|
71
112
|
}
|
|
72
113
|
function randomNonce() {
|
|
73
114
|
const bytes = new Uint8Array(32);
|
|
@@ -114,109 +155,82 @@ function formatCanonicalAmount(baseUnits, decimals) {
|
|
|
114
155
|
const sign = negative ? "-" : "";
|
|
115
156
|
return fractionPart.length > 0 ? `${sign}${integerPart}.${fractionPart}` : `${sign}${integerPart}`;
|
|
116
157
|
}
|
|
117
|
-
var
|
|
118
|
-
function
|
|
158
|
+
var SETTLEMENT_CONFIRMATION_TIMEOUT_MS = 6e4;
|
|
159
|
+
function createLocalFacilitatorBinding(client, network, isTransportError) {
|
|
160
|
+
const build = () => {
|
|
161
|
+
let failed = false;
|
|
162
|
+
const watch = (promise) => promise.catch((err) => {
|
|
163
|
+
if (isTransportError(err)) failed = true;
|
|
164
|
+
throw err;
|
|
165
|
+
});
|
|
166
|
+
const signer = {
|
|
167
|
+
address: client.account.address,
|
|
168
|
+
readContract: (args) => watch(client.readContract(args)),
|
|
169
|
+
verifyTypedData: (args) => watch(client.verifyTypedData(args)),
|
|
170
|
+
writeContract: (args) => watch(client.writeContract(args)),
|
|
171
|
+
sendTransaction: (args) => watch(client.sendTransaction(args)),
|
|
172
|
+
waitForTransactionReceipt: (args) => watch(client.waitForTransactionReceipt(args)),
|
|
173
|
+
getCode: (args) => watch(client.getCode(args))
|
|
174
|
+
};
|
|
175
|
+
const facilitator = registerExactEvmScheme(new x402Facilitator(), {
|
|
176
|
+
signer: toFacilitatorEvmSigner(signer, {
|
|
177
|
+
confirmationTimeoutMs: SETTLEMENT_CONFIRMATION_TIMEOUT_MS
|
|
178
|
+
}),
|
|
179
|
+
networks: network
|
|
180
|
+
});
|
|
181
|
+
return { facilitator, failed: () => failed };
|
|
182
|
+
};
|
|
119
183
|
return {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
184
|
+
kind: "local",
|
|
185
|
+
describe: "local (in-process)",
|
|
186
|
+
open() {
|
|
187
|
+
const { facilitator, failed } = build();
|
|
188
|
+
return {
|
|
189
|
+
verify: (payload, requirements) => facilitator.verify(payload, requirements),
|
|
190
|
+
settle: (payload, requirements) => facilitator.settle(payload, requirements),
|
|
191
|
+
transportFailed: failed
|
|
192
|
+
};
|
|
193
|
+
},
|
|
194
|
+
async supported() {
|
|
195
|
+
return build().facilitator.getSupported().kinds;
|
|
125
196
|
}
|
|
126
197
|
};
|
|
127
198
|
}
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
199
|
+
function createRemoteFacilitatorBinding(options, isTransportError) {
|
|
200
|
+
const auth = options.auth;
|
|
201
|
+
const createAuthHeaders = auth.type === "bearer" ? async () => {
|
|
202
|
+
const headers = { Authorization: `Bearer ${auth.token}` };
|
|
203
|
+
return { verify: headers, settle: headers, supported: headers };
|
|
204
|
+
} : void 0;
|
|
205
|
+
const client = new HTTPFacilitatorClient({
|
|
206
|
+
url: options.url,
|
|
207
|
+
timeoutMs: options.timeoutMs ?? SETTLEMENT_CONFIRMATION_TIMEOUT_MS,
|
|
208
|
+
...createAuthHeaders ? { createAuthHeaders } : {}
|
|
132
209
|
});
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
];
|
|
159
|
-
|
|
160
|
-
// src/payments/x402/dev-key-guard.ts
|
|
161
|
-
var WELL_KNOWN_DEV_KEYS = new Set(
|
|
162
|
-
ANVIL_WELL_KNOWN_ACCOUNTS.map((a) => a.privateKey.toLowerCase())
|
|
163
|
-
);
|
|
164
|
-
var WELL_KNOWN_DEV_ADDRESSES = new Set(
|
|
165
|
-
ANVIL_WELL_KNOWN_ACCOUNTS.map((a) => a.address.toLowerCase())
|
|
166
|
-
);
|
|
167
|
-
var PRIVATE_KEY_SHAPE = /^0x[0-9a-fA-F]{64}$/;
|
|
168
|
-
function isWellKnownDevKey(privateKey) {
|
|
169
|
-
return WELL_KNOWN_DEV_KEYS.has(privateKey.toLowerCase());
|
|
170
|
-
}
|
|
171
|
-
function isWellKnownDevAddress(address) {
|
|
172
|
-
return WELL_KNOWN_DEV_ADDRESSES.has(address.toLowerCase());
|
|
173
|
-
}
|
|
174
|
-
function isLikelyLocalOrPrivateHost(hostname) {
|
|
175
|
-
const h = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
176
|
-
if (h === "localhost" || h === "127.0.0.1" || h === "::1") return true;
|
|
177
|
-
if (/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
|
|
178
|
-
if (/^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
|
|
179
|
-
if (/^192\.168\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
|
|
180
|
-
if (/^172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
|
|
181
|
-
if (!h.includes(".") && !h.includes(":")) return true;
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
function parseHostnameOrThrow(rpcUrl) {
|
|
185
|
-
try {
|
|
186
|
-
return new URL(rpcUrl).hostname;
|
|
187
|
-
} catch (cause) {
|
|
188
|
-
throw new CommerceError(
|
|
189
|
-
"CONFIG_INVALID",
|
|
190
|
-
`x402 provider: rpcUrl "${rpcUrl}" is not a valid URL`,
|
|
191
|
-
{ cause }
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
function assertDevKeyIsLocalOnly(rpcUrl, signerPrivateKey) {
|
|
196
|
-
if (!PRIVATE_KEY_SHAPE.test(signerPrivateKey)) {
|
|
197
|
-
throw new CommerceError(
|
|
198
|
-
"CONFIG_INVALID",
|
|
199
|
-
'x402 provider: facilitator.signerPrivateKey is not a well-formed private key (expected "0x" followed by exactly 64 hex characters).'
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
if (!isWellKnownDevKey(signerPrivateKey)) return;
|
|
203
|
-
const hostname = parseHostnameOrThrow(rpcUrl);
|
|
204
|
-
if (!isLikelyLocalOrPrivateHost(hostname)) {
|
|
205
|
-
throw new CommerceError(
|
|
206
|
-
"CONFIG_INVALID",
|
|
207
|
-
`x402 provider: facilitator.signerPrivateKey is a well-known Anvil development key, but rpcUrl "${rpcUrl}" does not look like a local/private chain. A public dev key must never sign against a public network \u2014 it is instantly drainable and can grief real settlements via nonce exhaustion. Use a real facilitator key for any non-local RPC, or point rpcUrl at your local/dev chain.`
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
function assertPayToIsNotDevAddress(rpcUrl, payTo) {
|
|
212
|
-
if (!isWellKnownDevAddress(payTo)) return;
|
|
213
|
-
const hostname = parseHostnameOrThrow(rpcUrl);
|
|
214
|
-
if (!isLikelyLocalOrPrivateHost(hostname)) {
|
|
215
|
-
throw new CommerceError(
|
|
216
|
-
"CONFIG_INVALID",
|
|
217
|
-
`x402 provider: "payTo" (${payTo}) is a well-known Anvil development address, but rpcUrl "${rpcUrl}" does not look like a local/private chain. The private key behind that address is public knowledge, so any revenue settled to it is immediately spendable by anyone. Set "payTo" to your own merchant wallet for any non-local RPC, or point rpcUrl at your local/dev chain.`
|
|
218
|
-
);
|
|
219
|
-
}
|
|
210
|
+
return {
|
|
211
|
+
kind: "remote",
|
|
212
|
+
describe: `remote ${new URL(options.url).origin} (auth=${auth.type})`,
|
|
213
|
+
open() {
|
|
214
|
+
let failed = false;
|
|
215
|
+
const watch = async (call) => {
|
|
216
|
+
try {
|
|
217
|
+
return await call();
|
|
218
|
+
} catch (err) {
|
|
219
|
+
if (err instanceof FacilitatorResponseError || isTransportError(err)) failed = true;
|
|
220
|
+
throw err;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
return {
|
|
224
|
+
verify: (payload, requirements) => watch(() => client.verify(payload, requirements)),
|
|
225
|
+
settle: (payload, requirements) => watch(() => client.settle(payload, requirements)),
|
|
226
|
+
transportFailed: () => failed
|
|
227
|
+
};
|
|
228
|
+
},
|
|
229
|
+
async supported() {
|
|
230
|
+
const response = await client.getSupported();
|
|
231
|
+
return response.kinds;
|
|
232
|
+
}
|
|
233
|
+
};
|
|
220
234
|
}
|
|
221
235
|
function decodePaymentSubmission(payloadBase64) {
|
|
222
236
|
let json;
|
|
@@ -226,11 +240,11 @@ function decodePaymentSubmission(payloadBase64) {
|
|
|
226
240
|
} catch {
|
|
227
241
|
return void 0;
|
|
228
242
|
}
|
|
229
|
-
const result =
|
|
243
|
+
const result = PaymentPayloadV2Schema.safeParse(json);
|
|
230
244
|
return result.success ? result.data : void 0;
|
|
231
245
|
}
|
|
232
246
|
function isExactEvmPayload(payload) {
|
|
233
|
-
return payload.scheme === "exact" && typeof payload.payload === "object" && payload.payload !== null &&
|
|
247
|
+
return payload.accepted.scheme === "exact" && typeof payload.payload === "object" && payload.payload !== null && isEIP3009Payload(payload.payload) && typeof payload.payload.signature === "string";
|
|
234
248
|
}
|
|
235
249
|
function computeReplayKey(input) {
|
|
236
250
|
const encoded = encodeAbiParameters(
|
|
@@ -241,10 +255,12 @@ function computeReplayKey(input) {
|
|
|
241
255
|
}
|
|
242
256
|
|
|
243
257
|
// src/payments/x402/provider.ts
|
|
244
|
-
var
|
|
258
|
+
var X402_VERSION2 = 2;
|
|
259
|
+
var X402_PROTOCOL_VERSION = String(X402_VERSION2);
|
|
245
260
|
var DEFAULT_MAX_TIMEOUT_SECONDS = 60;
|
|
246
261
|
var DEFAULT_MIME_TYPE = "application/json";
|
|
247
262
|
var HEALTH_TIMEOUT_MS = 4e3;
|
|
263
|
+
var SETTLEMENT_PENDING_REASON = "settlement_pending";
|
|
248
264
|
var DEFAULT_IDS = {
|
|
249
265
|
next: (prefix) => `${prefix ? `${prefix}_` : ""}${crypto.randomUUID()}`
|
|
250
266
|
};
|
|
@@ -268,11 +284,20 @@ function createX402PaymentProvider(options) {
|
|
|
268
284
|
`x402 provider: "assetDecimals" must be a non-negative integer`
|
|
269
285
|
);
|
|
270
286
|
}
|
|
271
|
-
|
|
287
|
+
const { profile, mode } = resolveX402Deployment({
|
|
288
|
+
network: options.network,
|
|
289
|
+
payTo: options.payTo,
|
|
290
|
+
asset: options.asset,
|
|
291
|
+
facilitator: options.facilitator,
|
|
292
|
+
...options.allowMainnet !== void 0 ? { allowMainnet: options.allowMainnet } : {}
|
|
293
|
+
});
|
|
294
|
+
const network = options.network;
|
|
295
|
+
let binding;
|
|
272
296
|
if (options.facilitator.mode === "local") {
|
|
273
297
|
assertDevKeyIsLocalOnly(options.rpcUrl, options.facilitator.signerPrivateKey);
|
|
298
|
+
let client;
|
|
274
299
|
try {
|
|
275
|
-
|
|
300
|
+
client = createLocalFacilitatorClient(
|
|
276
301
|
options.rpcUrl,
|
|
277
302
|
options.facilitator.signerPrivateKey
|
|
278
303
|
);
|
|
@@ -283,8 +308,13 @@ function createX402PaymentProvider(options) {
|
|
|
283
308
|
{ cause }
|
|
284
309
|
);
|
|
285
310
|
}
|
|
311
|
+
binding = createLocalFacilitatorBinding(client, network, isProviderUnavailableError);
|
|
312
|
+
} else {
|
|
313
|
+
binding = createRemoteFacilitatorBinding(
|
|
314
|
+
{ url: options.facilitator.url, auth: options.facilitator.auth },
|
|
315
|
+
isProviderUnavailableError
|
|
316
|
+
);
|
|
286
317
|
}
|
|
287
|
-
const publicClient = createLocalPublicClient(options.rpcUrl);
|
|
288
318
|
const healthPublicClient = createLocalPublicClient(options.rpcUrl, HEALTH_TIMEOUT_MS);
|
|
289
319
|
const clock = options.clock ?? systemClock;
|
|
290
320
|
const ids = options.ids ?? DEFAULT_IDS;
|
|
@@ -294,43 +324,54 @@ function createX402PaymentProvider(options) {
|
|
|
294
324
|
name: "x402",
|
|
295
325
|
kind: "payment",
|
|
296
326
|
implementationVersion: PACKAGE_VERSION,
|
|
297
|
-
supportedSpec:
|
|
298
|
-
capabilities: [
|
|
327
|
+
supportedSpec: `x402/v${X402_VERSION2} scheme=exact family=eip155`,
|
|
328
|
+
capabilities: [
|
|
329
|
+
"exact-evm",
|
|
330
|
+
`${binding.kind}-facilitator`,
|
|
331
|
+
"eip-3009",
|
|
332
|
+
"caip-2",
|
|
333
|
+
`mode=${mode}`
|
|
334
|
+
],
|
|
299
335
|
status: "stable",
|
|
300
|
-
unsupported: [
|
|
336
|
+
unsupported: [
|
|
337
|
+
"svm",
|
|
338
|
+
"permit2",
|
|
339
|
+
"upto scheme",
|
|
340
|
+
"per-request signed facilitator credentials (e.g. CDP JWT)"
|
|
341
|
+
]
|
|
301
342
|
};
|
|
302
343
|
async function createRequirement(context) {
|
|
303
|
-
const
|
|
304
|
-
context.amount,
|
|
305
|
-
options.assetDecimals
|
|
306
|
-
).toString();
|
|
344
|
+
const amount = parseCanonicalAmount(context.amount, options.assetDecimals).toString();
|
|
307
345
|
const requirements = {
|
|
308
346
|
scheme: "exact",
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
network: options.network,
|
|
313
|
-
maxAmountRequired,
|
|
314
|
-
// Descriptive metadata only: `resource` is not part of the EIP-3009
|
|
315
|
-
// authorisation the buyer signs (`from`/`to`/`value`/`validAfter`/
|
|
316
|
-
// `validBefore`/`nonce`), nothing verifies against it, and the replay
|
|
317
|
-
// key is keyed on the authorisation rather than on this string. It is
|
|
318
|
-
// still what a facilitator, wallet or buyer agent logs and shows, so it
|
|
319
|
-
// carries `network` and `payTo` — both already public in this same
|
|
320
|
-
// challenge — to stay unambiguous across the wider x402 ecosystem,
|
|
321
|
-
// where the `agent-commerce` authority alone is not reserved to us.
|
|
322
|
-
resource: `resource://agent-commerce/${encodeURIComponent(options.network)}/${options.payTo}/resources/${encodeURIComponent(context.resource.id)}`,
|
|
323
|
-
description: context.resource.description ?? context.resource.name,
|
|
324
|
-
mimeType: DEFAULT_MIME_TYPE,
|
|
347
|
+
network,
|
|
348
|
+
asset: options.asset,
|
|
349
|
+
amount,
|
|
325
350
|
payTo: options.payTo,
|
|
326
351
|
maxTimeoutSeconds,
|
|
327
|
-
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
|
|
352
|
+
// `name`/`version` are what let a buyer and the facilitator build the
|
|
353
|
+
// same EIP-712 domain without an on-chain `version()` call, so
|
|
354
|
+
// verification stays deterministic against a token that has none.
|
|
355
|
+
//
|
|
356
|
+
// `assetTransferMethod` is stated rather than left to default: x402 v2's
|
|
357
|
+
// `exact`/EVM scheme also has a Permit2 path, and a client that picked it
|
|
358
|
+
// would produce a payload this provider cannot settle. Declaring the one
|
|
359
|
+
// supported method turns that into a client-side non-choice instead of a
|
|
360
|
+
// server-side rejection.
|
|
361
|
+
extra: {
|
|
362
|
+
name: options.assetName,
|
|
363
|
+
version: options.assetVersion,
|
|
364
|
+
assetTransferMethod: "eip3009"
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
const envelope = {
|
|
368
|
+
x402Version: X402_VERSION2,
|
|
369
|
+
resource: {
|
|
370
|
+
url: `resource://agent-commerce/${encodeURIComponent(options.network)}/${options.payTo}/resources/${encodeURIComponent(context.resource.id)}`,
|
|
371
|
+
description: context.resource.description ?? context.resource.name,
|
|
372
|
+
mimeType: DEFAULT_MIME_TYPE
|
|
373
|
+
},
|
|
374
|
+
accepts: [requirements]
|
|
334
375
|
};
|
|
335
376
|
const expiresAt = new Date(clock.now().getTime() + maxTimeoutSeconds * 1e3).toISOString();
|
|
336
377
|
return {
|
|
@@ -347,11 +388,12 @@ function createX402PaymentProvider(options) {
|
|
|
347
388
|
challenge: {
|
|
348
389
|
provider: "x402",
|
|
349
390
|
version: X402_PROTOCOL_VERSION,
|
|
350
|
-
accepts: [requirements]
|
|
391
|
+
accepts: [requirements],
|
|
392
|
+
envelope
|
|
351
393
|
}
|
|
352
394
|
};
|
|
353
395
|
}
|
|
354
|
-
async function verify
|
|
396
|
+
async function verify(context) {
|
|
355
397
|
const { requirement, submission } = context;
|
|
356
398
|
const rejected = (reason) => ({
|
|
357
399
|
status: "rejected",
|
|
@@ -379,6 +421,9 @@ function createX402PaymentProvider(options) {
|
|
|
379
421
|
if (!payload) {
|
|
380
422
|
return rejected("malformed_payment_payload");
|
|
381
423
|
}
|
|
424
|
+
if (payload.x402Version !== X402_VERSION2) {
|
|
425
|
+
return rejected("unsupported_x402_version");
|
|
426
|
+
}
|
|
382
427
|
if (!isExactEvmPayload(payload)) {
|
|
383
428
|
return rejected("unsupported_scheme");
|
|
384
429
|
}
|
|
@@ -386,35 +431,32 @@ function createX402PaymentProvider(options) {
|
|
|
386
431
|
if (!isAddress(authorization.from) || !isAddress(authorization.to)) {
|
|
387
432
|
return rejected("malformed_payment_payload");
|
|
388
433
|
}
|
|
389
|
-
if (payload.network !== options.network) {
|
|
434
|
+
if (payload.accepted.network !== options.network) {
|
|
390
435
|
return rejected("wrong_network");
|
|
391
436
|
}
|
|
392
437
|
if (getAddress(authorization.to) !== getAddress(options.payTo)) {
|
|
393
438
|
return rejected("wrong_recipient");
|
|
394
439
|
}
|
|
395
440
|
let authorizedValue;
|
|
396
|
-
let
|
|
441
|
+
let required;
|
|
397
442
|
try {
|
|
398
443
|
authorizedValue = BigInt(authorization.value);
|
|
399
|
-
|
|
444
|
+
required = BigInt(requirements.amount);
|
|
400
445
|
} catch {
|
|
401
446
|
return rejected("malformed_payment_payload");
|
|
402
447
|
}
|
|
403
|
-
if (authorizedValue <
|
|
448
|
+
if (authorizedValue < required) {
|
|
404
449
|
return rejected("wrong_amount");
|
|
405
450
|
}
|
|
451
|
+
const scope = binding.open();
|
|
406
452
|
let sdkResult;
|
|
407
453
|
try {
|
|
408
|
-
sdkResult = await verify(
|
|
409
|
-
publicClient,
|
|
410
|
-
payload,
|
|
411
|
-
requirements
|
|
412
|
-
);
|
|
454
|
+
sdkResult = await scope.verify(payload, requirements);
|
|
413
455
|
} catch (err) {
|
|
414
|
-
if (isProviderUnavailableError(err)) {
|
|
456
|
+
if (isProviderUnavailableError(err) || scope.transportFailed()) {
|
|
415
457
|
throw new CommerceError(
|
|
416
458
|
"PAYMENT_PROVIDER_UNAVAILABLE",
|
|
417
|
-
|
|
459
|
+
`x402 provider: ${binding.kind} facilitator unreachable during verify()`,
|
|
418
460
|
{
|
|
419
461
|
cause: err
|
|
420
462
|
}
|
|
@@ -424,10 +466,21 @@ function createX402PaymentProvider(options) {
|
|
|
424
466
|
return rejected("unexpected_verify_error");
|
|
425
467
|
}
|
|
426
468
|
if (!sdkResult.isValid) {
|
|
469
|
+
if (scope.transportFailed()) {
|
|
470
|
+
throw new CommerceError(
|
|
471
|
+
"PAYMENT_PROVIDER_UNAVAILABLE",
|
|
472
|
+
`x402 provider: ${binding.kind} facilitator unreachable during verify()`,
|
|
473
|
+
{ details: { reportedReason: sdkResult.invalidReason ?? "invalid_payment" } }
|
|
474
|
+
);
|
|
475
|
+
}
|
|
427
476
|
return rejected(sdkResult.invalidReason ?? "invalid_payment");
|
|
428
477
|
}
|
|
478
|
+
const payloadChainId = chainIdFromCaip2(payload.accepted.network);
|
|
479
|
+
if (payloadChainId === void 0) {
|
|
480
|
+
return rejected("wrong_network");
|
|
481
|
+
}
|
|
429
482
|
const replayKey = computeReplayKey({
|
|
430
|
-
chainId:
|
|
483
|
+
chainId: payloadChainId,
|
|
431
484
|
asset: getAddress(requirements.asset),
|
|
432
485
|
from: getAddress(authorization.from),
|
|
433
486
|
nonce: authorization.nonce
|
|
@@ -439,12 +492,12 @@ function createX402PaymentProvider(options) {
|
|
|
439
492
|
payee: getAddress(authorization.to),
|
|
440
493
|
amount: formatCanonicalAmount(authorizedValue, options.assetDecimals),
|
|
441
494
|
currency: requirement.currency,
|
|
442
|
-
network: payload.network,
|
|
495
|
+
network: payload.accepted.network,
|
|
443
496
|
asset: getAddress(requirements.asset),
|
|
444
497
|
replayKey
|
|
445
498
|
};
|
|
446
499
|
}
|
|
447
|
-
async function settle
|
|
500
|
+
async function settle(context) {
|
|
448
501
|
const { requirement, submission, verification } = context;
|
|
449
502
|
if (verification.status !== "verified") {
|
|
450
503
|
throw new CommerceError(
|
|
@@ -452,12 +505,6 @@ function createX402PaymentProvider(options) {
|
|
|
452
505
|
"x402 provider: settle() called without a successful verify()"
|
|
453
506
|
);
|
|
454
507
|
}
|
|
455
|
-
if (options.facilitator.mode !== "local" || facilitatorClient === void 0) {
|
|
456
|
-
throw new CommerceError(
|
|
457
|
-
"PROTOCOL_UNSUPPORTED",
|
|
458
|
-
'x402 provider: remote facilitator settlement is not implemented in v0.1.0-alpha; only { mode: "local" } is supported'
|
|
459
|
-
);
|
|
460
|
-
}
|
|
461
508
|
const requirements = requirement.challenge.accepts[0];
|
|
462
509
|
if (!requirements) {
|
|
463
510
|
throw new CommerceError(
|
|
@@ -472,30 +519,26 @@ function createX402PaymentProvider(options) {
|
|
|
472
519
|
"x402 provider: settle() received an undecodable payment payload"
|
|
473
520
|
);
|
|
474
521
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
}
|
|
482
|
-
|
|
522
|
+
const rejectedSettlement = (rejectionReason) => ({
|
|
523
|
+
status: "rejected",
|
|
524
|
+
provider: "x402",
|
|
525
|
+
amount: verification.amount,
|
|
526
|
+
currency: requirement.currency,
|
|
527
|
+
...verification.network !== void 0 ? { network: verification.network } : {},
|
|
528
|
+
...verification.asset !== void 0 ? { asset: verification.asset } : {},
|
|
529
|
+
...verification.replayKey !== void 0 ? { replayKey: verification.replayKey } : {},
|
|
530
|
+
rejectionReason
|
|
531
|
+
});
|
|
532
|
+
const scope = binding.open();
|
|
483
533
|
let sdkResult;
|
|
484
534
|
try {
|
|
485
|
-
sdkResult = await settle(
|
|
486
|
-
settlementClient,
|
|
487
|
-
payload,
|
|
488
|
-
requirements
|
|
489
|
-
);
|
|
535
|
+
sdkResult = await scope.settle(payload, requirements);
|
|
490
536
|
} catch (err) {
|
|
491
|
-
if (isProviderUnavailableError(err)) {
|
|
537
|
+
if (isProviderUnavailableError(err) || scope.transportFailed()) {
|
|
492
538
|
throw new CommerceError(
|
|
493
539
|
"PAYMENT_PROVIDER_UNAVAILABLE",
|
|
494
|
-
|
|
495
|
-
{
|
|
496
|
-
cause: err,
|
|
497
|
-
...broadcastTxHash !== void 0 ? { details: { transactionHash: broadcastTxHash } } : {}
|
|
498
|
-
}
|
|
540
|
+
`x402 provider: ${binding.kind} facilitator unreachable during settle()`,
|
|
541
|
+
{ cause: err }
|
|
499
542
|
);
|
|
500
543
|
}
|
|
501
544
|
const rejectionReason = isOnChainRevertError(err) ? "transaction_reverted" : "unexpected_settle_error";
|
|
@@ -503,28 +546,22 @@ function createX402PaymentProvider(options) {
|
|
|
503
546
|
{ err: describeError(err), rejectionReason },
|
|
504
547
|
"x402 settle(): settlement transaction failed"
|
|
505
548
|
);
|
|
506
|
-
return
|
|
507
|
-
status: "rejected",
|
|
508
|
-
provider: "x402",
|
|
509
|
-
amount: verification.amount,
|
|
510
|
-
currency: requirement.currency,
|
|
511
|
-
...verification.network !== void 0 ? { network: verification.network } : {},
|
|
512
|
-
...verification.asset !== void 0 ? { asset: verification.asset } : {},
|
|
513
|
-
...verification.replayKey !== void 0 ? { replayKey: verification.replayKey } : {},
|
|
514
|
-
rejectionReason
|
|
515
|
-
};
|
|
549
|
+
return rejectedSettlement(rejectionReason);
|
|
516
550
|
}
|
|
517
551
|
if (!sdkResult.success) {
|
|
552
|
+
if (sdkResult.errorReason === SETTLEMENT_PENDING_REASON || scope.transportFailed()) {
|
|
553
|
+
throw new CommerceError(
|
|
554
|
+
"PAYMENT_PROVIDER_UNAVAILABLE",
|
|
555
|
+
"x402 provider: settlement was broadcast but could not be confirmed",
|
|
556
|
+
{
|
|
557
|
+
...sdkResult.transaction ? { details: { transactionHash: sdkResult.transaction } } : {}
|
|
558
|
+
}
|
|
559
|
+
);
|
|
560
|
+
}
|
|
518
561
|
return {
|
|
519
|
-
|
|
520
|
-
provider: "x402",
|
|
521
|
-
amount: verification.amount,
|
|
522
|
-
currency: requirement.currency,
|
|
562
|
+
...rejectedSettlement(sdkResult.errorReason ?? "settlement_failed"),
|
|
523
563
|
network: sdkResult.network,
|
|
524
|
-
...
|
|
525
|
-
...sdkResult.payer !== void 0 ? { payer: sdkResult.payer } : {},
|
|
526
|
-
...verification.replayKey !== void 0 ? { replayKey: verification.replayKey } : {},
|
|
527
|
-
rejectionReason: sdkResult.errorReason ?? "settlement_failed"
|
|
564
|
+
...sdkResult.payer !== void 0 ? { payer: sdkResult.payer } : {}
|
|
528
565
|
};
|
|
529
566
|
}
|
|
530
567
|
return {
|
|
@@ -546,10 +583,10 @@ function createX402PaymentProvider(options) {
|
|
|
546
583
|
const checkedAt = clock.nowIso();
|
|
547
584
|
try {
|
|
548
585
|
const chainId = await healthPublicClient.getChainId();
|
|
549
|
-
if (chainId !==
|
|
586
|
+
if (chainId !== profile.chainId) {
|
|
550
587
|
return {
|
|
551
588
|
status: "fail",
|
|
552
|
-
detail: `RPC at ${options.rpcUrl} reports chain id ${chainId}, expected ${
|
|
589
|
+
detail: `RPC at ${options.rpcUrl} reports chain id ${chainId}, expected ${profile.chainId} (${profile.displayName})`,
|
|
553
590
|
checkedAt,
|
|
554
591
|
durationMs: clock.monotonicMs() - startedAt
|
|
555
592
|
};
|
|
@@ -563,10 +600,22 @@ function createX402PaymentProvider(options) {
|
|
|
563
600
|
durationMs: clock.monotonicMs() - startedAt
|
|
564
601
|
};
|
|
565
602
|
}
|
|
566
|
-
if (
|
|
603
|
+
if (binding.kind === "remote") {
|
|
604
|
+
const kinds = await binding.supported();
|
|
605
|
+
const supportsUs = kinds.some(
|
|
606
|
+
(kind) => kind.x402Version === X402_VERSION2 && kind.scheme === "exact" && kind.network === options.network
|
|
607
|
+
);
|
|
608
|
+
if (!supportsUs) {
|
|
609
|
+
return {
|
|
610
|
+
status: "fail",
|
|
611
|
+
detail: `Facilitator ${binding.describe} does not advertise x402 v${X402_VERSION2} scheme=exact on ${options.network} (${profile.displayName}); no payment on this deployment can settle`,
|
|
612
|
+
checkedAt,
|
|
613
|
+
durationMs: clock.monotonicMs() - startedAt
|
|
614
|
+
};
|
|
615
|
+
}
|
|
567
616
|
return {
|
|
568
|
-
status: "
|
|
569
|
-
detail:
|
|
617
|
+
status: "pass",
|
|
618
|
+
detail: `${describeDeploymentMode(mode)} \u2014 RPC ${options.rpcUrl} reachable, chain id ${profile.chainId} (${profile.displayName}), asset ${options.asset} has code, facilitator ${binding.describe} supports exact/${options.network}`,
|
|
570
619
|
checkedAt,
|
|
571
620
|
durationMs: clock.monotonicMs() - startedAt
|
|
572
621
|
};
|
|
@@ -575,14 +624,14 @@ function createX402PaymentProvider(options) {
|
|
|
575
624
|
if (!isAnvil) {
|
|
576
625
|
return {
|
|
577
626
|
status: "fail",
|
|
578
|
-
detail: `RPC at ${options.rpcUrl} reports chain id ${
|
|
627
|
+
detail: `RPC at ${options.rpcUrl} reports chain id ${profile.chainId} but does not answer "anvil_nodeInfo" \u2014 it does not look like a local Anvil dev node. Refusing to treat it as safe for a local-facilitator dev key.`,
|
|
579
628
|
checkedAt,
|
|
580
629
|
durationMs: clock.monotonicMs() - startedAt
|
|
581
630
|
};
|
|
582
631
|
}
|
|
583
632
|
return {
|
|
584
633
|
status: "pass",
|
|
585
|
-
detail:
|
|
634
|
+
detail: `${describeDeploymentMode(mode)} \u2014 RPC ${options.rpcUrl} reachable, chain id ${profile.chainId}, asset ${options.asset} has code, confirmed Anvil dev node`,
|
|
586
635
|
checkedAt,
|
|
587
636
|
durationMs: clock.monotonicMs() - startedAt
|
|
588
637
|
};
|
|
@@ -616,8 +665,8 @@ function createX402PaymentProvider(options) {
|
|
|
616
665
|
name: "x402",
|
|
617
666
|
descriptor,
|
|
618
667
|
createRequirement,
|
|
619
|
-
verify
|
|
620
|
-
settle
|
|
668
|
+
verify,
|
|
669
|
+
settle,
|
|
621
670
|
health
|
|
622
671
|
};
|
|
623
672
|
}
|