@naculus/connect-appkit-core 0.7.0 → 0.8.1

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 ADDED
@@ -0,0 +1,13 @@
1
+ # `@naculus/connect-appkit-core`
2
+
3
+ ## Authorization and settlement views
4
+
5
+ `describeAuthorization` builds network-free consent text from a connect-core
6
+ `Authorization` or `ListedAuthorization`. Pass asset metadata explicitly to
7
+ show symbols and decimal amounts; without it, the view deliberately shows the
8
+ asset address and base units. `explainSpend` turns every evaluator refusal into
9
+ UI text without changing the authorization decision.
10
+
11
+ `describePayment` marks every server-reported settlement as `unverified`.
12
+ `applySettlementVerification` is the only helper that changes that state, and
13
+ the settlement label becomes verified only for a `verified` result.
package/dist/index.cjs CHANGED
@@ -21,10 +21,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
23
  DEFAULT_NOTIFICATION_SETTINGS: () => DEFAULT_NOTIFICATION_SETTINGS,
24
+ DEFAULT_VERIFY_RETRY: () => DEFAULT_VERIFY_RETRY,
24
25
  InvalidUserOperationReceiptError: () => InvalidUserOperationReceiptError,
25
26
  NOTIFICATION_SETTINGS_KEY: () => NOTIFICATION_SETTINGS_KEY,
26
27
  __resetResolverForTests: () => __resetResolverForTests,
27
28
  addInAppNotification: () => addInAppNotification,
29
+ applySettlementVerification: () => applySettlementVerification,
28
30
  assertHexSignature: () => assertHexSignature,
29
31
  atomicSupportFor: () => atomicSupportFor,
30
32
  bareEvmAddress: () => bareEvmAddress,
@@ -45,13 +47,16 @@ __export(src_exports, {
45
47
  decodeRevertReason: () => decodeRevertReason,
46
48
  defaultSiwxDomain: () => defaultSiwxDomain,
47
49
  defaultSiwxUri: () => defaultSiwxUri,
50
+ describeAuthorization: () => describeAuthorization,
48
51
  describeChain: () => describeChain,
49
52
  describePayment: () => describePayment,
50
53
  eip155ChainIdToNumber: () => eip155ChainIdToNumber,
51
54
  encodeErc20Approve: () => encodeErc20Approve,
52
55
  encodeErc20Transfer: () => encodeErc20Transfer,
53
56
  encodeErc20TransferFrom: () => encodeErc20TransferFrom,
57
+ explainSpend: () => explainSpend,
54
58
  fetchUserOperationReceipt: () => fetchUserOperationReceipt,
59
+ formatAuthorizationAmount: () => formatAuthorizationAmount,
55
60
  getResolver: () => getResolver,
56
61
  getSharedSessionKeyManager: () => getSharedSessionKeyManager,
57
62
  initialWeb3State: () => initialWeb3State,
@@ -1374,7 +1379,125 @@ function createDelegationPolicyFlow(deps) {
1374
1379
  };
1375
1380
  }
1376
1381
 
1382
+ // src/authorization.ts
1383
+ var import_connect_core17 = require("@naculus/connect-core");
1384
+ function assetAddress(asset) {
1385
+ return asset.split(/[:/]/).at(-1);
1386
+ }
1387
+ function groupDigits(value) {
1388
+ return value.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
1389
+ }
1390
+ function formatAuthorizationAmount(amount, metadata) {
1391
+ const baseUnits = amount.toString();
1392
+ if (!metadata || !metadata.symbol || !Number.isSafeInteger(metadata.decimals) || metadata.decimals < 0) {
1393
+ return {
1394
+ formatted: `${groupDigits(baseUnits)} base units`
1395
+ };
1396
+ }
1397
+ const decimals = metadata.decimals;
1398
+ const padded = baseUnits.padStart(decimals + 1, "0");
1399
+ const integer = decimals === 0 ? padded : padded.slice(0, -decimals);
1400
+ const fraction = decimals === 0 ? "" : padded.slice(-decimals).replace(/0+$/, "");
1401
+ const value = `${groupDigits(integer)}${fraction ? `.${fraction}` : ""}`;
1402
+ return { formatted: `${value} ${metadata.symbol}` };
1403
+ }
1404
+ function relativeExpiry(expiresAt, now, locale) {
1405
+ const seconds = expiresAt - now;
1406
+ const absolute = Math.abs(seconds);
1407
+ const divisor = absolute >= 31536e3 ? 31536e3 : absolute >= 2592e3 ? 2592e3 : absolute >= 86400 ? 86400 : absolute >= 3600 ? 3600 : absolute >= 60 ? 60 : 1;
1408
+ const unit = divisor === 31536e3 ? "year" : divisor === 2592e3 ? "month" : divisor === 86400 ? "day" : divisor === 3600 ? "hour" : divisor === 60 ? "minute" : "second";
1409
+ return new Intl.RelativeTimeFormat(locale).format(
1410
+ Math.round(seconds / divisor),
1411
+ unit
1412
+ );
1413
+ }
1414
+ function describeAuthorization(input, options = {}) {
1415
+ const now = options.now ?? Math.floor(Date.now() / 1e3);
1416
+ const locale = options.locale ?? "en";
1417
+ const flags = "flags" in input ? input.flags : [];
1418
+ const label = "label" in input ? input.label ?? null : null;
1419
+ return {
1420
+ label,
1421
+ principal: input.principal ?? null,
1422
+ grants: input.grants.map((grant) => {
1423
+ const metadata = options.assets?.[grant.asset];
1424
+ return {
1425
+ asset: grant.asset,
1426
+ assetLabel: metadata?.symbol || assetAddress(grant.asset),
1427
+ recipients: grant.recipients.map((full) => ({
1428
+ shortened: full.length <= 14 ? full : `${full.slice(0, 6)}\u2026${full.slice(-6)}`,
1429
+ full
1430
+ })),
1431
+ perPayment: formatAuthorizationAmount(grant.maxPerPayment, metadata),
1432
+ total: formatAuthorizationAmount(grant.maxTotal, metadata),
1433
+ count: grant.maxCount ?? null,
1434
+ rails: grant.rails.map((r) => r.replace("-", " "))
1435
+ };
1436
+ }),
1437
+ expiry: {
1438
+ timestamp: input.expiresAt,
1439
+ absolute: new Intl.DateTimeFormat(locale, {
1440
+ year: "numeric",
1441
+ month: "short",
1442
+ day: "numeric",
1443
+ hour: "2-digit",
1444
+ minute: "2-digit",
1445
+ hourCycle: "h23",
1446
+ timeZone: "UTC",
1447
+ timeZoneName: "short"
1448
+ }).format(input.expiresAt * 1e3),
1449
+ relative: relativeExpiry(input.expiresAt, now, locale)
1450
+ },
1451
+ warnings: flags.map(
1452
+ (flag) => flag === "unrestricted-recipient-legacy" ? "This key can pay any recipient." : "This key can do more than shown here."
1453
+ )
1454
+ };
1455
+ }
1456
+ var REFUSAL_SENTENCES = {
1457
+ expired: "This authorization has expired.",
1458
+ "not-yet-valid": "This authorization is not valid yet.",
1459
+ "no-matching-grant": "No grant covers this asset.",
1460
+ "recipient-not-allowed": "This recipient is not allowed.",
1461
+ "over-per-payment": "This amount exceeds the per-payment limit.",
1462
+ "over-total": "This amount exceeds the remaining total limit.",
1463
+ "over-count": "This authorization has reached its payment count limit.",
1464
+ "rail-not-allowed": "This payment rail is not allowed.",
1465
+ "invalid-authorization": "The authorization or spend request is invalid."
1466
+ };
1467
+ function explainSpend(authorization, request, options = {}) {
1468
+ const verdict = (0, import_connect_core17.evaluateSpend)(authorization, request);
1469
+ if (!verdict.allow) {
1470
+ return {
1471
+ allowed: false,
1472
+ reason: verdict.reason,
1473
+ sentence: REFUSAL_SENTENCES[verdict.reason]
1474
+ };
1475
+ }
1476
+ const remainingTotal = authorization.grants[verdict.grant].maxTotal - request.spentSoFar - request.amount;
1477
+ const remainingTotalFormatted = formatAuthorizationAmount(
1478
+ remainingTotal,
1479
+ options.assets?.[authorization.grants[verdict.grant].asset]
1480
+ );
1481
+ return {
1482
+ allowed: true,
1483
+ grant: verdict.grant,
1484
+ remainingTotal,
1485
+ remainingTotalFormatted,
1486
+ sentence: `Allowed by grant ${verdict.grant + 1}; ${remainingTotalFormatted.formatted} remain.`
1487
+ };
1488
+ }
1489
+
1377
1490
  // src/payment-fetch.ts
1491
+ var DEFAULT_VERIFY_RETRY = { attempts: 5, delayMs: 4e3 };
1492
+ function applySettlementVerification(payment, result) {
1493
+ return {
1494
+ ...payment,
1495
+ verification: {
1496
+ status: result.status,
1497
+ ..."reason" in result && result.reason !== void 0 ? { reason: result.reason } : {}
1498
+ }
1499
+ };
1500
+ }
1378
1501
  function record(value) {
1379
1502
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
1380
1503
  }
@@ -1404,7 +1527,8 @@ function describePayment(input, result) {
1404
1527
  payTo: text(paid.payTo),
1405
1528
  amount: text(paid.amount),
1406
1529
  // A hash reported for another chain is not this payment's.
1407
- reference: settlement?.success === true && settlement.network === paid.network ? text(settlement.transaction) : null
1530
+ reference: settlement?.success === true && settlement.network === paid.network ? text(settlement.transaction) : null,
1531
+ verification: { status: "unverified" }
1408
1532
  };
1409
1533
  }
1410
1534
  const challenge = record(record(paid.challenge)?.params);
@@ -1418,7 +1542,8 @@ function describePayment(input, result) {
1418
1542
  asset: text(request.currency),
1419
1543
  payTo: text(request.recipient),
1420
1544
  amount: text(request.amount),
1421
- reference: text(record(result.receipt)?.reference)
1545
+ reference: text(record(result.receipt)?.reference),
1546
+ verification: { status: "unverified" }
1422
1547
  };
1423
1548
  }
1424
1549
  return {
@@ -1428,7 +1553,8 @@ function describePayment(input, result) {
1428
1553
  asset: null,
1429
1554
  payTo: null,
1430
1555
  amount: null,
1431
- reference: null
1556
+ reference: null,
1557
+ verification: { status: "unverified" }
1432
1558
  };
1433
1559
  }
1434
1560
 
@@ -1463,11 +1589,11 @@ function readMppRecoveryChannelId(error) {
1463
1589
  }
1464
1590
 
1465
1591
  // src/solana-session-key.ts
1466
- var import_connect_core17 = require("@naculus/connect-core");
1592
+ var import_connect_core18 = require("@naculus/connect-core");
1467
1593
  function send(rpc) {
1468
1594
  const sendTransaction = rpc.sendTransaction;
1469
1595
  if (!sendTransaction) {
1470
- throw new import_connect_core17.WalletError(
1596
+ throw new import_connect_core18.WalletError(
1471
1597
  "invalid_input",
1472
1598
  "The Solana RPC must implement sendTransaction to approve or revoke."
1473
1599
  );
@@ -1505,7 +1631,7 @@ function createSolanaSessionKeyFlow(deps) {
1505
1631
  } catch (cause) {
1506
1632
  await manager.revoke(draft.id).catch(() => {
1507
1633
  });
1508
- throw new import_connect_core17.WalletError(
1634
+ throw new import_connect_core18.WalletError(
1509
1635
  "rpc_error",
1510
1636
  `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.`
1511
1637
  );
@@ -1516,7 +1642,7 @@ function createSolanaSessionKeyFlow(deps) {
1516
1642
  async revoke(id) {
1517
1643
  const key = (await list()).find((k) => k.id === id);
1518
1644
  if (!key) {
1519
- throw new import_connect_core17.WalletError(
1645
+ throw new import_connect_core18.WalletError(
1520
1646
  "invalid_input",
1521
1647
  `No Solana session key ${id} for this owner.`
1522
1648
  );
@@ -1528,7 +1654,7 @@ function createSolanaSessionKeyFlow(deps) {
1528
1654
  rpc
1529
1655
  );
1530
1656
  const signed = await owner.signTransaction(transaction);
1531
- const revocation = (0, import_connect_core17.verifySignedOwnerTransaction)(signed, {
1657
+ const revocation = (0, import_connect_core18.verifySignedOwnerTransaction)(signed, {
1532
1658
  kind: "revoke",
1533
1659
  revocation: {
1534
1660
  owner: key.owner,
@@ -1543,7 +1669,7 @@ function createSolanaSessionKeyFlow(deps) {
1543
1669
  }
1544
1670
 
1545
1671
  // src/web3-store.ts
1546
- var import_connect_core18 = require("@naculus/connect-core");
1672
+ var import_connect_core19 = require("@naculus/connect-core");
1547
1673
  function withTimeout(promise, ms, label = "Operation") {
1548
1674
  let timeoutId;
1549
1675
  const timeout = new Promise((_, reject) => {
@@ -1565,7 +1691,7 @@ async function withRetry(fn, options) {
1565
1691
  lastError = error;
1566
1692
  if (attempt < options.maxRetries) {
1567
1693
  const delay = options.baseDelay * 2 ** attempt;
1568
- import_connect_core18.logger.warn(
1694
+ import_connect_core19.logger.warn(
1569
1695
  "appkit/core",
1570
1696
  `Retry attempt ${attempt + 1}/${options.maxRetries} after ${delay}ms`
1571
1697
  );
@@ -1680,10 +1806,12 @@ function siwxResultToSession(result, now = /* @__PURE__ */ new Date()) {
1680
1806
  // Annotate the CommonJS export names for ESM import in node:
1681
1807
  0 && (module.exports = {
1682
1808
  DEFAULT_NOTIFICATION_SETTINGS,
1809
+ DEFAULT_VERIFY_RETRY,
1683
1810
  InvalidUserOperationReceiptError,
1684
1811
  NOTIFICATION_SETTINGS_KEY,
1685
1812
  __resetResolverForTests,
1686
1813
  addInAppNotification,
1814
+ applySettlementVerification,
1687
1815
  assertHexSignature,
1688
1816
  atomicSupportFor,
1689
1817
  bareEvmAddress,
@@ -1704,13 +1832,16 @@ function siwxResultToSession(result, now = /* @__PURE__ */ new Date()) {
1704
1832
  decodeRevertReason,
1705
1833
  defaultSiwxDomain,
1706
1834
  defaultSiwxUri,
1835
+ describeAuthorization,
1707
1836
  describeChain,
1708
1837
  describePayment,
1709
1838
  eip155ChainIdToNumber,
1710
1839
  encodeErc20Approve,
1711
1840
  encodeErc20Transfer,
1712
1841
  encodeErc20TransferFrom,
1842
+ explainSpend,
1713
1843
  fetchUserOperationReceipt,
1844
+ formatAuthorizationAmount,
1714
1845
  getResolver,
1715
1846
  getSharedSessionKeyManager,
1716
1847
  initialWeb3State,