@chipi-stack/backend 14.1.1 → 14.2.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/dist/index.mjs CHANGED
@@ -1419,6 +1419,324 @@ var ChipiSessions = class {
1419
1419
  client: this.client
1420
1420
  });
1421
1421
  }
1422
+ // --- Spending Policy Methods ---
1423
+ // Wraps ISessionSpendingPolicy contract interface.
1424
+ // Spending policies enforce per-token limits on ERC-20 operations
1425
+ // (transfer, approve, increase_allowance) executed by session keys.
1426
+ /**
1427
+ * Split a bigint into Cairo u256 representation (low u128, high u128).
1428
+ */
1429
+ splitU256(value) {
1430
+ const mask = (1n << 128n) - 1n;
1431
+ return [num.toHex(value & mask), num.toHex(value >> 128n)];
1432
+ }
1433
+ /**
1434
+ * Reconstruct a bigint from Cairo u256 (low, high) hex strings.
1435
+ */
1436
+ joinU256(low, high) {
1437
+ return BigInt(low) + (BigInt(high) << 128n);
1438
+ }
1439
+ /**
1440
+ * Set a spending policy for a session key + token pair.
1441
+ *
1442
+ * This executes a sponsored transaction (using owner signature) to call
1443
+ * `set_spending_policy` on the CHIPI wallet contract.
1444
+ *
1445
+ * The contract enforces these limits automatically during `__execute__()` on
1446
+ * ERC-20 operations (transfer, approve, increase_allowance) made by the session.
1447
+ *
1448
+ * @param params - Spending policy parameters
1449
+ * @param bearerToken - Authentication token
1450
+ * @returns Transaction hash
1451
+ *
1452
+ * @example
1453
+ * ```typescript
1454
+ * // Set a spending limit: max 1 USDC per call, 50 USDC per day
1455
+ * const txHash = await sdk.sessions.setSpendingPolicy({
1456
+ * encryptKey: userEncryptKey,
1457
+ * wallet: userWallet,
1458
+ * spendingPolicyConfig: {
1459
+ * sessionPublicKey: session.publicKey,
1460
+ * token: USDC_ADDRESS,
1461
+ * maxPerCall: 1_000_000n, // 1 USDC (6 decimals)
1462
+ * maxPerWindow: 50_000_000n, // 50 USDC
1463
+ * windowSeconds: 86400, // 24 hours
1464
+ * }
1465
+ * }, bearerToken);
1466
+ * ```
1467
+ */
1468
+ async setSpendingPolicy(params, bearerToken) {
1469
+ const { encryptKey, wallet, spendingPolicyConfig } = params;
1470
+ const { sessionPublicKey, token, maxPerCall, maxPerWindow, windowSeconds } = spendingPolicyConfig;
1471
+ if (wallet.walletType && wallet.walletType !== "CHIPI") {
1472
+ throw new ChipiSessionError(
1473
+ `Spending policies only support CHIPI wallets. Got: ${wallet.walletType}`,
1474
+ SESSION_ERRORS.INVALID_WALLET_TYPE_FOR_SESSION
1475
+ );
1476
+ }
1477
+ const U256_MAX = (1n << 256n) - 1n;
1478
+ const U64_MAX = (1n << 64n) - 1n;
1479
+ if (!token || token.trim().length === 0) {
1480
+ throw new ChipiSessionError(
1481
+ `Invalid token address for spending policy: "${token}"`,
1482
+ SESSION_ERRORS.INVALID_SPENDING_POLICY
1483
+ );
1484
+ }
1485
+ if (typeof windowSeconds !== "number" || !Number.isFinite(windowSeconds) || !Number.isInteger(windowSeconds) || windowSeconds <= 0 || BigInt(windowSeconds) > U64_MAX) {
1486
+ throw new ChipiSessionError(
1487
+ `Invalid window duration: ${windowSeconds}. Must be a positive integer within u64 range.`,
1488
+ SESSION_ERRORS.INVALID_SPENDING_POLICY
1489
+ );
1490
+ }
1491
+ if (maxPerCall < 0n || maxPerCall > U256_MAX) {
1492
+ throw new ChipiSessionError(
1493
+ `Invalid maxPerCall: must be >= 0 and <= 2^256 - 1`,
1494
+ SESSION_ERRORS.INVALID_SPENDING_POLICY
1495
+ );
1496
+ }
1497
+ if (maxPerWindow < 0n || maxPerWindow > U256_MAX) {
1498
+ throw new ChipiSessionError(
1499
+ `Invalid maxPerWindow: must be >= 0 and <= 2^256 - 1`,
1500
+ SESSION_ERRORS.INVALID_SPENDING_POLICY
1501
+ );
1502
+ }
1503
+ if (maxPerWindow > 0n && maxPerCall > maxPerWindow) {
1504
+ throw new ChipiSessionError(
1505
+ `Invalid spending policy: maxPerCall (${maxPerCall}) cannot exceed maxPerWindow (${maxPerWindow})`,
1506
+ SESSION_ERRORS.INVALID_SPENDING_POLICY
1507
+ );
1508
+ }
1509
+ try {
1510
+ console.log("[ChipiSDK:Session:SetSpendingPolicy] Setting spending policy", {
1511
+ walletAddress: wallet.publicKey.slice(0, 15) + "...",
1512
+ sessionPubKey: sessionPublicKey.slice(0, 15) + "...",
1513
+ token: token.slice(0, 15) + "...",
1514
+ maxPerCall: maxPerCall.toString(),
1515
+ maxPerWindow: maxPerWindow.toString(),
1516
+ windowSeconds
1517
+ });
1518
+ const [maxPerCallLow, maxPerCallHigh] = this.splitU256(maxPerCall);
1519
+ const [maxPerWindowLow, maxPerWindowHigh] = this.splitU256(maxPerWindow);
1520
+ const calldata = [
1521
+ sessionPublicKey,
1522
+ token,
1523
+ maxPerCallLow,
1524
+ maxPerCallHigh,
1525
+ maxPerWindowLow,
1526
+ maxPerWindowHigh,
1527
+ num.toHex(windowSeconds)
1528
+ ];
1529
+ const txHash = await executePaymasterTransaction({
1530
+ params: {
1531
+ encryptKey,
1532
+ wallet: { ...wallet, walletType: "CHIPI" },
1533
+ calls: [
1534
+ {
1535
+ contractAddress: wallet.publicKey,
1536
+ entrypoint: SESSION_ENTRYPOINTS.SET_SPENDING_POLICY,
1537
+ calldata
1538
+ }
1539
+ ],
1540
+ saveToDatabase: false
1541
+ },
1542
+ bearerToken,
1543
+ client: this.client
1544
+ });
1545
+ console.log("[ChipiSDK:Session:SetSpendingPolicy] Spending policy set successfully", {
1546
+ txHash,
1547
+ sessionPubKey: sessionPublicKey.slice(0, 15) + "...",
1548
+ token: token.slice(0, 15) + "..."
1549
+ });
1550
+ return txHash;
1551
+ } catch (error) {
1552
+ const err = error instanceof Error ? error : new Error(String(error));
1553
+ console.error("[ChipiSDK:Session:SetSpendingPolicy] Failed to set spending policy", {
1554
+ error: err.message,
1555
+ walletAddress: wallet.publicKey?.slice(0, 15) + "...",
1556
+ sessionPubKey: sessionPublicKey?.slice(0, 15) + "...",
1557
+ token: token?.slice(0, 15) + "..."
1558
+ });
1559
+ throw error;
1560
+ }
1561
+ }
1562
+ /**
1563
+ * Query a spending policy from the smart contract.
1564
+ *
1565
+ * This is a read-only call that does not require signing or gas.
1566
+ * Returns zeroed data if no policy is set (meaning no enforcement).
1567
+ *
1568
+ * @param params - Query parameters
1569
+ * @returns Spending policy data including limits and current window state
1570
+ *
1571
+ * @example
1572
+ * ```typescript
1573
+ * const policy = await sdk.sessions.getSpendingPolicy({
1574
+ * walletAddress: userWallet.publicKey,
1575
+ * sessionPublicKey: session.publicKey,
1576
+ * token: USDC_ADDRESS,
1577
+ * });
1578
+ * if (policy.maxPerWindow > 0n) {
1579
+ * console.log(`Spent ${policy.spentInWindow} of ${policy.maxPerWindow} this window`);
1580
+ * }
1581
+ * ```
1582
+ */
1583
+ async getSpendingPolicy(params) {
1584
+ const { walletAddress, sessionPublicKey, token } = params;
1585
+ try {
1586
+ const provider = new RpcProvider({
1587
+ nodeUrl: WALLET_RPC_ENDPOINTS["CHIPI"]
1588
+ });
1589
+ console.log("[ChipiSDK:Session:GetSpendingPolicy] Querying spending policy", {
1590
+ walletAddress: walletAddress.slice(0, 15) + "...",
1591
+ sessionPubKey: sessionPublicKey.slice(0, 15) + "...",
1592
+ token: token.slice(0, 15) + "..."
1593
+ });
1594
+ const result = await provider.callContract({
1595
+ contractAddress: walletAddress,
1596
+ entrypoint: SESSION_ENTRYPOINTS.GET_SPENDING_POLICY,
1597
+ calldata: [sessionPublicKey, token]
1598
+ });
1599
+ const resultArray = Array.isArray(result) ? result : result.result || [];
1600
+ if (resultArray.length < 8) {
1601
+ console.warn("[ChipiSDK:Session:GetSpendingPolicy] Unexpected response format", {
1602
+ resultLength: resultArray.length,
1603
+ result: resultArray
1604
+ });
1605
+ return {
1606
+ maxPerCall: 0n,
1607
+ maxPerWindow: 0n,
1608
+ windowSeconds: 0,
1609
+ spentInWindow: 0n,
1610
+ windowStart: 0
1611
+ };
1612
+ }
1613
+ const policyData = {
1614
+ maxPerCall: this.joinU256(resultArray[0], resultArray[1]),
1615
+ maxPerWindow: this.joinU256(resultArray[2], resultArray[3]),
1616
+ windowSeconds: Number(BigInt(resultArray[4])),
1617
+ spentInWindow: this.joinU256(resultArray[5], resultArray[6]),
1618
+ windowStart: Number(BigInt(resultArray[7]))
1619
+ };
1620
+ console.log("[ChipiSDK:Session:GetSpendingPolicy] Spending policy retrieved", {
1621
+ maxPerCall: policyData.maxPerCall.toString(),
1622
+ maxPerWindow: policyData.maxPerWindow.toString(),
1623
+ windowSeconds: policyData.windowSeconds,
1624
+ spentInWindow: policyData.spentInWindow.toString(),
1625
+ windowStart: policyData.windowStart
1626
+ });
1627
+ return policyData;
1628
+ } catch (error) {
1629
+ const err = error instanceof Error ? error : new Error(String(error));
1630
+ const notFoundPatterns = [
1631
+ /entry point.*not found/i,
1632
+ /requested entrypoint was not found/i,
1633
+ /invalid message selector/i,
1634
+ /contract not found/i,
1635
+ /is not deployed/i
1636
+ ];
1637
+ const isNotFound = notFoundPatterns.some((p) => p.test(err.message));
1638
+ if (isNotFound) {
1639
+ console.log(
1640
+ "[ChipiSDK:Session:GetSpendingPolicy] No policy found (contract or entrypoint missing)",
1641
+ {
1642
+ walletAddress: walletAddress?.slice(0, 15) + "...",
1643
+ sessionPubKey: sessionPublicKey?.slice(0, 15) + "...",
1644
+ token: token?.slice(0, 15) + "..."
1645
+ }
1646
+ );
1647
+ return {
1648
+ maxPerCall: 0n,
1649
+ maxPerWindow: 0n,
1650
+ windowSeconds: 0,
1651
+ spentInWindow: 0n,
1652
+ windowStart: 0
1653
+ };
1654
+ }
1655
+ console.error("[ChipiSDK:Session:GetSpendingPolicy] RPC/network error", {
1656
+ error: err.message,
1657
+ walletAddress: walletAddress?.slice(0, 15) + "...",
1658
+ sessionPubKey: sessionPublicKey?.slice(0, 15) + "...",
1659
+ token: token?.slice(0, 15) + "..."
1660
+ });
1661
+ throw err;
1662
+ }
1663
+ }
1664
+ /**
1665
+ * Remove a spending policy for a session key + token pair.
1666
+ *
1667
+ * This executes a sponsored transaction (using owner signature) to call
1668
+ * `remove_spending_policy` on the CHIPI wallet contract.
1669
+ *
1670
+ * After removal, the session key has no spending limits for this token.
1671
+ *
1672
+ * @param params - Policy removal parameters
1673
+ * @param bearerToken - Authentication token
1674
+ * @returns Transaction hash
1675
+ *
1676
+ * @example
1677
+ * ```typescript
1678
+ * const txHash = await sdk.sessions.removeSpendingPolicy({
1679
+ * encryptKey: userEncryptKey,
1680
+ * wallet: userWallet,
1681
+ * sessionPublicKey: session.publicKey,
1682
+ * token: USDC_ADDRESS,
1683
+ * }, bearerToken);
1684
+ * ```
1685
+ */
1686
+ async removeSpendingPolicy(params, bearerToken) {
1687
+ const { encryptKey, wallet, sessionPublicKey, token } = params;
1688
+ if (wallet.walletType && wallet.walletType !== "CHIPI") {
1689
+ throw new ChipiSessionError(
1690
+ `Spending policies only support CHIPI wallets. Got: ${wallet.walletType}`,
1691
+ SESSION_ERRORS.INVALID_WALLET_TYPE_FOR_SESSION
1692
+ );
1693
+ }
1694
+ if (!token || token.trim().length === 0) {
1695
+ throw new ChipiSessionError(
1696
+ `Invalid token address for spending policy removal: "${token}"`,
1697
+ SESSION_ERRORS.INVALID_SPENDING_POLICY
1698
+ );
1699
+ }
1700
+ try {
1701
+ console.log("[ChipiSDK:Session:RemoveSpendingPolicy] Removing spending policy", {
1702
+ walletAddress: wallet.publicKey.slice(0, 15) + "...",
1703
+ sessionPubKey: sessionPublicKey.slice(0, 15) + "...",
1704
+ token: token.slice(0, 15) + "..."
1705
+ });
1706
+ const calldata = [sessionPublicKey, token];
1707
+ const txHash = await executePaymasterTransaction({
1708
+ params: {
1709
+ encryptKey,
1710
+ wallet: { ...wallet, walletType: "CHIPI" },
1711
+ calls: [
1712
+ {
1713
+ contractAddress: wallet.publicKey,
1714
+ entrypoint: SESSION_ENTRYPOINTS.REMOVE_SPENDING_POLICY,
1715
+ calldata
1716
+ }
1717
+ ],
1718
+ saveToDatabase: false
1719
+ },
1720
+ bearerToken,
1721
+ client: this.client
1722
+ });
1723
+ console.log("[ChipiSDK:Session:RemoveSpendingPolicy] Spending policy removed successfully", {
1724
+ txHash,
1725
+ sessionPubKey: sessionPublicKey.slice(0, 15) + "...",
1726
+ token: token.slice(0, 15) + "..."
1727
+ });
1728
+ return txHash;
1729
+ } catch (error) {
1730
+ const err = error instanceof Error ? error : new Error(String(error));
1731
+ console.error("[ChipiSDK:Session:RemoveSpendingPolicy] Failed to remove spending policy", {
1732
+ error: err.message,
1733
+ walletAddress: wallet.publicKey?.slice(0, 15) + "...",
1734
+ sessionPubKey: sessionPublicKey?.slice(0, 15) + "...",
1735
+ token: token?.slice(0, 15) + "..."
1736
+ });
1737
+ throw error;
1738
+ }
1739
+ }
1422
1740
  };
1423
1741
  var InMemoryNonceStore = class {
1424
1742
  constructor() {