uvd-x402-sdk 2.62.0 → 2.64.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 CHANGED
@@ -1280,3 +1280,92 @@ const isHealthy = await client.healthCheck();
1280
1280
  ## License
1281
1281
 
1282
1282
  MIT
1283
+
1284
+ ## DX402 — evidence that outlives the session
1285
+
1286
+ x402 settles payment on-chain forever but delivers the resource **once** and
1287
+ keeps nothing. DX402 seals a copy of the response to the payer's own public key
1288
+ — recovered from the payment signature itself — and anchors it. No registration,
1289
+ no extra round trip: paying *is* publishing your encryption key.
1290
+
1291
+ ### Seller: one call
1292
+
1293
+ ```ts
1294
+ import { anchorEvidence, evidenceHeader } from 'uvd-x402-sdk';
1295
+
1296
+ const result = await anchorEvidence(body, {
1297
+ paymentId, network: 'base', txHash,
1298
+ payer: payerAddr, payee: myAddr, payerKey: payerPubkey,
1299
+ sign: (digest) => myCustodian.sign(digest), // a callable, not a key
1300
+ });
1301
+ res.setHeader('X-Durable-Evidence', evidenceHeader(result));
1302
+ ```
1303
+
1304
+ **It never throws.** Every failure resolves to `result.skipped`, because
1305
+ evidence is an addition to the payment path and must never be a gate in front of
1306
+ it. An unreachable facilitator costs the receipt, never the sale.
1307
+
1308
+ `sign` takes a **callable rather than a private key** so a custodian can sign:
1309
+ it receives the 32-byte digest and returns a signature without the seed ever
1310
+ leaving it.
1311
+
1312
+ ### Buyer: come back months later
1313
+
1314
+ ```ts
1315
+ import { recoverEvidence, evidenceFromHeaders } from 'uvd-x402-sdk';
1316
+
1317
+ const evidence = evidenceFromHeaders(res.headers);
1318
+ const body = await recoverEvidence(evidence, myPrivateKey);
1319
+ ```
1320
+
1321
+ This needs permission from nobody. The ciphertext was sealed to the wallet that
1322
+ paid, so recovery is arithmetic rather than an access-control decision anyone
1323
+ could refuse. The `contentHash` check runs automatically and throws
1324
+ `ContentHashMismatch` — it is what catches a seller who anchored something other
1325
+ than what it served.
1326
+
1327
+ ### `verified` vs `signed` — read this before you branch on either
1328
+
1329
+ Since facilitator **1.87.0** a signature alone does not make an anchor final:
1330
+
1331
+ | field | means | supersedable by |
1332
+ |---|---|---|
1333
+ | `verified: true` | the **chain** confirmed this address is the payee | nothing — final |
1334
+ | `signed: true` | the claimant controls the address it *declared* | a verified anchor |
1335
+ | neither | anyone could have written it | either of the above |
1336
+
1337
+ To reach `verified` you must send `proofOfPayment`. Without it the facilitator
1338
+ has checked no chain and answers `notVerifiedReason: "dx402_proof_missing"` —
1339
+ your signature was still accepted (`signed: true`), authorship simply was not
1340
+ certified.
1341
+
1342
+ Why the split: `verified` was previously decided against the `payee` field *in
1343
+ the request*, which the caller supplies. Proving "I control the address I typed
1344
+ into my own request" was enough to own a stranger's evidence permanently.
1345
+
1346
+ ### Choosing where evidence is stored
1347
+
1348
+ ```ts
1349
+ import { availableBackends } from 'uvd-x402-sdk';
1350
+
1351
+ for (const b of await availableBackends()) {
1352
+ console.log(b.id, b.retention, b.revocable ? 'deletable' : 'IRREVERSIBLE');
1353
+ }
1354
+
1355
+ await anchorEvidence(body, { ...opts, storage: 'ipfs-private' });
1356
+ ```
1357
+
1358
+ Ask rather than assume: what exists depends on the deployment, and you may be
1359
+ pointed at a facilitator that is not ours. `revocable: false` means the
1360
+ `retentionUntil` in the **signed** receipt cannot be honoured — on public IPFS,
1361
+ unpinning removes the facilitator's copy, not the network's.
1362
+
1363
+ ### Limits
1364
+
1365
+ - Inline anchors cap at **64 KiB of request** (~47 KB of plaintext); the SDK
1366
+ returns `skipped: 'too_large'` before touching the network.
1367
+ - Anchoring with `retention: 'permanent'` is **irrevocable**.
1368
+ - On Solana, `verified` is not reachable yet — the on-chain gate cannot read
1369
+ that payment, so `signed: true` is the honest maximum.
1370
+
1371
+ Full guide: [DX402.md](https://github.com/UltravioletaDAO/x402-rs/blob/main/docs/DX402.md)
@@ -2342,7 +2342,10 @@ declare class AdvancedEscrowClient {
2342
2342
  * @param tier - Task tier determines timing parameters
2343
2343
  * @param salt - Random salt (auto-generated if not provided)
2344
2344
  */
2345
- buildPaymentInfo(receiver: string, amount: string, tier?: AdvancedEscrowTaskTier, salt?: string): AdvancedPaymentInfo;
2345
+ buildPaymentInfo(receiver: string, amount: string, tier?: AdvancedEscrowTaskTier, salt?: string, opts?: {
2346
+ deadline?: number;
2347
+ reviewWindowSec?: number;
2348
+ }): AdvancedPaymentInfo;
2346
2349
  /**
2347
2350
  * Compute the correct nonce (with PAYMENT_INFO_TYPEHASH).
2348
2351
  * Matches the on-chain AuthCaptureEscrow nonce derivation.
@@ -2342,7 +2342,10 @@ declare class AdvancedEscrowClient {
2342
2342
  * @param tier - Task tier determines timing parameters
2343
2343
  * @param salt - Random salt (auto-generated if not provided)
2344
2344
  */
2345
- buildPaymentInfo(receiver: string, amount: string, tier?: AdvancedEscrowTaskTier, salt?: string): AdvancedPaymentInfo;
2345
+ buildPaymentInfo(receiver: string, amount: string, tier?: AdvancedEscrowTaskTier, salt?: string, opts?: {
2346
+ deadline?: number;
2347
+ reviewWindowSec?: number;
2348
+ }): AdvancedPaymentInfo;
2346
2349
  /**
2347
2350
  * Compute the correct nonce (with PAYMENT_INFO_TYPEHASH).
2348
2351
  * Matches the on-chain AuthCaptureEscrow nonce derivation.
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ require('ethers');
4
+
3
5
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
6
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
7
  }) : x)(function(x) {
@@ -1088,6 +1090,8 @@ function decodeX402Header(encoded) {
1088
1090
  const json = decodeBase64Utf8(encoded);
1089
1091
  return JSON.parse(json);
1090
1092
  }
1093
+ var REVIEW_WINDOW_SEC = 7 * 24 * 3600;
1094
+ var REFUND_WINDOW_SEC = 7 * 24 * 3600;
1091
1095
 
1092
1096
  // src/backend/index.ts
1093
1097
  function parsePaymentHeader(headerValue) {
@@ -3504,9 +3508,13 @@ var AdvancedEscrowClient = class {
3504
3508
  * @param tier - Task tier determines timing parameters
3505
3509
  * @param salt - Random salt (auto-generated if not provided)
3506
3510
  */
3507
- buildPaymentInfo(receiver, amount, tier = "standard", salt) {
3511
+ buildPaymentInfo(receiver, amount, tier = "standard", salt, opts) {
3508
3512
  const now = Math.floor(Date.now() / 1e3);
3509
3513
  const t = TIER_TIMINGS[tier];
3514
+ const reviewWindow = opts?.reviewWindowSec ?? REVIEW_WINDOW_SEC;
3515
+ const reviewBase = Math.max(now, opts?.deadline ?? now);
3516
+ const authorizationExpiry = Math.max(now + t.auth, reviewBase + reviewWindow);
3517
+ const refundExpiry = Math.max(now + t.refund, authorizationExpiry + REFUND_WINDOW_SEC);
3510
3518
  let generatedSalt = salt;
3511
3519
  if (!generatedSalt) {
3512
3520
  const bytes = new Uint8Array(32);
@@ -3525,8 +3533,8 @@ var AdvancedEscrowClient = class {
3525
3533
  token: this.contracts.usdc,
3526
3534
  maxAmount: amount,
3527
3535
  preApprovalExpiry: now + t.pre,
3528
- authorizationExpiry: now + t.auth,
3529
- refundExpiry: now + t.refund,
3536
+ authorizationExpiry,
3537
+ refundExpiry,
3530
3538
  minFeeBps: 0,
3531
3539
  maxFeeBps: 800,
3532
3540
  feeReceiver: this.contracts.operator,
@@ -3538,8 +3546,8 @@ var AdvancedEscrowClient = class {
3538
3546
  * Matches the on-chain AuthCaptureEscrow nonce derivation.
3539
3547
  */
3540
3548
  async computeNonce(paymentInfo) {
3541
- const { ethers } = await import('ethers');
3542
- const piTuple = ethers.AbiCoder.defaultAbiCoder().encode(
3549
+ const { ethers: ethers2 } = await import('ethers');
3550
+ const piTuple = ethers2.AbiCoder.defaultAbiCoder().encode(
3543
3551
  ["bytes32", "tuple(address,address,address,address,uint120,uint48,uint48,uint48,uint16,uint16,address,uint256)"],
3544
3552
  [
3545
3553
  PAYMENT_INFO_TYPEHASH,
@@ -3560,12 +3568,12 @@ var AdvancedEscrowClient = class {
3560
3568
  ]
3561
3569
  ]
3562
3570
  );
3563
- const piHash = ethers.keccak256(piTuple);
3564
- const finalEncoded = ethers.AbiCoder.defaultAbiCoder().encode(
3571
+ const piHash = ethers2.keccak256(piTuple);
3572
+ const finalEncoded = ethers2.AbiCoder.defaultAbiCoder().encode(
3565
3573
  ["uint256", "address", "bytes32"],
3566
3574
  [this.chainId, this.contracts.escrow, piHash]
3567
3575
  );
3568
- return ethers.keccak256(finalEncoded);
3576
+ return ethers2.keccak256(finalEncoded);
3569
3577
  }
3570
3578
  /**
3571
3579
  * Sign ReceiveWithAuthorization for ERC-3009.
@@ -3722,17 +3730,17 @@ var AdvancedEscrowClient = class {
3722
3730
  async release(paymentInfo, amount) {
3723
3731
  if (!this.payerAddress) await this.init();
3724
3732
  try {
3725
- const { ethers } = await import('ethers');
3733
+ const { ethers: ethers2 } = await import('ethers');
3726
3734
  const isCreate3 = CREATE3_CHAIN_IDS.has(this.chainId);
3727
3735
  const abi = isCreate3 ? OPERATOR_ABI_CREATE3 : OPERATOR_ABI;
3728
3736
  const amt = amount || paymentInfo.maxAmount;
3729
3737
  const tuple = this.buildTuple(paymentInfo);
3730
3738
  if (this.walletAdapter) {
3731
- return this.sendViaAdapter(ethers, abi, (iface) => {
3739
+ return this.sendViaAdapter(ethers2, abi, (iface) => {
3732
3740
  return isCreate3 ? iface.encodeFunctionData("release", [tuple, amt, "0x"]) : iface.encodeFunctionData("release", [tuple, amt]);
3733
3741
  });
3734
3742
  }
3735
- const contract = new ethers.Contract(this.contracts.operator, abi, this.signer);
3743
+ const contract = new ethers2.Contract(this.contracts.operator, abi, this.signer);
3736
3744
  const tx = isCreate3 ? await contract.release(tuple, amt, "0x", { gasLimit: this.gasLimit }) : await contract.release(tuple, amt, { gasLimit: this.gasLimit });
3737
3745
  const receipt = await tx.wait();
3738
3746
  return {
@@ -3756,17 +3764,17 @@ var AdvancedEscrowClient = class {
3756
3764
  async refundInEscrow(paymentInfo, amount) {
3757
3765
  if (!this.payerAddress) await this.init();
3758
3766
  try {
3759
- const { ethers } = await import('ethers');
3767
+ const { ethers: ethers2 } = await import('ethers');
3760
3768
  const isCreate3 = CREATE3_CHAIN_IDS.has(this.chainId);
3761
3769
  const abi = isCreate3 ? OPERATOR_ABI_CREATE3 : OPERATOR_ABI;
3762
3770
  const amt = amount || paymentInfo.maxAmount;
3763
3771
  const tuple = this.buildTuple(paymentInfo);
3764
3772
  if (this.walletAdapter) {
3765
- return this.sendViaAdapter(ethers, abi, (iface) => {
3773
+ return this.sendViaAdapter(ethers2, abi, (iface) => {
3766
3774
  return isCreate3 ? iface.encodeFunctionData("refundInEscrow", [tuple, amt, "0x"]) : iface.encodeFunctionData("refundInEscrow", [tuple, amt]);
3767
3775
  });
3768
3776
  }
3769
- const contract = new ethers.Contract(this.contracts.operator, abi, this.signer);
3777
+ const contract = new ethers2.Contract(this.contracts.operator, abi, this.signer);
3770
3778
  const tx = isCreate3 ? await contract.refundInEscrow(tuple, amt, "0x", { gasLimit: this.gasLimit }) : await contract.refundInEscrow(tuple, amt, { gasLimit: this.gasLimit });
3771
3779
  const receipt = await tx.wait();
3772
3780
  return {
@@ -4029,7 +4037,7 @@ var AdvancedEscrowClient = class {
4029
4037
  async charge(paymentInfo, amount) {
4030
4038
  if (!this.payerAddress) await this.init();
4031
4039
  try {
4032
- const { ethers } = await import('ethers');
4040
+ const { ethers: ethers2 } = await import('ethers');
4033
4041
  const nonce = await this.computeNonce(paymentInfo);
4034
4042
  const amt = amount || paymentInfo.maxAmount;
4035
4043
  const auth = {
@@ -4041,10 +4049,10 @@ var AdvancedEscrowClient = class {
4041
4049
  nonce
4042
4050
  };
4043
4051
  const signature = await this.signErc3009(auth);
4044
- const collectorData = ethers.getBytes(signature);
4052
+ const collectorData = ethers2.getBytes(signature);
4045
4053
  const tuple = this.buildTuple(paymentInfo);
4046
4054
  if (this.walletAdapter) {
4047
- return this.sendViaAdapter(ethers, OPERATOR_ABI, (iface) => {
4055
+ return this.sendViaAdapter(ethers2, OPERATOR_ABI, (iface) => {
4048
4056
  return iface.encodeFunctionData("charge", [
4049
4057
  tuple,
4050
4058
  amt,
@@ -4053,7 +4061,7 @@ var AdvancedEscrowClient = class {
4053
4061
  ]);
4054
4062
  });
4055
4063
  }
4056
- const contract = new ethers.Contract(this.contracts.operator, OPERATOR_ABI, this.signer);
4064
+ const contract = new ethers2.Contract(this.contracts.operator, OPERATOR_ABI, this.signer);
4057
4065
  const tx = await contract.charge(
4058
4066
  tuple,
4059
4067
  amt,
@@ -4095,11 +4103,11 @@ var AdvancedEscrowClient = class {
4095
4103
  async refundPostEscrow(paymentInfo, amount, tokenCollector, collectorData) {
4096
4104
  if (!this.payerAddress) await this.init();
4097
4105
  try {
4098
- const { ethers } = await import('ethers');
4106
+ const { ethers: ethers2 } = await import('ethers');
4099
4107
  const amt = amount || paymentInfo.maxAmount;
4100
4108
  const tuple = this.buildTuple(paymentInfo);
4101
4109
  if (this.walletAdapter) {
4102
- return this.sendViaAdapter(ethers, OPERATOR_ABI, (iface) => {
4110
+ return this.sendViaAdapter(ethers2, OPERATOR_ABI, (iface) => {
4103
4111
  return iface.encodeFunctionData("refundPostEscrow", [
4104
4112
  tuple,
4105
4113
  amt,
@@ -4108,7 +4116,7 @@ var AdvancedEscrowClient = class {
4108
4116
  ]);
4109
4117
  });
4110
4118
  }
4111
- const contract = new ethers.Contract(this.contracts.operator, OPERATOR_ABI, this.signer);
4119
+ const contract = new ethers2.Contract(this.contracts.operator, OPERATOR_ABI, this.signer);
4112
4120
  const tx = await contract.refundPostEscrow(
4113
4121
  tuple,
4114
4122
  amt,