@devlab.group/agent-commerce 2.4.1 → 2.5.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 +115 -15
- package/dist/ap2.d.ts +24 -12
- package/dist/ap2.js +31 -13
- package/dist/ap2.js.map +1 -1
- package/dist/{chunk-DTKU3Z2M.js → chunk-3OTCFFFT.js} +37 -4
- package/dist/chunk-3OTCFFFT.js.map +1 -0
- package/dist/{chunk-QPV3EGJO.js → chunk-4K7FIBJM.js} +25 -4
- package/dist/chunk-4K7FIBJM.js.map +1 -0
- package/dist/{chunk-CXWC5LWM.js → chunk-FKQK5XZA.js} +652 -68
- package/dist/chunk-FKQK5XZA.js.map +1 -0
- package/dist/chunk-GA3O2A7R.js +92 -0
- package/dist/chunk-GA3O2A7R.js.map +1 -0
- package/dist/{chunk-WZW5ORZH.js → chunk-VUCMBGUR.js} +3 -3
- package/dist/{chunk-WZW5ORZH.js.map → chunk-VUCMBGUR.js.map} +1 -1
- package/dist/cli/{ap2-GWSP4LYY.js → ap2-YAPLERSC.js} +34 -15
- package/dist/cli/ap2-YAPLERSC.js.map +1 -0
- package/dist/cli/{chunk-G2JEY7MB.js → chunk-2J2IQQ4X.js} +8 -7
- package/dist/cli/chunk-2J2IQQ4X.js.map +1 -0
- package/dist/cli/chunk-2UPPX3FJ.js +311 -0
- package/dist/cli/chunk-2UPPX3FJ.js.map +1 -0
- package/dist/cli/{chunk-WYOGIANL.js → chunk-4H2QLHQI.js} +4 -4
- package/dist/cli/{chunk-WYOGIANL.js.map → chunk-4H2QLHQI.js.map} +1 -1
- package/dist/cli/{chunk-OZ4LAWBK.js → chunk-5R7DM5LM.js} +25 -4
- package/dist/cli/chunk-5R7DM5LM.js.map +1 -0
- package/dist/cli/chunk-663ALDZA.js +92 -0
- package/dist/cli/chunk-663ALDZA.js.map +1 -0
- package/dist/cli/{chunk-QQ3WY5L3.js → chunk-IDY2735M.js} +36 -104
- package/dist/cli/chunk-IDY2735M.js.map +1 -0
- package/dist/cli/chunk-JFVRG2HH.js +107 -0
- package/dist/cli/chunk-JFVRG2HH.js.map +1 -0
- package/dist/cli/chunk-M7NXVRU5.js +8 -0
- package/dist/cli/{chunk-ITWNSS2B.js.map → chunk-M7NXVRU5.js.map} +1 -1
- package/dist/cli/chunk-NIU5EMSB.js +119 -0
- package/dist/cli/chunk-NIU5EMSB.js.map +1 -0
- package/dist/cli/{chunk-TNMLDPUO.js → chunk-QA2OPHBF.js} +655 -70
- package/dist/cli/chunk-QA2OPHBF.js.map +1 -0
- package/dist/cli/{chunk-GJIMDMYT.js → chunk-QLUCNPRT.js} +404 -161
- package/dist/cli/chunk-QLUCNPRT.js.map +1 -0
- package/dist/cli/chunk-SYQLJXIU.js +105 -0
- package/dist/cli/chunk-SYQLJXIU.js.map +1 -0
- package/dist/cli/{chunk-3MGAJ5FX.js → chunk-VJ4EO4LE.js} +3 -3
- package/dist/cli/{chunk-3MGAJ5FX.js.map → chunk-VJ4EO4LE.js.map} +1 -1
- package/dist/cli/{chunk-PIMFPCNZ.js → chunk-Z34UMZBB.js} +234 -85
- package/dist/cli/chunk-Z34UMZBB.js.map +1 -0
- package/dist/cli/config-BWFMN2ZB.js +12 -0
- package/dist/cli/{config-UI4EDG4L.js.map → config-BWFMN2ZB.js.map} +1 -1
- package/dist/cli/index.js +258 -11
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{mcp-PGBRWZVA.js → mcp-KZEMXLHR.js} +5 -4
- package/dist/cli/mcp-KZEMXLHR.js.map +1 -0
- package/dist/cli/{near-5AY3HWYF.js → near-U6365VFX.js} +15 -4
- package/dist/cli/near-U6365VFX.js.map +1 -0
- package/dist/cli/payment-providers-SZL36H3Y.js +6 -0
- package/dist/cli/payment-providers-SZL36H3Y.js.map +1 -0
- package/dist/cli/{provider-I2ESL6RS.js → provider-6TKB5ADJ.js} +26 -15
- package/dist/cli/provider-6TKB5ADJ.js.map +1 -0
- package/dist/cli/receipts-ZGLJWMQG.js +8 -0
- package/dist/cli/receipts-ZGLJWMQG.js.map +1 -0
- package/dist/cli/reconcile-IC22MJD5.js +4 -0
- package/dist/cli/reconcile-IC22MJD5.js.map +1 -0
- package/dist/cli/{run-JK4ERPJV.js → run-ZTYPQI4D.js} +151 -115
- package/dist/cli/run-ZTYPQI4D.js.map +1 -0
- package/dist/cli/svm-KDSPDFJG.js +48 -0
- package/dist/cli/svm-KDSPDFJG.js.map +1 -0
- package/dist/cli/{x402-7WBFZQXV.js → x402-WLYBLODI.js} +10 -9
- package/dist/cli/x402-WLYBLODI.js.map +1 -0
- package/dist/index.d.ts +112 -12
- package/dist/index.js +948 -157
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +1 -2
- package/dist/mcp.js +1 -1
- package/dist/mpp.js +20 -10
- package/dist/mpp.js.map +1 -1
- package/dist/{near-D6C42VPE.js → near-FH7HPZV3.js} +15 -4
- package/dist/near-FH7HPZV3.js.map +1 -0
- package/dist/{protocol-adapter-C9odYl6l.d.ts → protocol-adapter-DEXpdp8m.d.ts} +102 -48
- package/dist/{runtime-Cl_BLVZV.d.ts → runtime-Dz2fSO-P.d.ts} +220 -2
- package/dist/svm-KNC3PHMS.js +48 -0
- package/dist/svm-KNC3PHMS.js.map +1 -0
- package/dist/x402.js +5 -5
- package/package.json +1 -1
- package/dist/authorization-DjAecwuW.d.ts +0 -115
- package/dist/chunk-CXWC5LWM.js.map +0 -1
- package/dist/chunk-DTKU3Z2M.js.map +0 -1
- package/dist/chunk-KA2AX5FH.js +0 -44
- package/dist/chunk-KA2AX5FH.js.map +0 -1
- package/dist/chunk-QPV3EGJO.js.map +0 -1
- package/dist/cli/ap2-GWSP4LYY.js.map +0 -1
- package/dist/cli/chunk-G2JEY7MB.js.map +0 -1
- package/dist/cli/chunk-GJIMDMYT.js.map +0 -1
- package/dist/cli/chunk-ITWNSS2B.js +0 -8
- package/dist/cli/chunk-OSTCU6ZK.js +0 -44
- package/dist/cli/chunk-OSTCU6ZK.js.map +0 -1
- package/dist/cli/chunk-OZ4LAWBK.js.map +0 -1
- package/dist/cli/chunk-PIMFPCNZ.js.map +0 -1
- package/dist/cli/chunk-QQ3WY5L3.js.map +0 -1
- package/dist/cli/chunk-TNMLDPUO.js.map +0 -1
- package/dist/cli/config-UI4EDG4L.js +0 -11
- package/dist/cli/mcp-PGBRWZVA.js.map +0 -1
- package/dist/cli/near-5AY3HWYF.js.map +0 -1
- package/dist/cli/provider-I2ESL6RS.js.map +0 -1
- package/dist/cli/run-JK4ERPJV.js.map +0 -1
- package/dist/cli/svm-3MMYFPC7.js +0 -33
- package/dist/cli/svm-3MMYFPC7.js.map +0 -1
- package/dist/cli/x402-7WBFZQXV.js.map +0 -1
- package/dist/near-D6C42VPE.js.map +0 -1
- package/dist/svm-QEUAUNPQ.js +0 -33
- package/dist/svm-QEUAUNPQ.js.map +0 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { CommerceError,
|
|
1
|
+
import { PACKAGE_VERSION } from './chunk-M7NXVRU5.js';
|
|
2
|
+
import { isWellKnownDevKey, assertPayToIsNotDevAddress, resolveX402Deployment, assertDevKeyIsLocalOnly, describeRpc, x402OfferName } from './chunk-4H2QLHQI.js';
|
|
3
|
+
import { findX402Network, normalizeAddress, canonicalJson, X402_DEFAULT_PAYMENT_FLOW, REUSE_REJECTED_AUTHORIZATION_METADATA_KEY, SETTLE_AFTER_BACKEND_METADATA_KEY, sameAddress, LOCAL_CHAIN_ID, isTransactionReference, isEvmAddress, describeDeploymentMode } from './chunk-5R7DM5LM.js';
|
|
4
|
+
import { systemClock, NOOP_LOGGER, RECONCILIATION_METADATA_KEY } from './chunk-IDY2735M.js';
|
|
5
|
+
import { CommerceError, redactedErrorText, isCommerceError } from './chunk-SYQLJXIU.js';
|
|
6
|
+
import { LOCATOR_VERSION, eip3009Locator, computeReplayKey, permit2Locator, computePermit2ReplayKey, isoFromSeconds } from './chunk-663ALDZA.js';
|
|
6
7
|
import { isRecord } from './chunk-3M6U7ILB.js';
|
|
7
|
-
import { x402ExactPermit2ProxyAddress, toFacilitatorEvmSigner, PERMIT2_ADDRESS } from '@x402/evm';
|
|
8
|
-
import { isAddress, keccak256, stringToBytes, getAddress, formatUnits, createWalletClient, http, publicActions, createPublicClient, HttpRequestError, TimeoutError, WaitForTransactionReceiptTimeoutError, BaseError, ContractFunctionRevertedError, erc20Abi } from 'viem';
|
|
8
|
+
import { x402ExactPermit2ProxyAddress, toFacilitatorEvmSigner, PERMIT2_ADDRESS, eip3009ABI, x402ExactPermit2ProxyABI } from '@x402/evm';
|
|
9
|
+
import { parseAbi, isAddress, keccak256, stringToBytes, isHash, getAddress, formatUnits, createWalletClient, http, publicActions, createPublicClient, HttpRequestError, TimeoutError, WaitForTransactionReceiptTimeoutError, BaseError, ContractFunctionRevertedError, erc20Abi, parseEventLogs, isAddressEqual, TransactionReceiptNotFoundError, decodeFunctionData } from 'viem';
|
|
9
10
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
10
11
|
import { HTTPFacilitatorClient } from '@x402/core/http';
|
|
11
12
|
import { SettleError, VerifyError } from '@x402/core/types';
|
|
@@ -54,10 +55,9 @@ function parseCanonicalAmount(amount, decimals) {
|
|
|
54
55
|
|
|
55
56
|
// src/payments/x402/provider/fee-payer.ts
|
|
56
57
|
var SUPPORTED_TTL_MS = 6e4;
|
|
57
|
-
function
|
|
58
|
+
function createSupportedSource(ctx) {
|
|
58
59
|
const { clock, remoteBinding } = ctx;
|
|
59
60
|
let supportedSnapshot;
|
|
60
|
-
const feePayers = /* @__PURE__ */ new Map();
|
|
61
61
|
function currentSupported() {
|
|
62
62
|
const now = clock.monotonicMs();
|
|
63
63
|
if (supportedSnapshot && now - supportedSnapshot.at < SUPPORTED_TTL_MS) {
|
|
@@ -72,6 +72,10 @@ function createSvmFeePayerSource(ctx) {
|
|
|
72
72
|
});
|
|
73
73
|
return response;
|
|
74
74
|
}
|
|
75
|
+
return currentSupported;
|
|
76
|
+
}
|
|
77
|
+
function createSvmFeePayerSource(currentSupported) {
|
|
78
|
+
const feePayers = /* @__PURE__ */ new Map();
|
|
75
79
|
async function svmFeePayer(offer) {
|
|
76
80
|
let supported;
|
|
77
81
|
try {
|
|
@@ -100,6 +104,43 @@ function createSvmFeePayerSource(ctx) {
|
|
|
100
104
|
}
|
|
101
105
|
return svmFeePayer;
|
|
102
106
|
}
|
|
107
|
+
function createNearRelayerSource(currentSupported, logger) {
|
|
108
|
+
return async (offer) => {
|
|
109
|
+
let supported;
|
|
110
|
+
try {
|
|
111
|
+
supported = await currentSupported();
|
|
112
|
+
} catch (err) {
|
|
113
|
+
logger.warn(
|
|
114
|
+
{ err: redactedErrorText(err) },
|
|
115
|
+
`x402 verify(): facilitator ${offer.binding.describe} did not answer /supported; the NEAR locator names no relayer`
|
|
116
|
+
);
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
const { signers } = supported;
|
|
120
|
+
return [.../* @__PURE__ */ new Set([...signers[offer.network] ?? [], ...signers["near:*"] ?? []])];
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/payments/x402/json-rpc.ts
|
|
125
|
+
async function jsonRpc(rpcUrl, method, params, timeoutMs) {
|
|
126
|
+
const response = await fetch(rpcUrl, {
|
|
127
|
+
method: "POST",
|
|
128
|
+
headers: { "content-type": "application/json" },
|
|
129
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
|
|
130
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
131
|
+
});
|
|
132
|
+
const body = await response.json();
|
|
133
|
+
if (body.error !== void 0) {
|
|
134
|
+
throw new CommerceError(
|
|
135
|
+
"PAYMENT_PROVIDER_UNAVAILABLE",
|
|
136
|
+
`RPC ${method} failed: ${JSON.stringify(body.error)}`,
|
|
137
|
+
{ details: { rpcError: body.error } }
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return body.result;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/payments/x402/provider/health.ts
|
|
103
144
|
function createHealthCheck(ctx) {
|
|
104
145
|
const { offers, clock, deploymentMode, remoteBinding, svm, near } = ctx;
|
|
105
146
|
async function offerHealth(offer, kinds) {
|
|
@@ -173,7 +214,7 @@ function createHealthCheck(ctx) {
|
|
|
173
214
|
async function svmOfferHealth(offer, kinds) {
|
|
174
215
|
const { profile, rpcOrigin, binding } = offer;
|
|
175
216
|
const svmModule = await svm();
|
|
176
|
-
const genesis = await
|
|
217
|
+
const genesis = await rpc(offer.rpcUrl, "getGenesisHash", []);
|
|
177
218
|
const cluster = typeof genesis === "string" ? `solana:${genesis.slice(0, 32)}` : void 0;
|
|
178
219
|
if (cluster !== offer.network) {
|
|
179
220
|
return {
|
|
@@ -181,10 +222,7 @@ function createHealthCheck(ctx) {
|
|
|
181
222
|
detail: `RPC at ${rpcOrigin} serves ${cluster ?? "an unknown cluster"}, expected ${offer.network} (${profile.displayName})`
|
|
182
223
|
};
|
|
183
224
|
}
|
|
184
|
-
const mint = await
|
|
185
|
-
offer.asset,
|
|
186
|
-
{ encoding: "base64" }
|
|
187
|
-
]);
|
|
225
|
+
const mint = await rpc(offer.rpcUrl, "getAccountInfo", [offer.asset, { encoding: "base64" }]);
|
|
188
226
|
const mintInfo = readMint(mint);
|
|
189
227
|
if (mintInfo === void 0) {
|
|
190
228
|
return {
|
|
@@ -204,7 +242,7 @@ function createHealthCheck(ctx) {
|
|
|
204
242
|
offer.asset,
|
|
205
243
|
tokenProgram
|
|
206
244
|
);
|
|
207
|
-
const account = await
|
|
245
|
+
const account = await rpc(offer.rpcUrl, "getAccountInfo", [
|
|
208
246
|
payeeAccount,
|
|
209
247
|
{ encoding: "base64" }
|
|
210
248
|
]);
|
|
@@ -232,7 +270,7 @@ function createHealthCheck(ctx) {
|
|
|
232
270
|
async function nearOfferHealth(offer, kinds) {
|
|
233
271
|
const { profile, rpcOrigin, binding, rpcUrl } = offer;
|
|
234
272
|
await near();
|
|
235
|
-
const status = await
|
|
273
|
+
const status = await rpc(rpcUrl, "status", []);
|
|
236
274
|
const chainId = isRecord(status) ? status["chain_id"] : void 0;
|
|
237
275
|
const expected = offer.network.slice("near:".length);
|
|
238
276
|
if (chainId !== expected) {
|
|
@@ -241,7 +279,7 @@ function createHealthCheck(ctx) {
|
|
|
241
279
|
detail: `RPC at ${rpcOrigin} serves chain ${String(chainId ?? "unknown")}, expected ${expected} (${profile.displayName})`
|
|
242
280
|
};
|
|
243
281
|
}
|
|
244
|
-
const token = await
|
|
282
|
+
const token = await rpc(rpcUrl, "query", {
|
|
245
283
|
request_type: "view_account",
|
|
246
284
|
finality: "final",
|
|
247
285
|
account_id: offer.asset
|
|
@@ -359,24 +397,9 @@ function readMint(result) {
|
|
|
359
397
|
const decimals = bytes[44];
|
|
360
398
|
return bytes.length >= 82 && bytes[45] === 1 && decimals !== void 0 ? { decimals, tokenProgram: owner } : void 0;
|
|
361
399
|
}
|
|
362
|
-
|
|
363
|
-
const response = await fetch(rpcUrl, {
|
|
364
|
-
method: "POST",
|
|
365
|
-
headers: { "content-type": "application/json" },
|
|
366
|
-
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
|
|
367
|
-
signal: AbortSignal.timeout(HEALTH_TIMEOUT_MS)
|
|
368
|
-
});
|
|
369
|
-
const body = await response.json();
|
|
370
|
-
if (body.error !== void 0) {
|
|
371
|
-
throw new CommerceError(
|
|
372
|
-
"PAYMENT_PROVIDER_UNAVAILABLE",
|
|
373
|
-
`RPC ${method} failed: ${JSON.stringify(body.error)}`
|
|
374
|
-
);
|
|
375
|
-
}
|
|
376
|
-
return body.result;
|
|
377
|
-
}
|
|
400
|
+
var rpc = (rpcUrl, method, params) => jsonRpc(rpcUrl, method, params, HEALTH_TIMEOUT_MS);
|
|
378
401
|
async function nearView(rpcUrl, accountId, method, args) {
|
|
379
|
-
const result = await
|
|
402
|
+
const result = await rpc(rpcUrl, "query", {
|
|
380
403
|
request_type: "call_function",
|
|
381
404
|
finality: "final",
|
|
382
405
|
account_id: accountId,
|
|
@@ -421,7 +444,7 @@ function buildLocalChain(rpcUrl, chainId = LOCAL_CHAIN_ID) {
|
|
|
421
444
|
function createLocalPublicClient(rpcUrl, timeoutMs, chainId) {
|
|
422
445
|
return createPublicClient({
|
|
423
446
|
chain: buildLocalChain(rpcUrl, chainId),
|
|
424
|
-
transport: http(rpcUrl, { timeout: timeoutMs } )
|
|
447
|
+
transport: http(rpcUrl, timeoutMs !== void 0 ? { timeout: timeoutMs } : void 0)
|
|
425
448
|
});
|
|
426
449
|
}
|
|
427
450
|
function createLocalFacilitatorClient(rpcUrl, signerPrivateKey, chainId) {
|
|
@@ -800,29 +823,6 @@ function offerCurrency(offer, quoted) {
|
|
|
800
823
|
const canonical = offer.profile.canonicalAsset;
|
|
801
824
|
return canonical !== void 0 && sameAddress(offer.profile, offer.asset, canonical.address) ? canonical.symbol : quoted;
|
|
802
825
|
}
|
|
803
|
-
|
|
804
|
-
// src/core/canonical-json.ts
|
|
805
|
-
var LONE_SURROGATE = new RegExp("\\p{Cs}", "u");
|
|
806
|
-
function canonicalJson(value) {
|
|
807
|
-
if (typeof value === "string")
|
|
808
|
-
return LONE_SURROGATE.test(value) ? void 0 : JSON.stringify(value);
|
|
809
|
-
if (typeof value === "number") return Number.isFinite(value) ? JSON.stringify(value) : void 0;
|
|
810
|
-
if (value === null || typeof value === "boolean") return JSON.stringify(value);
|
|
811
|
-
if (Array.isArray(value)) {
|
|
812
|
-
const items = value.map(canonicalJson);
|
|
813
|
-
return items.includes(void 0) ? void 0 : `[${items.join(",")}]`;
|
|
814
|
-
}
|
|
815
|
-
if (typeof value !== "object") return void 0;
|
|
816
|
-
const members = [];
|
|
817
|
-
for (const key of Object.keys(value).sort()) {
|
|
818
|
-
const member = canonicalJson(value[key]);
|
|
819
|
-
if (member === void 0 || LONE_SURROGATE.test(key)) return void 0;
|
|
820
|
-
members.push(`${JSON.stringify(key)}:${member}`);
|
|
821
|
-
}
|
|
822
|
-
return `{${members.join(",")}}`;
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
// src/payments/x402/provider/quote.ts
|
|
826
826
|
var QUOTE_EXTENSION = "erc-8409";
|
|
827
827
|
var QUOTE_REQUEST_SCHEME = "agent-commerce/request/v1";
|
|
828
828
|
var QUOTE_DOMAIN = { name: "Signed Service Payment Quotes", version: "1" };
|
|
@@ -966,6 +966,553 @@ function quoteRequestHash(resourceId, input) {
|
|
|
966
966
|
const canonical = canonicalJson({ input, resource: resourceId });
|
|
967
967
|
return canonical === void 0 ? void 0 : keccak256(stringToBytes(canonical));
|
|
968
968
|
}
|
|
969
|
+
var EVENTS = parseAbi([
|
|
970
|
+
"event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce)",
|
|
971
|
+
"event Transfer(address indexed from, address indexed to, uint256 value)"
|
|
972
|
+
]);
|
|
973
|
+
var NONCE_BITMAP_ABI = parseAbi([
|
|
974
|
+
"function nonceBitmap(address owner, uint256 wordPosition) view returns (uint256)"
|
|
975
|
+
]);
|
|
976
|
+
var CLOCK_MARGIN_SECONDS = 900n;
|
|
977
|
+
var LOG_CHUNK_BLOCKS = 10000n;
|
|
978
|
+
var MAX_WINDOW_BLOCKS = 200000n;
|
|
979
|
+
var UINT = /^\d{1,78}$/;
|
|
980
|
+
function parseEvmLocator(locator) {
|
|
981
|
+
const address = (key) => {
|
|
982
|
+
const value = locator[key];
|
|
983
|
+
return typeof value === "string" && isAddress(value) ? value : void 0;
|
|
984
|
+
};
|
|
985
|
+
const uint = (key) => {
|
|
986
|
+
const value = locator[key];
|
|
987
|
+
return typeof value === "string" && UINT.test(value) ? BigInt(value) : void 0;
|
|
988
|
+
};
|
|
989
|
+
const expiresAt = typeof locator["expiresAt"] === "string" ? locator["expiresAt"] : void 0;
|
|
990
|
+
if (locator["method"] === "eip3009") {
|
|
991
|
+
const [asset, from, to] = [address("asset"), address("from"), address("to")];
|
|
992
|
+
const [value, validAfter, validBefore] = [
|
|
993
|
+
uint("value"),
|
|
994
|
+
uint("validAfter"),
|
|
995
|
+
uint("validBefore")
|
|
996
|
+
];
|
|
997
|
+
const nonce = locator["nonce"];
|
|
998
|
+
if (!asset || !from || !to || value === void 0 || validAfter === void 0) return void 0;
|
|
999
|
+
if (validBefore === void 0 || typeof nonce !== "string" || !isHash(nonce)) return void 0;
|
|
1000
|
+
return { method: "eip3009", asset, from, to, value, nonce, validAfter, validBefore, expiresAt };
|
|
1001
|
+
}
|
|
1002
|
+
if (locator["method"] === "permit2") {
|
|
1003
|
+
const [asset, owner, to, proxy] = [
|
|
1004
|
+
address("asset"),
|
|
1005
|
+
address("owner"),
|
|
1006
|
+
address("to"),
|
|
1007
|
+
address("proxy")
|
|
1008
|
+
];
|
|
1009
|
+
const [amount, nonce, deadline, validAfter] = [
|
|
1010
|
+
uint("amount"),
|
|
1011
|
+
uint("nonce"),
|
|
1012
|
+
uint("deadline"),
|
|
1013
|
+
uint("validAfter")
|
|
1014
|
+
];
|
|
1015
|
+
if (!asset || !owner || !to || !proxy || amount === void 0 || nonce === void 0) {
|
|
1016
|
+
return void 0;
|
|
1017
|
+
}
|
|
1018
|
+
if (deadline === void 0 || validAfter === void 0) return void 0;
|
|
1019
|
+
return {
|
|
1020
|
+
method: "permit2",
|
|
1021
|
+
asset,
|
|
1022
|
+
owner,
|
|
1023
|
+
to,
|
|
1024
|
+
amount,
|
|
1025
|
+
nonce,
|
|
1026
|
+
deadline,
|
|
1027
|
+
validAfter,
|
|
1028
|
+
proxy,
|
|
1029
|
+
expiresAt
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
return void 0;
|
|
1033
|
+
}
|
|
1034
|
+
async function readEvmEvidence(input) {
|
|
1035
|
+
const { client, chainId, locator, attempt } = input;
|
|
1036
|
+
const reported = await client.getChainId();
|
|
1037
|
+
if (reported !== chainId) {
|
|
1038
|
+
return unknown("chain_mismatch", { chainId: reported, expectedChainId: chainId });
|
|
1039
|
+
}
|
|
1040
|
+
const finalized = await client.getBlock({ blockTag: "finalized" });
|
|
1041
|
+
const finalizedAt = isoFromSeconds(String(finalized.timestamp));
|
|
1042
|
+
const finality = {
|
|
1043
|
+
finalizedBlock: String(finalized.number),
|
|
1044
|
+
...finalizedAt !== void 0 ? { finalizedAt } : {}
|
|
1045
|
+
};
|
|
1046
|
+
const method = locator.method === "eip3009" ? eip3009(client, locator) : permit2(client, locator);
|
|
1047
|
+
const settled = (hash, block) => block <= finalized.number ? {
|
|
1048
|
+
outcome: "settled",
|
|
1049
|
+
reason: "transfer_final",
|
|
1050
|
+
externalReference: hash,
|
|
1051
|
+
evidence: { transactionHash: hash, blockNumber: String(block), ...finality }
|
|
1052
|
+
} : unknown("not_final", { transactionHash: hash, blockNumber: String(block), ...finality });
|
|
1053
|
+
const reference = attempt.externalReference;
|
|
1054
|
+
if (reference !== void 0 && isHash(reference)) {
|
|
1055
|
+
const block = await method.carries(reference);
|
|
1056
|
+
if (block !== void 0) return settled(reference, block);
|
|
1057
|
+
}
|
|
1058
|
+
if (!await method.used(finalized.number)) {
|
|
1059
|
+
if (finalized.timestamp >= method.endsAt) {
|
|
1060
|
+
return {
|
|
1061
|
+
outcome: "not-settled",
|
|
1062
|
+
reason: "expired_unused",
|
|
1063
|
+
evidence: { nonceUsed: false, ...finality }
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
if (await method.used()) return unknown("not_final", finality);
|
|
1067
|
+
return {
|
|
1068
|
+
...unknown("not_expired", finality),
|
|
1069
|
+
...locator.expiresAt !== void 0 ? { retryAfter: locator.expiresAt } : {}
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
const createdAt = BigInt(Math.floor(Date.parse(attempt.createdAt) / 1e3));
|
|
1073
|
+
const fromSeconds = max(method.validAfter, createdAt - CLOCK_MARGIN_SECONDS);
|
|
1074
|
+
const fromBlock = await firstBlockAt(client, fromSeconds, finalized);
|
|
1075
|
+
const endBlock = await firstBlockAt(client, method.endsAt, finalized) - 1n;
|
|
1076
|
+
const toBlock = min(endBlock, fromBlock + MAX_WINDOW_BLOCKS - 1n);
|
|
1077
|
+
const window = { fromBlock: String(fromBlock), toBlock: String(toBlock), ...finality };
|
|
1078
|
+
for (const [start, end] of logRanges(fromBlock, toBlock)) {
|
|
1079
|
+
for (const hash of await method.candidates(start, end)) {
|
|
1080
|
+
const block = await method.carries(hash);
|
|
1081
|
+
if (block !== void 0) return settled(hash, block);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
if (toBlock < endBlock) {
|
|
1085
|
+
return unknown("search_window_too_wide", { ...window, endBlock: String(endBlock) });
|
|
1086
|
+
}
|
|
1087
|
+
return unknown("used_without_record", { nonceUsed: true, ...window });
|
|
1088
|
+
}
|
|
1089
|
+
function eip3009(client, locator) {
|
|
1090
|
+
const { asset, from, to, value, nonce } = locator;
|
|
1091
|
+
return {
|
|
1092
|
+
endsAt: locator.validBefore,
|
|
1093
|
+
validAfter: locator.validAfter,
|
|
1094
|
+
used: (blockNumber) => client.readContract({
|
|
1095
|
+
address: asset,
|
|
1096
|
+
abi: eip3009ABI,
|
|
1097
|
+
functionName: "authorizationState",
|
|
1098
|
+
args: [from, nonce],
|
|
1099
|
+
...blockNumber !== void 0 ? { blockNumber } : {}
|
|
1100
|
+
}),
|
|
1101
|
+
async candidates(fromBlock, toBlock) {
|
|
1102
|
+
const logs = await client.getLogs({
|
|
1103
|
+
address: asset,
|
|
1104
|
+
event: EVENTS[0],
|
|
1105
|
+
args: { authorizer: from, nonce },
|
|
1106
|
+
fromBlock,
|
|
1107
|
+
toBlock
|
|
1108
|
+
});
|
|
1109
|
+
return logs.map((log) => log.transactionHash);
|
|
1110
|
+
},
|
|
1111
|
+
async carries(hash) {
|
|
1112
|
+
const receipt = await receiptOf(client, hash);
|
|
1113
|
+
if (receipt?.status !== "success") return void 0;
|
|
1114
|
+
const logs = parseEventLogs({ abi: EVENTS, logs: receipt.logs }).filter(
|
|
1115
|
+
(log) => isAddressEqual(log.address, asset)
|
|
1116
|
+
);
|
|
1117
|
+
const used = logs.some(
|
|
1118
|
+
(log) => log.eventName === "AuthorizationUsed" && isAddressEqual(log.args.authorizer, from) && log.args.nonce.toLowerCase() === nonce.toLowerCase()
|
|
1119
|
+
);
|
|
1120
|
+
return used && logs.some((log) => isTransfer(log, from, to, value)) ? receipt.blockNumber : void 0;
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
function permit2(client, locator) {
|
|
1125
|
+
const { asset, owner, to, amount, nonce, proxy } = locator;
|
|
1126
|
+
return {
|
|
1127
|
+
// Permit2 accepts a transfer while the timestamp is at most the deadline
|
|
1128
|
+
endsAt: locator.deadline + 1n,
|
|
1129
|
+
validAfter: locator.validAfter,
|
|
1130
|
+
async used(blockNumber) {
|
|
1131
|
+
const bitmap = await client.readContract({
|
|
1132
|
+
address: PERMIT2_ADDRESS,
|
|
1133
|
+
abi: NONCE_BITMAP_ABI,
|
|
1134
|
+
functionName: "nonceBitmap",
|
|
1135
|
+
args: [owner, nonce >> 8n],
|
|
1136
|
+
...blockNumber !== void 0 ? { blockNumber } : {}
|
|
1137
|
+
});
|
|
1138
|
+
return (bitmap >> (nonce & 0xffn) & 1n) === 1n;
|
|
1139
|
+
},
|
|
1140
|
+
// Match the nonce in proxy calldata to distinguish transfers with the
|
|
1141
|
+
// same owner, recipient and amount
|
|
1142
|
+
async candidates(fromBlock, toBlock) {
|
|
1143
|
+
const logs = await client.getLogs({
|
|
1144
|
+
address: asset,
|
|
1145
|
+
event: EVENTS[1],
|
|
1146
|
+
args: { from: owner, to },
|
|
1147
|
+
fromBlock,
|
|
1148
|
+
toBlock
|
|
1149
|
+
});
|
|
1150
|
+
return [...new Set(logs.map((log) => log.transactionHash))];
|
|
1151
|
+
},
|
|
1152
|
+
async carries(hash) {
|
|
1153
|
+
const receipt = await receiptOf(client, hash);
|
|
1154
|
+
if (receipt?.status !== "success" || !receipt.to || !isAddressEqual(receipt.to, proxy)) {
|
|
1155
|
+
return void 0;
|
|
1156
|
+
}
|
|
1157
|
+
const call = proxyCall((await client.getTransaction({ hash })).input);
|
|
1158
|
+
const named = call !== void 0 && isAddressEqual(call.owner, owner) && call.nonce === nonce && isAddressEqual(call.asset, asset) && isAddressEqual(call.to, to);
|
|
1159
|
+
const transferred = parseEventLogs({ abi: EVENTS, logs: receipt.logs }).some(
|
|
1160
|
+
(log) => isAddressEqual(log.address, asset) && isTransfer(log, owner, to, amount)
|
|
1161
|
+
);
|
|
1162
|
+
return named && transferred ? receipt.blockNumber : void 0;
|
|
1163
|
+
}
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
function proxyCall(input) {
|
|
1167
|
+
let call;
|
|
1168
|
+
try {
|
|
1169
|
+
call = decodeProxyInput(input);
|
|
1170
|
+
} catch {
|
|
1171
|
+
return void 0;
|
|
1172
|
+
}
|
|
1173
|
+
const parts = call.functionName === "settle" ? call.args : call.functionName === "settleWithPermit" ? [call.args[1], call.args[2], call.args[3]] : void 0;
|
|
1174
|
+
if (parts === void 0) return void 0;
|
|
1175
|
+
const [permit, owner, witness] = parts;
|
|
1176
|
+
return { owner, nonce: permit.nonce, asset: permit.permitted.token, to: witness.to };
|
|
1177
|
+
}
|
|
1178
|
+
var decodeProxyInput = (data) => decodeFunctionData({ abi: x402ExactPermit2ProxyABI, data });
|
|
1179
|
+
function isTransfer(log, from, to, value) {
|
|
1180
|
+
return log.eventName === "Transfer" && isAddressEqual(log.args.from, from) && isAddressEqual(log.args.to, to) && log.args.value === value;
|
|
1181
|
+
}
|
|
1182
|
+
async function receiptOf(client, hash) {
|
|
1183
|
+
try {
|
|
1184
|
+
return await client.getTransactionReceipt({ hash });
|
|
1185
|
+
} catch (err) {
|
|
1186
|
+
if (err instanceof TransactionReceiptNotFoundError) return void 0;
|
|
1187
|
+
throw err;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
function logRanges(fromBlock, toBlock) {
|
|
1191
|
+
const ranges = [];
|
|
1192
|
+
for (let start = fromBlock; start <= toBlock; start += LOG_CHUNK_BLOCKS) {
|
|
1193
|
+
ranges.push([start, min(start + LOG_CHUNK_BLOCKS - 1n, toBlock)]);
|
|
1194
|
+
}
|
|
1195
|
+
return ranges;
|
|
1196
|
+
}
|
|
1197
|
+
async function firstBlockAt(client, seconds, finalized) {
|
|
1198
|
+
if (seconds > finalized.timestamp) return finalized.number + 1n;
|
|
1199
|
+
const timestampOf = async (blockNumber) => (await client.getBlock({ blockNumber })).timestamp;
|
|
1200
|
+
let low = 0n;
|
|
1201
|
+
let lowTime = await timestampOf(low);
|
|
1202
|
+
if (lowTime >= seconds) return 0n;
|
|
1203
|
+
let high = finalized.number;
|
|
1204
|
+
let highTime = finalized.timestamp;
|
|
1205
|
+
for (let step = 0; high - low > 1n; step++) {
|
|
1206
|
+
let middle = (low + high) / 2n;
|
|
1207
|
+
if (step % 2 === 0 && highTime > lowTime) {
|
|
1208
|
+
const guess = low + (seconds - lowTime) * (high - low) / (highTime - lowTime);
|
|
1209
|
+
middle = min(max(guess, low + 1n), high - 1n);
|
|
1210
|
+
}
|
|
1211
|
+
const timestamp = await timestampOf(middle);
|
|
1212
|
+
if (timestamp >= seconds) [high, highTime] = [middle, timestamp];
|
|
1213
|
+
else [low, lowTime] = [middle, timestamp];
|
|
1214
|
+
}
|
|
1215
|
+
return high;
|
|
1216
|
+
}
|
|
1217
|
+
function unknown(reason, evidence) {
|
|
1218
|
+
return { outcome: "unknown", reason, ...evidence !== void 0 ? { evidence } : {} };
|
|
1219
|
+
}
|
|
1220
|
+
var max = (a, b) => a > b ? a : b;
|
|
1221
|
+
var min = (a, b) => a < b ? a : b;
|
|
1222
|
+
|
|
1223
|
+
// src/payments/x402/reconcile/near.ts
|
|
1224
|
+
var UINT2 = /^\d{1,20}$/;
|
|
1225
|
+
var TX_HASH = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
1226
|
+
function parseNearLocator(locator) {
|
|
1227
|
+
const text = (key) => {
|
|
1228
|
+
const value = locator[key];
|
|
1229
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
1230
|
+
};
|
|
1231
|
+
const uint = (key) => {
|
|
1232
|
+
const value = text(key);
|
|
1233
|
+
return value !== void 0 && UINT2.test(value) ? value : void 0;
|
|
1234
|
+
};
|
|
1235
|
+
const [senderId, publicKey, asset, receiver] = [
|
|
1236
|
+
text("senderId"),
|
|
1237
|
+
text("publicKey"),
|
|
1238
|
+
text("asset"),
|
|
1239
|
+
text("receiver")
|
|
1240
|
+
];
|
|
1241
|
+
const [nonce, maxBlockHeight, amount] = [uint("nonce"), uint("maxBlockHeight"), uint("amount")];
|
|
1242
|
+
const relayers = locator["relayers"] ?? [];
|
|
1243
|
+
if (!senderId || !publicKey || !asset || !receiver || !nonce || !maxBlockHeight || !amount) {
|
|
1244
|
+
return void 0;
|
|
1245
|
+
}
|
|
1246
|
+
if (!Array.isArray(relayers) || !relayers.every((id) => typeof id === "string")) {
|
|
1247
|
+
return void 0;
|
|
1248
|
+
}
|
|
1249
|
+
return {
|
|
1250
|
+
senderId,
|
|
1251
|
+
publicKey,
|
|
1252
|
+
nonce: BigInt(nonce),
|
|
1253
|
+
maxBlockHeight: BigInt(maxBlockHeight),
|
|
1254
|
+
asset,
|
|
1255
|
+
receiver,
|
|
1256
|
+
amount,
|
|
1257
|
+
relayers
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
async function readNearEvidence(input) {
|
|
1261
|
+
const { rpc: rpc2, network, locator, attempt } = input;
|
|
1262
|
+
const status = await rpc2("status", []);
|
|
1263
|
+
const chainId = isRecord(status) ? status["chain_id"] : void 0;
|
|
1264
|
+
const expected = network.slice("near:".length);
|
|
1265
|
+
if (chainId !== expected) {
|
|
1266
|
+
return unknown("chain_mismatch", { chainId, expectedChainId: expected });
|
|
1267
|
+
}
|
|
1268
|
+
const reference = attempt.externalReference;
|
|
1269
|
+
if (reference !== void 0 && TX_HASH.test(reference)) {
|
|
1270
|
+
for (const relayer of locator.relayers) {
|
|
1271
|
+
const outcome = await txStatus(rpc2, reference, relayer);
|
|
1272
|
+
if (outcome === void 0) continue;
|
|
1273
|
+
if (outcome.final_execution_status !== "FINAL") {
|
|
1274
|
+
return unknown("not_final", { transactionHash: reference });
|
|
1275
|
+
}
|
|
1276
|
+
if (carries(outcome, locator)) {
|
|
1277
|
+
return {
|
|
1278
|
+
outcome: "settled",
|
|
1279
|
+
reason: "transfer_final",
|
|
1280
|
+
externalReference: reference,
|
|
1281
|
+
evidence: { transactionHash: reference, relayer }
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
break;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
const block = await rpc2("block", { finality: "final" });
|
|
1288
|
+
const height = block.header?.height;
|
|
1289
|
+
if (typeof height !== "number" || !Number.isSafeInteger(height)) {
|
|
1290
|
+
throw new CommerceError("PAYMENT_PROVIDER_UNAVAILABLE", "block returned no height");
|
|
1291
|
+
}
|
|
1292
|
+
const finality = { finalBlock: String(height) };
|
|
1293
|
+
let key;
|
|
1294
|
+
try {
|
|
1295
|
+
key = await rpc2("query", {
|
|
1296
|
+
request_type: "view_access_key",
|
|
1297
|
+
block_id: height,
|
|
1298
|
+
account_id: locator.senderId,
|
|
1299
|
+
public_key: locator.publicKey
|
|
1300
|
+
});
|
|
1301
|
+
} catch (err) {
|
|
1302
|
+
if (rpcErrorName(err) === "UNKNOWN_ACCESS_KEY") return unknown("access_key_missing", finality);
|
|
1303
|
+
throw err;
|
|
1304
|
+
}
|
|
1305
|
+
if (typeof key.error === "string") return unknown("access_key_missing", finality);
|
|
1306
|
+
if (typeof key.nonce !== "number" || !Number.isSafeInteger(key.nonce)) {
|
|
1307
|
+
throw new CommerceError("PAYMENT_PROVIDER_UNAVAILABLE", "view_access_key returned no nonce");
|
|
1308
|
+
}
|
|
1309
|
+
const evidence = { keyNonce: String(key.nonce), ...finality };
|
|
1310
|
+
if (BigInt(key.nonce) >= locator.nonce) return unknown("used_without_record", evidence);
|
|
1311
|
+
if (BigInt(height) > locator.maxBlockHeight) {
|
|
1312
|
+
return { outcome: "not-settled", reason: "expired_unused", evidence };
|
|
1313
|
+
}
|
|
1314
|
+
return unknown("not_expired", { maxBlockHeight: String(locator.maxBlockHeight), ...evidence });
|
|
1315
|
+
}
|
|
1316
|
+
async function txStatus(rpc2, hash, sender) {
|
|
1317
|
+
try {
|
|
1318
|
+
return await rpc2("EXPERIMENTAL_tx_status", {
|
|
1319
|
+
tx_hash: hash,
|
|
1320
|
+
sender_account_id: sender,
|
|
1321
|
+
wait_until: "NONE"
|
|
1322
|
+
});
|
|
1323
|
+
} catch (err) {
|
|
1324
|
+
if (rpcErrorName(err) === "UNKNOWN_TRANSACTION") return void 0;
|
|
1325
|
+
throw err;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
function carries(outcome, locator) {
|
|
1329
|
+
const delegated = (outcome.transaction?.actions ?? []).some((action) => {
|
|
1330
|
+
const delegate = action.Delegate?.delegate_action;
|
|
1331
|
+
return delegate?.sender_id === locator.senderId && delegate.public_key === locator.publicKey && String(delegate.nonce) === String(locator.nonce);
|
|
1332
|
+
});
|
|
1333
|
+
const succeeded = new Set(
|
|
1334
|
+
(outcome.receipts_outcome ?? []).filter((entry) => isRecord(entry.outcome?.status) && "SuccessValue" in entry.outcome.status).map((entry) => entry.id)
|
|
1335
|
+
);
|
|
1336
|
+
const transferred = (outcome.receipts ?? []).some(
|
|
1337
|
+
(receipt) => receipt.receiver_id === locator.asset && receipt.predecessor_id === locator.senderId && succeeded.has(receipt.receipt_id) && (receipt.receipt?.Action?.actions ?? []).some((action) => {
|
|
1338
|
+
const call = action.FunctionCall;
|
|
1339
|
+
if (call?.method_name !== "ft_transfer" || typeof call.args !== "string") return false;
|
|
1340
|
+
const args = parseArgs(call.args);
|
|
1341
|
+
return args?.["receiver_id"] === locator.receiver && args["amount"] === locator.amount;
|
|
1342
|
+
})
|
|
1343
|
+
);
|
|
1344
|
+
return delegated && transferred;
|
|
1345
|
+
}
|
|
1346
|
+
function parseArgs(base64) {
|
|
1347
|
+
try {
|
|
1348
|
+
const args = JSON.parse(Buffer.from(base64, "base64").toString("utf8"));
|
|
1349
|
+
return isRecord(args) ? args : void 0;
|
|
1350
|
+
} catch {
|
|
1351
|
+
return void 0;
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
function rpcErrorName(err) {
|
|
1355
|
+
const rpcError = isCommerceError(err) ? err.details?.["rpcError"] : void 0;
|
|
1356
|
+
return isRecord(rpcError) && isRecord(rpcError["cause"]) ? rpcError["cause"]["name"] : void 0;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
// src/payments/x402/reconcile/svm.ts
|
|
1360
|
+
var CLOCK_MARGIN_SECONDS2 = 900;
|
|
1361
|
+
var HISTORY_PAGE = 1e3;
|
|
1362
|
+
var MAX_HISTORY_PAGES = 5;
|
|
1363
|
+
var BASE58 = /^[1-9A-HJ-NP-Za-km-z]+$/;
|
|
1364
|
+
var MESSAGE_HASH = /^[A-Za-z0-9+/]{43}=$/;
|
|
1365
|
+
function parseSvmLocator(locator) {
|
|
1366
|
+
const { messageHash, recentBlockhash, payer } = locator;
|
|
1367
|
+
if (typeof messageHash !== "string" || !MESSAGE_HASH.test(messageHash)) return void 0;
|
|
1368
|
+
if (!isBase58(recentBlockhash, 32, 44)) return void 0;
|
|
1369
|
+
if (payer !== void 0 && !isBase58(payer, 32, 44)) return void 0;
|
|
1370
|
+
return { messageHash, recentBlockhash, payer };
|
|
1371
|
+
}
|
|
1372
|
+
async function readSvmEvidence(input) {
|
|
1373
|
+
const { rpc: rpc2, network, locator, attempt, messageHashOf } = input;
|
|
1374
|
+
const genesis = await rpc2("getGenesisHash", []);
|
|
1375
|
+
const served = `solana:${String(genesis).slice(0, 32)}`;
|
|
1376
|
+
if (served !== network) {
|
|
1377
|
+
return unknown("chain_mismatch", { network: served, expectedNetwork: network });
|
|
1378
|
+
}
|
|
1379
|
+
const carries2 = async (id) => {
|
|
1380
|
+
const landed = await rpc2("getTransaction", [
|
|
1381
|
+
id,
|
|
1382
|
+
{ encoding: "base64", commitment: "finalized", maxSupportedTransactionVersion: 0 }
|
|
1383
|
+
]);
|
|
1384
|
+
if (landed?.meta?.err !== null || typeof landed.slot !== "number") return void 0;
|
|
1385
|
+
const [wire] = Array.isArray(landed.transaction) ? landed.transaction : [];
|
|
1386
|
+
return typeof wire === "string" && messageHashOf(wire) === locator.messageHash ? landed.slot : void 0;
|
|
1387
|
+
};
|
|
1388
|
+
const settled = (id, slot) => ({
|
|
1389
|
+
outcome: "settled",
|
|
1390
|
+
reason: "transfer_final",
|
|
1391
|
+
externalReference: id,
|
|
1392
|
+
evidence: { transactionId: id, slot: String(slot) }
|
|
1393
|
+
});
|
|
1394
|
+
const reference = attempt.externalReference;
|
|
1395
|
+
if (reference !== void 0 && isBase58(reference, 64, 88)) {
|
|
1396
|
+
const slot = await carries2(reference);
|
|
1397
|
+
if (slot !== void 0) return settled(reference, slot);
|
|
1398
|
+
}
|
|
1399
|
+
if (locator.payer !== void 0) {
|
|
1400
|
+
const createdAt = Math.floor(Date.parse(attempt.createdAt) / 1e3);
|
|
1401
|
+
const from = createdAt - CLOCK_MARGIN_SECONDS2;
|
|
1402
|
+
const to = createdAt + CLOCK_MARGIN_SECONDS2;
|
|
1403
|
+
let before;
|
|
1404
|
+
search: for (let page = 0; ; page += 1) {
|
|
1405
|
+
if (page === MAX_HISTORY_PAGES) return unknown("history_too_long", { payer: locator.payer });
|
|
1406
|
+
const entries = await rpc2("getSignaturesForAddress", [
|
|
1407
|
+
locator.payer,
|
|
1408
|
+
{
|
|
1409
|
+
commitment: "finalized",
|
|
1410
|
+
limit: HISTORY_PAGE,
|
|
1411
|
+
...before !== void 0 ? { before } : {}
|
|
1412
|
+
}
|
|
1413
|
+
]);
|
|
1414
|
+
if (!Array.isArray(entries)) {
|
|
1415
|
+
throw new CommerceError(
|
|
1416
|
+
"PAYMENT_PROVIDER_UNAVAILABLE",
|
|
1417
|
+
"getSignaturesForAddress returned no list"
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
for (const entry of entries) {
|
|
1421
|
+
if (typeof entry.signature !== "string") continue;
|
|
1422
|
+
before = entry.signature;
|
|
1423
|
+
const time = typeof entry.blockTime === "number" ? entry.blockTime : void 0;
|
|
1424
|
+
if (time !== void 0 && time < from) break search;
|
|
1425
|
+
if (entry.err !== null || time !== void 0 && time > to) continue;
|
|
1426
|
+
const slot = await carries2(entry.signature);
|
|
1427
|
+
if (slot !== void 0) return settled(entry.signature, slot);
|
|
1428
|
+
}
|
|
1429
|
+
if (entries.length < HISTORY_PAGE) break;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
const validity = await rpc2("isBlockhashValid", [
|
|
1433
|
+
locator.recentBlockhash,
|
|
1434
|
+
{ commitment: "confirmed" }
|
|
1435
|
+
]);
|
|
1436
|
+
const valid = isRecord(validity) ? validity["value"] : void 0;
|
|
1437
|
+
if (typeof valid !== "boolean") {
|
|
1438
|
+
throw new CommerceError("PAYMENT_PROVIDER_UNAVAILABLE", "isBlockhashValid returned no value");
|
|
1439
|
+
}
|
|
1440
|
+
return unknown(valid ? "not_expired" : "blockhash_expired", {
|
|
1441
|
+
payerSearched: locator.payer !== void 0
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
function isBase58(value, minLength, maxLength) {
|
|
1445
|
+
return typeof value === "string" && value.length >= minLength && value.length <= maxLength && BASE58.test(value);
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
// src/payments/x402/provider/reconcile.ts
|
|
1449
|
+
var RECONCILE_TIMEOUT_MS = 3e4;
|
|
1450
|
+
function createReconcile(ctx) {
|
|
1451
|
+
const clients = /* @__PURE__ */ new Map();
|
|
1452
|
+
const evmClient = (offer) => {
|
|
1453
|
+
let client = clients.get(offer);
|
|
1454
|
+
if (client === void 0) {
|
|
1455
|
+
client = createLocalPublicClient(offer.rpcUrl, RECONCILE_TIMEOUT_MS, offer.profile.chainId);
|
|
1456
|
+
clients.set(offer, client);
|
|
1457
|
+
}
|
|
1458
|
+
return client;
|
|
1459
|
+
};
|
|
1460
|
+
const rpc2 = (offer) => (method, params) => jsonRpc(offer.rpcUrl, method, params, RECONCILE_TIMEOUT_MS);
|
|
1461
|
+
const read = async (offer, evidence) => {
|
|
1462
|
+
try {
|
|
1463
|
+
return await evidence();
|
|
1464
|
+
} catch (err) {
|
|
1465
|
+
return unknown("rpc_error", { rpc: offer.rpcOrigin, error: redactedErrorText(err) });
|
|
1466
|
+
}
|
|
1467
|
+
};
|
|
1468
|
+
return async ({ attempt, locator }) => {
|
|
1469
|
+
if (locator === void 0) return unknown("no_locator");
|
|
1470
|
+
if (locator["v"] !== LOCATOR_VERSION) return unknown("unsupported_locator_version");
|
|
1471
|
+
const network = locator["network"];
|
|
1472
|
+
if (typeof network !== "string") return unknown("unsupported_family");
|
|
1473
|
+
const offerFor = (family) => ctx.offers.find(
|
|
1474
|
+
(candidate) => candidate.family === family && candidate.network === network
|
|
1475
|
+
);
|
|
1476
|
+
const notConfigured = unknown("network_not_configured", { network });
|
|
1477
|
+
if (network.startsWith("eip155:")) {
|
|
1478
|
+
const parsed = parseEvmLocator(locator);
|
|
1479
|
+
if (parsed === void 0) return unknown("invalid_locator");
|
|
1480
|
+
const offer = offerFor("evm");
|
|
1481
|
+
if (offer === void 0) return notConfigured;
|
|
1482
|
+
return read(
|
|
1483
|
+
offer,
|
|
1484
|
+
() => readEvmEvidence({
|
|
1485
|
+
client: evmClient(offer),
|
|
1486
|
+
chainId: offer.profile.chainId,
|
|
1487
|
+
locator: parsed,
|
|
1488
|
+
attempt
|
|
1489
|
+
})
|
|
1490
|
+
);
|
|
1491
|
+
}
|
|
1492
|
+
if (network.startsWith("solana:")) {
|
|
1493
|
+
const parsed = parseSvmLocator(locator);
|
|
1494
|
+
if (parsed === void 0) return unknown("invalid_locator");
|
|
1495
|
+
const offer = offerFor("svm");
|
|
1496
|
+
if (offer === void 0) return notConfigured;
|
|
1497
|
+
const { messageHashOf } = await ctx.svm();
|
|
1498
|
+
return read(
|
|
1499
|
+
offer,
|
|
1500
|
+
() => readSvmEvidence({ rpc: rpc2(offer), network, locator: parsed, attempt, messageHashOf })
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
if (network.startsWith("near:")) {
|
|
1504
|
+
const parsed = parseNearLocator(locator);
|
|
1505
|
+
if (parsed === void 0) return unknown("invalid_locator");
|
|
1506
|
+
const offer = offerFor("near");
|
|
1507
|
+
if (offer === void 0) return notConfigured;
|
|
1508
|
+
return read(
|
|
1509
|
+
offer,
|
|
1510
|
+
() => readNearEvidence({ rpc: rpc2(offer), network, locator: parsed, attempt })
|
|
1511
|
+
);
|
|
1512
|
+
}
|
|
1513
|
+
return unknown("unsupported_family");
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
969
1516
|
function decodePaymentSubmission(payloadBase64) {
|
|
970
1517
|
const json = decodeJson(payloadBase64);
|
|
971
1518
|
if (json === void 0) return void 0;
|
|
@@ -1140,12 +1687,13 @@ function readTransfer(payload, offer) {
|
|
|
1140
1687
|
from: getAddress(from2),
|
|
1141
1688
|
nonce: nonce2
|
|
1142
1689
|
}),
|
|
1690
|
+
locator: eip3009Locator(offer.network, offer.asset, authorization),
|
|
1143
1691
|
recipientMismatch: "invalid_exact_evm_payload_recipient_mismatch",
|
|
1144
1692
|
amountMismatch: "invalid_exact_evm_payload_authorization_value_mismatch"
|
|
1145
1693
|
};
|
|
1146
1694
|
}
|
|
1147
1695
|
if (!isExactPermit2Payload(payload)) return "unsupported_scheme";
|
|
1148
|
-
const { from, spender, permitted, witness, nonce } = payload.payload.permit2Authorization;
|
|
1696
|
+
const { from, spender, permitted, witness, nonce, deadline } = payload.payload.permit2Authorization;
|
|
1149
1697
|
if (getAddress(spender) !== getAddress(x402ExactPermit2ProxyAddress)) {
|
|
1150
1698
|
return "invalid_permit2_spender";
|
|
1151
1699
|
}
|
|
@@ -1155,6 +1703,16 @@ function readTransfer(payload, offer) {
|
|
|
1155
1703
|
to: getAddress(witness.to),
|
|
1156
1704
|
value: permitted.amount,
|
|
1157
1705
|
replayKey: computePermit2ReplayKey({ chainId, owner: getAddress(from), nonce }),
|
|
1706
|
+
locator: permit2Locator(offer.network, {
|
|
1707
|
+
asset: permitted.token,
|
|
1708
|
+
owner: from,
|
|
1709
|
+
to: witness.to,
|
|
1710
|
+
amount: permitted.amount,
|
|
1711
|
+
nonce,
|
|
1712
|
+
deadline,
|
|
1713
|
+
validAfter: witness.validAfter,
|
|
1714
|
+
proxy: spender
|
|
1715
|
+
}),
|
|
1158
1716
|
recipientMismatch: "invalid_permit2_recipient_mismatch",
|
|
1159
1717
|
amountMismatch: "permit2_amount_mismatch"
|
|
1160
1718
|
};
|
|
@@ -1329,7 +1887,8 @@ function createSettle(ctx) {
|
|
|
1329
1887
|
}
|
|
1330
1888
|
return settle;
|
|
1331
1889
|
}
|
|
1332
|
-
|
|
1890
|
+
var RELAYER_GRACE_MS = 1e3;
|
|
1891
|
+
function createVerify(ctx, nearRelayers) {
|
|
1333
1892
|
const { offersFor, clock, logger, svm, near } = ctx;
|
|
1334
1893
|
async function verify(context) {
|
|
1335
1894
|
const { requirement, submission } = context;
|
|
@@ -1372,11 +1931,13 @@ function createVerify(ctx) {
|
|
|
1372
1931
|
}
|
|
1373
1932
|
let transfer;
|
|
1374
1933
|
let replayKey;
|
|
1934
|
+
let locator;
|
|
1375
1935
|
let derivedPayer;
|
|
1376
1936
|
if (offer.family === "svm") {
|
|
1377
|
-
const
|
|
1378
|
-
if (typeof
|
|
1379
|
-
replayKey =
|
|
1937
|
+
const inspected = (await svm()).inspectSvmPayload(payload.payload, offer.network);
|
|
1938
|
+
if (typeof inspected === "string") return rejected(inspected);
|
|
1939
|
+
replayKey = inspected.replayKey;
|
|
1940
|
+
locator = inspected.locator;
|
|
1380
1941
|
} else if (offer.family === "near") {
|
|
1381
1942
|
const inspected = (await near()).inspectNearPayload(payload.payload, {
|
|
1382
1943
|
network: offer.network,
|
|
@@ -1386,12 +1947,14 @@ function createVerify(ctx) {
|
|
|
1386
1947
|
});
|
|
1387
1948
|
if (typeof inspected === "string") return rejected(inspected);
|
|
1388
1949
|
replayKey = inspected.replayKey;
|
|
1950
|
+
locator = inspected.locator;
|
|
1389
1951
|
derivedPayer = inspected.payer;
|
|
1390
1952
|
} else {
|
|
1391
1953
|
const read = readTransfer(payload, offer);
|
|
1392
1954
|
if (typeof read === "string") return rejected(read);
|
|
1393
1955
|
transfer = read;
|
|
1394
1956
|
replayKey = read.replayKey;
|
|
1957
|
+
locator = read.locator;
|
|
1395
1958
|
derivedPayer = read.from;
|
|
1396
1959
|
}
|
|
1397
1960
|
if (payload.accepted.network !== offer.network) {
|
|
@@ -1421,6 +1984,7 @@ function createVerify(ctx) {
|
|
|
1421
1984
|
return rejected(transfer.amountMismatch);
|
|
1422
1985
|
}
|
|
1423
1986
|
}
|
|
1987
|
+
const relayers = offer.family === "near" ? nearRelayers(offer) : void 0;
|
|
1424
1988
|
const scope = offer.binding.open();
|
|
1425
1989
|
let sdkResult;
|
|
1426
1990
|
try {
|
|
@@ -1457,6 +2021,9 @@ function createVerify(ctx) {
|
|
|
1457
2021
|
return rejected(sanitizeReason(sdkResult.invalidReason, "invalid_payment"));
|
|
1458
2022
|
}
|
|
1459
2023
|
const payer = derivedPayer ?? (sdkResult.payer !== void 0 && normalizeAddress(offer.profile, sdkResult.payer) ? sdkResult.payer : void 0);
|
|
2024
|
+
if (offer.family === "svm" && payer !== void 0) locator = { ...locator, payer };
|
|
2025
|
+
const relayerIds = relayers !== void 0 ? await withinGrace(relayers) : [];
|
|
2026
|
+
if (relayerIds.length > 0) locator = { ...locator, relayers: relayerIds };
|
|
1460
2027
|
return {
|
|
1461
2028
|
status: "verified",
|
|
1462
2029
|
provider: "x402",
|
|
@@ -1466,10 +2033,26 @@ function createVerify(ctx) {
|
|
|
1466
2033
|
currency: offerCurrency(offer, requirement.currency),
|
|
1467
2034
|
network: payload.accepted.network,
|
|
1468
2035
|
asset: offer.family === "evm" ? getAddress(requirements.asset) : offer.asset,
|
|
1469
|
-
replayKey
|
|
2036
|
+
replayKey,
|
|
2037
|
+
metadata: { [RECONCILIATION_METADATA_KEY]: locator }
|
|
1470
2038
|
};
|
|
1471
2039
|
}
|
|
1472
2040
|
return verify;
|
|
2041
|
+
async function withinGrace(relayers) {
|
|
2042
|
+
let timer;
|
|
2043
|
+
const late = new Promise((resolve) => {
|
|
2044
|
+
timer = setTimeout(() => resolve(void 0), RELAYER_GRACE_MS);
|
|
2045
|
+
timer.unref();
|
|
2046
|
+
});
|
|
2047
|
+
const ids = await Promise.race([relayers, late]).finally(() => clearTimeout(timer));
|
|
2048
|
+
if (ids === void 0) {
|
|
2049
|
+
logger.warn(
|
|
2050
|
+
{ graceMs: RELAYER_GRACE_MS },
|
|
2051
|
+
"x402 verify(): /supported is slow; the NEAR locator names no relayer"
|
|
2052
|
+
);
|
|
2053
|
+
}
|
|
2054
|
+
return ids ?? [];
|
|
2055
|
+
}
|
|
1473
2056
|
}
|
|
1474
2057
|
|
|
1475
2058
|
// src/payments/x402/provider/index.ts
|
|
@@ -1538,8 +2121,8 @@ function createX402PaymentProvider(options) {
|
|
|
1538
2121
|
"auth-capture scheme"
|
|
1539
2122
|
]
|
|
1540
2123
|
};
|
|
1541
|
-
const svmModule = hasSvm ? loadOptionalModule(() => import('./svm-
|
|
1542
|
-
const nearModule = hasNear ? loadOptionalModule(() => import('./near-
|
|
2124
|
+
const svmModule = hasSvm ? loadOptionalModule(() => import('./svm-KDSPDFJG.js'), "Solana offers need @x402/svm and @solana/kit") : void 0;
|
|
2125
|
+
const nearModule = hasNear ? loadOptionalModule(() => import('./near-U6365VFX.js'), "NEAR offers need @x402/near") : void 0;
|
|
1543
2126
|
const svm = async () => {
|
|
1544
2127
|
if (!svmModule)
|
|
1545
2128
|
throw new CommerceError("INTERNAL_ERROR", "x402 provider: no Solana offer is configured");
|
|
@@ -1564,11 +2147,12 @@ function createX402PaymentProvider(options) {
|
|
|
1564
2147
|
svm,
|
|
1565
2148
|
near
|
|
1566
2149
|
};
|
|
2150
|
+
const supported = createSupportedSource(ctx);
|
|
1567
2151
|
const { createRequirement, selectRequirement } = createRequirementBuilders(
|
|
1568
2152
|
ctx,
|
|
1569
|
-
createSvmFeePayerSource(
|
|
2153
|
+
createSvmFeePayerSource(supported)
|
|
1570
2154
|
);
|
|
1571
|
-
const verify = createVerify(ctx);
|
|
2155
|
+
const verify = createVerify(ctx, createNearRelayerSource(supported, logger));
|
|
1572
2156
|
const settle = createSettle(ctx);
|
|
1573
2157
|
const health = createHealthCheck(ctx);
|
|
1574
2158
|
return {
|
|
@@ -1579,6 +2163,7 @@ function createX402PaymentProvider(options) {
|
|
|
1579
2163
|
...severalOffers ? { selectRequirement } : {},
|
|
1580
2164
|
verify,
|
|
1581
2165
|
settle,
|
|
2166
|
+
reconcile: createReconcile(ctx),
|
|
1582
2167
|
health,
|
|
1583
2168
|
...quotes ? {
|
|
1584
2169
|
quote: {
|
|
@@ -1601,5 +2186,5 @@ function loadOptionalModule(load, missing) {
|
|
|
1601
2186
|
}
|
|
1602
2187
|
|
|
1603
2188
|
export { DEFAULT_IDS, chainIdFromCaip2, createX402PaymentProvider, parseCanonicalAmount };
|
|
1604
|
-
//# sourceMappingURL=chunk-
|
|
1605
|
-
//# sourceMappingURL=chunk-
|
|
2189
|
+
//# sourceMappingURL=chunk-QA2OPHBF.js.map
|
|
2190
|
+
//# sourceMappingURL=chunk-QA2OPHBF.js.map
|