uvd-x402-sdk 2.60.0 → 2.62.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/dist/index.d.mts CHANGED
@@ -271,6 +271,18 @@ interface AnchorOptions {
271
271
  * payment wallet works fine here, because this key only ever decrypts.
272
272
  */
273
273
  sellerEncryptionKey?: Uint8Array;
274
+ /**
275
+ * The settlement proof, in the facilitator's `ProofOfPayment` shape
276
+ * (`transactionHash`, `blockNumber`, `network`, `payer`, `payee`, `amount`,
277
+ * `token`, `timestamp`, `paymentHash`).
278
+ *
279
+ * **The only thing that reaches `verified: true`.** Without it the
280
+ * facilitator has checked no chain, so it records the anchor as provisional
281
+ * and answers `notVerifiedReason: "dx402_proof_missing"`: the signature is
282
+ * accepted (`signed: true`) but authorship is not certified, and a claim that
283
+ * proves more may supersede it.
284
+ */
285
+ proofOfPayment?: Record<string, unknown>;
274
286
  /**
275
287
  * `(digest) => "0x..."`. A callable rather than a private key is what lets a
276
288
  * custodian sign: it receives the digest and returns the signature without the
@@ -284,6 +296,32 @@ interface AnchorOptions {
284
296
  facilitator?: string;
285
297
  fetch?: typeof fetch;
286
298
  }
299
+ /**
300
+ * The digest the facilitator will ACTUALLY verify, chosen by the payee's curve.
301
+ *
302
+ * The gate dispatches on the payee and checks **one** form -- never both. A
303
+ * secp256k1 payee is verified against the digest built with its REAL address and
304
+ * chain id; an ed25519 payee, whose address does not fit the `address` field, is
305
+ * verified against the zero address and chain id 0.
306
+ *
307
+ * Signing the wrong form throws nothing. It yields a signature that never
308
+ * verifies, the anchor silently stays **provisional**, and a provisional anchor
309
+ * can be superseded by anyone -- the very hijack a signed anchor exists to stop.
310
+ * Reproduced against production by KarmaKadabra, 2026-08-19: with everything else
311
+ * identical, the ed25519 form was refused (`409 dx402_already_anchored`) and the
312
+ * EVM form superseded the provisional.
313
+ *
314
+ * `pointer` stays empty on both branches: this call sends `sealed`, so the
315
+ * facilitator issues the pointer and you cannot sign what you have not seen.
316
+ */
317
+ declare function sellerDigestFor(paymentId: string, contentHash: string, payee: string, network: string): Uint8Array | undefined;
318
+ /**
319
+ * Largest `POST /dx402/anchor` request the facilitator accepts, mirroring its
320
+ * `MAX_REQUEST_BODY_BYTES` (default 64 KiB, an anti-OOM bound on every route).
321
+ * After base64 inflation and ~600 bytes of metadata this leaves ~47 KB of
322
+ * plaintext.
323
+ */
324
+ declare const ANCHOR_MAX_REQUEST_BYTES: number;
287
325
  declare function anchorEvidence(body: Uint8Array, opts: AnchorOptions): Promise<Record<string, unknown>>;
288
326
  /** Encode an anchor result for the `X-Durable-Evidence` response header. */
289
327
  declare function evidenceHeader(evidence: unknown): string;
@@ -935,4 +973,4 @@ interface EscrowPreAuthParams {
935
973
  */
936
974
  declare function buildEscrowPreAuth(wallet: EscrowPreAuthSigner, params: EscrowPreAuthParams): Promise<string>;
937
975
 
938
- export { type AnchorOptions, type AnchoredEvidence, ContentHashMismatch, type CreateSignedFetchConfig, DX402Error, type ERC8128RequestOptions, ESCROW_DEPOSIT_LIMIT_USD, ESCROW_TIER_WINDOWS, EVENT_KINDS, EVIDENCE_HEADER, type EscrowNetworkConfig, type EscrowPaymentInfo, type EscrowPreAuthParams, type EscrowPreAuthSigner, type EscrowTierWindows, type EvidenceMode, EvidenceSkipped, FACILITATOR_ADDRESSES, type FacilitatorAddresses, KEEPALIVE_INTERVAL_MS, OPERATOR_FEE_BPS, type RecipientRole, type SSEFrame, SSEParser, type SignRequestOptions, type SignRequestWithSignerOptions, type SignatureBaseParams, type SignatureHeaders, type SignatureParamsInput, SigningWalletAdapter, type StreamTrafficEventsOptions, type TrafficEvent, type TrafficEventKind, TrafficStreamError, ZERO_ADDRESS, anchorDigest, anchorEvidence, buildEscrowPreAuth, buildSignatureBase, buildSignatureParams, computeEscrowNonce, contentHash, createSignedFetch, dereferencePointer, paymentId as dx402PaymentId, ed25519ToX25519, evidenceFromHeaders, evidenceHeader, fetchNonce, getFacilitatorAddress, isEndToEnd, matchesFilters, parseEvidenceHeader, parseSealed, parseTrafficEvent, payerKeyFromEvmSignature, payerKeyFromSolanaAddress, recoverEvidence, sealEvidence, sealEvidenceTo, sealedRoles, signAnchorEd25519, signAnchorEvm, signRequest, signRequestWithSigner, signRequestWithWallet, streamTrafficEvents, unseal };
976
+ export { ANCHOR_MAX_REQUEST_BYTES, type AnchorOptions, type AnchoredEvidence, ContentHashMismatch, type CreateSignedFetchConfig, DX402Error, type ERC8128RequestOptions, ESCROW_DEPOSIT_LIMIT_USD, ESCROW_TIER_WINDOWS, EVENT_KINDS, EVIDENCE_HEADER, type EscrowNetworkConfig, type EscrowPaymentInfo, type EscrowPreAuthParams, type EscrowPreAuthSigner, type EscrowTierWindows, type EvidenceMode, EvidenceSkipped, FACILITATOR_ADDRESSES, type FacilitatorAddresses, KEEPALIVE_INTERVAL_MS, OPERATOR_FEE_BPS, type RecipientRole, type SSEFrame, SSEParser, type SignRequestOptions, type SignRequestWithSignerOptions, type SignatureBaseParams, type SignatureHeaders, type SignatureParamsInput, SigningWalletAdapter, type StreamTrafficEventsOptions, type TrafficEvent, type TrafficEventKind, TrafficStreamError, ZERO_ADDRESS, anchorDigest, anchorEvidence, buildEscrowPreAuth, buildSignatureBase, buildSignatureParams, computeEscrowNonce, contentHash, createSignedFetch, dereferencePointer, paymentId as dx402PaymentId, ed25519ToX25519, evidenceFromHeaders, evidenceHeader, fetchNonce, getFacilitatorAddress, isEndToEnd, matchesFilters, parseEvidenceHeader, parseSealed, parseTrafficEvent, payerKeyFromEvmSignature, payerKeyFromSolanaAddress, recoverEvidence, sealEvidence, sealEvidenceTo, sealedRoles, sellerDigestFor, signAnchorEd25519, signAnchorEvm, signRequest, signRequestWithSigner, signRequestWithWallet, streamTrafficEvents, unseal };
package/dist/index.d.ts CHANGED
@@ -271,6 +271,18 @@ interface AnchorOptions {
271
271
  * payment wallet works fine here, because this key only ever decrypts.
272
272
  */
273
273
  sellerEncryptionKey?: Uint8Array;
274
+ /**
275
+ * The settlement proof, in the facilitator's `ProofOfPayment` shape
276
+ * (`transactionHash`, `blockNumber`, `network`, `payer`, `payee`, `amount`,
277
+ * `token`, `timestamp`, `paymentHash`).
278
+ *
279
+ * **The only thing that reaches `verified: true`.** Without it the
280
+ * facilitator has checked no chain, so it records the anchor as provisional
281
+ * and answers `notVerifiedReason: "dx402_proof_missing"`: the signature is
282
+ * accepted (`signed: true`) but authorship is not certified, and a claim that
283
+ * proves more may supersede it.
284
+ */
285
+ proofOfPayment?: Record<string, unknown>;
274
286
  /**
275
287
  * `(digest) => "0x..."`. A callable rather than a private key is what lets a
276
288
  * custodian sign: it receives the digest and returns the signature without the
@@ -284,6 +296,32 @@ interface AnchorOptions {
284
296
  facilitator?: string;
285
297
  fetch?: typeof fetch;
286
298
  }
299
+ /**
300
+ * The digest the facilitator will ACTUALLY verify, chosen by the payee's curve.
301
+ *
302
+ * The gate dispatches on the payee and checks **one** form -- never both. A
303
+ * secp256k1 payee is verified against the digest built with its REAL address and
304
+ * chain id; an ed25519 payee, whose address does not fit the `address` field, is
305
+ * verified against the zero address and chain id 0.
306
+ *
307
+ * Signing the wrong form throws nothing. It yields a signature that never
308
+ * verifies, the anchor silently stays **provisional**, and a provisional anchor
309
+ * can be superseded by anyone -- the very hijack a signed anchor exists to stop.
310
+ * Reproduced against production by KarmaKadabra, 2026-08-19: with everything else
311
+ * identical, the ed25519 form was refused (`409 dx402_already_anchored`) and the
312
+ * EVM form superseded the provisional.
313
+ *
314
+ * `pointer` stays empty on both branches: this call sends `sealed`, so the
315
+ * facilitator issues the pointer and you cannot sign what you have not seen.
316
+ */
317
+ declare function sellerDigestFor(paymentId: string, contentHash: string, payee: string, network: string): Uint8Array | undefined;
318
+ /**
319
+ * Largest `POST /dx402/anchor` request the facilitator accepts, mirroring its
320
+ * `MAX_REQUEST_BODY_BYTES` (default 64 KiB, an anti-OOM bound on every route).
321
+ * After base64 inflation and ~600 bytes of metadata this leaves ~47 KB of
322
+ * plaintext.
323
+ */
324
+ declare const ANCHOR_MAX_REQUEST_BYTES: number;
287
325
  declare function anchorEvidence(body: Uint8Array, opts: AnchorOptions): Promise<Record<string, unknown>>;
288
326
  /** Encode an anchor result for the `X-Durable-Evidence` response header. */
289
327
  declare function evidenceHeader(evidence: unknown): string;
@@ -935,4 +973,4 @@ interface EscrowPreAuthParams {
935
973
  */
936
974
  declare function buildEscrowPreAuth(wallet: EscrowPreAuthSigner, params: EscrowPreAuthParams): Promise<string>;
937
975
 
938
- export { type AnchorOptions, type AnchoredEvidence, ContentHashMismatch, type CreateSignedFetchConfig, DX402Error, type ERC8128RequestOptions, ESCROW_DEPOSIT_LIMIT_USD, ESCROW_TIER_WINDOWS, EVENT_KINDS, EVIDENCE_HEADER, type EscrowNetworkConfig, type EscrowPaymentInfo, type EscrowPreAuthParams, type EscrowPreAuthSigner, type EscrowTierWindows, type EvidenceMode, EvidenceSkipped, FACILITATOR_ADDRESSES, type FacilitatorAddresses, KEEPALIVE_INTERVAL_MS, OPERATOR_FEE_BPS, type RecipientRole, type SSEFrame, SSEParser, type SignRequestOptions, type SignRequestWithSignerOptions, type SignatureBaseParams, type SignatureHeaders, type SignatureParamsInput, SigningWalletAdapter, type StreamTrafficEventsOptions, type TrafficEvent, type TrafficEventKind, TrafficStreamError, ZERO_ADDRESS, anchorDigest, anchorEvidence, buildEscrowPreAuth, buildSignatureBase, buildSignatureParams, computeEscrowNonce, contentHash, createSignedFetch, dereferencePointer, paymentId as dx402PaymentId, ed25519ToX25519, evidenceFromHeaders, evidenceHeader, fetchNonce, getFacilitatorAddress, isEndToEnd, matchesFilters, parseEvidenceHeader, parseSealed, parseTrafficEvent, payerKeyFromEvmSignature, payerKeyFromSolanaAddress, recoverEvidence, sealEvidence, sealEvidenceTo, sealedRoles, signAnchorEd25519, signAnchorEvm, signRequest, signRequestWithSigner, signRequestWithWallet, streamTrafficEvents, unseal };
976
+ export { ANCHOR_MAX_REQUEST_BYTES, type AnchorOptions, type AnchoredEvidence, ContentHashMismatch, type CreateSignedFetchConfig, DX402Error, type ERC8128RequestOptions, ESCROW_DEPOSIT_LIMIT_USD, ESCROW_TIER_WINDOWS, EVENT_KINDS, EVIDENCE_HEADER, type EscrowNetworkConfig, type EscrowPaymentInfo, type EscrowPreAuthParams, type EscrowPreAuthSigner, type EscrowTierWindows, type EvidenceMode, EvidenceSkipped, FACILITATOR_ADDRESSES, type FacilitatorAddresses, KEEPALIVE_INTERVAL_MS, OPERATOR_FEE_BPS, type RecipientRole, type SSEFrame, SSEParser, type SignRequestOptions, type SignRequestWithSignerOptions, type SignatureBaseParams, type SignatureHeaders, type SignatureParamsInput, SigningWalletAdapter, type StreamTrafficEventsOptions, type TrafficEvent, type TrafficEventKind, TrafficStreamError, ZERO_ADDRESS, anchorDigest, anchorEvidence, buildEscrowPreAuth, buildSignatureBase, buildSignatureParams, computeEscrowNonce, contentHash, createSignedFetch, dereferencePointer, paymentId as dx402PaymentId, ed25519ToX25519, evidenceFromHeaders, evidenceHeader, fetchNonce, getFacilitatorAddress, isEndToEnd, matchesFilters, parseEvidenceHeader, parseSealed, parseTrafficEvent, payerKeyFromEvmSignature, payerKeyFromSolanaAddress, recoverEvidence, sealEvidence, sealEvidenceTo, sealedRoles, sellerDigestFor, signAnchorEd25519, signAnchorEvm, signRequest, signRequestWithSigner, signRequestWithWallet, streamTrafficEvents, unseal };
package/dist/index.js CHANGED
@@ -4,9 +4,9 @@ var aes = require('@noble/ciphers/aes');
4
4
  var ed25519 = require('@noble/curves/ed25519');
5
5
  var secp256k1 = require('@noble/curves/secp256k1');
6
6
  var hkdf = require('@noble/hashes/hkdf');
7
+ var utils = require('@noble/hashes/utils');
7
8
  var sha3 = require('@noble/hashes/sha3');
8
9
  var sha2 = require('@noble/hashes/sha2');
9
- var utils = require('@noble/hashes/utils');
10
10
  var ethers = require('ethers');
11
11
 
12
12
  // src/dx402.ts
@@ -1194,24 +1194,20 @@ function b64urlDecode(value) {
1194
1194
  }
1195
1195
  function hexToBytes(hex) {
1196
1196
  const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
1197
- if (clean.length % 2 !== 0) throw new DX402Error("odd-length hex string");
1198
- const out = new Uint8Array(clean.length / 2);
1199
- for (let i = 0; i < out.length; i++) {
1200
- out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
1197
+ try {
1198
+ return utils.hexToBytes(clean);
1199
+ } catch (e) {
1200
+ throw new DX402Error(`invalid hex: ${e.message}`);
1201
1201
  }
1202
- return out;
1203
- }
1204
- function bytesToHex(bytes) {
1205
- return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
1206
1202
  }
1207
1203
  function contentHash(body) {
1208
- return "0x" + bytesToHex(sha3.keccak_256(body));
1204
+ return "0x" + utils.bytesToHex(sha3.keccak_256(body));
1209
1205
  }
1210
1206
  function paymentId(caip2Network, txHash) {
1211
1207
  const enc = new TextEncoder();
1212
1208
  const clean = txHash.startsWith("0x") ? txHash.slice(2) : txHash;
1213
1209
  const preimage = new Uint8Array([...enc.encode(caip2Network), ...enc.encode(clean)]);
1214
- return "0x" + bytesToHex(sha3.keccak_256(preimage));
1210
+ return "0x" + utils.bytesToHex(sha3.keccak_256(preimage));
1215
1211
  }
1216
1212
  function parseEvidenceHeader(value) {
1217
1213
  let payload;
@@ -1494,12 +1490,12 @@ function signAnchorEd25519(privateKey, paymentId2, contentHash2, pointer = "") {
1494
1490
  throw new DX402Error(`ed25519 seed must be 32 bytes, got ${privateKey.length}`);
1495
1491
  }
1496
1492
  const digest = anchorDigest(paymentId2, contentHash2, pointer, ZERO_ADDRESS, 0);
1497
- return "0x" + bytesToHex(ed25519.ed25519.sign(digest, privateKey));
1493
+ return "0x" + utils.bytesToHex(ed25519.ed25519.sign(digest, privateKey));
1498
1494
  }
1499
1495
  function signAnchorEvm(privateKey, paymentId2, contentHash2, pointer, payee, chainId) {
1500
1496
  const digest = anchorDigest(paymentId2, contentHash2, pointer, payee, chainId);
1501
1497
  const sig = secp256k1.secp256k1.sign(digest, privateKey);
1502
- return "0x" + bytesToHex(sig.toCompactRawBytes()) + (sig.recovery === 1 ? "01" : "00");
1498
+ return "0x" + utils.bytesToHex(sig.toCompactRawBytes()) + (sig.recovery === 1 ? "01" : "00");
1503
1499
  }
1504
1500
  function payerKeyFromSolanaAddress(address) {
1505
1501
  if (!address || !address.trim()) throw new DX402Error("empty Solana address");
@@ -1578,7 +1574,12 @@ function isEvmAddress(value) {
1578
1574
  return /^0x[0-9a-fA-F]{40}$/.test((value ?? "").trim());
1579
1575
  }
1580
1576
  function chainIdFor(network) {
1581
- const name = (network ?? "").includes(":") ? network.split(":")[1] ?? "" : network;
1577
+ const raw = network ?? "";
1578
+ if (raw.toLowerCase().startsWith("eip155:")) {
1579
+ const tail = raw.slice("eip155:".length);
1580
+ if (/^\d+$/.test(tail)) return Number(tail);
1581
+ }
1582
+ const name = raw.includes(":") ? raw.split(":")[1] ?? "" : raw;
1582
1583
  const byId = Number(name);
1583
1584
  if (Number.isFinite(byId) && byId > 0) return getChainById(byId)?.chainId;
1584
1585
  return getChainByName(name)?.chainId;
@@ -1586,7 +1587,8 @@ function chainIdFor(network) {
1586
1587
  function sellerDigestFor(paymentId2, contentHash2, payee, network) {
1587
1588
  if (isEvmAddress(payee)) {
1588
1589
  const chainId = chainIdFor(network);
1589
- if (chainId) return anchorDigest(paymentId2, contentHash2, "", payee, chainId);
1590
+ if (!chainId) return void 0;
1591
+ return anchorDigest(paymentId2, contentHash2, "", payee, chainId);
1590
1592
  }
1591
1593
  return anchorDigest(paymentId2, contentHash2, "", ZERO_ADDRESS, 0);
1592
1594
  }
@@ -1622,10 +1624,17 @@ async function anchorEvidence(body, opts) {
1622
1624
  mode: "direct",
1623
1625
  retention: opts.retention ?? "90d"
1624
1626
  };
1627
+ let unsigned;
1628
+ if (opts.proofOfPayment) {
1629
+ payload.proofOfPayment = opts.proofOfPayment;
1630
+ }
1625
1631
  if (opts.sign) {
1626
- payload.sellerSignature = await opts.sign(
1627
- sellerDigestFor(opts.paymentId, hash, opts.payee, opts.network)
1628
- );
1632
+ const digest = sellerDigestFor(opts.paymentId, hash, opts.payee, opts.network);
1633
+ if (digest === void 0) {
1634
+ unsigned = "unknown_chain_id";
1635
+ } else {
1636
+ payload.sellerSignature = await opts.sign(digest);
1637
+ }
1629
1638
  }
1630
1639
  const wire = JSON.stringify(payload);
1631
1640
  if (new TextEncoder().encode(wire).length > ANCHOR_MAX_REQUEST_BYTES) {
@@ -1649,14 +1658,16 @@ async function anchorEvidence(body, opts) {
1649
1658
  }
1650
1659
  return { v: 1, skipped: "anchor_failed", status: res.status, error };
1651
1660
  }
1652
- return await res.json();
1661
+ const out = await res.json();
1662
+ if (unsigned) out.unsigned = unsigned;
1663
+ return out;
1653
1664
  } catch {
1654
1665
  return { v: 1, skipped: "anchor_failed" };
1655
1666
  }
1656
1667
  }
1657
1668
  function evidenceHeader(evidence) {
1658
- const json = JSON.stringify(evidence);
1659
- return btoa(json).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1669
+ const bytes = new TextEncoder().encode(JSON.stringify(evidence));
1670
+ return toBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1660
1671
  }
1661
1672
 
1662
1673
  // src/types/index.ts
@@ -4289,6 +4300,7 @@ var ESCROW_TIMEOUT_MS = {
4289
4300
  // Monad: 90s
4290
4301
  };
4291
4302
 
4303
+ exports.ANCHOR_MAX_REQUEST_BYTES = ANCHOR_MAX_REQUEST_BYTES;
4292
4304
  exports.CAIP2_IDENTIFIERS = CAIP2_IDENTIFIERS;
4293
4305
  exports.CAIP2_TO_CHAIN = CAIP2_TO_CHAIN;
4294
4306
  exports.ContentHashMismatch = ContentHashMismatch;
@@ -4393,6 +4405,7 @@ exports.recoverEvidence = recoverEvidence;
4393
4405
  exports.sealEvidence = sealEvidence;
4394
4406
  exports.sealEvidenceTo = sealEvidenceTo;
4395
4407
  exports.sealedRoles = sealedRoles;
4408
+ exports.sellerDigestFor = sellerDigestFor;
4396
4409
  exports.signAnchorEd25519 = signAnchorEd25519;
4397
4410
  exports.signAnchorEvm = signAnchorEvm;
4398
4411
  exports.signRequest = signRequest;