uvd-x402-sdk 2.57.0 → 2.58.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
@@ -229,6 +229,71 @@ declare function signAnchorEd25519(privateKey: Uint8Array, paymentId: string, co
229
229
  * anchor provisional.
230
230
  */
231
231
  declare function signAnchorEvm(privateKey: Uint8Array, paymentId: string, contentHash: string, pointer: string, payee: string, chainId: number): string;
232
+ /**
233
+ * Derive the encryption target from a Solana (or Fogo) address.
234
+ *
235
+ * On ed25519 chains the address **is** the public key, so this needs no
236
+ * signature and no lookup. Rejects anything that does not decode to exactly 32
237
+ * bytes: a short decode silently padded up to 32 produces a small-order point,
238
+ * which fails a layer later with a message that points nowhere near the cause.
239
+ */
240
+ declare function payerKeyFromSolanaAddress(address: string): Uint8Array;
241
+ /**
242
+ * Seal `body` so every listed recipient can read it, and nobody else.
243
+ *
244
+ * The body is encrypted **once**; only the content key is wrapped per recipient,
245
+ * so adding the seller costs about sixty bytes rather than a second copy of the
246
+ * payload. That is what makes it practical for a seller to keep a readable copy
247
+ * of what it delivered — and answer a false "that is not what you sent" —
248
+ * instead of paying to anchor evidence it cannot open.
249
+ *
250
+ * A single payer recipient is emitted as format **v1, byte-for-byte**, so
251
+ * nothing already anchored becomes unreadable and readers still on v1 keep
252
+ * working.
253
+ */
254
+ declare function sealEvidenceTo(body: Uint8Array, recipients: Array<{
255
+ role: RecipientRole;
256
+ key: Uint8Array;
257
+ }>, paymentIdValue: string): Uint8Array;
258
+ interface AnchorOptions {
259
+ paymentId: string;
260
+ network: string;
261
+ txHash: string;
262
+ payer: string;
263
+ payee: string;
264
+ /** The buyer's encryption key, from `payerKeyFromSolanaAddress` or similar. */
265
+ payerKey: Uint8Array;
266
+ /**
267
+ * Your **public** key, to keep a readable copy so you can answer a false
268
+ * "that is not what you sent".
269
+ *
270
+ * It does not have to be your payment key, and should not be — a custodial
271
+ * payment wallet works fine here, because this key only ever decrypts.
272
+ */
273
+ sellerEncryptionKey?: Uint8Array;
274
+ /**
275
+ * `(digest) => "0x..."`. A callable rather than a private key is what lets a
276
+ * custodian sign: it receives the digest and returns the signature without the
277
+ * seed ever leaving it.
278
+ *
279
+ * Without one the anchor is **provisional** — it holds the slot, but a signed
280
+ * anchor for the same payment supersedes it.
281
+ */
282
+ sign?: (digest: Uint8Array) => string | Promise<string>;
283
+ retention?: string;
284
+ facilitator?: string;
285
+ fetch?: typeof fetch;
286
+ }
287
+ /**
288
+ * Seal a response body, anchor it, and return the `X-Durable-Evidence` value.
289
+ *
290
+ * **It never throws.** Every failure resolves to a skip notice, because evidence
291
+ * is an addition to the payment path and must never be a gate in front of it —
292
+ * an unreachable facilitator has to cost the receipt, never the sale.
293
+ */
294
+ declare function anchorEvidence(body: Uint8Array, opts: AnchorOptions): Promise<Record<string, unknown>>;
295
+ /** Encode an anchor result for the `X-Durable-Evidence` response header. */
296
+ declare function evidenceHeader(evidence: unknown): string;
232
297
 
233
298
  /**
234
299
  * Facilitator wallet addresses by chain type
@@ -877,4 +942,4 @@ interface EscrowPreAuthParams {
877
942
  */
878
943
  declare function buildEscrowPreAuth(wallet: EscrowPreAuthSigner, params: EscrowPreAuthParams): Promise<string>;
879
944
 
880
- export { 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, buildEscrowPreAuth, buildSignatureBase, buildSignatureParams, computeEscrowNonce, contentHash, createSignedFetch, dereferencePointer, paymentId as dx402PaymentId, ed25519ToX25519, evidenceFromHeaders, fetchNonce, getFacilitatorAddress, isEndToEnd, matchesFilters, parseEvidenceHeader, parseSealed, parseTrafficEvent, payerKeyFromEvmSignature, recoverEvidence, sealEvidence, sealedRoles, signAnchorEd25519, signAnchorEvm, signRequest, signRequestWithSigner, signRequestWithWallet, streamTrafficEvents, unseal };
945
+ 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 };
package/dist/index.d.ts CHANGED
@@ -229,6 +229,71 @@ declare function signAnchorEd25519(privateKey: Uint8Array, paymentId: string, co
229
229
  * anchor provisional.
230
230
  */
231
231
  declare function signAnchorEvm(privateKey: Uint8Array, paymentId: string, contentHash: string, pointer: string, payee: string, chainId: number): string;
232
+ /**
233
+ * Derive the encryption target from a Solana (or Fogo) address.
234
+ *
235
+ * On ed25519 chains the address **is** the public key, so this needs no
236
+ * signature and no lookup. Rejects anything that does not decode to exactly 32
237
+ * bytes: a short decode silently padded up to 32 produces a small-order point,
238
+ * which fails a layer later with a message that points nowhere near the cause.
239
+ */
240
+ declare function payerKeyFromSolanaAddress(address: string): Uint8Array;
241
+ /**
242
+ * Seal `body` so every listed recipient can read it, and nobody else.
243
+ *
244
+ * The body is encrypted **once**; only the content key is wrapped per recipient,
245
+ * so adding the seller costs about sixty bytes rather than a second copy of the
246
+ * payload. That is what makes it practical for a seller to keep a readable copy
247
+ * of what it delivered — and answer a false "that is not what you sent" —
248
+ * instead of paying to anchor evidence it cannot open.
249
+ *
250
+ * A single payer recipient is emitted as format **v1, byte-for-byte**, so
251
+ * nothing already anchored becomes unreadable and readers still on v1 keep
252
+ * working.
253
+ */
254
+ declare function sealEvidenceTo(body: Uint8Array, recipients: Array<{
255
+ role: RecipientRole;
256
+ key: Uint8Array;
257
+ }>, paymentIdValue: string): Uint8Array;
258
+ interface AnchorOptions {
259
+ paymentId: string;
260
+ network: string;
261
+ txHash: string;
262
+ payer: string;
263
+ payee: string;
264
+ /** The buyer's encryption key, from `payerKeyFromSolanaAddress` or similar. */
265
+ payerKey: Uint8Array;
266
+ /**
267
+ * Your **public** key, to keep a readable copy so you can answer a false
268
+ * "that is not what you sent".
269
+ *
270
+ * It does not have to be your payment key, and should not be — a custodial
271
+ * payment wallet works fine here, because this key only ever decrypts.
272
+ */
273
+ sellerEncryptionKey?: Uint8Array;
274
+ /**
275
+ * `(digest) => "0x..."`. A callable rather than a private key is what lets a
276
+ * custodian sign: it receives the digest and returns the signature without the
277
+ * seed ever leaving it.
278
+ *
279
+ * Without one the anchor is **provisional** — it holds the slot, but a signed
280
+ * anchor for the same payment supersedes it.
281
+ */
282
+ sign?: (digest: Uint8Array) => string | Promise<string>;
283
+ retention?: string;
284
+ facilitator?: string;
285
+ fetch?: typeof fetch;
286
+ }
287
+ /**
288
+ * Seal a response body, anchor it, and return the `X-Durable-Evidence` value.
289
+ *
290
+ * **It never throws.** Every failure resolves to a skip notice, because evidence
291
+ * is an addition to the payment path and must never be a gate in front of it —
292
+ * an unreachable facilitator has to cost the receipt, never the sale.
293
+ */
294
+ declare function anchorEvidence(body: Uint8Array, opts: AnchorOptions): Promise<Record<string, unknown>>;
295
+ /** Encode an anchor result for the `X-Durable-Evidence` response header. */
296
+ declare function evidenceHeader(evidence: unknown): string;
232
297
 
233
298
  /**
234
299
  * Facilitator wallet addresses by chain type
@@ -877,4 +942,4 @@ interface EscrowPreAuthParams {
877
942
  */
878
943
  declare function buildEscrowPreAuth(wallet: EscrowPreAuthSigner, params: EscrowPreAuthParams): Promise<string>;
879
944
 
880
- export { 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, buildEscrowPreAuth, buildSignatureBase, buildSignatureParams, computeEscrowNonce, contentHash, createSignedFetch, dereferencePointer, paymentId as dx402PaymentId, ed25519ToX25519, evidenceFromHeaders, fetchNonce, getFacilitatorAddress, isEndToEnd, matchesFilters, parseEvidenceHeader, parseSealed, parseTrafficEvent, payerKeyFromEvmSignature, recoverEvidence, sealEvidence, sealedRoles, signAnchorEd25519, signAnchorEvm, signRequest, signRequestWithSigner, signRequestWithWallet, streamTrafficEvents, unseal };
945
+ 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 };
package/dist/index.js CHANGED
@@ -356,6 +356,127 @@ function signAnchorEvm(privateKey, paymentId2, contentHash2, pointer, payee, cha
356
356
  const sig = secp256k1.secp256k1.sign(digest, privateKey);
357
357
  return "0x" + bytesToHex(sig.toCompactRawBytes()) + (sig.recovery === 1 ? "01" : "00");
358
358
  }
359
+ function payerKeyFromSolanaAddress(address) {
360
+ if (!address || !address.trim()) throw new DX402Error("empty Solana address");
361
+ const alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
362
+ let num = 0n;
363
+ for (const ch of address) {
364
+ const idx = alphabet.indexOf(ch);
365
+ if (idx < 0) throw new DX402Error(`invalid base58 character '${ch}' in address`);
366
+ num = num * 58n + BigInt(idx);
367
+ }
368
+ let hex = num.toString(16);
369
+ if (hex.length % 2) hex = "0" + hex;
370
+ const body = hexToBytes(hex);
371
+ const leading = address.length - address.replace(/^1+/, "").length;
372
+ const decoded = new Uint8Array(leading + body.length);
373
+ decoded.set(body, leading);
374
+ if (decoded.length !== 32) {
375
+ throw new DX402Error(`Solana address decodes to ${decoded.length} bytes, expected 32`);
376
+ }
377
+ return ed25519ToX25519(decoded);
378
+ }
379
+ function sealEvidenceTo(body, recipients, paymentIdValue) {
380
+ if (recipients.length === 0) {
381
+ throw new DX402Error("an envelope with no recipients could never be opened");
382
+ }
383
+ const aad = new TextEncoder().encode(paymentIdValue);
384
+ const cek = utils.randomBytes(CEK_LEN);
385
+ const bodyNonce = utils.randomBytes(NONCE_LEN);
386
+ const ciphertext = aes.gcm(cek, bodyNonce, aad).encrypt(body);
387
+ const wrapped = recipients.map(({ role, key }) => {
388
+ let algByte;
389
+ let ephemeral;
390
+ let shared;
391
+ if (key.length === 33) {
392
+ algByte = 1;
393
+ const priv = secp256k1.secp256k1.utils.randomPrivateKey();
394
+ ephemeral = secp256k1.secp256k1.getPublicKey(priv, true);
395
+ shared = secp256k1.secp256k1.getSharedSecret(priv, key, true).subarray(1);
396
+ } else if (key.length === 32) {
397
+ algByte = 2;
398
+ const priv = utils.randomBytes(32);
399
+ ephemeral = ed25519.x25519.getPublicKey(priv);
400
+ shared = ed25519.x25519.getSharedSecret(priv, key);
401
+ if (shared.every((b) => b === 0)) {
402
+ throw new DX402Error("degenerate ECDH result (small-order public key)");
403
+ }
404
+ } else {
405
+ throw new DX402Error(
406
+ `public key must be 33 bytes (secp256k1) or 32 (X25519), got ${key.length}`
407
+ );
408
+ }
409
+ const wrapKey = hkdf.hkdf(sha2.sha256, shared, aad, HKDF_INFO, 32);
410
+ const cekNonce = utils.randomBytes(NONCE_LEN);
411
+ return {
412
+ role,
413
+ algByte,
414
+ ephemeral,
415
+ cekNonce,
416
+ wrappedCek: aes.gcm(wrapKey, cekNonce, aad).encrypt(cek)
417
+ };
418
+ });
419
+ const singlePayer = wrapped.length === 1 && wrapped[0].role === "payer";
420
+ const parts = [MAGIC, new Uint8Array([singlePayer ? FORMAT_V1 : FORMAT_V2])];
421
+ if (!singlePayer) parts.push(new Uint8Array([wrapped.length]));
422
+ for (const r of wrapped) {
423
+ if (!singlePayer) parts.push(new Uint8Array([ROLE_NAMES.indexOf(r.role)]));
424
+ parts.push(new Uint8Array([r.algByte, r.ephemeral.length]));
425
+ parts.push(r.ephemeral, r.cekNonce);
426
+ parts.push(new Uint8Array([r.wrappedCek.length >> 8 & 255, r.wrappedCek.length & 255]));
427
+ parts.push(r.wrappedCek);
428
+ }
429
+ parts.push(bodyNonce, ciphertext);
430
+ return concatBytes(...parts);
431
+ }
432
+ async function anchorEvidence(body, opts) {
433
+ try {
434
+ const recipients = [
435
+ { role: "payer", key: opts.payerKey }
436
+ ];
437
+ if (opts.sellerEncryptionKey) {
438
+ recipients.push({ role: "seller", key: opts.sellerEncryptionKey });
439
+ }
440
+ const blob = sealEvidenceTo(body, recipients, opts.paymentId);
441
+ const hash = contentHash(body);
442
+ const payload = {
443
+ paymentId: opts.paymentId,
444
+ network: opts.network,
445
+ txHash: opts.txHash,
446
+ payer: opts.payer,
447
+ payee: opts.payee,
448
+ sealed: btoa(String.fromCharCode(...blob)),
449
+ backend: "s3",
450
+ contentHash: hash,
451
+ keyAlg: opts.payerKey.length === 32 ? "ECIES-X25519" : "ECIES-secp256k1",
452
+ mode: "direct",
453
+ retention: opts.retention ?? "90d"
454
+ };
455
+ if (opts.sign) {
456
+ payload.sellerSignature = await opts.sign(
457
+ anchorDigest(opts.paymentId, hash, "", ZERO_ADDRESS, 0)
458
+ );
459
+ }
460
+ const base = (opts.facilitator ?? "https://facilitator.ultravioletadao.xyz").replace(
461
+ /\/+$/,
462
+ ""
463
+ );
464
+ const doFetch = opts.fetch ?? fetch;
465
+ const res = await doFetch(`${base}/dx402/anchor`, {
466
+ method: "POST",
467
+ headers: { "content-type": "application/json" },
468
+ body: JSON.stringify(payload)
469
+ });
470
+ if (!res.ok) return { v: 1, skipped: "anchor_failed" };
471
+ return await res.json();
472
+ } catch {
473
+ return { v: 1, skipped: "anchor_failed" };
474
+ }
475
+ }
476
+ function evidenceHeader(evidence) {
477
+ const json = JSON.stringify(evidence);
478
+ return btoa(json).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
479
+ }
359
480
 
360
481
  // src/types/index.ts
361
482
  var CAIP2_IDENTIFIERS = {
@@ -4161,6 +4282,7 @@ exports.X402_CORS_HEADERS = X402_CORS_HEADERS;
4161
4282
  exports.X402_HEADER_NAMES = X402_HEADER_NAMES;
4162
4283
  exports.ZERO_ADDRESS = ZERO_ADDRESS;
4163
4284
  exports.anchorDigest = anchorDigest;
4285
+ exports.anchorEvidence = anchorEvidence;
4164
4286
  exports.buildEscrowPreAuth = buildEscrowPreAuth;
4165
4287
  exports.buildPaymentRequirements = buildPaymentRequirements;
4166
4288
  exports.buildSettleRequest = buildSettleRequest;
@@ -4193,6 +4315,7 @@ exports.encodeBase64Json = encodeBase64Json;
4193
4315
  exports.encodeBase64Utf8 = encodeBase64Utf8;
4194
4316
  exports.encodeX402Header = encodeX402Header;
4195
4317
  exports.evidenceFromHeaders = evidenceFromHeaders;
4318
+ exports.evidenceHeader = evidenceHeader;
4196
4319
  exports.extractPaymentFromHeaders = extractPaymentFromHeaders;
4197
4320
  exports.fetchNonce = fetchNonce;
4198
4321
  exports.generatePaymentOptions = generatePaymentOptions;
@@ -4229,8 +4352,10 @@ exports.parseNetworkIdentifier = parseNetworkIdentifier;
4229
4352
  exports.parseSealed = parseSealed;
4230
4353
  exports.parseTrafficEvent = parseTrafficEvent;
4231
4354
  exports.payerKeyFromEvmSignature = payerKeyFromEvmSignature;
4355
+ exports.payerKeyFromSolanaAddress = payerKeyFromSolanaAddress;
4232
4356
  exports.recoverEvidence = recoverEvidence;
4233
4357
  exports.sealEvidence = sealEvidence;
4358
+ exports.sealEvidenceTo = sealEvidenceTo;
4234
4359
  exports.sealedRoles = sealedRoles;
4235
4360
  exports.signAnchorEd25519 = signAnchorEd25519;
4236
4361
  exports.signAnchorEvm = signAnchorEvm;