uvd-x402-sdk 2.63.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 +89 -0
- package/dist/index.d.mts +43 -1
- package/dist/index.d.ts +43 -1
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +15 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/dx402.ts +59 -0
- package/src/index.ts +2 -0
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)
|
package/dist/index.d.mts
CHANGED
|
@@ -283,6 +283,17 @@ interface AnchorOptions {
|
|
|
283
283
|
* proves more may supersede it.
|
|
284
284
|
*/
|
|
285
285
|
proofOfPayment?: Record<string, unknown>;
|
|
286
|
+
/**
|
|
287
|
+
* Which backend to anchor to (`"s3"`, `"ipfs-private"`, `"ipfs-public"`).
|
|
288
|
+
* Omit to take the facilitator's default.
|
|
289
|
+
*
|
|
290
|
+
* Ask {@link availableBackends} what a given facilitator offers rather than
|
|
291
|
+
* assuming — it depends on the deployment, and you may be pointed at one that
|
|
292
|
+
* is not ours. **`ipfs-public` is irreversible**: the bytes become permanently
|
|
293
|
+
* resolvable by anyone, and nobody, the facilitator included, can take them
|
|
294
|
+
* down.
|
|
295
|
+
*/
|
|
296
|
+
storage?: string;
|
|
286
297
|
/**
|
|
287
298
|
* `(digest) => "0x..."`. A callable rather than a private key is what lets a
|
|
288
299
|
* custodian sign: it receives the digest and returns the signature without the
|
|
@@ -323,6 +334,37 @@ declare function sellerDigestFor(paymentId: string, contentHash: string, payee:
|
|
|
323
334
|
*/
|
|
324
335
|
declare const ANCHOR_MAX_REQUEST_BYTES: number;
|
|
325
336
|
declare function anchorEvidence(body: Uint8Array, opts: AnchorOptions): Promise<Record<string, unknown>>;
|
|
337
|
+
/** One storage option a facilitator offers, from `GET /dx402/stats`. */
|
|
338
|
+
interface BackendOffer {
|
|
339
|
+
id: string;
|
|
340
|
+
retention: string;
|
|
341
|
+
/**
|
|
342
|
+
* Whether the bytes can actually be removed when retention expires.
|
|
343
|
+
*
|
|
344
|
+
* `false` means the `retentionUntil` in the signed receipt **cannot be
|
|
345
|
+
* honoured**: on public IPFS, unpinning removes the facilitator's copy, not
|
|
346
|
+
* the network's.
|
|
347
|
+
*/
|
|
348
|
+
revocable: boolean;
|
|
349
|
+
/** Whether anyone resolves the bytes without going through the facilitator. */
|
|
350
|
+
public: boolean;
|
|
351
|
+
enabled: boolean;
|
|
352
|
+
disabledReason?: string;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Ask a facilitator which storage backends it actually offers.
|
|
356
|
+
*
|
|
357
|
+
* Ask instead of assuming. What exists depends on the deployment — one without
|
|
358
|
+
* a Pinata credential offers only `s3` — and an integrator may be pointed at a
|
|
359
|
+
* facilitator that is not ours. A hardcoded list in your code is a promise
|
|
360
|
+
* somebody else has to keep.
|
|
361
|
+
*
|
|
362
|
+
* Resolves to `[]` rather than rejecting when the facilitator is unreachable or
|
|
363
|
+
* does not run DX402 — same discipline as {@link anchorEvidence}.
|
|
364
|
+
*/
|
|
365
|
+
declare function availableBackends(facilitator?: string, opts?: {
|
|
366
|
+
fetch?: typeof fetch;
|
|
367
|
+
}): Promise<BackendOffer[]>;
|
|
326
368
|
/** Encode an anchor result for the `X-Durable-Evidence` response header. */
|
|
327
369
|
declare function evidenceHeader(evidence: unknown): string;
|
|
328
370
|
|
|
@@ -973,4 +1015,4 @@ interface EscrowPreAuthParams {
|
|
|
973
1015
|
*/
|
|
974
1016
|
declare function buildEscrowPreAuth(wallet: EscrowPreAuthSigner, params: EscrowPreAuthParams): Promise<string>;
|
|
975
1017
|
|
|
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 };
|
|
1018
|
+
export { ANCHOR_MAX_REQUEST_BYTES, type AnchorOptions, type AnchoredEvidence, type BackendOffer, 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, availableBackends, 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
|
@@ -283,6 +283,17 @@ interface AnchorOptions {
|
|
|
283
283
|
* proves more may supersede it.
|
|
284
284
|
*/
|
|
285
285
|
proofOfPayment?: Record<string, unknown>;
|
|
286
|
+
/**
|
|
287
|
+
* Which backend to anchor to (`"s3"`, `"ipfs-private"`, `"ipfs-public"`).
|
|
288
|
+
* Omit to take the facilitator's default.
|
|
289
|
+
*
|
|
290
|
+
* Ask {@link availableBackends} what a given facilitator offers rather than
|
|
291
|
+
* assuming — it depends on the deployment, and you may be pointed at one that
|
|
292
|
+
* is not ours. **`ipfs-public` is irreversible**: the bytes become permanently
|
|
293
|
+
* resolvable by anyone, and nobody, the facilitator included, can take them
|
|
294
|
+
* down.
|
|
295
|
+
*/
|
|
296
|
+
storage?: string;
|
|
286
297
|
/**
|
|
287
298
|
* `(digest) => "0x..."`. A callable rather than a private key is what lets a
|
|
288
299
|
* custodian sign: it receives the digest and returns the signature without the
|
|
@@ -323,6 +334,37 @@ declare function sellerDigestFor(paymentId: string, contentHash: string, payee:
|
|
|
323
334
|
*/
|
|
324
335
|
declare const ANCHOR_MAX_REQUEST_BYTES: number;
|
|
325
336
|
declare function anchorEvidence(body: Uint8Array, opts: AnchorOptions): Promise<Record<string, unknown>>;
|
|
337
|
+
/** One storage option a facilitator offers, from `GET /dx402/stats`. */
|
|
338
|
+
interface BackendOffer {
|
|
339
|
+
id: string;
|
|
340
|
+
retention: string;
|
|
341
|
+
/**
|
|
342
|
+
* Whether the bytes can actually be removed when retention expires.
|
|
343
|
+
*
|
|
344
|
+
* `false` means the `retentionUntil` in the signed receipt **cannot be
|
|
345
|
+
* honoured**: on public IPFS, unpinning removes the facilitator's copy, not
|
|
346
|
+
* the network's.
|
|
347
|
+
*/
|
|
348
|
+
revocable: boolean;
|
|
349
|
+
/** Whether anyone resolves the bytes without going through the facilitator. */
|
|
350
|
+
public: boolean;
|
|
351
|
+
enabled: boolean;
|
|
352
|
+
disabledReason?: string;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Ask a facilitator which storage backends it actually offers.
|
|
356
|
+
*
|
|
357
|
+
* Ask instead of assuming. What exists depends on the deployment — one without
|
|
358
|
+
* a Pinata credential offers only `s3` — and an integrator may be pointed at a
|
|
359
|
+
* facilitator that is not ours. A hardcoded list in your code is a promise
|
|
360
|
+
* somebody else has to keep.
|
|
361
|
+
*
|
|
362
|
+
* Resolves to `[]` rather than rejecting when the facilitator is unreachable or
|
|
363
|
+
* does not run DX402 — same discipline as {@link anchorEvidence}.
|
|
364
|
+
*/
|
|
365
|
+
declare function availableBackends(facilitator?: string, opts?: {
|
|
366
|
+
fetch?: typeof fetch;
|
|
367
|
+
}): Promise<BackendOffer[]>;
|
|
326
368
|
/** Encode an anchor result for the `X-Durable-Evidence` response header. */
|
|
327
369
|
declare function evidenceHeader(evidence: unknown): string;
|
|
328
370
|
|
|
@@ -973,4 +1015,4 @@ interface EscrowPreAuthParams {
|
|
|
973
1015
|
*/
|
|
974
1016
|
declare function buildEscrowPreAuth(wallet: EscrowPreAuthSigner, params: EscrowPreAuthParams): Promise<string>;
|
|
975
1017
|
|
|
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 };
|
|
1018
|
+
export { ANCHOR_MAX_REQUEST_BYTES, type AnchorOptions, type AnchoredEvidence, type BackendOffer, 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, availableBackends, 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
|
@@ -1628,6 +1628,9 @@ async function anchorEvidence(body, opts) {
|
|
|
1628
1628
|
if (opts.proofOfPayment) {
|
|
1629
1629
|
payload.proofOfPayment = opts.proofOfPayment;
|
|
1630
1630
|
}
|
|
1631
|
+
if (opts.storage) {
|
|
1632
|
+
payload.storage = opts.storage;
|
|
1633
|
+
}
|
|
1631
1634
|
if (opts.sign) {
|
|
1632
1635
|
const digest = sellerDigestFor(opts.paymentId, hash, opts.payee, opts.network);
|
|
1633
1636
|
if (digest === void 0) {
|
|
@@ -1665,6 +1668,17 @@ async function anchorEvidence(body, opts) {
|
|
|
1665
1668
|
return { v: 1, skipped: "anchor_failed" };
|
|
1666
1669
|
}
|
|
1667
1670
|
}
|
|
1671
|
+
async function availableBackends(facilitator = "https://facilitator.ultravioletadao.xyz", opts = {}) {
|
|
1672
|
+
try {
|
|
1673
|
+
const doFetch = opts.fetch ?? fetch;
|
|
1674
|
+
const res = await doFetch(`${facilitator.replace(/\/+$/, "")}/dx402/stats`);
|
|
1675
|
+
if (!res.ok) return [];
|
|
1676
|
+
const body = await res.json();
|
|
1677
|
+
return body.backends ?? [];
|
|
1678
|
+
} catch {
|
|
1679
|
+
return [];
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1668
1682
|
function evidenceHeader(evidence) {
|
|
1669
1683
|
const bytes = new TextEncoder().encode(JSON.stringify(evidence));
|
|
1670
1684
|
return toBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
@@ -4331,6 +4345,7 @@ exports.X402_HEADER_NAMES = X402_HEADER_NAMES;
|
|
|
4331
4345
|
exports.ZERO_ADDRESS = ZERO_ADDRESS;
|
|
4332
4346
|
exports.anchorDigest = anchorDigest;
|
|
4333
4347
|
exports.anchorEvidence = anchorEvidence;
|
|
4348
|
+
exports.availableBackends = availableBackends;
|
|
4334
4349
|
exports.buildEscrowPreAuth = buildEscrowPreAuth;
|
|
4335
4350
|
exports.buildPaymentRequirements = buildPaymentRequirements;
|
|
4336
4351
|
exports.buildSettleRequest = buildSettleRequest;
|