uvd-x402-sdk 2.95.0 → 2.97.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 +34 -5
- package/dist/adapters/index.d.mts +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +2 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/index.mjs +2 -1
- package/dist/adapters/index.mjs.map +1 -1
- package/dist/backend/index.d.mts +3 -2
- package/dist/backend/index.d.ts +3 -2
- package/dist/backend/index.js +498 -30
- package/dist/backend/index.js.map +1 -1
- package/dist/backend/index.mjs +475 -31
- package/dist/backend/index.mjs.map +1 -1
- package/dist/{index-CF2HBEAJ.d.mts → index--bevvYU9.d.ts} +3 -1
- package/dist/{index-CkHu2s9r.d.ts → index-B2i1mGpW.d.mts} +3 -1
- package/dist/{index-DnxubrYX.d.mts → index-BsKsZzP3.d.mts} +1 -1
- package/dist/{index-DnxubrYX.d.ts → index-BsKsZzP3.d.ts} +1 -1
- package/dist/{index-CIUGPbu8.d.mts → index-ClOAmprt.d.mts} +232 -18
- package/dist/{index-DTbVW7EU.d.ts → index-DNTFp6WT.d.ts} +232 -18
- package/dist/index.d.mts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +512 -43
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +489 -44
- package/dist/index.mjs.map +1 -1
- package/dist/providers/algorand/index.d.mts +1 -1
- package/dist/providers/algorand/index.d.ts +1 -1
- package/dist/providers/algorand/index.js +2 -1
- package/dist/providers/algorand/index.js.map +1 -1
- package/dist/providers/algorand/index.mjs +2 -1
- package/dist/providers/algorand/index.mjs.map +1 -1
- package/dist/providers/evm/index.d.mts +1 -1
- package/dist/providers/evm/index.d.ts +1 -1
- package/dist/providers/evm/index.js +2 -1
- package/dist/providers/evm/index.js.map +1 -1
- package/dist/providers/evm/index.mjs +2 -1
- package/dist/providers/evm/index.mjs.map +1 -1
- package/dist/providers/hedera/index.d.mts +1 -1
- package/dist/providers/hedera/index.d.ts +1 -1
- package/dist/providers/near/index.d.mts +1 -1
- package/dist/providers/near/index.d.ts +1 -1
- package/dist/providers/near/index.js +2 -1
- package/dist/providers/near/index.js.map +1 -1
- package/dist/providers/near/index.mjs +2 -1
- package/dist/providers/near/index.mjs.map +1 -1
- package/dist/providers/solana/index.d.mts +1 -1
- package/dist/providers/solana/index.d.ts +1 -1
- package/dist/providers/solana/index.js +2 -1
- package/dist/providers/solana/index.js.map +1 -1
- package/dist/providers/solana/index.mjs +2 -1
- package/dist/providers/solana/index.mjs.map +1 -1
- package/dist/providers/stellar/index.d.mts +1 -1
- package/dist/providers/stellar/index.d.ts +1 -1
- package/dist/providers/stellar/index.js +2 -1
- package/dist/providers/stellar/index.js.map +1 -1
- package/dist/providers/stellar/index.mjs +2 -1
- package/dist/providers/stellar/index.mjs.map +1 -1
- package/dist/providers/sui/index.d.mts +1 -1
- package/dist/providers/sui/index.d.ts +1 -1
- package/dist/providers/sui/index.js +2 -1
- package/dist/providers/sui/index.js.map +1 -1
- package/dist/providers/sui/index.mjs +2 -1
- package/dist/providers/sui/index.mjs.map +1 -1
- package/dist/providers/xrpl/index.d.mts +1 -1
- package/dist/providers/xrpl/index.d.ts +1 -1
- package/dist/providers/xrpl/index.js +2 -1
- package/dist/providers/xrpl/index.js.map +1 -1
- package/dist/providers/xrpl/index.mjs +2 -1
- package/dist/providers/xrpl/index.mjs.map +1 -1
- package/dist/react/index.d.mts +4 -3
- package/dist/react/index.d.ts +4 -3
- package/dist/react/index.js +153 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +153 -1
- package/dist/react/index.mjs.map +1 -1
- package/dist/react/picker/index.d.mts +1 -1
- package/dist/react/picker/index.d.ts +1 -1
- package/dist/react/picker/index.js +2 -1
- package/dist/react/picker/index.js.map +1 -1
- package/dist/react/picker/index.mjs +2 -1
- package/dist/react/picker/index.mjs.map +1 -1
- package/dist/receipts-Co6C9FPO.d.ts +111 -0
- package/dist/receipts-D0zg-8uK.d.mts +111 -0
- package/dist/utils/index.d.mts +2 -2
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.js +2 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/index.mjs +2 -1
- package/dist/utils/index.mjs.map +1 -1
- package/dist/{validity-SD6nwegA.d.mts → validity-BcPTzuK8.d.mts} +1 -1
- package/dist/{validity-BrvXzCEF.d.ts → validity-CoCrj9pZ.d.ts} +1 -1
- package/package.json +1 -1
- package/src/backend/facilitator-error.ts +87 -2
- package/src/backend/index.ts +445 -41
- package/src/chains/index.ts +2 -1
- package/src/client/X402Client.ts +5 -0
- package/src/fixtures/facilitator-receipts-v1.json +299 -0
- package/src/index.ts +18 -0
- package/src/receipts.ts +237 -0
package/dist/react/index.mjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { ethers } from 'ethers';
|
|
2
2
|
import { createContext, useMemo, useRef, useCallback, useState, useEffect, useContext } from 'react';
|
|
3
|
+
import { ed25519 } from '@noble/curves/ed25519';
|
|
4
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
5
|
+
import { bytesToHex } from '@noble/hashes/utils';
|
|
3
6
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
7
|
|
|
5
8
|
var __defProp = Object.defineProperty;
|
|
@@ -803,7 +806,8 @@ var init_chains = __esm({
|
|
|
803
806
|
// Pinned by src/arc-testnet.test.ts.
|
|
804
807
|
//
|
|
805
808
|
// Mainnet (5042) and testnet (5042002) were verified independently against
|
|
806
|
-
// their official RPCs on 2026-09-16. EURC domains checked on 2026-09-17;
|
|
809
|
+
// their official RPCs on 2026-09-16. EURC domains checked on 2026-09-17; funded EURC payments settled on Arc mainnet on
|
|
810
|
+
// 2026-09-22 (x402 v1/v2, docs/networks/arc.md); testnet pending.
|
|
807
811
|
arc: {
|
|
808
812
|
chainId: 5042,
|
|
809
813
|
chainIdHex: "0x13b2",
|
|
@@ -1803,6 +1807,151 @@ var init_solana = __esm({
|
|
|
1803
1807
|
solana_default = SVMProvider;
|
|
1804
1808
|
}
|
|
1805
1809
|
});
|
|
1810
|
+
var RECEIPT_ISSUER = "https://facilitator.ultravioletadao.xyz";
|
|
1811
|
+
var encoder = new TextEncoder();
|
|
1812
|
+
var decoder = new TextDecoder("utf-8", { fatal: true });
|
|
1813
|
+
var digest = (value) => bytesToHex(sha256(value));
|
|
1814
|
+
function canonicalReceiptJson(value) {
|
|
1815
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
|
|
1816
|
+
if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) return String(value);
|
|
1817
|
+
if (Array.isArray(value)) return `[${value.map(canonicalReceiptJson).join(",")}]`;
|
|
1818
|
+
if (typeof value === "object" && value) {
|
|
1819
|
+
const object = value;
|
|
1820
|
+
const keys = Object.keys(object).sort();
|
|
1821
|
+
if (keys.some((key) => !/^[\x00-\x7f]*$/.test(key))) throw new Error("non-ASCII canonical key");
|
|
1822
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalReceiptJson(object[key])}`).join(",")}}`;
|
|
1823
|
+
}
|
|
1824
|
+
throw new Error("unsupported canonical value");
|
|
1825
|
+
}
|
|
1826
|
+
function receiptCommitment(domain, value) {
|
|
1827
|
+
return digest(encoder.encode(`${domain}
|
|
1828
|
+
${canonicalReceiptJson(value)}`));
|
|
1829
|
+
}
|
|
1830
|
+
function encode64(bytes) {
|
|
1831
|
+
if (typeof Buffer !== "undefined") return Buffer.from(bytes).toString("base64");
|
|
1832
|
+
return btoa(Array.from(bytes, (b) => String.fromCharCode(b)).join(""));
|
|
1833
|
+
}
|
|
1834
|
+
function decode64(value) {
|
|
1835
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(value) || value.length % 4 !== 0) throw new Error("invalid base64");
|
|
1836
|
+
if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(value, "base64"));
|
|
1837
|
+
return Uint8Array.from(atob(value), (c) => c.charCodeAt(0));
|
|
1838
|
+
}
|
|
1839
|
+
function decodeUrl64(value) {
|
|
1840
|
+
const padded = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
1841
|
+
return decode64(padded + "=".repeat((4 - padded.length % 4) % 4));
|
|
1842
|
+
}
|
|
1843
|
+
function parseFacilitatorReceipt(value) {
|
|
1844
|
+
if (value === null || value === void 0) return null;
|
|
1845
|
+
const r = value;
|
|
1846
|
+
if (r.schemaVersion !== 1 || !["verified", "pending", "confirmed", "rejected", "unknown"].includes(r.status)) throw new Error("unsupported receipt version or state");
|
|
1847
|
+
for (const key of ["receiptId", "issuer", "operation", "network", "scheme", "asset", "amount", "payTo", "paymentRequestHash", "authorizationId"]) {
|
|
1848
|
+
if (typeof r[key] !== "string" || !r[key]) throw new Error(`invalid receipt ${key}`);
|
|
1849
|
+
}
|
|
1850
|
+
if (!Number.isSafeInteger(r.revision) || r.revision < 1 || !r.request || !r.retry) throw new Error("incomplete receipt");
|
|
1851
|
+
if (r.requestHashVersion !== "uvd-x402-request-v1" || r.requestHash !== receiptCommitment(r.requestHashVersion, r.request)) throw new Error("receipt request hash mismatch");
|
|
1852
|
+
for (const key of ["network", "scheme", "asset", "amount", "payTo", "purchaseId"]) {
|
|
1853
|
+
if (r[key] !== r.request[key]) throw new Error("receipt terms mismatch");
|
|
1854
|
+
}
|
|
1855
|
+
if (!/^[0-9]+$/.test(r.amount)) throw new Error("receipt amount must be an atomic integer string");
|
|
1856
|
+
if (r.status === "confirmed" && (r.operation !== "settle" || !r.settlement?.id)) throw new Error("confirmation has no settlement");
|
|
1857
|
+
return r;
|
|
1858
|
+
}
|
|
1859
|
+
function verifyFacilitatorReceipt(receipt, jwks, issuer = RECEIPT_ISSUER) {
|
|
1860
|
+
try {
|
|
1861
|
+
parseFacilitatorReceipt(receipt);
|
|
1862
|
+
if (receipt.issuer !== issuer || receipt.proof?.type !== "jws" || !receipt.proof.jws) return false;
|
|
1863
|
+
const parts = receipt.proof.jws.split(".");
|
|
1864
|
+
if (parts.length !== 3) return false;
|
|
1865
|
+
const [protectedHeader, payload, signature] = parts;
|
|
1866
|
+
const header = JSON.parse(decoder.decode(decodeUrl64(protectedHeader)));
|
|
1867
|
+
if (header.alg !== "EdDSA" || header.typ !== "uvd-facilitator-receipt+jws") return false;
|
|
1868
|
+
const keys = jwks.keys.filter((k) => k.kid === header.kid && k.kty === "OKP" && k.crv === "Ed25519");
|
|
1869
|
+
if (keys.length !== 1 || !keys[0].x) return false;
|
|
1870
|
+
if (decoder.decode(decodeUrl64(payload)) !== canonicalReceiptJson({ ...receipt, proof: null })) return false;
|
|
1871
|
+
return ed25519.verify(decodeUrl64(signature), encoder.encode(`${protectedHeader}.${payload}`), decodeUrl64(keys[0].x));
|
|
1872
|
+
} catch {
|
|
1873
|
+
return false;
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
function purchaseContextHeader(context) {
|
|
1877
|
+
return encode64(encoder.encode(canonicalReceiptJson({
|
|
1878
|
+
purchaseId: context.purchaseId,
|
|
1879
|
+
accessToken: context.accessToken,
|
|
1880
|
+
method: context.method,
|
|
1881
|
+
url: context.url,
|
|
1882
|
+
bodySha256: context.bodySha256
|
|
1883
|
+
})));
|
|
1884
|
+
}
|
|
1885
|
+
function receiptFromResponse(response) {
|
|
1886
|
+
const values = [response.headers.get("PAYMENT-RESPONSE"), response.headers.get("X-PAYMENT-RESPONSE")].filter((v) => v !== null);
|
|
1887
|
+
if (!values.length) return null;
|
|
1888
|
+
if (new Set(values).size !== 1 || values[0].includes(",") || values[0].length > 32768) throw new Error("ambiguous or oversized payment response header");
|
|
1889
|
+
return parseFacilitatorReceipt(JSON.parse(decoder.decode(decode64(values[0]))).receipt);
|
|
1890
|
+
}
|
|
1891
|
+
async function fetchWithReceipt(fetchClient, url, options) {
|
|
1892
|
+
const { context, persist, trustedKeys, issuer = RECEIPT_ISSUER } = options;
|
|
1893
|
+
const transport = options.fetchImpl || globalThis.fetch;
|
|
1894
|
+
const method = (options.method || "GET").toUpperCase();
|
|
1895
|
+
const request = new Request(url, { ...options.init, method });
|
|
1896
|
+
const bytes = new Uint8Array(await request.arrayBuffer());
|
|
1897
|
+
const bodyHash = digest(bytes);
|
|
1898
|
+
if (context.method && (context.method !== method || context.url !== request.url || context.bodySha256 !== bodyHash)) throw new Error("purchase context cannot be reused for another HTTP request");
|
|
1899
|
+
context.method = method;
|
|
1900
|
+
context.url = request.url;
|
|
1901
|
+
context.bodySha256 = bodyHash;
|
|
1902
|
+
const init = { ...options.init, method, headers: new Headers(request.headers), body: ["GET", "HEAD"].includes(method) ? void 0 : bytes };
|
|
1903
|
+
let receipt = parseFacilitatorReceipt(context.receipt);
|
|
1904
|
+
let transportError;
|
|
1905
|
+
const persistCopy = async () => {
|
|
1906
|
+
await persist(JSON.parse(JSON.stringify(context)));
|
|
1907
|
+
};
|
|
1908
|
+
const send = async (input, sendOptions) => {
|
|
1909
|
+
const headers = new Headers(sendOptions?.headers);
|
|
1910
|
+
const paid = headers.get("X-PAYMENT") || headers.get("PAYMENT-SIGNATURE");
|
|
1911
|
+
if (paid) {
|
|
1912
|
+
const paymentHeaders = {};
|
|
1913
|
+
for (const name of ["X-PAYMENT", "PAYMENT-SIGNATURE"]) {
|
|
1914
|
+
const value = headers.get(name);
|
|
1915
|
+
if (value) paymentHeaders[name] = value;
|
|
1916
|
+
}
|
|
1917
|
+
if (context.paymentHeaders && JSON.stringify(context.paymentHeaders) !== JSON.stringify(paymentHeaders)) throw new Error("refusing a new authorization for an existing purchase");
|
|
1918
|
+
context.paymentHeaders = paymentHeaders;
|
|
1919
|
+
headers.set("X-UVD-Purchase", purchaseContextHeader(context));
|
|
1920
|
+
await persistCopy();
|
|
1921
|
+
}
|
|
1922
|
+
try {
|
|
1923
|
+
return await transport(input, { ...sendOptions, headers });
|
|
1924
|
+
} catch (error) {
|
|
1925
|
+
transportError = error;
|
|
1926
|
+
throw error;
|
|
1927
|
+
}
|
|
1928
|
+
};
|
|
1929
|
+
try {
|
|
1930
|
+
let response;
|
|
1931
|
+
if (context.paymentHeaders) {
|
|
1932
|
+
const headers = new Headers(init.headers);
|
|
1933
|
+
for (const [name, value] of Object.entries(context.paymentHeaders)) headers.set(name, value);
|
|
1934
|
+
response = await send(request.url, { ...init, headers });
|
|
1935
|
+
} else {
|
|
1936
|
+
response = await fetchClient(request.url, { ...options, method, init, fetchImpl: send });
|
|
1937
|
+
}
|
|
1938
|
+
const incoming = receiptFromResponse(response);
|
|
1939
|
+
if (incoming) {
|
|
1940
|
+
if (incoming.issuer !== issuer || Object.entries({ purchaseId: context.purchaseId, method, url: context.url, bodySha256: bodyHash }).some(([key, value]) => incoming.request[key] !== value)) throw new Error("receipt does not bind this purchase");
|
|
1941
|
+
if (receipt?.operation === "settle" && (incoming.receiptId !== receipt.receiptId || incoming.revision < receipt.revision)) throw new Error("receipt revision conflict");
|
|
1942
|
+
if (trustedKeys && !verifyFacilitatorReceipt(incoming, trustedKeys, issuer)) throw new Error("receipt signature verification failed");
|
|
1943
|
+
receipt = incoming;
|
|
1944
|
+
context.receipt = incoming;
|
|
1945
|
+
await persistCopy();
|
|
1946
|
+
}
|
|
1947
|
+
const proofVerified = !!(receipt && trustedKeys && verifyFacilitatorReceipt(receipt, trustedKeys, issuer));
|
|
1948
|
+
if (receipt && trustedKeys && !proofVerified) throw new Error("receipt signature verification failed");
|
|
1949
|
+
return { response, receipt, paymentState: receipt ? receipt.status : context.paymentHeaders ? "unknown" : "not_required", context, proofVerified };
|
|
1950
|
+
} catch (error) {
|
|
1951
|
+
if (!transportError) throw error;
|
|
1952
|
+
return { response: null, receipt, paymentState: "unknown", context, proofVerified: false, error };
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1806
1955
|
|
|
1807
1956
|
// src/client/X402Client.ts
|
|
1808
1957
|
init_types();
|
|
@@ -2435,6 +2584,9 @@ var X402Client = class _X402Client {
|
|
|
2435
2584
|
* const data = await res.json();
|
|
2436
2585
|
* ```
|
|
2437
2586
|
*/
|
|
2587
|
+
async fetchWithReceipt(url, options) {
|
|
2588
|
+
return fetchWithReceipt((target, init) => this.fetch(target, init), url, options);
|
|
2589
|
+
}
|
|
2438
2590
|
async fetch(url, options = {}) {
|
|
2439
2591
|
if (!this.connectedAddress) {
|
|
2440
2592
|
throw new X402Error("Wallet not connected", "WALLET_NOT_CONNECTED");
|