@naculus/connect-appkit-core 0.9.0 → 0.11.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.cjs CHANGED
@@ -44,6 +44,7 @@ __export(src_exports, {
44
44
  createNotification: () => createNotification,
45
45
  createNotificationSettingsStorage: () => createNotificationSettingsStorage,
46
46
  createSolanaSessionKeyFlow: () => createSolanaSessionKeyFlow,
47
+ createX402WalletSigner: () => createX402WalletSigner,
47
48
  decodeRevertReason: () => decodeRevertReason,
48
49
  defaultSiwxDomain: () => defaultSiwxDomain,
49
50
  defaultSiwxUri: () => defaultSiwxUri,
@@ -56,6 +57,7 @@ __export(src_exports, {
56
57
  encodeErc20TransferFrom: () => encodeErc20TransferFrom,
57
58
  explainSpend: () => explainSpend,
58
59
  fetchUserOperationReceipt: () => fetchUserOperationReceipt,
60
+ findX402Provider: () => findX402Provider,
59
61
  formatAuthorizationAmount: () => formatAuthorizationAmount,
60
62
  getResolver: () => getResolver,
61
63
  getSharedSessionKeyManager: () => getSharedSessionKeyManager,
@@ -86,6 +88,7 @@ __export(src_exports, {
86
88
  resolveRevertReason: () => resolveRevertReason,
87
89
  resolveSimulationEndpoint: () => resolveSimulationEndpoint,
88
90
  resolveUserOpChain: () => resolveUserOpChain,
91
+ resolveX402Wallet: () => resolveX402Wallet,
89
92
  sameExecutionIntent: () => sameExecutionIntent,
90
93
  saveNotificationSettings: () => saveNotificationSettings,
91
94
  selectChainCapabilities: () => selectChainCapabilities,
@@ -1381,39 +1384,44 @@ function createDelegationPolicyFlow(deps) {
1381
1384
 
1382
1385
  // src/authorization.ts
1383
1386
  var import_connect_core17 = require("@naculus/connect-core");
1384
- function assetAddress(asset) {
1385
- return asset.split(/[:/]/).at(-1);
1386
- }
1387
1387
  function groupDigits(value) {
1388
1388
  let out = "";
1389
1389
  for (let i = 0; i < value.length; i++)
1390
1390
  out += (i && (value.length - i) % 3 === 0 ? "," : "") + value[i];
1391
1391
  return out;
1392
1392
  }
1393
- function trimTrailingZeros(value) {
1394
- let end = value.length;
1395
- while (value[end - 1] === "0") end--;
1396
- return value.slice(0, end);
1393
+ function trustedMetadata(asset, options) {
1394
+ const trust = !options.trustedAssets ? "unchecked" : options.trustedAssets.some(
1395
+ (t) => canonicalAssetId(t) === canonicalAssetId(asset)
1396
+ ) ? "trusted" : "unverified";
1397
+ return [trust, trust === "unverified" ? void 0 : options.assets?.[asset]];
1398
+ }
1399
+ function canonicalAssetId(asset) {
1400
+ return asset.replace(
1401
+ /^(eip155:[^/]+\/erc20:)(.+)$/,
1402
+ (_, prefix, reference) => prefix + reference.toLowerCase()
1403
+ );
1397
1404
  }
1398
1405
  function formatAuthorizationAmount(amount, metadata) {
1399
1406
  const baseUnits = amount.toString();
1400
- if (!metadata || !metadata.symbol || !Number.isSafeInteger(metadata.decimals) || metadata.decimals < 0 || metadata.decimals > 255) {
1407
+ if (!metadata?.symbol || !Number.isSafeInteger(metadata.decimals) || metadata.decimals < 0 || metadata.decimals > 255) {
1401
1408
  return {
1402
1409
  formatted: `${groupDigits(baseUnits)} base units`
1403
1410
  };
1404
1411
  }
1405
1412
  const decimals = metadata.decimals;
1406
1413
  const padded = baseUnits.padStart(decimals + 1, "0");
1407
- const integer = decimals === 0 ? padded : padded.slice(0, -decimals);
1408
- const fraction = decimals === 0 ? "" : trimTrailingZeros(padded.slice(-decimals));
1409
- const value = `${groupDigits(integer)}${fraction ? `.${fraction}` : ""}`;
1410
- return { formatted: `${value} ${metadata.symbol}` };
1414
+ const integer = padded.slice(0, decimals ? -decimals : void 0);
1415
+ let fraction = decimals ? padded.slice(-decimals) : "";
1416
+ while (fraction.endsWith("0")) fraction = fraction.slice(0, -1);
1417
+ return {
1418
+ formatted: `${groupDigits(integer)}${fraction ? `.${fraction}` : ""} ${metadata.symbol}`
1419
+ };
1411
1420
  }
1412
1421
  function relativeExpiry(expiresAt, now, locale) {
1413
1422
  const seconds = expiresAt - now;
1414
1423
  const absolute = Math.abs(seconds);
1415
- const divisor = absolute >= 31536e3 ? 31536e3 : absolute >= 2592e3 ? 2592e3 : absolute >= 86400 ? 86400 : absolute >= 3600 ? 3600 : absolute >= 60 ? 60 : 1;
1416
- const unit = divisor === 31536e3 ? "year" : divisor === 2592e3 ? "month" : divisor === 86400 ? "day" : divisor === 3600 ? "hour" : divisor === 60 ? "minute" : "second";
1424
+ const [divisor, unit] = absolute >= 31536e3 ? [31536e3, "year"] : absolute >= 2592e3 ? [2592e3, "month"] : absolute >= 86400 ? [86400, "day"] : absolute >= 3600 ? [3600, "hour"] : absolute >= 60 ? [60, "minute"] : [1, "second"];
1417
1425
  const value = Math.round(seconds / divisor);
1418
1426
  if (typeof Intl.RelativeTimeFormat !== "function") {
1419
1427
  const count = Math.abs(value);
@@ -1422,6 +1430,25 @@ function relativeExpiry(expiresAt, now, locale) {
1422
1430
  }
1423
1431
  return new Intl.RelativeTimeFormat(locale).format(value, unit);
1424
1432
  }
1433
+ function absoluteUtc(timestamp, locale) {
1434
+ return new Intl.DateTimeFormat(locale, {
1435
+ year: "numeric",
1436
+ month: "short",
1437
+ day: "numeric",
1438
+ hour: "2-digit",
1439
+ minute: "2-digit",
1440
+ hourCycle: "h23",
1441
+ timeZone: "UTC",
1442
+ timeZoneName: "short"
1443
+ }).format(timestamp * 1e3);
1444
+ }
1445
+ function formatPeriodLength(seconds) {
1446
+ const [amount, unit] = seconds % 604800 === 0 ? [seconds / 604800, "week"] : seconds % 86400 === 0 ? [seconds / 86400, "day"] : seconds % 3600 === 0 ? [seconds / 3600, "hour"] : seconds % 60 === 0 ? [seconds / 60, "minute"] : [seconds, "second"];
1447
+ return `${amount} ${unit}${amount === 1 ? "" : "s"}`;
1448
+ }
1449
+ function formatAuthorizationEnforcement(enforcement) {
1450
+ return enforcement === "on-chain" ? "Limits enforced by the blockchain" : enforcement === "device" ? "Limits enforced by this device" : null;
1451
+ }
1425
1452
  function describeAuthorization(input, options = {}) {
1426
1453
  const now = options.now ?? Math.floor(Date.now() / 1e3);
1427
1454
  const locale = options.locale ?? "en";
@@ -1431,10 +1458,11 @@ function describeAuthorization(input, options = {}) {
1431
1458
  label,
1432
1459
  principal: input.principal ?? null,
1433
1460
  grants: input.grants.map((grant) => {
1434
- const metadata = options.assets?.[grant.asset];
1461
+ const [assetTrust, metadata] = trustedMetadata(grant.asset, options);
1435
1462
  return {
1436
1463
  asset: grant.asset,
1437
- assetLabel: metadata?.symbol || assetAddress(grant.asset),
1464
+ assetTrust,
1465
+ assetLabel: metadata?.symbol || grant.asset.split(":").at(-1),
1438
1466
  recipients: grant.recipients.map((full) => ({
1439
1467
  shortened: full.length <= 14 ? full : `${full.slice(0, 6)}\u2026${full.slice(-6)}`,
1440
1468
  full
@@ -1442,23 +1470,23 @@ function describeAuthorization(input, options = {}) {
1442
1470
  perPayment: formatAuthorizationAmount(grant.maxPerPayment, metadata),
1443
1471
  total: formatAuthorizationAmount(grant.maxTotal, metadata),
1444
1472
  count: grant.maxCount ?? null,
1473
+ period: grant.period ? {
1474
+ amount: formatAuthorizationAmount(grant.period.amount, metadata),
1475
+ every: formatPeriodLength(grant.period.seconds),
1476
+ startsAt: absoluteUtc(grant.period.start, locale)
1477
+ } : null,
1445
1478
  rails: grant.rails.map((r) => r.replace("-", " "))
1446
1479
  };
1447
1480
  }),
1448
1481
  expiry: {
1449
1482
  timestamp: input.expiresAt,
1450
- absolute: new Intl.DateTimeFormat(locale, {
1451
- year: "numeric",
1452
- month: "short",
1453
- day: "numeric",
1454
- hour: "2-digit",
1455
- minute: "2-digit",
1456
- hourCycle: "h23",
1457
- timeZone: "UTC",
1458
- timeZoneName: "short"
1459
- }).format(input.expiresAt * 1e3),
1483
+ absolute: absoluteUtc(input.expiresAt, locale),
1460
1484
  relative: relativeExpiry(input.expiresAt, now, locale)
1461
1485
  },
1486
+ enforcement: options.enforcement ?? null,
1487
+ enforcementLabel: formatAuthorizationEnforcement(
1488
+ options.enforcement ?? null
1489
+ ),
1462
1490
  warnings: flags.map(
1463
1491
  (flag) => flag === "unrestricted-recipient-legacy" ? "This key can pay any recipient." : "This key can do more than shown here."
1464
1492
  )
@@ -1472,6 +1500,7 @@ var REFUSAL_SENTENCES = {
1472
1500
  "over-per-payment": "This amount exceeds the per-payment limit.",
1473
1501
  "over-total": "This amount exceeds the remaining total limit.",
1474
1502
  "over-count": "This authorization has reached its payment count limit.",
1503
+ "period-limit-exceeded": "This amount exceeds the limit for the current period.",
1475
1504
  "rail-not-allowed": "This payment rail is not allowed.",
1476
1505
  "invalid-authorization": "The authorization or spend request is invalid."
1477
1506
  };
@@ -1487,7 +1516,7 @@ function explainSpend(authorization, request, options = {}) {
1487
1516
  const remainingTotal = authorization.grants[verdict.grant].maxTotal - request.spentSoFar - request.amount;
1488
1517
  const remainingTotalFormatted = formatAuthorizationAmount(
1489
1518
  remainingTotal,
1490
- options.assets?.[authorization.grants[verdict.grant].asset]
1519
+ trustedMetadata(authorization.grants[verdict.grant].asset, options)[1]
1491
1520
  );
1492
1521
  return {
1493
1522
  allowed: true,
@@ -1569,6 +1598,57 @@ function describePayment(input, result) {
1569
1598
  };
1570
1599
  }
1571
1600
 
1601
+ // src/x402-wallet.ts
1602
+ var import_connect_core18 = require("@naculus/connect-core");
1603
+ var import_payments_x402 = require("@naculus/payments-x402");
1604
+ function findX402Provider(session, sources) {
1605
+ if (!session) return null;
1606
+ if (session.walletType === "eip6963" || session.id.startsWith("eip6963-")) {
1607
+ return sources.injected?.find((wallet) => wallet.id === session.walletId)?.provider ?? null;
1608
+ }
1609
+ if (session.walletType === "walletconnect") {
1610
+ return sources.walletConnect ?? null;
1611
+ }
1612
+ return null;
1613
+ }
1614
+ function unavailable(reason) {
1615
+ return { provider: null, address: null, reason };
1616
+ }
1617
+ function resolveX402Wallet(session, provider) {
1618
+ if (!session) return unavailable("no-session");
1619
+ const namespace = session.namespaces.eip155;
1620
+ if (!namespace) return unavailable("not-evm");
1621
+ const isInjected = session.walletType === "eip6963" || session.id.startsWith("eip6963-");
1622
+ const isWalletConnect = session.walletType === "walletconnect";
1623
+ if (!isInjected && !isWalletConnect || !provider?.request) {
1624
+ return unavailable("unsupported-wallet-type");
1625
+ }
1626
+ for (const account of namespace.accounts) {
1627
+ const parsed = (0, import_connect_core18.parseCaip10)(account);
1628
+ const address = parsed?.namespace === "eip155" ? parsed.address : account;
1629
+ if ((0, import_connect_core18.isEvmAddress)(address)) {
1630
+ return {
1631
+ provider,
1632
+ address,
1633
+ reason: null
1634
+ };
1635
+ }
1636
+ }
1637
+ return unavailable("not-evm");
1638
+ }
1639
+ function createX402WalletSigner(session, provider, switchChain) {
1640
+ const resolved = resolveX402Wallet(session, provider);
1641
+ if (resolved.reason) return { signer: null, reason: resolved.reason };
1642
+ return {
1643
+ signer: (0, import_payments_x402.walletX402Signer)({
1644
+ provider: resolved.provider,
1645
+ address: resolved.address,
1646
+ switchChain
1647
+ }),
1648
+ reason: null
1649
+ };
1650
+ }
1651
+
1572
1652
  // src/mpp-session.ts
1573
1653
  function record2(value) {
1574
1654
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
@@ -1600,11 +1680,11 @@ function readMppRecoveryChannelId(error) {
1600
1680
  }
1601
1681
 
1602
1682
  // src/solana-session-key.ts
1603
- var import_connect_core18 = require("@naculus/connect-core");
1683
+ var import_connect_core19 = require("@naculus/connect-core");
1604
1684
  function send(rpc) {
1605
1685
  const sendTransaction = rpc.sendTransaction;
1606
1686
  if (!sendTransaction) {
1607
- throw new import_connect_core18.WalletError(
1687
+ throw new import_connect_core19.WalletError(
1608
1688
  "invalid_input",
1609
1689
  "The Solana RPC must implement sendTransaction to approve or revoke."
1610
1690
  );
@@ -1642,7 +1722,7 @@ function createSolanaSessionKeyFlow(deps) {
1642
1722
  } catch (cause) {
1643
1723
  await manager.revoke(draft.id).catch(() => {
1644
1724
  });
1645
- throw new import_connect_core18.WalletError(
1725
+ throw new import_connect_core19.WalletError(
1646
1726
  "rpc_error",
1647
1727
  `The approval could not be confirmed as sent (${cause instanceof Error ? cause.message : String(cause)}); the key is revoked here. Call revoke("${draft.id}") to send the on-chain Revoke in case it landed.`
1648
1728
  );
@@ -1653,7 +1733,7 @@ function createSolanaSessionKeyFlow(deps) {
1653
1733
  async revoke(id) {
1654
1734
  const key = (await list()).find((k) => k.id === id);
1655
1735
  if (!key) {
1656
- throw new import_connect_core18.WalletError(
1736
+ throw new import_connect_core19.WalletError(
1657
1737
  "invalid_input",
1658
1738
  `No Solana session key ${id} for this owner.`
1659
1739
  );
@@ -1665,7 +1745,7 @@ function createSolanaSessionKeyFlow(deps) {
1665
1745
  rpc
1666
1746
  );
1667
1747
  const signed = await owner.signTransaction(transaction);
1668
- const revocation = (0, import_connect_core18.verifySignedOwnerTransaction)(signed, {
1748
+ const revocation = (0, import_connect_core19.verifySignedOwnerTransaction)(signed, {
1669
1749
  kind: "revoke",
1670
1750
  revocation: {
1671
1751
  owner: key.owner,
@@ -1680,7 +1760,7 @@ function createSolanaSessionKeyFlow(deps) {
1680
1760
  }
1681
1761
 
1682
1762
  // src/web3-store.ts
1683
- var import_connect_core19 = require("@naculus/connect-core");
1763
+ var import_connect_core20 = require("@naculus/connect-core");
1684
1764
  function withTimeout(promise, ms, label = "Operation") {
1685
1765
  let timeoutId;
1686
1766
  const timeout = new Promise((_, reject) => {
@@ -1702,7 +1782,7 @@ async function withRetry(fn, options) {
1702
1782
  lastError = error;
1703
1783
  if (attempt < options.maxRetries) {
1704
1784
  const delay = options.baseDelay * 2 ** attempt;
1705
- import_connect_core19.logger.warn(
1785
+ import_connect_core20.logger.warn(
1706
1786
  "appkit/core",
1707
1787
  `Retry attempt ${attempt + 1}/${options.maxRetries} after ${delay}ms`
1708
1788
  );
@@ -1840,6 +1920,7 @@ function siwxResultToSession(result, now = /* @__PURE__ */ new Date()) {
1840
1920
  createNotification,
1841
1921
  createNotificationSettingsStorage,
1842
1922
  createSolanaSessionKeyFlow,
1923
+ createX402WalletSigner,
1843
1924
  decodeRevertReason,
1844
1925
  defaultSiwxDomain,
1845
1926
  defaultSiwxUri,
@@ -1852,6 +1933,7 @@ function siwxResultToSession(result, now = /* @__PURE__ */ new Date()) {
1852
1933
  encodeErc20TransferFrom,
1853
1934
  explainSpend,
1854
1935
  fetchUserOperationReceipt,
1936
+ findX402Provider,
1855
1937
  formatAuthorizationAmount,
1856
1938
  getResolver,
1857
1939
  getSharedSessionKeyManager,
@@ -1882,6 +1964,7 @@ function siwxResultToSession(result, now = /* @__PURE__ */ new Date()) {
1882
1964
  resolveRevertReason,
1883
1965
  resolveSimulationEndpoint,
1884
1966
  resolveUserOpChain,
1967
+ resolveX402Wallet,
1885
1968
  sameExecutionIntent,
1886
1969
  saveNotificationSettings,
1887
1970
  selectChainCapabilities,