uvd-x402-sdk 2.56.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 +102 -1
- package/dist/index.d.ts +102 -1
- package/dist/index.js +198 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +192 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/dx402.ts +352 -1
- package/src/dx402.vectors.json +17 -0
- package/src/index.ts +9 -1
package/dist/index.d.mts
CHANGED
|
@@ -193,6 +193,107 @@ declare function payerKeyFromEvmSignature(signature: Uint8Array | string, digest
|
|
|
193
193
|
* Returns the bytes to upload. Nothing here touches the network.
|
|
194
194
|
*/
|
|
195
195
|
declare function sealEvidence(body: Uint8Array, payerKey: Uint8Array, paymentIdValue: string): Uint8Array;
|
|
196
|
+
/** The zero address, for the ed25519 form of the digest. */
|
|
197
|
+
declare const ZERO_ADDRESS: string;
|
|
198
|
+
/**
|
|
199
|
+
* The 32-byte digest a seller signs to prove an anchor is theirs.
|
|
200
|
+
*
|
|
201
|
+
* One canonical message across every curve. `payee` is the EVM address for a
|
|
202
|
+
* secp256k1 payee and the **zero address** for an ed25519 one — an ed25519
|
|
203
|
+
* address does not fit the `address` field, and the binding is already
|
|
204
|
+
* established by which key verifies the signature.
|
|
205
|
+
*
|
|
206
|
+
* `pointer` is whatever you send in the anchor, or the **empty string** when you
|
|
207
|
+
* send `sealed` and the facilitator issues the pointer itself: you cannot sign a
|
|
208
|
+
* value you have not seen.
|
|
209
|
+
*
|
|
210
|
+
* Getting this wrong throws nothing — it produces a signature that simply never
|
|
211
|
+
* verifies, and the anchor stays provisional with no clue why. The tests pin it
|
|
212
|
+
* against digests emitted by the facilitator's own Rust implementation.
|
|
213
|
+
*/
|
|
214
|
+
declare function anchorDigest(paymentId: string, contentHash: string, pointer: string, payee: string, chainId: number): Uint8Array;
|
|
215
|
+
/**
|
|
216
|
+
* Sign an anchor authorization with a Solana / Stellar ed25519 key.
|
|
217
|
+
*
|
|
218
|
+
* A Solana payee cannot produce an EIP-712 signature at all — its address is an
|
|
219
|
+
* ed25519 key — so requiring one would leave that chain unable to prove
|
|
220
|
+
* authorship even once the on-chain gate is enforced. This closes it today, with
|
|
221
|
+
* no RPC.
|
|
222
|
+
*/
|
|
223
|
+
declare function signAnchorEd25519(privateKey: Uint8Array, paymentId: string, contentHash: string, pointer?: string): string;
|
|
224
|
+
/**
|
|
225
|
+
* Sign an anchor authorization with an EVM secp256k1 key.
|
|
226
|
+
*
|
|
227
|
+
* `payee` must be the address of `privateKey` — the facilitator recovers the
|
|
228
|
+
* signer and compares, so declaring somebody else's address simply leaves the
|
|
229
|
+
* anchor provisional.
|
|
230
|
+
*/
|
|
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;
|
|
196
297
|
|
|
197
298
|
/**
|
|
198
299
|
* Facilitator wallet addresses by chain type
|
|
@@ -841,4 +942,4 @@ interface EscrowPreAuthParams {
|
|
|
841
942
|
*/
|
|
842
943
|
declare function buildEscrowPreAuth(wallet: EscrowPreAuthSigner, params: EscrowPreAuthParams): Promise<string>;
|
|
843
944
|
|
|
844
|
-
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, buildEscrowPreAuth, buildSignatureBase, buildSignatureParams, computeEscrowNonce, contentHash, createSignedFetch, dereferencePointer, paymentId as dx402PaymentId, ed25519ToX25519, evidenceFromHeaders, fetchNonce, getFacilitatorAddress, isEndToEnd, matchesFilters, parseEvidenceHeader, parseSealed, parseTrafficEvent, payerKeyFromEvmSignature, recoverEvidence, sealEvidence, sealedRoles, 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
|
@@ -193,6 +193,107 @@ declare function payerKeyFromEvmSignature(signature: Uint8Array | string, digest
|
|
|
193
193
|
* Returns the bytes to upload. Nothing here touches the network.
|
|
194
194
|
*/
|
|
195
195
|
declare function sealEvidence(body: Uint8Array, payerKey: Uint8Array, paymentIdValue: string): Uint8Array;
|
|
196
|
+
/** The zero address, for the ed25519 form of the digest. */
|
|
197
|
+
declare const ZERO_ADDRESS: string;
|
|
198
|
+
/**
|
|
199
|
+
* The 32-byte digest a seller signs to prove an anchor is theirs.
|
|
200
|
+
*
|
|
201
|
+
* One canonical message across every curve. `payee` is the EVM address for a
|
|
202
|
+
* secp256k1 payee and the **zero address** for an ed25519 one — an ed25519
|
|
203
|
+
* address does not fit the `address` field, and the binding is already
|
|
204
|
+
* established by which key verifies the signature.
|
|
205
|
+
*
|
|
206
|
+
* `pointer` is whatever you send in the anchor, or the **empty string** when you
|
|
207
|
+
* send `sealed` and the facilitator issues the pointer itself: you cannot sign a
|
|
208
|
+
* value you have not seen.
|
|
209
|
+
*
|
|
210
|
+
* Getting this wrong throws nothing — it produces a signature that simply never
|
|
211
|
+
* verifies, and the anchor stays provisional with no clue why. The tests pin it
|
|
212
|
+
* against digests emitted by the facilitator's own Rust implementation.
|
|
213
|
+
*/
|
|
214
|
+
declare function anchorDigest(paymentId: string, contentHash: string, pointer: string, payee: string, chainId: number): Uint8Array;
|
|
215
|
+
/**
|
|
216
|
+
* Sign an anchor authorization with a Solana / Stellar ed25519 key.
|
|
217
|
+
*
|
|
218
|
+
* A Solana payee cannot produce an EIP-712 signature at all — its address is an
|
|
219
|
+
* ed25519 key — so requiring one would leave that chain unable to prove
|
|
220
|
+
* authorship even once the on-chain gate is enforced. This closes it today, with
|
|
221
|
+
* no RPC.
|
|
222
|
+
*/
|
|
223
|
+
declare function signAnchorEd25519(privateKey: Uint8Array, paymentId: string, contentHash: string, pointer?: string): string;
|
|
224
|
+
/**
|
|
225
|
+
* Sign an anchor authorization with an EVM secp256k1 key.
|
|
226
|
+
*
|
|
227
|
+
* `payee` must be the address of `privateKey` — the facilitator recovers the
|
|
228
|
+
* signer and compares, so declaring somebody else's address simply leaves the
|
|
229
|
+
* anchor provisional.
|
|
230
|
+
*/
|
|
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;
|
|
196
297
|
|
|
197
298
|
/**
|
|
198
299
|
* Facilitator wallet addresses by chain type
|
|
@@ -841,4 +942,4 @@ interface EscrowPreAuthParams {
|
|
|
841
942
|
*/
|
|
842
943
|
declare function buildEscrowPreAuth(wallet: EscrowPreAuthSigner, params: EscrowPreAuthParams): Promise<string>;
|
|
843
944
|
|
|
844
|
-
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, buildEscrowPreAuth, buildSignatureBase, buildSignatureParams, computeEscrowNonce, contentHash, createSignedFetch, dereferencePointer, paymentId as dx402PaymentId, ed25519ToX25519, evidenceFromHeaders, fetchNonce, getFacilitatorAddress, isEndToEnd, matchesFilters, parseEvidenceHeader, parseSealed, parseTrafficEvent, payerKeyFromEvmSignature, recoverEvidence, sealEvidence, sealedRoles, 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
|
@@ -289,6 +289,194 @@ function sealEvidence(body, payerKey, paymentIdValue) {
|
|
|
289
289
|
out.set(ciphertext, pos);
|
|
290
290
|
return out;
|
|
291
291
|
}
|
|
292
|
+
var ANCHOR_DOMAIN_NAME = "DX402 Anchor";
|
|
293
|
+
var ANCHOR_DOMAIN_VERSION = "1";
|
|
294
|
+
var ANCHOR_TYPE = "Dx402AnchorAuthorization(bytes32 paymentId,bytes32 contentHash,string pointer,address payee)";
|
|
295
|
+
var EIP712_DOMAIN_TYPE = "EIP712Domain(string name,string version,uint256 chainId)";
|
|
296
|
+
var ZERO_ADDRESS = "0x" + "00".repeat(20);
|
|
297
|
+
function concatBytes(...parts) {
|
|
298
|
+
const total = parts.reduce((n, p) => n + p.length, 0);
|
|
299
|
+
const out = new Uint8Array(total);
|
|
300
|
+
let pos = 0;
|
|
301
|
+
for (const p of parts) {
|
|
302
|
+
out.set(p, pos);
|
|
303
|
+
pos += p.length;
|
|
304
|
+
}
|
|
305
|
+
return out;
|
|
306
|
+
}
|
|
307
|
+
function uint256(n) {
|
|
308
|
+
const out = new Uint8Array(32);
|
|
309
|
+
let v = BigInt(n);
|
|
310
|
+
for (let i = 31; i >= 0 && v > 0n; i--) {
|
|
311
|
+
out[i] = Number(v & 0xffn);
|
|
312
|
+
v >>= 8n;
|
|
313
|
+
}
|
|
314
|
+
return out;
|
|
315
|
+
}
|
|
316
|
+
function anchorDigest(paymentId2, contentHash2, pointer, payee, chainId) {
|
|
317
|
+
const b32 = (value, field) => {
|
|
318
|
+
const raw = hexToBytes(value);
|
|
319
|
+
if (raw.length !== 32) throw new DX402Error(`${field} must be 32 bytes, got ${raw.length}`);
|
|
320
|
+
return raw;
|
|
321
|
+
};
|
|
322
|
+
const addr = hexToBytes(payee);
|
|
323
|
+
if (addr.length !== 20) {
|
|
324
|
+
throw new DX402Error(`payee must be a 20-byte address, got ${addr.length}`);
|
|
325
|
+
}
|
|
326
|
+
const enc = new TextEncoder();
|
|
327
|
+
const domainSeparator = sha3.keccak_256(
|
|
328
|
+
concatBytes(
|
|
329
|
+
sha3.keccak_256(enc.encode(EIP712_DOMAIN_TYPE)),
|
|
330
|
+
sha3.keccak_256(enc.encode(ANCHOR_DOMAIN_NAME)),
|
|
331
|
+
sha3.keccak_256(enc.encode(ANCHOR_DOMAIN_VERSION)),
|
|
332
|
+
uint256(chainId)
|
|
333
|
+
)
|
|
334
|
+
);
|
|
335
|
+
const structHash = sha3.keccak_256(
|
|
336
|
+
concatBytes(
|
|
337
|
+
sha3.keccak_256(enc.encode(ANCHOR_TYPE)),
|
|
338
|
+
b32(paymentId2, "paymentId"),
|
|
339
|
+
b32(contentHash2, "contentHash"),
|
|
340
|
+
sha3.keccak_256(enc.encode(pointer)),
|
|
341
|
+
new Uint8Array(12),
|
|
342
|
+
addr
|
|
343
|
+
)
|
|
344
|
+
);
|
|
345
|
+
return sha3.keccak_256(concatBytes(new Uint8Array([25, 1]), domainSeparator, structHash));
|
|
346
|
+
}
|
|
347
|
+
function signAnchorEd25519(privateKey, paymentId2, contentHash2, pointer = "") {
|
|
348
|
+
if (privateKey.length !== 32) {
|
|
349
|
+
throw new DX402Error(`ed25519 seed must be 32 bytes, got ${privateKey.length}`);
|
|
350
|
+
}
|
|
351
|
+
const digest = anchorDigest(paymentId2, contentHash2, pointer, ZERO_ADDRESS, 0);
|
|
352
|
+
return "0x" + bytesToHex(ed25519.ed25519.sign(digest, privateKey));
|
|
353
|
+
}
|
|
354
|
+
function signAnchorEvm(privateKey, paymentId2, contentHash2, pointer, payee, chainId) {
|
|
355
|
+
const digest = anchorDigest(paymentId2, contentHash2, pointer, payee, chainId);
|
|
356
|
+
const sig = secp256k1.secp256k1.sign(digest, privateKey);
|
|
357
|
+
return "0x" + bytesToHex(sig.toCompactRawBytes()) + (sig.recovery === 1 ? "01" : "00");
|
|
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
|
+
}
|
|
292
480
|
|
|
293
481
|
// src/types/index.ts
|
|
294
482
|
var CAIP2_IDENTIFIERS = {
|
|
@@ -3118,7 +3306,7 @@ function buildSignatureParams(params) {
|
|
|
3118
3306
|
parts.push(`alg="${ALG}"`);
|
|
3119
3307
|
return parts.join(";");
|
|
3120
3308
|
}
|
|
3121
|
-
var
|
|
3309
|
+
var ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000";
|
|
3122
3310
|
var USDC_DECIMALS = 6;
|
|
3123
3311
|
var ESCROW_DEPOSIT_LIMIT_USD = 100;
|
|
3124
3312
|
var OPERATOR_FEE_BPS = 1300;
|
|
@@ -3167,7 +3355,7 @@ function computeEscrowNonce(chainId, escrowAddress, paymentInfoTypehash, pi) {
|
|
|
3167
3355
|
const coder = ethers.ethers.AbiCoder.defaultAbiCoder();
|
|
3168
3356
|
const piTuple = [
|
|
3169
3357
|
ethers.ethers.getAddress(pi.operator),
|
|
3170
|
-
|
|
3358
|
+
ZERO_ADDRESS2,
|
|
3171
3359
|
// payer = 0 for the payer-agnostic hash
|
|
3172
3360
|
ethers.ethers.getAddress(pi.receiver),
|
|
3173
3361
|
ethers.ethers.getAddress(pi.token),
|
|
@@ -4092,6 +4280,9 @@ exports.X402Client = X402Client;
|
|
|
4092
4280
|
exports.X402Error = X402Error;
|
|
4093
4281
|
exports.X402_CORS_HEADERS = X402_CORS_HEADERS;
|
|
4094
4282
|
exports.X402_HEADER_NAMES = X402_HEADER_NAMES;
|
|
4283
|
+
exports.ZERO_ADDRESS = ZERO_ADDRESS;
|
|
4284
|
+
exports.anchorDigest = anchorDigest;
|
|
4285
|
+
exports.anchorEvidence = anchorEvidence;
|
|
4095
4286
|
exports.buildEscrowPreAuth = buildEscrowPreAuth;
|
|
4096
4287
|
exports.buildPaymentRequirements = buildPaymentRequirements;
|
|
4097
4288
|
exports.buildSettleRequest = buildSettleRequest;
|
|
@@ -4124,6 +4315,7 @@ exports.encodeBase64Json = encodeBase64Json;
|
|
|
4124
4315
|
exports.encodeBase64Utf8 = encodeBase64Utf8;
|
|
4125
4316
|
exports.encodeX402Header = encodeX402Header;
|
|
4126
4317
|
exports.evidenceFromHeaders = evidenceFromHeaders;
|
|
4318
|
+
exports.evidenceHeader = evidenceHeader;
|
|
4127
4319
|
exports.extractPaymentFromHeaders = extractPaymentFromHeaders;
|
|
4128
4320
|
exports.fetchNonce = fetchNonce;
|
|
4129
4321
|
exports.generatePaymentOptions = generatePaymentOptions;
|
|
@@ -4160,9 +4352,13 @@ exports.parseNetworkIdentifier = parseNetworkIdentifier;
|
|
|
4160
4352
|
exports.parseSealed = parseSealed;
|
|
4161
4353
|
exports.parseTrafficEvent = parseTrafficEvent;
|
|
4162
4354
|
exports.payerKeyFromEvmSignature = payerKeyFromEvmSignature;
|
|
4355
|
+
exports.payerKeyFromSolanaAddress = payerKeyFromSolanaAddress;
|
|
4163
4356
|
exports.recoverEvidence = recoverEvidence;
|
|
4164
4357
|
exports.sealEvidence = sealEvidence;
|
|
4358
|
+
exports.sealEvidenceTo = sealEvidenceTo;
|
|
4165
4359
|
exports.sealedRoles = sealedRoles;
|
|
4360
|
+
exports.signAnchorEd25519 = signAnchorEd25519;
|
|
4361
|
+
exports.signAnchorEvm = signAnchorEvm;
|
|
4166
4362
|
exports.signRequest = signRequest;
|
|
4167
4363
|
exports.signRequestWithSigner = signRequestWithSigner;
|
|
4168
4364
|
exports.signRequestWithWallet = signRequestWithWallet;
|