@naculus/connect-appkit-core 0.6.0 → 0.8.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 +13 -0
- package/dist/index.cjs +188 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +142 -6
- package/dist/index.d.ts +142 -6
- package/dist/index.js +173 -3
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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,
|
|
@@ -67,8 +72,13 @@ __export(src_exports, {
|
|
|
67
72
|
normalizeCapabilities: () => normalizeCapabilities,
|
|
68
73
|
normalizeEip155ChainId: () => normalizeEip155ChainId,
|
|
69
74
|
parseUserOperationReceipt: () => parseUserOperationReceipt,
|
|
75
|
+
readCurrentMppSessionChannel: () => readCurrentMppSessionChannel,
|
|
70
76
|
readDecimals: () => readDecimals,
|
|
77
|
+
readMppRecoveryChannelId: () => readMppRecoveryChannelId,
|
|
78
|
+
readMppSessionChannel: () => readMppSessionChannel,
|
|
79
|
+
readMppSessionReceipt: () => readMppSessionReceipt,
|
|
71
80
|
readSolanaRoles: () => readSolanaRoles,
|
|
81
|
+
replayNotificationSettings: () => replayNotificationSettings,
|
|
72
82
|
resetSharedSessionKeyManager: () => resetSharedSessionKeyManager,
|
|
73
83
|
resolveChain: () => resolveChain,
|
|
74
84
|
resolveContextSimulationEndpoint: () => resolveContextSimulationEndpoint,
|
|
@@ -573,6 +583,13 @@ function saveNotificationSettings(storage, settings) {
|
|
|
573
583
|
function updateNotificationSettings(current, update) {
|
|
574
584
|
return { ...current, ...update };
|
|
575
585
|
}
|
|
586
|
+
function replayNotificationSettings(persisted, updates) {
|
|
587
|
+
let settings = persisted ?? DEFAULT_NOTIFICATION_SETTINGS;
|
|
588
|
+
for (const update of updates) {
|
|
589
|
+
settings = updateNotificationSettings(settings, update(settings));
|
|
590
|
+
}
|
|
591
|
+
return settings;
|
|
592
|
+
}
|
|
576
593
|
function channelsUpdate(channels) {
|
|
577
594
|
const update = {};
|
|
578
595
|
if (channels.includes("telegram")) update.telegram = true;
|
|
@@ -1362,7 +1379,125 @@ function createDelegationPolicyFlow(deps) {
|
|
|
1362
1379
|
};
|
|
1363
1380
|
}
|
|
1364
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
|
+
|
|
1365
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
|
+
}
|
|
1366
1501
|
function record(value) {
|
|
1367
1502
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
1368
1503
|
}
|
|
@@ -1392,7 +1527,8 @@ function describePayment(input, result) {
|
|
|
1392
1527
|
payTo: text(paid.payTo),
|
|
1393
1528
|
amount: text(paid.amount),
|
|
1394
1529
|
// A hash reported for another chain is not this payment's.
|
|
1395
|
-
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" }
|
|
1396
1532
|
};
|
|
1397
1533
|
}
|
|
1398
1534
|
const challenge = record(record(paid.challenge)?.params);
|
|
@@ -1406,7 +1542,8 @@ function describePayment(input, result) {
|
|
|
1406
1542
|
asset: text(request.currency),
|
|
1407
1543
|
payTo: text(request.recipient),
|
|
1408
1544
|
amount: text(request.amount),
|
|
1409
|
-
reference: text(record(result.receipt)?.reference)
|
|
1545
|
+
reference: text(record(result.receipt)?.reference),
|
|
1546
|
+
verification: { status: "unverified" }
|
|
1410
1547
|
};
|
|
1411
1548
|
}
|
|
1412
1549
|
return {
|
|
@@ -1416,16 +1553,47 @@ function describePayment(input, result) {
|
|
|
1416
1553
|
asset: null,
|
|
1417
1554
|
payTo: null,
|
|
1418
1555
|
amount: null,
|
|
1419
|
-
reference: null
|
|
1556
|
+
reference: null,
|
|
1557
|
+
verification: { status: "unverified" }
|
|
1420
1558
|
};
|
|
1421
1559
|
}
|
|
1422
1560
|
|
|
1561
|
+
// src/mpp-session.ts
|
|
1562
|
+
function record2(value) {
|
|
1563
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
1564
|
+
}
|
|
1565
|
+
function readMppSessionChannel(value) {
|
|
1566
|
+
const channel = record2(value);
|
|
1567
|
+
if (!channel || typeof channel.channelId !== "string" || typeof channel.payer !== "string" || typeof channel.payee !== "string" || typeof channel.mint !== "string" || typeof channel.channelProgram !== "string" || typeof channel.deposit !== "bigint" || typeof channel.gracePeriodSeconds !== "number" || typeof channel.openSlot !== "bigint" || typeof channel.salt !== "bigint") {
|
|
1568
|
+
return null;
|
|
1569
|
+
}
|
|
1570
|
+
return channel;
|
|
1571
|
+
}
|
|
1572
|
+
function readCurrentMppSessionChannel(session) {
|
|
1573
|
+
for (let index = session.channels.length - 1; index >= 0; index--) {
|
|
1574
|
+
const channel = readMppSessionChannel(session.channels[index]);
|
|
1575
|
+
if (channel) return channel;
|
|
1576
|
+
}
|
|
1577
|
+
return null;
|
|
1578
|
+
}
|
|
1579
|
+
function readMppSessionReceipt(value) {
|
|
1580
|
+
const receipt = record2(value);
|
|
1581
|
+
if (receipt?.status !== "success" || typeof receipt.method !== "string" || typeof receipt.timestamp !== "string" || typeof receipt.reference !== "string") {
|
|
1582
|
+
return null;
|
|
1583
|
+
}
|
|
1584
|
+
return receipt;
|
|
1585
|
+
}
|
|
1586
|
+
function readMppRecoveryChannelId(error) {
|
|
1587
|
+
const channelId = record2(error)?.channelId;
|
|
1588
|
+
return typeof channelId === "string" && channelId !== "" ? channelId : null;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1423
1591
|
// src/solana-session-key.ts
|
|
1424
|
-
var
|
|
1592
|
+
var import_connect_core18 = require("@naculus/connect-core");
|
|
1425
1593
|
function send(rpc) {
|
|
1426
1594
|
const sendTransaction = rpc.sendTransaction;
|
|
1427
1595
|
if (!sendTransaction) {
|
|
1428
|
-
throw new
|
|
1596
|
+
throw new import_connect_core18.WalletError(
|
|
1429
1597
|
"invalid_input",
|
|
1430
1598
|
"The Solana RPC must implement sendTransaction to approve or revoke."
|
|
1431
1599
|
);
|
|
@@ -1463,7 +1631,7 @@ function createSolanaSessionKeyFlow(deps) {
|
|
|
1463
1631
|
} catch (cause) {
|
|
1464
1632
|
await manager.revoke(draft.id).catch(() => {
|
|
1465
1633
|
});
|
|
1466
|
-
throw new
|
|
1634
|
+
throw new import_connect_core18.WalletError(
|
|
1467
1635
|
"rpc_error",
|
|
1468
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.`
|
|
1469
1637
|
);
|
|
@@ -1474,7 +1642,7 @@ function createSolanaSessionKeyFlow(deps) {
|
|
|
1474
1642
|
async revoke(id) {
|
|
1475
1643
|
const key = (await list()).find((k) => k.id === id);
|
|
1476
1644
|
if (!key) {
|
|
1477
|
-
throw new
|
|
1645
|
+
throw new import_connect_core18.WalletError(
|
|
1478
1646
|
"invalid_input",
|
|
1479
1647
|
`No Solana session key ${id} for this owner.`
|
|
1480
1648
|
);
|
|
@@ -1486,7 +1654,7 @@ function createSolanaSessionKeyFlow(deps) {
|
|
|
1486
1654
|
rpc
|
|
1487
1655
|
);
|
|
1488
1656
|
const signed = await owner.signTransaction(transaction);
|
|
1489
|
-
const revocation = (0,
|
|
1657
|
+
const revocation = (0, import_connect_core18.verifySignedOwnerTransaction)(signed, {
|
|
1490
1658
|
kind: "revoke",
|
|
1491
1659
|
revocation: {
|
|
1492
1660
|
owner: key.owner,
|
|
@@ -1501,7 +1669,7 @@ function createSolanaSessionKeyFlow(deps) {
|
|
|
1501
1669
|
}
|
|
1502
1670
|
|
|
1503
1671
|
// src/web3-store.ts
|
|
1504
|
-
var
|
|
1672
|
+
var import_connect_core19 = require("@naculus/connect-core");
|
|
1505
1673
|
function withTimeout(promise, ms, label = "Operation") {
|
|
1506
1674
|
let timeoutId;
|
|
1507
1675
|
const timeout = new Promise((_, reject) => {
|
|
@@ -1523,7 +1691,7 @@ async function withRetry(fn, options) {
|
|
|
1523
1691
|
lastError = error;
|
|
1524
1692
|
if (attempt < options.maxRetries) {
|
|
1525
1693
|
const delay = options.baseDelay * 2 ** attempt;
|
|
1526
|
-
|
|
1694
|
+
import_connect_core19.logger.warn(
|
|
1527
1695
|
"appkit/core",
|
|
1528
1696
|
`Retry attempt ${attempt + 1}/${options.maxRetries} after ${delay}ms`
|
|
1529
1697
|
);
|
|
@@ -1638,10 +1806,12 @@ function siwxResultToSession(result, now = /* @__PURE__ */ new Date()) {
|
|
|
1638
1806
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1639
1807
|
0 && (module.exports = {
|
|
1640
1808
|
DEFAULT_NOTIFICATION_SETTINGS,
|
|
1809
|
+
DEFAULT_VERIFY_RETRY,
|
|
1641
1810
|
InvalidUserOperationReceiptError,
|
|
1642
1811
|
NOTIFICATION_SETTINGS_KEY,
|
|
1643
1812
|
__resetResolverForTests,
|
|
1644
1813
|
addInAppNotification,
|
|
1814
|
+
applySettlementVerification,
|
|
1645
1815
|
assertHexSignature,
|
|
1646
1816
|
atomicSupportFor,
|
|
1647
1817
|
bareEvmAddress,
|
|
@@ -1662,13 +1832,16 @@ function siwxResultToSession(result, now = /* @__PURE__ */ new Date()) {
|
|
|
1662
1832
|
decodeRevertReason,
|
|
1663
1833
|
defaultSiwxDomain,
|
|
1664
1834
|
defaultSiwxUri,
|
|
1835
|
+
describeAuthorization,
|
|
1665
1836
|
describeChain,
|
|
1666
1837
|
describePayment,
|
|
1667
1838
|
eip155ChainIdToNumber,
|
|
1668
1839
|
encodeErc20Approve,
|
|
1669
1840
|
encodeErc20Transfer,
|
|
1670
1841
|
encodeErc20TransferFrom,
|
|
1842
|
+
explainSpend,
|
|
1671
1843
|
fetchUserOperationReceipt,
|
|
1844
|
+
formatAuthorizationAmount,
|
|
1672
1845
|
getResolver,
|
|
1673
1846
|
getSharedSessionKeyManager,
|
|
1674
1847
|
initialWeb3State,
|
|
@@ -1684,8 +1857,13 @@ function siwxResultToSession(result, now = /* @__PURE__ */ new Date()) {
|
|
|
1684
1857
|
normalizeCapabilities,
|
|
1685
1858
|
normalizeEip155ChainId,
|
|
1686
1859
|
parseUserOperationReceipt,
|
|
1860
|
+
readCurrentMppSessionChannel,
|
|
1687
1861
|
readDecimals,
|
|
1862
|
+
readMppRecoveryChannelId,
|
|
1863
|
+
readMppSessionChannel,
|
|
1864
|
+
readMppSessionReceipt,
|
|
1688
1865
|
readSolanaRoles,
|
|
1866
|
+
replayNotificationSettings,
|
|
1689
1867
|
resetSharedSessionKeyManager,
|
|
1690
1868
|
resolveChain,
|
|
1691
1869
|
resolveContextSimulationEndpoint,
|