uvd-x402-sdk 2.100.0 → 2.102.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 +95 -6
- package/dist/adapters/index.d.mts +3 -2
- package/dist/adapters/index.d.ts +3 -2
- package/dist/adapters/index.js +522 -126
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/index.mjs +522 -126
- package/dist/adapters/index.mjs.map +1 -1
- package/dist/backend/index.d.mts +2 -2
- package/dist/backend/index.d.ts +2 -2
- package/dist/backend/index.js +490 -64
- package/dist/backend/index.js.map +1 -1
- package/dist/backend/index.mjs +484 -65
- package/dist/backend/index.mjs.map +1 -1
- package/dist/erc8128/index.d.mts +1 -1
- package/dist/erc8128/index.d.ts +1 -1
- package/dist/{index-prkNXpLX.d.mts → index-DKUUBqZO.d.mts} +220 -16
- package/dist/{index-CYMXb5eC.d.ts → index-Di8sCYHx.d.ts} +220 -16
- package/dist/index.d.mts +11 -5
- package/dist/index.d.ts +11 -5
- package/dist/index.js +738 -165
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +735 -166
- package/dist/index.mjs.map +1 -1
- package/dist/ows-BB6Sgj0W.d.ts +342 -0
- package/dist/ows-DazT4KQH.d.mts +342 -0
- package/dist/providers/evm/index.js +31 -1
- package/dist/providers/evm/index.js.map +1 -1
- package/dist/providers/evm/index.mjs +31 -1
- package/dist/providers/evm/index.mjs.map +1 -1
- package/dist/providers/stellar/index.js +28 -0
- package/dist/providers/stellar/index.js.map +1 -1
- package/dist/providers/stellar/index.mjs +28 -0
- package/dist/providers/stellar/index.mjs.map +1 -1
- package/dist/providers/sui/index.js +27 -0
- package/dist/providers/sui/index.js.map +1 -1
- package/dist/providers/sui/index.mjs +27 -0
- package/dist/providers/sui/index.mjs.map +1 -1
- package/dist/react/index.js +49 -4
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +49 -4
- package/dist/react/index.mjs.map +1 -1
- package/dist/{wallet-w7BnImDG.d.mts → wallet-T0NFUUkT.d.mts} +3 -2
- package/dist/{wallet-w7BnImDG.d.ts → wallet-T0NFUUkT.d.ts} +3 -2
- package/package.json +3 -2
- package/src/adapters/env-key.ts +5 -1
- package/src/adapters/index.ts +8 -1
- package/src/adapters/ows.ts +664 -154
- package/src/backend/index.ts +635 -22
- package/src/client/X402Client.ts +23 -3
- package/src/escrow-preauth.ts +32 -4
- package/src/index.ts +21 -1
- package/src/lifecycle-auth.ts +37 -31
- package/src/providers/evm/index.ts +9 -1
- package/src/providers/stellar/index.ts +5 -0
- package/src/providers/sui/index.ts +4 -0
- package/src/utils/uint.ts +252 -0
- package/src/wallet.ts +3 -2
- package/dist/ows-C-KmORG9.d.ts +0 -221
- package/dist/ows-Z9v4GxOZ.d.mts +0 -221
package/dist/adapters/index.js
CHANGED
|
@@ -1205,9 +1205,9 @@ function encodeBase64Json(value) {
|
|
|
1205
1205
|
// src/utils/x402.ts
|
|
1206
1206
|
function chainToCAIP2(chainName) {
|
|
1207
1207
|
const chain = getChainByName(chainName);
|
|
1208
|
-
const
|
|
1209
|
-
if (
|
|
1210
|
-
return
|
|
1208
|
+
const caip22 = CAIP2_IDENTIFIERS[(chain?.name ?? chainName).toLowerCase()];
|
|
1209
|
+
if (caip22) {
|
|
1210
|
+
return caip22;
|
|
1211
1211
|
}
|
|
1212
1212
|
if (chain) {
|
|
1213
1213
|
if (chain.networkType === "evm") {
|
|
@@ -1443,6 +1443,110 @@ function useX402Wagmi(walletClient) {
|
|
|
1443
1443
|
createPaymentFull
|
|
1444
1444
|
};
|
|
1445
1445
|
}
|
|
1446
|
+
var INTEGER_TYPE = /^u?int\d*$/;
|
|
1447
|
+
function isRecord(value) {
|
|
1448
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1449
|
+
}
|
|
1450
|
+
function inexactNumberMessage(field, value) {
|
|
1451
|
+
return `${field} is the JSON number ${String(value)}, larger in magnitude than Number.MAX_SAFE_INTEGER (2**53 - 1): JSON.parse has already rounded it, and signing it would sign a different integer than the one that was written. Send uints as decimal or 0x-hex strings (or a bigint)`;
|
|
1452
|
+
}
|
|
1453
|
+
function numberRefusal(path, value, code) {
|
|
1454
|
+
return new X402Error(
|
|
1455
|
+
Number.isInteger(value) ? inexactNumberMessage(path, value) : `${path} must be an integer, got the JSON number ${String(value)}`,
|
|
1456
|
+
code
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
function findInexactNumber(value, path, seen = /* @__PURE__ */ new Set()) {
|
|
1460
|
+
if (typeof value === "number") {
|
|
1461
|
+
return Number.isSafeInteger(value) ? null : { path, value };
|
|
1462
|
+
}
|
|
1463
|
+
if (typeof value !== "object" || value === null || seen.has(value)) return null;
|
|
1464
|
+
seen.add(value);
|
|
1465
|
+
const entries = Array.isArray(value) ? value.map((item, i) => [`${path}[${i}]`, item]) : Object.entries(value).map(([key, item]) => [`${path}.${key}`, item]);
|
|
1466
|
+
for (const [itemPath, item] of entries) {
|
|
1467
|
+
const hit = findInexactNumber(item, itemPath, seen);
|
|
1468
|
+
if (hit) return hit;
|
|
1469
|
+
}
|
|
1470
|
+
return null;
|
|
1471
|
+
}
|
|
1472
|
+
function assertNoInexactNumber(value, path, code = "INVALID_CONFIG") {
|
|
1473
|
+
const hit = findInexactNumber(value, path);
|
|
1474
|
+
if (hit) throw numberRefusal(hit.path, hit.value, code);
|
|
1475
|
+
}
|
|
1476
|
+
function rootStruct(structs, primaryType) {
|
|
1477
|
+
try {
|
|
1478
|
+
return ethers.ethers.TypedDataEncoder.from(
|
|
1479
|
+
structs
|
|
1480
|
+
).primaryType;
|
|
1481
|
+
} catch {
|
|
1482
|
+
return typeof primaryType === "string" && Array.isArray(structs[primaryType]) ? primaryType : null;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
function checkTyped(type, value, path, structs, code, ancestors = /* @__PURE__ */ new Set()) {
|
|
1486
|
+
if (typeof value === "object" && value !== null) {
|
|
1487
|
+
if (ancestors.has(value)) return;
|
|
1488
|
+
ancestors.add(value);
|
|
1489
|
+
}
|
|
1490
|
+
try {
|
|
1491
|
+
const array = /^(.*)\[(\d*)\]$/.exec(type);
|
|
1492
|
+
if (array) {
|
|
1493
|
+
if (Array.isArray(value)) {
|
|
1494
|
+
value.forEach(
|
|
1495
|
+
(item, i) => checkTyped(array[1], item, `${path}[${i}]`, structs, code, ancestors)
|
|
1496
|
+
);
|
|
1497
|
+
}
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
const struct = structs[type];
|
|
1501
|
+
if (Array.isArray(struct)) {
|
|
1502
|
+
if (!isRecord(value)) return;
|
|
1503
|
+
for (const field of struct) {
|
|
1504
|
+
if (typeof field?.name === "string" && typeof field.type === "string") {
|
|
1505
|
+
checkTyped(
|
|
1506
|
+
field.type,
|
|
1507
|
+
value[field.name],
|
|
1508
|
+
`${path}.${field.name}`,
|
|
1509
|
+
structs,
|
|
1510
|
+
code,
|
|
1511
|
+
ancestors
|
|
1512
|
+
);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
if (INTEGER_TYPE.test(type) && typeof value === "number" && !Number.isSafeInteger(value)) {
|
|
1518
|
+
throw numberRefusal(path, value, code);
|
|
1519
|
+
}
|
|
1520
|
+
} finally {
|
|
1521
|
+
if (typeof value === "object" && value !== null) ancestors.delete(value);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
function assertTypedDataIntegersExact(doc, where = "typed data", code = "INVALID_CONFIG") {
|
|
1525
|
+
if (!isRecord(doc)) return;
|
|
1526
|
+
const { domain, types, message, primaryType } = doc;
|
|
1527
|
+
if (isRecord(domain) && typeof domain.chainId === "number" && !Number.isSafeInteger(domain.chainId)) {
|
|
1528
|
+
throw numberRefusal(`${where} domain.chainId`, domain.chainId, code);
|
|
1529
|
+
}
|
|
1530
|
+
if (!isRecord(message)) return;
|
|
1531
|
+
const structs = isRecord(types) ? { ...types } : {};
|
|
1532
|
+
delete structs["EIP712Domain"];
|
|
1533
|
+
const root = rootStruct(structs, primaryType);
|
|
1534
|
+
if (root) {
|
|
1535
|
+
checkTyped(root, message, `${where} message`, structs, code);
|
|
1536
|
+
if (typeof primaryType === "string" && primaryType !== root && Array.isArray(structs[primaryType])) {
|
|
1537
|
+
checkTyped(primaryType, message, `${where} message`, structs, code);
|
|
1538
|
+
}
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
assertNoInexactNumber(message, `${where} message`, code);
|
|
1542
|
+
}
|
|
1543
|
+
function parseTypedDataJson(text, where = "typed data", code = "INVALID_CONFIG") {
|
|
1544
|
+
const doc = JSON.parse(text);
|
|
1545
|
+
assertTypedDataIntegersExact(doc, where, code);
|
|
1546
|
+
return doc;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
// src/adapters/env-key.ts
|
|
1446
1550
|
var EnvKeyAdapter = class {
|
|
1447
1551
|
wallet;
|
|
1448
1552
|
/**
|
|
@@ -1482,9 +1586,12 @@ var EnvKeyAdapter = class {
|
|
|
1482
1586
|
*
|
|
1483
1587
|
* @param typedData - JSON string with `domain`, `types`, `primaryType`, and `message` fields
|
|
1484
1588
|
* @returns Object with signature and v/r/s components
|
|
1589
|
+
* @throws {X402Error} `INVALID_CONFIG` when an integer field of `message`
|
|
1590
|
+
* (or `domain.chainId`) is a JSON number above 2**53 - 1: `JSON.parse`
|
|
1591
|
+
* rounded it, so it is refused instead of signed. Write uints as strings.
|
|
1485
1592
|
*/
|
|
1486
1593
|
async signTypedData(typedData) {
|
|
1487
|
-
const parsed =
|
|
1594
|
+
const parsed = parseTypedDataJson(typedData);
|
|
1488
1595
|
const { domain, types, message } = parsed;
|
|
1489
1596
|
const cleanTypes = { ...types };
|
|
1490
1597
|
delete cleanTypes["EIP712Domain"];
|
|
@@ -1585,121 +1692,342 @@ var EnvKeyAdapter = class {
|
|
|
1585
1692
|
};
|
|
1586
1693
|
}
|
|
1587
1694
|
};
|
|
1695
|
+
var OWS_FUNCTIONS = ["getWallet", "signMessage", "signTypedData", "signTransaction"];
|
|
1696
|
+
var MIGRATION = "pass the module and the wallet: new OWSWalletAdapter(ows, { wallet: '<name or id>', passphrase }) with import * as ows from '@open-wallet-standard/core'";
|
|
1697
|
+
var EIP712_DOMAIN_FIELDS = [
|
|
1698
|
+
["name", "string"],
|
|
1699
|
+
["version", "string"],
|
|
1700
|
+
["chainId", "uint256"],
|
|
1701
|
+
["verifyingContract", "address"],
|
|
1702
|
+
["salt", "bytes32"]
|
|
1703
|
+
];
|
|
1704
|
+
var TRANSACTION_FIELDS = [
|
|
1705
|
+
"type",
|
|
1706
|
+
"to",
|
|
1707
|
+
"nonce",
|
|
1708
|
+
"gasLimit",
|
|
1709
|
+
"gasPrice",
|
|
1710
|
+
"maxPriorityFeePerGas",
|
|
1711
|
+
"maxFeePerGas",
|
|
1712
|
+
"maxFeePerBlobGas",
|
|
1713
|
+
"data",
|
|
1714
|
+
"value",
|
|
1715
|
+
"chainId",
|
|
1716
|
+
"signature",
|
|
1717
|
+
"accessList",
|
|
1718
|
+
"authorizationList",
|
|
1719
|
+
"blobVersionedHashes",
|
|
1720
|
+
"kzg",
|
|
1721
|
+
"blobWrapperVersion",
|
|
1722
|
+
"blobs"
|
|
1723
|
+
];
|
|
1724
|
+
var ETHERS_NAME_OF = { input: "data", gas: "gasLimit" };
|
|
1725
|
+
var TWO_127 = 1n << 127n;
|
|
1726
|
+
var TWO_128 = 1n << 128n;
|
|
1727
|
+
var TWO_256 = 1n << 256n;
|
|
1728
|
+
function isRecord2(value) {
|
|
1729
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1730
|
+
}
|
|
1731
|
+
function shown(value) {
|
|
1732
|
+
if (typeof value === "bigint") return `${value}n`;
|
|
1733
|
+
try {
|
|
1734
|
+
return JSON.stringify(value) ?? String(value);
|
|
1735
|
+
} catch {
|
|
1736
|
+
return String(value);
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
function refuse(message) {
|
|
1740
|
+
return new X402Error(`${message}; nothing was signed`, "INVALID_CONFIG");
|
|
1741
|
+
}
|
|
1742
|
+
function toInteger(value, where) {
|
|
1743
|
+
if (typeof value === "bigint") return value;
|
|
1744
|
+
if (typeof value === "number" && Number.isSafeInteger(value)) return BigInt(value);
|
|
1745
|
+
if (typeof value === "number" && Number.isInteger(value)) throw refuse(inexactNumberMessage(where, value));
|
|
1746
|
+
if (typeof value === "string" && /^-?(0[xX][0-9a-fA-F]+|[0-9]+)$/.test(value)) {
|
|
1747
|
+
return value.startsWith("-") ? -BigInt(value.slice(1)) : BigInt(value);
|
|
1748
|
+
}
|
|
1749
|
+
throw refuse(
|
|
1750
|
+
`${where}: ${shown(value)} is not an integer (a safe-integer number, a decimal string or 0x-hex)`
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
function integerWire(n) {
|
|
1754
|
+
if (n >= -TWO_127 && n < TWO_128) return n.toString();
|
|
1755
|
+
if (n > 0n) {
|
|
1756
|
+
const digits = n.toString(16);
|
|
1757
|
+
return `0x${digits.length % 2 ? "0" : ""}${digits}`;
|
|
1758
|
+
}
|
|
1759
|
+
return `0x${(n + TWO_256).toString(16).padStart(64, "0")}`;
|
|
1760
|
+
}
|
|
1761
|
+
function owsValue(kind, value, types, where) {
|
|
1762
|
+
const array = /^(.*)\[(\d*)\]$/.exec(kind);
|
|
1763
|
+
if (array) {
|
|
1764
|
+
const [, inner, size] = array;
|
|
1765
|
+
if (!Array.isArray(value)) throw refuse(`${where}: ${kind} needs an array, got ${shown(value)}`);
|
|
1766
|
+
if (size !== "" && value.length !== Number(size)) {
|
|
1767
|
+
throw refuse(`${where}: ${kind} needs ${size} items, got ${value.length}`);
|
|
1768
|
+
}
|
|
1769
|
+
return value.map((item, i) => owsValue(inner, item, types, `${where}[${i}]`));
|
|
1770
|
+
}
|
|
1771
|
+
const struct = types[kind];
|
|
1772
|
+
if (struct) {
|
|
1773
|
+
if (!isRecord2(value)) throw refuse(`${where}: ${kind} needs an object, got ${shown(value)}`);
|
|
1774
|
+
const out = {};
|
|
1775
|
+
for (const field of struct) {
|
|
1776
|
+
if (value[field.name] === void 0) throw refuse(`${where}: ${kind} lacks ${field.name}`);
|
|
1777
|
+
out[field.name] = owsValue(field.type, value[field.name], types, `${where}.${field.name}`);
|
|
1778
|
+
}
|
|
1779
|
+
return out;
|
|
1780
|
+
}
|
|
1781
|
+
const integer = /^(u?)int(\d+)$/.exec(kind);
|
|
1782
|
+
if (integer) {
|
|
1783
|
+
const bits = BigInt(integer[2]);
|
|
1784
|
+
const n = toInteger(value, where);
|
|
1785
|
+
const [low, high] = integer[1] ? [0n, 1n << bits] : [-(1n << bits - 1n), 1n << bits - 1n];
|
|
1786
|
+
if (n < low || n >= high) {
|
|
1787
|
+
throw new X402Error(
|
|
1788
|
+
`${where}: ${shown(value)} is out of range for ${kind}; nothing was signed`,
|
|
1789
|
+
"INVALID_AMOUNT"
|
|
1790
|
+
);
|
|
1791
|
+
}
|
|
1792
|
+
return integerWire(n);
|
|
1793
|
+
}
|
|
1794
|
+
if (kind === "bool") {
|
|
1795
|
+
if (typeof value !== "boolean") throw refuse(`${where}: bool needs true or false, got ${shown(value)}`);
|
|
1796
|
+
return value;
|
|
1797
|
+
}
|
|
1798
|
+
if (kind === "string") {
|
|
1799
|
+
if (typeof value !== "string") throw refuse(`${where}: string needs a string, got ${shown(value)}`);
|
|
1800
|
+
return value;
|
|
1801
|
+
}
|
|
1802
|
+
if (kind === "address") {
|
|
1803
|
+
try {
|
|
1804
|
+
if (typeof value !== "string") throw new Error();
|
|
1805
|
+
return ethers.ethers.getAddress(value);
|
|
1806
|
+
} catch {
|
|
1807
|
+
throw refuse(`${where}: ${shown(value)} is not an address`);
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
const bytes = /^bytes(\d*)$/.exec(kind);
|
|
1811
|
+
if (bytes) {
|
|
1812
|
+
let raw;
|
|
1813
|
+
try {
|
|
1814
|
+
if (typeof value !== "string") throw new Error();
|
|
1815
|
+
raw = ethers.ethers.getBytes(value);
|
|
1816
|
+
} catch {
|
|
1817
|
+
throw refuse(`${where}: ${kind} needs 0x-hex, got ${shown(value)}`);
|
|
1818
|
+
}
|
|
1819
|
+
if (bytes[1] !== "" && raw.length !== Number(bytes[1])) {
|
|
1820
|
+
throw refuse(`${where}: ${kind} needs ${bytes[1]} bytes, got ${raw.length}`);
|
|
1821
|
+
}
|
|
1822
|
+
return ethers.ethers.hexlify(raw);
|
|
1823
|
+
}
|
|
1824
|
+
throw refuse(`${where}: unsupported EIP-712 type ${kind}`);
|
|
1825
|
+
}
|
|
1826
|
+
function prepareTypedData(input) {
|
|
1827
|
+
if (!isRecord2(input)) throw refuse("typed data must be an object with domain, types and message");
|
|
1828
|
+
const { domain, types, message, primaryType } = input;
|
|
1829
|
+
if (!isRecord2(domain)) throw refuse("typed data needs a domain object");
|
|
1830
|
+
if (!isRecord2(types)) throw refuse("typed data needs a types object");
|
|
1831
|
+
if (!isRecord2(message)) throw refuse("typed data needs a message object");
|
|
1832
|
+
const structs = { ...types };
|
|
1833
|
+
delete structs["EIP712Domain"];
|
|
1834
|
+
let root;
|
|
1835
|
+
try {
|
|
1836
|
+
root = ethers.ethers.TypedDataEncoder.from(structs).primaryType;
|
|
1837
|
+
} catch (error) {
|
|
1838
|
+
throw refuse(`typed data types are invalid (${error instanceof Error ? error.message : error})`);
|
|
1839
|
+
}
|
|
1840
|
+
if (primaryType !== void 0 && primaryType !== root) {
|
|
1841
|
+
throw refuse(
|
|
1842
|
+
`primaryType ${shown(primaryType)} is not ${root}, the root struct ethers signs for these types`
|
|
1843
|
+
);
|
|
1844
|
+
}
|
|
1845
|
+
for (const key of Object.keys(domain)) {
|
|
1846
|
+
if (!EIP712_DOMAIN_FIELDS.some(([name]) => name === key)) {
|
|
1847
|
+
throw refuse(`domain.${key} is not an EIP-712 domain field`);
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
const all = {
|
|
1851
|
+
EIP712Domain: EIP712_DOMAIN_FIELDS.filter(([name]) => domain[name] != null).map(
|
|
1852
|
+
([name, type]) => ({ name, type })
|
|
1853
|
+
),
|
|
1854
|
+
...structs
|
|
1855
|
+
};
|
|
1856
|
+
const owsDomain = owsValue("EIP712Domain", domain, all, "domain");
|
|
1857
|
+
const owsMessage = owsValue(root, message, all, "message");
|
|
1858
|
+
const chainId = domain["chainId"] != null ? toInteger(domain["chainId"], "domain.chainId") : 0n;
|
|
1859
|
+
return {
|
|
1860
|
+
document: JSON.stringify({ types: all, primaryType: root, domain: owsDomain, message: owsMessage }),
|
|
1861
|
+
domain,
|
|
1862
|
+
types: structs,
|
|
1863
|
+
message,
|
|
1864
|
+
chain: chainId > 0n ? `eip155:${chainId}` : null
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
function evmSignature(result, what) {
|
|
1868
|
+
const hex = String(result?.signature ?? "").replace(/^0x/, "");
|
|
1869
|
+
if (!/^[0-9a-fA-F]{130}$/.test(hex)) {
|
|
1870
|
+
throw new X402Error(`OWS ${what} returned a signature that is not 65 bytes of hex`, "PAYMENT_FAILED");
|
|
1871
|
+
}
|
|
1872
|
+
const bytes = ethers.ethers.getBytes(`0x${hex}`);
|
|
1873
|
+
const v = bytes[64] < 27 ? bytes[64] + 27 : bytes[64];
|
|
1874
|
+
if (v !== 27 && v !== 28) {
|
|
1875
|
+
throw new X402Error(`OWS ${what} returned a recovery byte of ${bytes[64]}`, "PAYMENT_FAILED");
|
|
1876
|
+
}
|
|
1877
|
+
return ethers.ethers.Signature.from({
|
|
1878
|
+
r: ethers.ethers.hexlify(bytes.subarray(0, 32)),
|
|
1879
|
+
s: ethers.ethers.hexlify(bytes.subarray(32, 64)),
|
|
1880
|
+
v
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
function caip2(network) {
|
|
1884
|
+
if (typeof network !== "string") {
|
|
1885
|
+
throw new X402Error(
|
|
1886
|
+
`OWSWalletAdapter: options.network must be a string (an SDK network name or eip155:<id>), got ${typeof network}`,
|
|
1887
|
+
"INVALID_CONFIG"
|
|
1888
|
+
);
|
|
1889
|
+
}
|
|
1890
|
+
const caip = /^eip155:([1-9][0-9]*)$/.exec(network);
|
|
1891
|
+
if (caip) return network;
|
|
1892
|
+
const chain = getChainByName(network);
|
|
1893
|
+
if (!chain || chain.networkType !== "evm") {
|
|
1894
|
+
throw new X402Error(
|
|
1895
|
+
`OWSWalletAdapter signs for an EVM network; got ${shown(network)}`,
|
|
1896
|
+
"CHAIN_NOT_SUPPORTED"
|
|
1897
|
+
);
|
|
1898
|
+
}
|
|
1899
|
+
return `eip155:${chain.chainId}`;
|
|
1900
|
+
}
|
|
1588
1901
|
var OWSWalletAdapter = class {
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
* @throws {X402Error} if the wallet has no accounts
|
|
1598
|
-
*/
|
|
1599
|
-
constructor(owsWallet, accountIndex = 0) {
|
|
1600
|
-
if (!owsWallet || !owsWallet.accounts || owsWallet.accounts.length === 0) {
|
|
1902
|
+
#ows;
|
|
1903
|
+
#wallet;
|
|
1904
|
+
#passphrase;
|
|
1905
|
+
#vaultPath;
|
|
1906
|
+
#chain;
|
|
1907
|
+
#address;
|
|
1908
|
+
constructor(ows, options) {
|
|
1909
|
+
if (isRecord2(ows) && "accounts" in ows && (options === void 0 || typeof options === "number")) {
|
|
1601
1910
|
throw new X402Error(
|
|
1602
|
-
|
|
1603
|
-
"
|
|
1911
|
+
`OWSWalletAdapter no longer takes a wallet object with accounts (the deprecated OWSWallet shape, which never worked with @open-wallet-standard/core 1.x); ${MIGRATION}. Nothing was signed.`,
|
|
1912
|
+
"INVALID_CONFIG"
|
|
1604
1913
|
);
|
|
1605
1914
|
}
|
|
1606
|
-
const
|
|
1607
|
-
|
|
1915
|
+
const missing = OWS_FUNCTIONS.filter(
|
|
1916
|
+
(name) => typeof ows?.[name] !== "function"
|
|
1917
|
+
);
|
|
1918
|
+
if (missing.length > 0) {
|
|
1608
1919
|
throw new X402Error(
|
|
1609
|
-
`
|
|
1610
|
-
"
|
|
1920
|
+
`OWSWalletAdapter takes the @open-wallet-standard/core module (import * as ows from '@open-wallet-standard/core'); it lacks ${missing.join(", ")}. Since the OWS rewrite the adapter no longer takes a wallet object with accounts.`,
|
|
1921
|
+
"INVALID_CONFIG"
|
|
1611
1922
|
);
|
|
1612
1923
|
}
|
|
1613
|
-
|
|
1614
|
-
|
|
1924
|
+
const opts = isRecord2(options) ? options : {};
|
|
1925
|
+
if (typeof opts.wallet !== "string" || opts.wallet === "") {
|
|
1926
|
+
throw new X402Error("OWSWalletAdapter needs options.wallet: the name or id of a wallet in the vault", "INVALID_CONFIG");
|
|
1927
|
+
}
|
|
1928
|
+
if (opts.passphrase != null && typeof opts.passphrase !== "string") {
|
|
1929
|
+
throw new X402Error("OWSWalletAdapter: options.passphrase must be a string", "INVALID_CONFIG");
|
|
1930
|
+
}
|
|
1931
|
+
this.#ows = ows;
|
|
1932
|
+
this.#wallet = opts.wallet;
|
|
1933
|
+
this.#passphrase = opts.passphrase ?? (typeof process !== "undefined" ? process.env?.OWS_PASSPHRASE : void 0) ?? null;
|
|
1934
|
+
this.#vaultPath = opts.vaultPath ?? null;
|
|
1935
|
+
this.#chain = caip2(opts.network ?? "base");
|
|
1936
|
+
const info = this.#call("getWallet", () => this.#ows.getWallet(this.#wallet, this.#vaultPath), "WALLET_NOT_FOUND");
|
|
1937
|
+
const account = (info?.accounts ?? []).find((a) => String(a.chainId).startsWith("eip155:"));
|
|
1938
|
+
if (!account) {
|
|
1939
|
+
throw new X402Error(`OWS wallet ${shown(this.#wallet)} has no EVM account`, "WALLET_NOT_FOUND");
|
|
1940
|
+
}
|
|
1941
|
+
this.#address = ethers.ethers.getAddress(account.address);
|
|
1615
1942
|
}
|
|
1616
1943
|
/**
|
|
1617
|
-
* Get the checksummed EVM wallet address
|
|
1944
|
+
* Get the checksummed EVM wallet address (read from the vault once, when
|
|
1945
|
+
* the adapter was created; no passphrase needed).
|
|
1618
1946
|
*/
|
|
1619
1947
|
getAddress() {
|
|
1620
|
-
return this
|
|
1948
|
+
return this.#address;
|
|
1621
1949
|
}
|
|
1622
1950
|
/**
|
|
1623
|
-
* Sign a message using EIP-191 personal_sign
|
|
1951
|
+
* Sign a message using EIP-191 personal_sign (the string as UTF-8, as
|
|
1952
|
+
* `EnvKeyAdapter` signs it).
|
|
1624
1953
|
*
|
|
1625
1954
|
* @param message - The message string to sign
|
|
1626
|
-
* @returns Hex-encoded signature
|
|
1955
|
+
* @returns Hex-encoded 65-byte signature, v = 27/28
|
|
1627
1956
|
*/
|
|
1628
1957
|
async signMessage(message) {
|
|
1958
|
+
if (typeof message !== "string") throw refuse(`signMessage takes a string, got ${typeof message}`);
|
|
1959
|
+
const result = this.#call(
|
|
1960
|
+
"signMessage",
|
|
1961
|
+
() => this.#ows.signMessage(this.#wallet, this.#chain, message, this.#passphrase, "utf8", null, this.#vaultPath)
|
|
1962
|
+
);
|
|
1963
|
+
const signature = evmSignature(result, "signMessage").serialized;
|
|
1964
|
+
let signer = "";
|
|
1629
1965
|
try {
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
message
|
|
1633
|
-
});
|
|
1634
|
-
return result.signature;
|
|
1635
|
-
} catch (error) {
|
|
1636
|
-
throw new X402Error(
|
|
1637
|
-
`OWS signMessage failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1638
|
-
"PAYMENT_FAILED",
|
|
1639
|
-
error
|
|
1640
|
-
);
|
|
1966
|
+
signer = ethers.ethers.verifyMessage(message, signature);
|
|
1967
|
+
} catch {
|
|
1641
1968
|
}
|
|
1969
|
+
this.#checkSigner(signer, "signMessage");
|
|
1970
|
+
return signature;
|
|
1642
1971
|
}
|
|
1643
1972
|
/**
|
|
1644
1973
|
* Sign EIP-712 typed structured data.
|
|
1645
1974
|
*
|
|
1646
|
-
* @param typedData - JSON string with `domain`, `types`, `
|
|
1975
|
+
* @param typedData - JSON string with `domain`, `types`, `message` and
|
|
1976
|
+
* optionally `primaryType` (it must then be the root struct)
|
|
1647
1977
|
* @returns Object with signature and v/r/s components
|
|
1978
|
+
* @throws {X402Error} `INVALID_AMOUNT` for an integer out of its type's
|
|
1979
|
+
* range and `INVALID_CONFIG` for anything else ethers would not encode,
|
|
1980
|
+
* both before signing; `PAYMENT_FAILED` if OWS fails or signs a digest
|
|
1981
|
+
* other than the one ethers computes (the signature is not returned).
|
|
1648
1982
|
*/
|
|
1649
1983
|
async signTypedData(typedData) {
|
|
1650
|
-
|
|
1984
|
+
let parsed;
|
|
1651
1985
|
try {
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
types: parsed.types,
|
|
1656
|
-
primaryType: parsed.primaryType,
|
|
1657
|
-
message: parsed.message
|
|
1658
|
-
});
|
|
1659
|
-
const sig = ethers.ethers.Signature.from(result.signature);
|
|
1660
|
-
return {
|
|
1661
|
-
signature: result.signature,
|
|
1662
|
-
v: sig.v,
|
|
1663
|
-
r: sig.r,
|
|
1664
|
-
s: sig.s
|
|
1665
|
-
};
|
|
1666
|
-
} catch (error) {
|
|
1667
|
-
throw new X402Error(
|
|
1668
|
-
`OWS signTypedData failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1669
|
-
"PAYMENT_FAILED",
|
|
1670
|
-
error
|
|
1671
|
-
);
|
|
1986
|
+
parsed = JSON.parse(typedData);
|
|
1987
|
+
} catch {
|
|
1988
|
+
throw refuse("typedData is not a JSON string");
|
|
1672
1989
|
}
|
|
1990
|
+
return this.#signTyped(parsed);
|
|
1673
1991
|
}
|
|
1674
1992
|
/**
|
|
1675
|
-
* Sign
|
|
1993
|
+
* Sign an EVM transaction.
|
|
1676
1994
|
*
|
|
1677
|
-
*
|
|
1678
|
-
*
|
|
1679
|
-
*
|
|
1995
|
+
* OWS signs keccak256 of the bytes it is given and returns only the
|
|
1996
|
+
* signature, so the transaction is parsed and serialised with ethers here,
|
|
1997
|
+
* the way `EnvKeyAdapter` (ethers.Wallet) does it, EIP-155 `v` included.
|
|
1680
1998
|
*
|
|
1681
|
-
*
|
|
1682
|
-
*
|
|
1999
|
+
* A sender, given as `from` or implied by an existing signature, must be
|
|
2000
|
+
* this wallet: it is taken out, as ethers takes it out; any other sender is
|
|
2001
|
+
* refused before signing.
|
|
2002
|
+
*
|
|
2003
|
+
* @param unsignedTx - Hex-encoded transaction (ethers serialized), an
|
|
2004
|
+
* `ethers.Transaction`, or a transaction object ethers' `Transaction.from`
|
|
2005
|
+
* accepts, plus `from`
|
|
2006
|
+
* @returns Hex-encoded signed raw transaction, ready for broadcast
|
|
1683
2007
|
*/
|
|
1684
2008
|
async signTransaction(unsignedTx) {
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
2009
|
+
const tx = this.#unsignedTransaction(unsignedTx);
|
|
2010
|
+
const chain = tx.chainId > 0n ? `eip155:${tx.chainId}` : this.#chain;
|
|
2011
|
+
const result = this.#call(
|
|
2012
|
+
"signTransaction",
|
|
2013
|
+
() => this.#ows.signTransaction(
|
|
2014
|
+
this.#wallet,
|
|
2015
|
+
chain,
|
|
2016
|
+
tx.unsignedSerialized.slice(2),
|
|
2017
|
+
this.#passphrase,
|
|
2018
|
+
null,
|
|
2019
|
+
this.#vaultPath
|
|
2020
|
+
)
|
|
2021
|
+
);
|
|
2022
|
+
tx.signature = evmSignature(result, "signTransaction");
|
|
2023
|
+
const signed = tx.serialized;
|
|
2024
|
+
this.#checkSigner(ethers.ethers.Transaction.from(signed).from ?? "", "signTransaction");
|
|
2025
|
+
return signed;
|
|
1700
2026
|
}
|
|
1701
2027
|
/**
|
|
1702
|
-
* Sign an EIP-3009
|
|
2028
|
+
* Sign an EIP-3009 TransferWithAuthorization for USDC: the typed data
|
|
2029
|
+
* `EnvKeyAdapter.signEIP3009` builds, signed through {@link signTypedData}
|
|
2030
|
+
* (OWS has no EIP-3009 call of its own).
|
|
1703
2031
|
*
|
|
1704
2032
|
* @param params - EIP-3009 parameters
|
|
1705
2033
|
* @returns Signed authorization ready for facilitator relay
|
|
@@ -1717,65 +2045,133 @@ var OWSWalletAdapter = class {
|
|
|
1717
2045
|
}
|
|
1718
2046
|
const chainId = params.chainId ?? chain.chainId;
|
|
1719
2047
|
const usdcAddress = params.usdcContract ?? chain.usdc.address;
|
|
1720
|
-
const from = this
|
|
2048
|
+
const from = this.#address;
|
|
1721
2049
|
const to = ethers.ethers.getAddress(params.to);
|
|
1722
2050
|
const value = ethers.ethers.parseUnits(params.amountUsdc.toString(), chain.usdc.decimals);
|
|
1723
2051
|
const validAfter = params.validAfter ?? 0;
|
|
1724
2052
|
const validBefore = params.validBefore ?? Math.floor(Date.now() / 1e3) + 300;
|
|
1725
2053
|
const nonce = ethers.ethers.hexlify(ethers.ethers.randomBytes(32));
|
|
1726
|
-
const
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
2054
|
+
const signed = await this.#signTyped({
|
|
2055
|
+
domain: {
|
|
2056
|
+
name: chain.usdc.name,
|
|
2057
|
+
version: chain.usdc.version,
|
|
2058
|
+
chainId,
|
|
2059
|
+
verifyingContract: usdcAddress
|
|
2060
|
+
},
|
|
2061
|
+
types: {
|
|
2062
|
+
TransferWithAuthorization: [
|
|
2063
|
+
{ name: "from", type: "address" },
|
|
2064
|
+
{ name: "to", type: "address" },
|
|
2065
|
+
{ name: "value", type: "uint256" },
|
|
2066
|
+
{ name: "validAfter", type: "uint256" },
|
|
2067
|
+
{ name: "validBefore", type: "uint256" },
|
|
2068
|
+
{ name: "nonce", type: "bytes32" }
|
|
2069
|
+
]
|
|
2070
|
+
},
|
|
2071
|
+
primaryType: "TransferWithAuthorization",
|
|
2072
|
+
message: { from, to, value, validAfter, validBefore, nonce }
|
|
2073
|
+
});
|
|
2074
|
+
return {
|
|
1743
2075
|
from,
|
|
1744
2076
|
to,
|
|
1745
2077
|
value: value.toString(),
|
|
1746
2078
|
validAfter: validAfter.toString(),
|
|
1747
2079
|
validBefore: validBefore.toString(),
|
|
1748
|
-
nonce
|
|
2080
|
+
nonce,
|
|
2081
|
+
v: signed.v,
|
|
2082
|
+
r: signed.r,
|
|
2083
|
+
s: signed.s,
|
|
2084
|
+
signature: signed.signature
|
|
1749
2085
|
};
|
|
1750
|
-
|
|
2086
|
+
}
|
|
2087
|
+
// --------------------------------------------------------------------------
|
|
2088
|
+
async #signTyped(input) {
|
|
2089
|
+
const typed = prepareTypedData(input);
|
|
2090
|
+
const result = this.#call(
|
|
2091
|
+
"signTypedData",
|
|
2092
|
+
() => this.#ows.signTypedData(
|
|
2093
|
+
this.#wallet,
|
|
2094
|
+
typed.chain ?? this.#chain,
|
|
2095
|
+
typed.document,
|
|
2096
|
+
this.#passphrase,
|
|
2097
|
+
null,
|
|
2098
|
+
this.#vaultPath
|
|
2099
|
+
)
|
|
2100
|
+
);
|
|
2101
|
+
const sig = evmSignature(result, "signTypedData");
|
|
2102
|
+
let signer;
|
|
1751
2103
|
try {
|
|
1752
|
-
|
|
1753
|
-
account: { address: this.address },
|
|
1754
|
-
domain,
|
|
1755
|
-
types,
|
|
1756
|
-
primaryType: "TransferWithAuthorization",
|
|
1757
|
-
message
|
|
1758
|
-
});
|
|
2104
|
+
signer = ethers.ethers.verifyTypedData(typed.domain, typed.types, typed.message, sig);
|
|
1759
2105
|
} catch (error) {
|
|
1760
2106
|
throw new X402Error(
|
|
1761
|
-
`
|
|
1762
|
-
"
|
|
1763
|
-
error
|
|
2107
|
+
`ethers cannot encode this typed data (${error instanceof Error ? error.message : error}); the OWS signature is not returned`,
|
|
2108
|
+
"INVALID_CONFIG"
|
|
1764
2109
|
);
|
|
1765
2110
|
}
|
|
1766
|
-
|
|
1767
|
-
return {
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
2111
|
+
this.#checkSigner(signer, "signTypedData");
|
|
2112
|
+
return { signature: sig.serialized, v: sig.v, r: sig.r, s: sig.s };
|
|
2113
|
+
}
|
|
2114
|
+
#unsignedTransaction(input) {
|
|
2115
|
+
const senders = [];
|
|
2116
|
+
let tx;
|
|
2117
|
+
try {
|
|
2118
|
+
if (typeof input === "string") {
|
|
2119
|
+
tx = ethers.ethers.Transaction.from(input);
|
|
2120
|
+
} else if (typeof input === "object" && input !== null) {
|
|
2121
|
+
const source = input;
|
|
2122
|
+
for (const key of Object.keys(source)) {
|
|
2123
|
+
if (key === "from" || key === "hash" || TRANSACTION_FIELDS.includes(key)) continue;
|
|
2124
|
+
const ethersName = ETHERS_NAME_OF[key];
|
|
2125
|
+
throw refuse(
|
|
2126
|
+
`transaction key ${shown(key)} is not one ethers reads` + (ethersName ? `; ethers calls it ${ethersName}` : "")
|
|
2127
|
+
);
|
|
2128
|
+
}
|
|
2129
|
+
if (source.from != null) senders.push(source.from);
|
|
2130
|
+
const fields = {};
|
|
2131
|
+
for (const key of TRANSACTION_FIELDS) {
|
|
2132
|
+
if (source[key] != null) fields[key] = source[key];
|
|
2133
|
+
}
|
|
2134
|
+
tx = ethers.ethers.Transaction.from(fields);
|
|
2135
|
+
} else {
|
|
2136
|
+
throw new Error(`got ${typeof input}`);
|
|
2137
|
+
}
|
|
2138
|
+
} catch (error) {
|
|
2139
|
+
if (error instanceof X402Error) throw error;
|
|
2140
|
+
throw refuse(
|
|
2141
|
+
`not a transaction ethers can read (${error instanceof Error ? error.message : error})`
|
|
2142
|
+
);
|
|
2143
|
+
}
|
|
2144
|
+
if (tx.signature) senders.push(tx.from);
|
|
2145
|
+
for (const sender of senders) {
|
|
2146
|
+
let address = "";
|
|
2147
|
+
try {
|
|
2148
|
+
address = ethers.ethers.getAddress(sender);
|
|
2149
|
+
} catch {
|
|
2150
|
+
}
|
|
2151
|
+
if (address !== this.#address) {
|
|
2152
|
+
throw refuse(`transaction from ${shown(sender)} is not this wallet (${this.#address})`);
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
tx.signature = null;
|
|
2156
|
+
return tx;
|
|
2157
|
+
}
|
|
2158
|
+
#checkSigner(signer, what) {
|
|
2159
|
+
if (signer !== this.#address) {
|
|
2160
|
+
throw new X402Error(
|
|
2161
|
+
`OWS ${what} returned a signature that does not recover to ${this.#address} over the digest ethers computes (the one EnvKeyAdapter signs); it is not returned`,
|
|
2162
|
+
"PAYMENT_FAILED"
|
|
2163
|
+
);
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
/** Run one OWS call; its error becomes an X402Error without the passphrase in it. */
|
|
2167
|
+
#call(what, run, code = "PAYMENT_FAILED") {
|
|
2168
|
+
try {
|
|
2169
|
+
return run();
|
|
2170
|
+
} catch (error) {
|
|
2171
|
+
let message = error instanceof Error ? error.message : String(error);
|
|
2172
|
+
if (this.#passphrase) message = message.split(this.#passphrase).join("[redacted]");
|
|
2173
|
+
throw new X402Error(`OWS ${what} failed: ${message}`, code);
|
|
2174
|
+
}
|
|
1779
2175
|
}
|
|
1780
2176
|
};
|
|
1781
2177
|
|