uvd-x402-sdk 2.104.1 → 2.104.2
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 +88 -1
- package/dist/adapters/index.d.mts +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/backend/index.d.mts +3 -3
- package/dist/backend/index.d.ts +3 -3
- package/dist/backend/index.js +760 -26
- package/dist/backend/index.js.map +1 -1
- package/dist/backend/index.mjs +760 -28
- package/dist/backend/index.mjs.map +1 -1
- package/dist/{escrow-contracts-C59jszp3.d.ts → escrow-contracts-Bf3PU_Ub.d.ts} +496 -4
- package/dist/{escrow-contracts-Dgcyu7Fh.d.mts → escrow-contracts-amyDktMD.d.mts} +496 -4
- package/dist/{index-QswKm6BF.d.ts → index-CZHgEr58.d.ts} +2 -2
- package/dist/{index-DagQ74ZQ.d.ts → index-DARXHuQl.d.mts} +1 -1
- package/dist/{index-DagQ74ZQ.d.mts → index-DARXHuQl.d.ts} +1 -1
- package/dist/{index-CS4qStqy.d.mts → index-jNJd8jEb.d.mts} +2 -2
- package/dist/index.d.mts +21 -428
- package/dist/index.d.ts +21 -428
- package/dist/index.js +510 -28
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +509 -29
- package/dist/index.mjs.map +1 -1
- package/dist/providers/algorand/index.d.mts +1 -1
- package/dist/providers/algorand/index.d.ts +1 -1
- package/dist/providers/evm/index.d.mts +1 -1
- package/dist/providers/evm/index.d.ts +1 -1
- package/dist/providers/hedera/index.d.mts +1 -1
- package/dist/providers/hedera/index.d.ts +1 -1
- package/dist/providers/near/index.d.mts +1 -1
- package/dist/providers/near/index.d.ts +1 -1
- package/dist/providers/solana/index.d.mts +1 -1
- package/dist/providers/solana/index.d.ts +1 -1
- package/dist/providers/stellar/index.d.mts +1 -1
- package/dist/providers/stellar/index.d.ts +1 -1
- package/dist/providers/sui/index.d.mts +1 -1
- package/dist/providers/sui/index.d.ts +1 -1
- package/dist/providers/xrpl/index.d.mts +1 -1
- package/dist/providers/xrpl/index.d.ts +1 -1
- package/dist/react/index.d.mts +4 -4
- package/dist/react/index.d.ts +4 -4
- package/dist/react/picker/index.d.mts +1 -1
- package/dist/react/picker/index.d.ts +1 -1
- package/dist/{receipts-8XsMeo1y.d.ts → receipts-CMuWVtO0.d.ts} +1 -1
- package/dist/{receipts-COPJ_BY9.d.mts → receipts-DsOSK0yB.d.mts} +1 -1
- package/dist/utils/index.d.mts +2 -2
- package/dist/utils/index.d.ts +2 -2
- package/dist/{validity-BpwXbfdj.d.ts → validity-BshzF_Ga.d.ts} +1 -1
- package/dist/{validity-eGGXxzpi.d.mts → validity-y_M1oNCt.d.mts} +1 -1
- package/package.json +1 -1
- package/src/backend/durable.ts +582 -0
- package/src/backend/index.ts +149 -5
- package/src/escrow-preauth.ts +38 -33
- package/src/index.ts +7 -0
package/dist/backend/index.js
CHANGED
|
@@ -4,6 +4,11 @@ var ed25519 = require('@noble/curves/ed25519');
|
|
|
4
4
|
var sha256 = require('@noble/hashes/sha256');
|
|
5
5
|
var utils = require('@noble/hashes/utils');
|
|
6
6
|
var ethers = require('ethers');
|
|
7
|
+
var aes = require('@noble/ciphers/aes');
|
|
8
|
+
var secp256k1 = require('@noble/curves/secp256k1');
|
|
9
|
+
var hkdf = require('@noble/hashes/hkdf');
|
|
10
|
+
var sha3 = require('@noble/hashes/sha3');
|
|
11
|
+
var sha2 = require('@noble/hashes/sha2');
|
|
7
12
|
|
|
8
13
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
9
14
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
@@ -1594,6 +1599,9 @@ var CHAIN_ALIASES = {
|
|
|
1594
1599
|
// Renamed in 2.85.0: the facilitator advertises the mainnet as `xrpl`.
|
|
1595
1600
|
"xrpl-mainnet": "xrpl"
|
|
1596
1601
|
};
|
|
1602
|
+
function getChainById(chainId) {
|
|
1603
|
+
return Object.values(SUPPORTED_CHAINS).find((chain) => chain.chainId === chainId);
|
|
1604
|
+
}
|
|
1597
1605
|
function getChainByName(name) {
|
|
1598
1606
|
const key = name.toLowerCase();
|
|
1599
1607
|
return SUPPORTED_CHAINS[key] ?? SUPPORTED_CHAINS[CHAIN_ALIASES[key] ?? ""];
|
|
@@ -1936,15 +1944,6 @@ var DEFAULT_MIN_FEE_BPS = 0;
|
|
|
1936
1944
|
var DEFAULT_MAX_FEE_BPS = OPERATOR_FEE_BPS + MAX_PROTOCOL_FEE_BPS;
|
|
1937
1945
|
var REVIEW_WINDOW_SEC = 7 * 24 * 3600;
|
|
1938
1946
|
var REFUND_WINDOW_SEC = 7 * 24 * 3600;
|
|
1939
|
-
Object.freeze(
|
|
1940
|
-
Object.fromEntries(
|
|
1941
|
-
["arc", "arc-testnet"].map((network) => {
|
|
1942
|
-
const chain = getChainByName(network);
|
|
1943
|
-
if (!chain) throw new Error(`The chain registry has no '${network}'.`);
|
|
1944
|
-
return [chain.chainId, Object.freeze({ name: chain.usdc.name, version: chain.usdc.version })];
|
|
1945
|
-
})
|
|
1946
|
-
)
|
|
1947
|
-
);
|
|
1948
1947
|
var LIFECYCLE_DOMAIN_NAME = "x402 escrow lifecycle";
|
|
1949
1948
|
var LIFECYCLE_DOMAIN_VERSION = "1";
|
|
1950
1949
|
var LIFECYCLE_MAX_DEADLINE_SECS = 900;
|
|
@@ -2045,7 +2044,7 @@ function toUintString(value, field) {
|
|
|
2045
2044
|
}
|
|
2046
2045
|
function buildLifecycleTypedData(params) {
|
|
2047
2046
|
const { action, paymentInfo, payer, amount, chainId } = params;
|
|
2048
|
-
const { deadline, nonce } = resolveOrderTiming(params);
|
|
2047
|
+
const { deadline: deadline2, nonce } = resolveOrderTiming(params);
|
|
2049
2048
|
if (!LIFECYCLE_ACTIONS.includes(action)) {
|
|
2050
2049
|
throw new X402Error(
|
|
2051
2050
|
`unknown lifecycle action ${String(action)}; the facilitator only signs ${LIFECYCLE_ACTIONS.join(", ")}`,
|
|
@@ -2072,7 +2071,7 @@ function buildLifecycleTypedData(params) {
|
|
|
2072
2071
|
message: {
|
|
2073
2072
|
action,
|
|
2074
2073
|
amount: amountBig.toString(),
|
|
2075
|
-
deadline: toUintString(
|
|
2074
|
+
deadline: toUintString(deadline2, "deadline"),
|
|
2076
2075
|
nonce: nonceToBytes32(nonce),
|
|
2077
2076
|
paymentInfo: {
|
|
2078
2077
|
operator: ethers.ethers.getAddress(paymentInfo.operator),
|
|
@@ -2286,8 +2285,668 @@ async function facilitatorFetch(url, init, options) {
|
|
|
2286
2285
|
await sleep((error.retryAfterSeconds ?? DEFAULT_RETRY_AFTER_SECONDS) * 1e3);
|
|
2287
2286
|
}
|
|
2288
2287
|
}
|
|
2288
|
+
var EVIDENCE_HEADER = "X-Durable-Evidence";
|
|
2289
|
+
var MAGIC = new Uint8Array([68, 88, 52, 48, 50]);
|
|
2290
|
+
var FORMAT_V1 = 1;
|
|
2291
|
+
var FORMAT_V2 = 2;
|
|
2292
|
+
var ROLE_NAMES = ["payer", "seller", "auditor"];
|
|
2293
|
+
var NONCE_LEN = 12;
|
|
2294
|
+
var CEK_LEN = 32;
|
|
2295
|
+
var HKDF_INFO = new TextEncoder().encode("DX402-v1-wrap");
|
|
2296
|
+
var DX402Error = class extends Error {
|
|
2297
|
+
constructor(message) {
|
|
2298
|
+
super(message);
|
|
2299
|
+
this.name = "DX402Error";
|
|
2300
|
+
}
|
|
2301
|
+
};
|
|
2302
|
+
function hexToBytes(hex) {
|
|
2303
|
+
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
2304
|
+
try {
|
|
2305
|
+
return utils.hexToBytes(clean);
|
|
2306
|
+
} catch (e) {
|
|
2307
|
+
throw new DX402Error(`invalid hex: ${e.message}`);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
function contentHash(body) {
|
|
2311
|
+
return "0x" + utils.bytesToHex(sha3.keccak_256(body));
|
|
2312
|
+
}
|
|
2313
|
+
function paymentId(caip2Network, txHash) {
|
|
2314
|
+
const enc = new TextEncoder();
|
|
2315
|
+
const clean = txHash.startsWith("0x") ? txHash.slice(2) : txHash;
|
|
2316
|
+
const preimage = new Uint8Array([...enc.encode(caip2Network), ...enc.encode(clean)]);
|
|
2317
|
+
return "0x" + utils.bytesToHex(sha3.keccak_256(preimage));
|
|
2318
|
+
}
|
|
2319
|
+
var ANCHOR_DOMAIN_NAME = "DX402 Anchor";
|
|
2320
|
+
var ANCHOR_DOMAIN_VERSION = "1";
|
|
2321
|
+
var ANCHOR_TYPE = "Dx402AnchorAuthorization(bytes32 paymentId,bytes32 contentHash,string pointer,address payee)";
|
|
2322
|
+
var EIP712_DOMAIN_TYPE = "EIP712Domain(string name,string version,uint256 chainId)";
|
|
2323
|
+
var ZERO_ADDRESS = "0x" + "00".repeat(20);
|
|
2324
|
+
function concatBytes(...parts) {
|
|
2325
|
+
const total = parts.reduce((n, p) => n + p.length, 0);
|
|
2326
|
+
const out = new Uint8Array(total);
|
|
2327
|
+
let pos = 0;
|
|
2328
|
+
for (const p of parts) {
|
|
2329
|
+
out.set(p, pos);
|
|
2330
|
+
pos += p.length;
|
|
2331
|
+
}
|
|
2332
|
+
return out;
|
|
2333
|
+
}
|
|
2334
|
+
function uint256(n) {
|
|
2335
|
+
const out = new Uint8Array(32);
|
|
2336
|
+
let v = BigInt(n);
|
|
2337
|
+
for (let i = 31; i >= 0 && v > 0n; i--) {
|
|
2338
|
+
out[i] = Number(v & 0xffn);
|
|
2339
|
+
v >>= 8n;
|
|
2340
|
+
}
|
|
2341
|
+
return out;
|
|
2342
|
+
}
|
|
2343
|
+
function anchorDigest(paymentId2, contentHash2, pointer, payee, chainId) {
|
|
2344
|
+
const b32 = (value, field) => {
|
|
2345
|
+
const raw = hexToBytes(value);
|
|
2346
|
+
if (raw.length !== 32) throw new DX402Error(`${field} must be 32 bytes, got ${raw.length}`);
|
|
2347
|
+
return raw;
|
|
2348
|
+
};
|
|
2349
|
+
const addr = hexToBytes(payee);
|
|
2350
|
+
if (addr.length !== 20) {
|
|
2351
|
+
throw new DX402Error(`payee must be a 20-byte address, got ${addr.length}`);
|
|
2352
|
+
}
|
|
2353
|
+
const enc = new TextEncoder();
|
|
2354
|
+
const domainSeparator = sha3.keccak_256(
|
|
2355
|
+
concatBytes(
|
|
2356
|
+
sha3.keccak_256(enc.encode(EIP712_DOMAIN_TYPE)),
|
|
2357
|
+
sha3.keccak_256(enc.encode(ANCHOR_DOMAIN_NAME)),
|
|
2358
|
+
sha3.keccak_256(enc.encode(ANCHOR_DOMAIN_VERSION)),
|
|
2359
|
+
uint256(chainId)
|
|
2360
|
+
)
|
|
2361
|
+
);
|
|
2362
|
+
const structHash = sha3.keccak_256(
|
|
2363
|
+
concatBytes(
|
|
2364
|
+
sha3.keccak_256(enc.encode(ANCHOR_TYPE)),
|
|
2365
|
+
b32(paymentId2, "paymentId"),
|
|
2366
|
+
b32(contentHash2, "contentHash"),
|
|
2367
|
+
sha3.keccak_256(enc.encode(pointer)),
|
|
2368
|
+
new Uint8Array(12),
|
|
2369
|
+
addr
|
|
2370
|
+
)
|
|
2371
|
+
);
|
|
2372
|
+
return sha3.keccak_256(concatBytes(new Uint8Array([25, 1]), domainSeparator, structHash));
|
|
2373
|
+
}
|
|
2374
|
+
function sealEvidenceTo(body, recipients, paymentIdValue) {
|
|
2375
|
+
if (recipients.length === 0) {
|
|
2376
|
+
throw new DX402Error("an envelope with no recipients could never be opened");
|
|
2377
|
+
}
|
|
2378
|
+
const aad = new TextEncoder().encode(paymentIdValue);
|
|
2379
|
+
const cek = utils.randomBytes(CEK_LEN);
|
|
2380
|
+
const bodyNonce = utils.randomBytes(NONCE_LEN);
|
|
2381
|
+
const ciphertext = aes.gcm(cek, bodyNonce, aad).encrypt(body);
|
|
2382
|
+
const wrapped = recipients.map(({ role, key }) => {
|
|
2383
|
+
let algByte;
|
|
2384
|
+
let ephemeral;
|
|
2385
|
+
let shared;
|
|
2386
|
+
if (key.length === 33) {
|
|
2387
|
+
algByte = 1;
|
|
2388
|
+
const priv = secp256k1.secp256k1.utils.randomPrivateKey();
|
|
2389
|
+
ephemeral = secp256k1.secp256k1.getPublicKey(priv, true);
|
|
2390
|
+
shared = secp256k1.secp256k1.getSharedSecret(priv, key, true).subarray(1);
|
|
2391
|
+
} else if (key.length === 32) {
|
|
2392
|
+
algByte = 2;
|
|
2393
|
+
const priv = utils.randomBytes(32);
|
|
2394
|
+
ephemeral = ed25519.x25519.getPublicKey(priv);
|
|
2395
|
+
shared = ed25519.x25519.getSharedSecret(priv, key);
|
|
2396
|
+
if (shared.every((b) => b === 0)) {
|
|
2397
|
+
throw new DX402Error("degenerate ECDH result (small-order public key)");
|
|
2398
|
+
}
|
|
2399
|
+
} else {
|
|
2400
|
+
throw new DX402Error(
|
|
2401
|
+
`public key must be 33 bytes (secp256k1) or 32 (X25519), got ${key.length}`
|
|
2402
|
+
);
|
|
2403
|
+
}
|
|
2404
|
+
const wrapKey = hkdf.hkdf(sha2.sha256, shared, aad, HKDF_INFO, 32);
|
|
2405
|
+
const cekNonce = utils.randomBytes(NONCE_LEN);
|
|
2406
|
+
return {
|
|
2407
|
+
role,
|
|
2408
|
+
algByte,
|
|
2409
|
+
ephemeral,
|
|
2410
|
+
cekNonce,
|
|
2411
|
+
wrappedCek: aes.gcm(wrapKey, cekNonce, aad).encrypt(cek)
|
|
2412
|
+
};
|
|
2413
|
+
});
|
|
2414
|
+
const singlePayer = wrapped.length === 1 && wrapped[0].role === "payer";
|
|
2415
|
+
const parts = [MAGIC, new Uint8Array([singlePayer ? FORMAT_V1 : FORMAT_V2])];
|
|
2416
|
+
if (!singlePayer) parts.push(new Uint8Array([wrapped.length]));
|
|
2417
|
+
for (const r of wrapped) {
|
|
2418
|
+
if (!singlePayer) parts.push(new Uint8Array([ROLE_NAMES.indexOf(r.role)]));
|
|
2419
|
+
parts.push(new Uint8Array([r.algByte, r.ephemeral.length]));
|
|
2420
|
+
parts.push(r.ephemeral, r.cekNonce);
|
|
2421
|
+
parts.push(new Uint8Array([r.wrappedCek.length >> 8 & 255, r.wrappedCek.length & 255]));
|
|
2422
|
+
parts.push(r.wrappedCek);
|
|
2423
|
+
}
|
|
2424
|
+
parts.push(bodyNonce, ciphertext);
|
|
2425
|
+
return concatBytes(...parts);
|
|
2426
|
+
}
|
|
2427
|
+
function isEvmAddress(value) {
|
|
2428
|
+
return /^0x[0-9a-fA-F]{40}$/.test((value ?? "").trim());
|
|
2429
|
+
}
|
|
2430
|
+
function chainIdFor(network) {
|
|
2431
|
+
const raw = network ?? "";
|
|
2432
|
+
if (raw.toLowerCase().startsWith("eip155:")) {
|
|
2433
|
+
const tail = raw.slice("eip155:".length);
|
|
2434
|
+
if (/^\d+$/.test(tail)) return Number(tail);
|
|
2435
|
+
}
|
|
2436
|
+
const name = raw.includes(":") ? raw.split(":")[1] ?? "" : raw;
|
|
2437
|
+
const byId = Number(name);
|
|
2438
|
+
if (Number.isFinite(byId) && byId > 0) return getChainById(byId)?.chainId;
|
|
2439
|
+
return getChainByName(name)?.chainId;
|
|
2440
|
+
}
|
|
2441
|
+
function sellerDigestFor(paymentId2, contentHash2, payee, network, pointer = "") {
|
|
2442
|
+
if (isEvmAddress(payee)) {
|
|
2443
|
+
const chainId = chainIdFor(network);
|
|
2444
|
+
if (!chainId) return void 0;
|
|
2445
|
+
return anchorDigest(paymentId2, contentHash2, pointer, payee, chainId);
|
|
2446
|
+
}
|
|
2447
|
+
return anchorDigest(paymentId2, contentHash2, pointer, ZERO_ADDRESS, 0);
|
|
2448
|
+
}
|
|
2449
|
+
var ANCHOR_MAX_REQUEST_BYTES = 64 * 1024;
|
|
2450
|
+
function toBase64(bytes) {
|
|
2451
|
+
let out = "";
|
|
2452
|
+
const CHUNK = 32768;
|
|
2453
|
+
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
2454
|
+
out += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
|
|
2455
|
+
}
|
|
2456
|
+
return btoa(out);
|
|
2457
|
+
}
|
|
2458
|
+
function backendForPointer(pointer) {
|
|
2459
|
+
if (pointer.startsWith("ipfs://")) return "ipfs";
|
|
2460
|
+
if (pointer.startsWith("ar://")) return "arweave";
|
|
2461
|
+
return "s3";
|
|
2462
|
+
}
|
|
2463
|
+
async function anchorEvidence(body, opts) {
|
|
2464
|
+
try {
|
|
2465
|
+
const recipients = [
|
|
2466
|
+
{ role: "payer", key: opts.payerKey }
|
|
2467
|
+
];
|
|
2468
|
+
if (opts.sellerEncryptionKey) {
|
|
2469
|
+
recipients.push({ role: "seller", key: opts.sellerEncryptionKey });
|
|
2470
|
+
}
|
|
2471
|
+
const blob = sealEvidenceTo(body, recipients, opts.paymentId);
|
|
2472
|
+
const hash = contentHash(body);
|
|
2473
|
+
let pointer;
|
|
2474
|
+
if (opts.upload) {
|
|
2475
|
+
try {
|
|
2476
|
+
pointer = await opts.upload(blob);
|
|
2477
|
+
} catch {
|
|
2478
|
+
return { v: 1, skipped: "anchor_failed", stage: "upload" };
|
|
2479
|
+
}
|
|
2480
|
+
if (typeof pointer !== "string" || pointer.trim() === "") {
|
|
2481
|
+
return { v: 1, skipped: "anchor_failed", stage: "upload" };
|
|
2482
|
+
}
|
|
2483
|
+
pointer = pointer.trim();
|
|
2484
|
+
}
|
|
2485
|
+
const payload = {
|
|
2486
|
+
paymentId: opts.paymentId,
|
|
2487
|
+
network: opts.network,
|
|
2488
|
+
txHash: opts.txHash,
|
|
2489
|
+
payer: opts.payer,
|
|
2490
|
+
payee: opts.payee,
|
|
2491
|
+
// Exactly one of these. The facilitator dispatches on their presence:
|
|
2492
|
+
// `sealed` -> it hosts and derives the pointer; `pointer` alone -> it uses
|
|
2493
|
+
// yours; neither -> error (x402-rs `dx402/service.rs`).
|
|
2494
|
+
...pointer !== void 0 ? { pointer } : { sealed: toBase64(blob) },
|
|
2495
|
+
// Declared, not measured. Since x402-rs 2.3.0 the facilitator records
|
|
2496
|
+
// the store that ACTUALLY took the bytes and returns that, so a response
|
|
2497
|
+
// may name a different backend than the request did -- `ipfs` where you
|
|
2498
|
+
// said `s3`, because a deployment whose primary is Pinata wrote there.
|
|
2499
|
+
// That is the facilitator correcting the record, not a mismatch to
|
|
2500
|
+
// assert on.
|
|
2501
|
+
backend: opts.backend ?? (pointer !== void 0 ? backendForPointer(pointer) : "s3"),
|
|
2502
|
+
contentHash: hash,
|
|
2503
|
+
keyAlg: opts.payerKey.length === 32 ? "ECIES-X25519" : "ECIES-secp256k1",
|
|
2504
|
+
mode: "direct",
|
|
2505
|
+
retention: opts.retention ?? "90d"
|
|
2506
|
+
};
|
|
2507
|
+
let unsigned;
|
|
2508
|
+
if (opts.proofOfPayment) {
|
|
2509
|
+
payload.proofOfPayment = opts.proofOfPayment;
|
|
2510
|
+
}
|
|
2511
|
+
if (opts.storage) {
|
|
2512
|
+
payload.storage = opts.storage;
|
|
2513
|
+
}
|
|
2514
|
+
if (opts.sign) {
|
|
2515
|
+
const digest2 = sellerDigestFor(opts.paymentId, hash, opts.payee, opts.network, pointer ?? "");
|
|
2516
|
+
if (digest2 === void 0) {
|
|
2517
|
+
unsigned = "unknown_chain_id";
|
|
2518
|
+
} else {
|
|
2519
|
+
payload.sellerSignature = await opts.sign(digest2);
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
const wire = JSON.stringify(payload);
|
|
2523
|
+
if (pointer === void 0 && new TextEncoder().encode(wire).length > ANCHOR_MAX_REQUEST_BYTES) {
|
|
2524
|
+
return { v: 1, skipped: "too_large" };
|
|
2525
|
+
}
|
|
2526
|
+
const base = (opts.facilitator ?? "https://facilitator.ultravioletadao.xyz").replace(
|
|
2527
|
+
/\/+$/,
|
|
2528
|
+
""
|
|
2529
|
+
);
|
|
2530
|
+
const owner = {};
|
|
2531
|
+
bindStackKey(owner, opts);
|
|
2532
|
+
const res = await stackKeyFetch(owner, `${base}/dx402/anchor`, {
|
|
2533
|
+
method: "POST",
|
|
2534
|
+
headers: { "content-type": "application/json" },
|
|
2535
|
+
body: wire
|
|
2536
|
+
}, opts.fetch);
|
|
2537
|
+
if (!res.ok) {
|
|
2538
|
+
let error;
|
|
2539
|
+
try {
|
|
2540
|
+
error = (await res.json()).error;
|
|
2541
|
+
} catch {
|
|
2542
|
+
}
|
|
2543
|
+
return { v: 1, skipped: "anchor_failed", status: res.status, error };
|
|
2544
|
+
}
|
|
2545
|
+
const out = await res.json();
|
|
2546
|
+
if (unsigned) out.unsigned = unsigned;
|
|
2547
|
+
return out;
|
|
2548
|
+
} catch (error) {
|
|
2549
|
+
if (error instanceof StackKeyRedirectError) {
|
|
2550
|
+
return { v: 1, skipped: "anchor_failed", status: error.status, error: error.message };
|
|
2551
|
+
}
|
|
2552
|
+
return { v: 1, skipped: "anchor_failed" };
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
function evidenceHeader(evidence) {
|
|
2556
|
+
const bytes = new TextEncoder().encode(JSON.stringify(evidence));
|
|
2557
|
+
return toBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
// src/backend/durable.ts
|
|
2561
|
+
var DURABLE_DEFAULT_TIMEOUT_MS = 5e3;
|
|
2562
|
+
var MAX_TIMEOUT_MS = 2 ** 31 - 1;
|
|
2563
|
+
var skip = (reason) => ({ v: 1, skipped: reason });
|
|
2564
|
+
function createDurableAnchorer(durable, paywall) {
|
|
2565
|
+
if (durable === void 0) return void 0;
|
|
2566
|
+
if (durable === null || typeof durable !== "object" || typeof durable.payerKey !== "function") {
|
|
2567
|
+
throw new TypeError("durable.payerKey must be a function");
|
|
2568
|
+
}
|
|
2569
|
+
const maxBodyBytes = durable.maxBodyBytes ?? ANCHOR_MAX_REQUEST_BYTES;
|
|
2570
|
+
if (!Number.isSafeInteger(maxBodyBytes) || maxBodyBytes <= 0) {
|
|
2571
|
+
throw new TypeError("durable.maxBodyBytes must be a positive integer");
|
|
2572
|
+
}
|
|
2573
|
+
const timeoutMs = durable.timeoutMs ?? DURABLE_DEFAULT_TIMEOUT_MS;
|
|
2574
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > MAX_TIMEOUT_MS) {
|
|
2575
|
+
throw new TypeError(`durable.timeoutMs must be an integer from 1 to ${MAX_TIMEOUT_MS}`);
|
|
2576
|
+
}
|
|
2577
|
+
const facilitator = durable.facilitator ?? paywall.facilitator;
|
|
2578
|
+
return {
|
|
2579
|
+
maxBodyBytes,
|
|
2580
|
+
timeoutMs,
|
|
2581
|
+
anchor: async (body, payment, settle, signal) => {
|
|
2582
|
+
try {
|
|
2583
|
+
const txHash = settle.transactionHash;
|
|
2584
|
+
const network = chainToCAIP2(settle.network || payment.requirements.network);
|
|
2585
|
+
let payerKey;
|
|
2586
|
+
try {
|
|
2587
|
+
payerKey = await durable.payerKey({ payment, settle });
|
|
2588
|
+
} catch {
|
|
2589
|
+
return skip("no_payer_key");
|
|
2590
|
+
}
|
|
2591
|
+
if (!(payerKey instanceof Uint8Array) || payerKey.length === 0) return skip("no_payer_key");
|
|
2592
|
+
if (signal.aborted) return skip("timeout");
|
|
2593
|
+
const baseFetch = durable.fetch ?? fetch;
|
|
2594
|
+
const boundFetch = (input, init) => {
|
|
2595
|
+
if (signal.aborted) return Promise.reject(new Error("durable evidence deadline passed"));
|
|
2596
|
+
return baseFetch(input, { ...init, signal });
|
|
2597
|
+
};
|
|
2598
|
+
return await anchorEvidence(body, {
|
|
2599
|
+
paymentId: paymentId(network, txHash),
|
|
2600
|
+
network,
|
|
2601
|
+
txHash,
|
|
2602
|
+
payer: settle.payer || payment.verifyResult.payer || "",
|
|
2603
|
+
payee: payment.requirements.payTo,
|
|
2604
|
+
payerKey,
|
|
2605
|
+
sellerEncryptionKey: durable.sellerEncryptionKey,
|
|
2606
|
+
proofOfPayment: settle.proofOfPayment,
|
|
2607
|
+
sign: durable.sign,
|
|
2608
|
+
upload: durable.upload,
|
|
2609
|
+
backend: durable.backend,
|
|
2610
|
+
retention: durable.retention,
|
|
2611
|
+
storage: durable.storage,
|
|
2612
|
+
facilitator,
|
|
2613
|
+
stackKey: paywall.stackKey,
|
|
2614
|
+
stackKeyHosts: paywall.stackKeyHosts,
|
|
2615
|
+
fetch: boundFetch
|
|
2616
|
+
});
|
|
2617
|
+
} catch {
|
|
2618
|
+
return skip("anchor_failed");
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
};
|
|
2622
|
+
}
|
|
2623
|
+
var UNCONFIRMED_RECEIPT = /* @__PURE__ */ new Set(["pending", "unknown", "rejected"]);
|
|
2624
|
+
function settledForEvidence(settle) {
|
|
2625
|
+
return settle?.success === true && settle.replayed !== true && typeof settle.transactionHash === "string" && settle.transactionHash !== "" && !UNCONFIRMED_RECEIPT.has(String(settle.receipt?.status ?? ""));
|
|
2626
|
+
}
|
|
2627
|
+
function deadline(ms) {
|
|
2628
|
+
const controller = new AbortController();
|
|
2629
|
+
let release = () => {
|
|
2630
|
+
};
|
|
2631
|
+
const expired = new Promise((resolve) => {
|
|
2632
|
+
const timer = setTimeout(() => {
|
|
2633
|
+
controller.abort();
|
|
2634
|
+
resolve();
|
|
2635
|
+
}, ms);
|
|
2636
|
+
release = () => clearTimeout(timer);
|
|
2637
|
+
});
|
|
2638
|
+
return { signal: controller.signal, expired, clear: release };
|
|
2639
|
+
}
|
|
2640
|
+
async function anchorWithin(anchorer, body, payment, settle, clock) {
|
|
2641
|
+
if (clock.signal.aborted) return skip("timeout");
|
|
2642
|
+
return Promise.race([
|
|
2643
|
+
anchorer.anchor(body, payment, settle, clock.signal),
|
|
2644
|
+
clock.expired.then(() => skip("timeout"))
|
|
2645
|
+
]);
|
|
2646
|
+
}
|
|
2647
|
+
function concat(chunks, size) {
|
|
2648
|
+
const out = new Uint8Array(size);
|
|
2649
|
+
let offset = 0;
|
|
2650
|
+
for (const chunk of chunks) {
|
|
2651
|
+
out.set(chunk, offset);
|
|
2652
|
+
offset += chunk.length;
|
|
2653
|
+
}
|
|
2654
|
+
return out;
|
|
2655
|
+
}
|
|
2656
|
+
function isEventStream(contentType) {
|
|
2657
|
+
return (contentType ?? "").split(";")[0].trim().toLowerCase() === "text/event-stream";
|
|
2658
|
+
}
|
|
2659
|
+
function wantsEvidence(run, status, hasHeader) {
|
|
2660
|
+
if (!settledForEvidence(run.settled)) return false;
|
|
2661
|
+
if (status < 200 || status > 299) return false;
|
|
2662
|
+
if ((run.method ?? "").toUpperCase() === "HEAD") return false;
|
|
2663
|
+
return !hasHeader;
|
|
2664
|
+
}
|
|
2665
|
+
function rebuilt(response, body, evidence) {
|
|
2666
|
+
const headers = new Headers(response.headers);
|
|
2667
|
+
headers.set(EVIDENCE_HEADER, evidenceHeader(evidence));
|
|
2668
|
+
return new Response(body, { status: response.status, statusText: response.statusText, headers });
|
|
2669
|
+
}
|
|
2670
|
+
function resumed(chunks, reader, pending) {
|
|
2671
|
+
let next = pending;
|
|
2672
|
+
return new ReadableStream({
|
|
2673
|
+
async pull(controller) {
|
|
2674
|
+
const head = chunks.shift();
|
|
2675
|
+
if (head) {
|
|
2676
|
+
controller.enqueue(head);
|
|
2677
|
+
return;
|
|
2678
|
+
}
|
|
2679
|
+
const read = next ?? reader.read();
|
|
2680
|
+
next = void 0;
|
|
2681
|
+
try {
|
|
2682
|
+
const { done, value } = await read;
|
|
2683
|
+
if (done) controller.close();
|
|
2684
|
+
else controller.enqueue(value);
|
|
2685
|
+
} catch (error) {
|
|
2686
|
+
controller.error(error);
|
|
2687
|
+
}
|
|
2688
|
+
},
|
|
2689
|
+
cancel(reason) {
|
|
2690
|
+
return reader.cancel(reason);
|
|
2691
|
+
}
|
|
2692
|
+
});
|
|
2693
|
+
}
|
|
2694
|
+
async function withDurableEvidence(response, run) {
|
|
2695
|
+
if (!wantsEvidence(run, response.status, response.headers.has(EVIDENCE_HEADER))) return response;
|
|
2696
|
+
const { anchorer } = run;
|
|
2697
|
+
const body = response.body;
|
|
2698
|
+
if (body === null || response.bodyUsed || body.locked || isEventStream(response.headers.get("content-type"))) {
|
|
2699
|
+
return response.bodyUsed || body?.locked ? response : rebuilt(response, body, skip("not_capturable"));
|
|
2700
|
+
}
|
|
2701
|
+
const declared = Number(response.headers.get("content-length"));
|
|
2702
|
+
if (Number.isFinite(declared) && declared > anchorer.maxBodyBytes) {
|
|
2703
|
+
return rebuilt(response, body, skip("too_large"));
|
|
2704
|
+
}
|
|
2705
|
+
const clock = deadline(anchorer.timeoutMs);
|
|
2706
|
+
const reader = body.getReader();
|
|
2707
|
+
const chunks = [];
|
|
2708
|
+
let size = 0;
|
|
2709
|
+
try {
|
|
2710
|
+
for (; ; ) {
|
|
2711
|
+
const read = reader.read();
|
|
2712
|
+
const step = await Promise.race([read, clock.expired.then(() => "timeout")]);
|
|
2713
|
+
if (step === "timeout") {
|
|
2714
|
+
return rebuilt(response, resumed(chunks, reader, read), skip("timeout"));
|
|
2715
|
+
}
|
|
2716
|
+
if (step.done) break;
|
|
2717
|
+
const chunk = step.value instanceof Uint8Array ? step.value : new Uint8Array(step.value);
|
|
2718
|
+
chunks.push(chunk);
|
|
2719
|
+
size += chunk.length;
|
|
2720
|
+
if (size > anchorer.maxBodyBytes) {
|
|
2721
|
+
clock.clear();
|
|
2722
|
+
return rebuilt(response, resumed(chunks, reader, void 0), skip("too_large"));
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
} catch (error) {
|
|
2726
|
+
const failed = new ReadableStream({
|
|
2727
|
+
pull(controller) {
|
|
2728
|
+
const head = chunks.shift();
|
|
2729
|
+
if (head) controller.enqueue(head);
|
|
2730
|
+
else controller.error(error);
|
|
2731
|
+
}
|
|
2732
|
+
});
|
|
2733
|
+
clock.clear();
|
|
2734
|
+
return rebuilt(response, failed, skip("not_capturable"));
|
|
2735
|
+
}
|
|
2736
|
+
const bytes = concat(chunks, size);
|
|
2737
|
+
try {
|
|
2738
|
+
const evidence = await anchorWithin(anchorer, bytes, run.payment, run.settled, clock);
|
|
2739
|
+
return rebuilt(response, bytes, evidence);
|
|
2740
|
+
} finally {
|
|
2741
|
+
clock.clear();
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
function isDurableNodeResponse(res) {
|
|
2745
|
+
const r = res;
|
|
2746
|
+
return !!r && typeof r.write === "function" && typeof r.end === "function" && typeof r.setHeader === "function" && typeof r.getHeader === "function";
|
|
2747
|
+
}
|
|
2748
|
+
var headersSentError = () => Object.assign(new Error("Cannot set headers after they are sent to the client"), { code: "ERR_HTTP_HEADERS_SENT" });
|
|
2749
|
+
function sealEnded(res) {
|
|
2750
|
+
const target = res;
|
|
2751
|
+
const refuse = { value: () => {
|
|
2752
|
+
throw headersSentError();
|
|
2753
|
+
} };
|
|
2754
|
+
const overrides = {
|
|
2755
|
+
headersSent: { get: () => true },
|
|
2756
|
+
writableEnded: { get: () => true },
|
|
2757
|
+
setHeader: refuse,
|
|
2758
|
+
removeHeader: refuse,
|
|
2759
|
+
writeHead: refuse
|
|
2760
|
+
};
|
|
2761
|
+
const saved = [];
|
|
2762
|
+
for (const [key, descriptor] of Object.entries(overrides)) {
|
|
2763
|
+
const own = Object.getOwnPropertyDescriptor(target, key);
|
|
2764
|
+
try {
|
|
2765
|
+
Object.defineProperty(target, key, { ...descriptor, configurable: true });
|
|
2766
|
+
saved.push([key, own]);
|
|
2767
|
+
} catch {
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
let close;
|
|
2771
|
+
const socket = res.socket;
|
|
2772
|
+
const writable = () => !socket || socket.destroyed !== true && socket.writable !== false;
|
|
2773
|
+
const destroySocket = socket && typeof socket.destroy === "function" ? socket.destroy : void 0;
|
|
2774
|
+
const closers = [];
|
|
2775
|
+
for (const holder of [target, socket]) {
|
|
2776
|
+
if (!holder || typeof holder.destroy !== "function") continue;
|
|
2777
|
+
const original = holder.destroy;
|
|
2778
|
+
const own = Object.getOwnPropertyDescriptor(holder, "destroy");
|
|
2779
|
+
try {
|
|
2780
|
+
Object.defineProperty(holder, "destroy", {
|
|
2781
|
+
configurable: true,
|
|
2782
|
+
writable: true,
|
|
2783
|
+
value: (...args) => {
|
|
2784
|
+
if (args[0] !== void 0 && args[0] !== null || !writable()) return original.apply(holder, args);
|
|
2785
|
+
if (!close) {
|
|
2786
|
+
let ran = false;
|
|
2787
|
+
close = () => {
|
|
2788
|
+
if (ran) return;
|
|
2789
|
+
ran = true;
|
|
2790
|
+
if (destroySocket) destroySocket.call(socket);
|
|
2791
|
+
else original.call(holder);
|
|
2792
|
+
};
|
|
2793
|
+
}
|
|
2794
|
+
return holder;
|
|
2795
|
+
}
|
|
2796
|
+
});
|
|
2797
|
+
closers.push([holder, own]);
|
|
2798
|
+
} catch {
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
return {
|
|
2802
|
+
restore: () => {
|
|
2803
|
+
for (const [key, own] of saved) {
|
|
2804
|
+
if (own) Object.defineProperty(target, key, own);
|
|
2805
|
+
else delete target[key];
|
|
2806
|
+
}
|
|
2807
|
+
for (const [holder, own] of closers) {
|
|
2808
|
+
if (own) Object.defineProperty(holder, "destroy", own);
|
|
2809
|
+
else delete holder.destroy;
|
|
2810
|
+
}
|
|
2811
|
+
},
|
|
2812
|
+
close: () => close,
|
|
2813
|
+
writable
|
|
2814
|
+
};
|
|
2815
|
+
}
|
|
2816
|
+
function toBytes(chunk, encoding) {
|
|
2817
|
+
if (chunk === void 0 || chunk === null) return new Uint8Array();
|
|
2818
|
+
if (typeof chunk === "string") {
|
|
2819
|
+
return Buffer.from(chunk, typeof encoding === "string" ? encoding : "utf8");
|
|
2820
|
+
}
|
|
2821
|
+
if (chunk instanceof Uint8Array) return chunk;
|
|
2822
|
+
return void 0;
|
|
2823
|
+
}
|
|
2824
|
+
function captureNodeResponse(res, anchorer, payment, settled, method) {
|
|
2825
|
+
const write = res.write.bind(res);
|
|
2826
|
+
const end = res.end.bind(res);
|
|
2827
|
+
let state = "capturing";
|
|
2828
|
+
const lateEnds = [];
|
|
2829
|
+
const chunks = [];
|
|
2830
|
+
let size = 0;
|
|
2831
|
+
let clock;
|
|
2832
|
+
const run = () => ({ anchorer, payment, settled: settled(), method });
|
|
2833
|
+
const eligible = () => !res.headersSent && wantsEvidence(run(), res.statusCode ?? 200, res.getHeader(EVIDENCE_HEADER) !== void 0);
|
|
2834
|
+
const note = (reason) => {
|
|
2835
|
+
try {
|
|
2836
|
+
if (eligible()) res.setHeader(EVIDENCE_HEADER, evidenceHeader(skip(reason)));
|
|
2837
|
+
} catch {
|
|
2838
|
+
}
|
|
2839
|
+
};
|
|
2840
|
+
const passThrough = (reason) => {
|
|
2841
|
+
if (state !== "capturing") return;
|
|
2842
|
+
state = "passing";
|
|
2843
|
+
clock?.clear();
|
|
2844
|
+
note(reason);
|
|
2845
|
+
if (size > 0) write(concat(chunks, size));
|
|
2846
|
+
chunks.length = 0;
|
|
2847
|
+
};
|
|
2848
|
+
const callbackOf = (args) => {
|
|
2849
|
+
const last = args[args.length - 1];
|
|
2850
|
+
return typeof last === "function" ? last : void 0;
|
|
2851
|
+
};
|
|
2852
|
+
const hold = (chunk, encoding) => {
|
|
2853
|
+
if (res.headersSent) {
|
|
2854
|
+
passThrough("not_capturable");
|
|
2855
|
+
return false;
|
|
2856
|
+
}
|
|
2857
|
+
if (isEventStream(String(res.getHeader("content-type") ?? ""))) {
|
|
2858
|
+
passThrough("not_capturable");
|
|
2859
|
+
return false;
|
|
2860
|
+
}
|
|
2861
|
+
const bytes = toBytes(chunk, encoding);
|
|
2862
|
+
if (bytes === void 0) {
|
|
2863
|
+
passThrough("not_capturable");
|
|
2864
|
+
return false;
|
|
2865
|
+
}
|
|
2866
|
+
if (size + bytes.length > anchorer.maxBodyBytes) {
|
|
2867
|
+
passThrough("too_large");
|
|
2868
|
+
return false;
|
|
2869
|
+
}
|
|
2870
|
+
if (!clock) {
|
|
2871
|
+
clock = deadline(anchorer.timeoutMs);
|
|
2872
|
+
void clock.expired.then(() => passThrough("timeout"));
|
|
2873
|
+
}
|
|
2874
|
+
if (bytes.length > 0) {
|
|
2875
|
+
chunks.push(bytes);
|
|
2876
|
+
size += bytes.length;
|
|
2877
|
+
}
|
|
2878
|
+
return true;
|
|
2879
|
+
};
|
|
2880
|
+
res.write = (...args) => {
|
|
2881
|
+
if (state === "ending") {
|
|
2882
|
+
const cb = callbackOf(args.slice(1));
|
|
2883
|
+
const error = Object.assign(new Error("write after end"), { code: "ERR_STREAM_WRITE_AFTER_END" });
|
|
2884
|
+
if (cb) queueMicrotask(() => cb(error));
|
|
2885
|
+
return false;
|
|
2886
|
+
}
|
|
2887
|
+
if (state === "capturing" && hold(args[0], typeof args[1] === "function" ? void 0 : args[1])) {
|
|
2888
|
+
const cb = callbackOf(args.slice(1));
|
|
2889
|
+
if (cb) queueMicrotask(() => cb());
|
|
2890
|
+
return true;
|
|
2891
|
+
}
|
|
2892
|
+
return write(...args);
|
|
2893
|
+
};
|
|
2894
|
+
res.end = (...args) => {
|
|
2895
|
+
if (state === "ending") {
|
|
2896
|
+
const cb2 = callbackOf(args);
|
|
2897
|
+
if (cb2) lateEnds.push(cb2);
|
|
2898
|
+
return res;
|
|
2899
|
+
}
|
|
2900
|
+
if (state !== "capturing") return end(...args);
|
|
2901
|
+
const chunk = typeof args[0] === "function" ? void 0 : args[0];
|
|
2902
|
+
const encoding = typeof args[1] === "function" ? void 0 : args[1];
|
|
2903
|
+
const cb = callbackOf(args);
|
|
2904
|
+
if (!hold(chunk, encoding)) return end(...args);
|
|
2905
|
+
const bytes = concat(chunks, size);
|
|
2906
|
+
chunks.length = 0;
|
|
2907
|
+
if (!eligible()) {
|
|
2908
|
+
state = "done";
|
|
2909
|
+
clock?.clear();
|
|
2910
|
+
return cb ? end(bytes, cb) : end(bytes);
|
|
2911
|
+
}
|
|
2912
|
+
state = "ending";
|
|
2913
|
+
const statusCode = res.statusCode;
|
|
2914
|
+
const sealed = sealEnded(res);
|
|
2915
|
+
const finish = (evidence) => {
|
|
2916
|
+
clock?.clear();
|
|
2917
|
+
sealed.restore();
|
|
2918
|
+
if (statusCode !== void 0) res.statusCode = statusCode;
|
|
2919
|
+
try {
|
|
2920
|
+
if (!res.headersSent) res.setHeader(EVIDENCE_HEADER, evidenceHeader(evidence));
|
|
2921
|
+
} catch {
|
|
2922
|
+
}
|
|
2923
|
+
state = "done";
|
|
2924
|
+
const close = sealed.close();
|
|
2925
|
+
const done = close ? (error) => {
|
|
2926
|
+
if (cb) cb(error);
|
|
2927
|
+
close();
|
|
2928
|
+
} : cb;
|
|
2929
|
+
const ended = done ? end(bytes, done) : end(bytes);
|
|
2930
|
+
for (const late of lateEnds.splice(0)) end(late);
|
|
2931
|
+
if (close && !sealed.writable()) close();
|
|
2932
|
+
return ended;
|
|
2933
|
+
};
|
|
2934
|
+
const current = run();
|
|
2935
|
+
const timer = clock ?? deadline(anchorer.timeoutMs);
|
|
2936
|
+
clock = timer;
|
|
2937
|
+
anchorWithin(anchorer, bytes, payment, current.settled, timer).then(finish, () => finish(skip("anchor_failed")));
|
|
2938
|
+
return res;
|
|
2939
|
+
};
|
|
2940
|
+
}
|
|
2289
2941
|
|
|
2290
2942
|
// src/backend/index.ts
|
|
2943
|
+
function durableAnchorerFor(options) {
|
|
2944
|
+
return createDurableAnchorer(options.durable, {
|
|
2945
|
+
facilitator: options.facilitatorUrl || options.baseUrl || "https://facilitator.ultravioletadao.xyz",
|
|
2946
|
+
stackKey: options.stackKey,
|
|
2947
|
+
stackKeyHosts: options.stackKeyHosts
|
|
2948
|
+
});
|
|
2949
|
+
}
|
|
2291
2950
|
function parsePaymentHeader(headerValue) {
|
|
2292
2951
|
if (!headerValue) {
|
|
2293
2952
|
return null;
|
|
@@ -3290,6 +3949,7 @@ function createPaymentMiddleware(getRequirements, options = {}) {
|
|
|
3290
3949
|
stackKeyHosts: options.stackKeyHosts
|
|
3291
3950
|
});
|
|
3292
3951
|
const settlementStrategy = options.settlementStrategy || "before-handler";
|
|
3952
|
+
const anchorer = durableAnchorerFor(options);
|
|
3293
3953
|
return async (req, res, next) => {
|
|
3294
3954
|
const payment = extractPaymentFromHeaders(req.headers);
|
|
3295
3955
|
if (!payment) {
|
|
@@ -3332,13 +3992,17 @@ function createPaymentMiddleware(getRequirements, options = {}) {
|
|
|
3332
3992
|
});
|
|
3333
3993
|
return;
|
|
3334
3994
|
}
|
|
3995
|
+
let settled;
|
|
3335
3996
|
req.x402 = createVerifiedPaymentState(
|
|
3336
3997
|
client,
|
|
3337
3998
|
payment,
|
|
3338
3999
|
requirements,
|
|
3339
4000
|
verifyResult,
|
|
3340
4001
|
binding,
|
|
3341
|
-
(result) =>
|
|
4002
|
+
(result) => {
|
|
4003
|
+
settled = result;
|
|
4004
|
+
setExpressPaymentHeaders(res, result);
|
|
4005
|
+
}
|
|
3342
4006
|
);
|
|
3343
4007
|
if (settlementStrategy === "before-handler") {
|
|
3344
4008
|
const settleResult = await req.x402.settle();
|
|
@@ -3356,6 +4020,9 @@ function createPaymentMiddleware(getRequirements, options = {}) {
|
|
|
3356
4020
|
return;
|
|
3357
4021
|
}
|
|
3358
4022
|
}
|
|
4023
|
+
if (anchorer && isDurableNodeResponse(res) && (req.method ?? "").toUpperCase() !== "HEAD") {
|
|
4024
|
+
captureNodeResponse(res, anchorer, req.x402, () => settled, req.method);
|
|
4025
|
+
}
|
|
3359
4026
|
next();
|
|
3360
4027
|
};
|
|
3361
4028
|
}
|
|
@@ -3382,7 +4049,8 @@ function setHonoPaymentHeaders(c, result) {
|
|
|
3382
4049
|
c.header(name, value);
|
|
3383
4050
|
}
|
|
3384
4051
|
}
|
|
3385
|
-
function createAcceptsPaymentGate(
|
|
4052
|
+
function createAcceptsPaymentGate(input) {
|
|
4053
|
+
const options = input.discoverable === void 0 ? input : { ...input, accepts: discoverableAccepts(input.accepts, input.discoverable) };
|
|
3386
4054
|
const client = new FacilitatorClient({
|
|
3387
4055
|
baseUrl: options.facilitatorUrl || options.baseUrl,
|
|
3388
4056
|
timeout: options.timeout,
|
|
@@ -3462,7 +4130,10 @@ function createAcceptsPaymentGate(options) {
|
|
|
3462
4130
|
requirement,
|
|
3463
4131
|
verifyResult,
|
|
3464
4132
|
binding,
|
|
3465
|
-
|
|
4133
|
+
(result) => {
|
|
4134
|
+
hooks.onSettled?.(result);
|
|
4135
|
+
hooks.onResult(result);
|
|
4136
|
+
}
|
|
3466
4137
|
);
|
|
3467
4138
|
hooks.onVerified?.(verifiedPayment);
|
|
3468
4139
|
if (settlementStrategy === "before-handler") {
|
|
@@ -3479,15 +4150,68 @@ function createAcceptsPaymentGate(options) {
|
|
|
3479
4150
|
return { payment: verifiedPayment };
|
|
3480
4151
|
};
|
|
3481
4152
|
}
|
|
4153
|
+
function isLiteralObject(value) {
|
|
4154
|
+
if (!isObject(value) || Array.isArray(value)) return false;
|
|
4155
|
+
const proto = Object.getPrototypeOf(value);
|
|
4156
|
+
return proto === Object.prototype || proto === null;
|
|
4157
|
+
}
|
|
4158
|
+
function withDiscoverable(accept, discoverable = true) {
|
|
4159
|
+
if (typeof discoverable !== "boolean") {
|
|
4160
|
+
throw new Error(`discoverable must be true or false, got ${describeValue(discoverable)}`);
|
|
4161
|
+
}
|
|
4162
|
+
if (!isLiteralObject(accept)) {
|
|
4163
|
+
throw new Error(`discoverable: an accept must be an object, got ${describeValue(accept)}`);
|
|
4164
|
+
}
|
|
4165
|
+
if (typeof accept.network !== "string") {
|
|
4166
|
+
throw new Error(`discoverable: the accept has no network, got ${describeValue(accept.network)}`);
|
|
4167
|
+
}
|
|
4168
|
+
if (isNativeHedera(accept.network)) {
|
|
4169
|
+
throw new Error(`discoverable: a native Hedera accept (${accept.network}) carries only feePayer in extra`);
|
|
4170
|
+
}
|
|
4171
|
+
if (accept.extra !== void 0 && !isLiteralObject(accept.extra)) {
|
|
4172
|
+
throw new Error(`discoverable: extra must be a plain object to carry discoverable, got ${describeValue(accept.extra)}`);
|
|
4173
|
+
}
|
|
4174
|
+
return { ...accept, extra: { ...accept.extra ?? {}, discoverable } };
|
|
4175
|
+
}
|
|
4176
|
+
function describeValue(value) {
|
|
4177
|
+
if (Array.isArray(value)) return "an array";
|
|
4178
|
+
if (value === null) return "null";
|
|
4179
|
+
if (typeof value === "object") return isLiteralObject(value) ? "an object" : "an object that is not plain";
|
|
4180
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
4181
|
+
return `${typeof value} ${String(value)}`;
|
|
4182
|
+
}
|
|
4183
|
+
function isNativeHedera(network) {
|
|
4184
|
+
return getChainByName(parseNetworkIdentifier(network))?.networkType === "hedera";
|
|
4185
|
+
}
|
|
4186
|
+
function discoverableAccepts(accepts, discoverable) {
|
|
4187
|
+
if (typeof discoverable !== "boolean") {
|
|
4188
|
+
throw new Error(`discoverable must be true or false, got ${describeValue(discoverable)}`);
|
|
4189
|
+
}
|
|
4190
|
+
if (!Array.isArray(accepts)) return accepts;
|
|
4191
|
+
return accepts.map((accept) => isLiteralObject(accept) && typeof accept.network === "string" && isNativeHedera(accept.network) ? accept : withDiscoverable(accept, discoverable));
|
|
4192
|
+
}
|
|
3482
4193
|
function createHonoMiddleware(options) {
|
|
3483
4194
|
const gate = createAcceptsPaymentGate(options);
|
|
4195
|
+
const anchorer = durableAnchorerFor(options);
|
|
3484
4196
|
return async (c, next) => {
|
|
4197
|
+
let settled;
|
|
3485
4198
|
const outcome = await gate(c.req, {
|
|
3486
4199
|
onResult: (result) => setHonoPaymentHeaders(c, result),
|
|
3487
|
-
onVerified: (payment) => c.set?.("x402", payment)
|
|
4200
|
+
onVerified: (payment) => c.set?.("x402", payment),
|
|
4201
|
+
onSettled: (result) => {
|
|
4202
|
+
settled = result;
|
|
4203
|
+
}
|
|
3488
4204
|
});
|
|
3489
4205
|
if ("reply" in outcome) return respondHono(c, outcome.reply);
|
|
3490
4206
|
await next();
|
|
4207
|
+
if (anchorer && c.res instanceof Response) {
|
|
4208
|
+
c.res = await withDurableEvidence(c.res, {
|
|
4209
|
+
anchorer,
|
|
4210
|
+
payment: outcome.payment,
|
|
4211
|
+
settled,
|
|
4212
|
+
method: c.req.method || c.req.raw?.method
|
|
4213
|
+
});
|
|
4214
|
+
}
|
|
3491
4215
|
};
|
|
3492
4216
|
}
|
|
3493
4217
|
function isRequestLike(value) {
|
|
@@ -3512,9 +4236,11 @@ function withPaymentHeaders(response, result) {
|
|
|
3512
4236
|
}
|
|
3513
4237
|
function createFetchPaywall(options) {
|
|
3514
4238
|
const gate = createAcceptsPaymentGate(options);
|
|
4239
|
+
const anchorer = durableAnchorerFor(options);
|
|
3515
4240
|
return (handler) => async (input, ...rest) => {
|
|
3516
4241
|
const request = requestOf(input);
|
|
3517
4242
|
let receipted;
|
|
4243
|
+
let settled;
|
|
3518
4244
|
const outcome = await gate(
|
|
3519
4245
|
{ header: (name) => request.headers.get(name) ?? void 0, url: request.url, method: request.method, raw: request },
|
|
3520
4246
|
{
|
|
@@ -3522,19 +4248,23 @@ function createFetchPaywall(options) {
|
|
|
3522
4248
|
if (!result.receipt) return;
|
|
3523
4249
|
paymentResponseHeaders(propagatedResult(result));
|
|
3524
4250
|
receipted = result;
|
|
4251
|
+
},
|
|
4252
|
+
onSettled: (result) => {
|
|
4253
|
+
settled = result;
|
|
3525
4254
|
}
|
|
3526
4255
|
}
|
|
3527
4256
|
);
|
|
3528
4257
|
if ("reply" in outcome) {
|
|
3529
4258
|
const { status, headers, body } = outcome.reply;
|
|
3530
|
-
const
|
|
4259
|
+
const response2 = new Response(JSON.stringify(body), {
|
|
3531
4260
|
status,
|
|
3532
4261
|
headers: { "Content-Type": "application/json", ...headers }
|
|
3533
4262
|
});
|
|
3534
|
-
return withPaymentHeaders(
|
|
4263
|
+
return withPaymentHeaders(response2, receipted);
|
|
3535
4264
|
}
|
|
4265
|
+
let response;
|
|
3536
4266
|
try {
|
|
3537
|
-
|
|
4267
|
+
response = withPaymentHeaders(await handler(input, outcome.payment, ...rest), receipted);
|
|
3538
4268
|
} catch (error) {
|
|
3539
4269
|
if (!receipted) throw error;
|
|
3540
4270
|
if (error instanceof Response) throw withPaymentHeaders(error, receipted);
|
|
@@ -3545,6 +4275,8 @@ function createFetchPaywall(options) {
|
|
|
3545
4275
|
headers: { "Content-Type": "text/plain; charset=UTF-8" }
|
|
3546
4276
|
}), receipted);
|
|
3547
4277
|
}
|
|
4278
|
+
if (!anchorer) return response;
|
|
4279
|
+
return withDurableEvidence(response, { anchorer, payment: outcome.payment, settled, method: request.method });
|
|
3548
4280
|
};
|
|
3549
4281
|
}
|
|
3550
4282
|
var MAX_SEARCH_LEN = 128;
|
|
@@ -5615,13 +6347,13 @@ var Erc8004Client = class {
|
|
|
5615
6347
|
async waitForRegistration(jobId, options) {
|
|
5616
6348
|
const pollIntervalMs = options?.pollIntervalMs ?? 2e3;
|
|
5617
6349
|
const timeoutMs = options?.timeoutMs ?? 3e5;
|
|
5618
|
-
const
|
|
6350
|
+
const deadline2 = Date.now() + timeoutMs;
|
|
5619
6351
|
for (; ; ) {
|
|
5620
6352
|
const job = await this.getRegisterStatus(jobId);
|
|
5621
6353
|
if (isRegisterJobTerminal(job)) {
|
|
5622
6354
|
return job;
|
|
5623
6355
|
}
|
|
5624
|
-
if (Date.now() >=
|
|
6356
|
+
if (Date.now() >= deadline2) {
|
|
5625
6357
|
throw new RegistrationPendingError(jobId, job.status, timeoutMs);
|
|
5626
6358
|
}
|
|
5627
6359
|
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
@@ -5722,7 +6454,7 @@ function buildErc8004PaymentRequirements(options) {
|
|
|
5722
6454
|
};
|
|
5723
6455
|
}
|
|
5724
6456
|
var PAYMENT_INFO_TYPEHASH = "0xae68ac7ce30c86ece8196b61a7c486d8f0061f575037fbd34e7fe4e2820c6591";
|
|
5725
|
-
var
|
|
6457
|
+
var ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000";
|
|
5726
6458
|
var DEPOSIT_LIMIT_USDC = "100000000";
|
|
5727
6459
|
var ESCROW_TIMEOUT_MS = {
|
|
5728
6460
|
1: 96e4,
|
|
@@ -6003,7 +6735,7 @@ var AdvancedEscrowClient = class {
|
|
|
6003
6735
|
PAYMENT_INFO_TYPEHASH,
|
|
6004
6736
|
[
|
|
6005
6737
|
paymentInfo.operator,
|
|
6006
|
-
|
|
6738
|
+
ZERO_ADDRESS2,
|
|
6007
6739
|
// payer = 0 for payer-agnostic hash
|
|
6008
6740
|
paymentInfo.receiver,
|
|
6009
6741
|
paymentInfo.token,
|
|
@@ -6664,7 +7396,7 @@ var AdvancedEscrowClient = class {
|
|
|
6664
7396
|
return await this.sendV3OperatorCall(ethers5, this.v3Provider(ethers5), "refund", [
|
|
6665
7397
|
tuple,
|
|
6666
7398
|
amt,
|
|
6667
|
-
tokenCollector ||
|
|
7399
|
+
tokenCollector || ZERO_ADDRESS2,
|
|
6668
7400
|
collectorData || "0x"
|
|
6669
7401
|
]);
|
|
6670
7402
|
}
|
|
@@ -6673,7 +7405,7 @@ var AdvancedEscrowClient = class {
|
|
|
6673
7405
|
return iface.encodeFunctionData("refundPostEscrow", [
|
|
6674
7406
|
tuple,
|
|
6675
7407
|
amt,
|
|
6676
|
-
tokenCollector ||
|
|
7408
|
+
tokenCollector || ZERO_ADDRESS2,
|
|
6677
7409
|
collectorData || "0x"
|
|
6678
7410
|
]);
|
|
6679
7411
|
});
|
|
@@ -6682,7 +7414,7 @@ var AdvancedEscrowClient = class {
|
|
|
6682
7414
|
const tx = await contract.refundPostEscrow(
|
|
6683
7415
|
tuple,
|
|
6684
7416
|
amt,
|
|
6685
|
-
tokenCollector ||
|
|
7417
|
+
tokenCollector || ZERO_ADDRESS2,
|
|
6686
7418
|
collectorData || "0x",
|
|
6687
7419
|
{ gasLimit: this.gasLimit }
|
|
6688
7420
|
);
|
|
@@ -6837,6 +7569,7 @@ exports.DEFAULT_FACILITATOR_RETRIES = DEFAULT_FACILITATOR_RETRIES;
|
|
|
6837
7569
|
exports.DEFAULT_MAX_SEARCH_LEN = DEFAULT_MAX_SEARCH_LEN;
|
|
6838
7570
|
exports.DEFAULT_RETRY_AFTER_SECONDS = DEFAULT_RETRY_AFTER_SECONDS;
|
|
6839
7571
|
exports.DEPOSIT_LIMIT_USDC = DEPOSIT_LIMIT_USDC;
|
|
7572
|
+
exports.DURABLE_DEFAULT_TIMEOUT_MS = DURABLE_DEFAULT_TIMEOUT_MS;
|
|
6840
7573
|
exports.ERC8004_CONTRACTS = ERC8004_CONTRACTS;
|
|
6841
7574
|
exports.ERC8004_EXTENSION_ID = ERC8004_EXTENSION_ID;
|
|
6842
7575
|
exports.ESCROW_CONTRACTS = ESCROW_CONTRACTS;
|
|
@@ -6876,7 +7609,7 @@ exports.USDC_DOMAIN_NAME = USDC_DOMAIN_NAME;
|
|
|
6876
7609
|
exports.WRITER_LEASE_REASONS = WRITER_LEASE_REASONS;
|
|
6877
7610
|
exports.X402_CORS_HEADERS = X402_CORS_HEADERS;
|
|
6878
7611
|
exports.X402_HEADER_NAMES = X402_HEADER_NAMES;
|
|
6879
|
-
exports.ZERO_ADDRESS =
|
|
7612
|
+
exports.ZERO_ADDRESS = ZERO_ADDRESS2;
|
|
6880
7613
|
exports.bazaarExtension = bazaarExtension;
|
|
6881
7614
|
exports.buildErc8004PaymentRequirements = buildErc8004PaymentRequirements;
|
|
6882
7615
|
exports.buildPaymentConflictResponse = buildPaymentConflictResponse;
|
|
@@ -6937,5 +7670,6 @@ exports.toResourceInfoV2 = toResourceInfoV2;
|
|
|
6937
7670
|
exports.validatePurchaseContext = validatePurchaseContext;
|
|
6938
7671
|
exports.verifyFacilitatorReceipt = verifyFacilitatorReceipt;
|
|
6939
7672
|
exports.wireNetwork = wireNetwork;
|
|
7673
|
+
exports.withDiscoverable = withDiscoverable;
|
|
6940
7674
|
//# sourceMappingURL=index.js.map
|
|
6941
7675
|
//# sourceMappingURL=index.js.map
|