@pafi-dev/issuer 0.5.33 → 0.5.35

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.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Address, Hex, PublicClient, WalletClient } from 'viem';
2
- import { PointTokenDomainConfig, PartialUserOperation, BurnRequest, PoolKey, UserOpTypedData } from '@pafi-dev/core';
2
+ import { PointTokenDomainConfig, PartialUserOperation, BurnRequest, PoolKey, UserOpTypedData, decodeBatchExecuteCalls, BROKER_HASHES, ENTRY_POINT_V08 } from '@pafi-dev/core';
3
3
  export { PAFI_SUBGRAPH_URL } from '@pafi-dev/core';
4
4
 
5
5
  /**
@@ -1528,444 +1528,105 @@ declare function handleClaimStatus(params: MintStatusParams): Promise<MintStatus
1528
1528
  declare function handleRedeemStatus(params: BurnStatusParams): Promise<BurnStatusResponse>;
1529
1529
 
1530
1530
  /**
1531
- * Config for `createSubgraphPoolsProvider`.
1531
+ * A pending UserOp serialized for persistent storage (Redis, Postgres, memory).
1532
+ *
1533
+ * All bigint fields are stored as decimal strings so the entry can be
1534
+ * JSON-serialized without precision loss. Convert back to bigint before
1535
+ * calling `computeUserOpHash` or `serializeUserOpToJsonRpc`.
1532
1536
  */
1533
- interface SubgraphPoolsProviderConfig {
1534
- /**
1535
- * Fully qualified subgraph GraphQL endpoint.
1536
- * Defaults to the PAFI-hosted subgraph (`PAFI_SUBGRAPH_URL`).
1537
- * Override only when pointing at a staging or custom deployment.
1538
- */
1539
- subgraphUrl?: string;
1540
- /**
1541
- * Cache TTL in milliseconds. Pool discovery is near-static — a 30s
1542
- * cache removes subgraph load without meaningfully delaying UX.
1543
- * Set to 0 to disable caching. Default: 30_000.
1544
- */
1545
- cacheTtlMs?: number;
1546
- /**
1547
- * Optional fetch override for test harnesses. Defaults to global `fetch`.
1548
- */
1549
- fetchImpl?: typeof fetch;
1537
+ interface PendingUserOpEntry {
1538
+ sender: Address;
1539
+ nonce: string;
1540
+ callData: Hex;
1541
+ callGasLimit: string;
1542
+ verificationGasLimit: string;
1543
+ preVerificationGas: string;
1544
+ maxFeePerGas: string;
1545
+ maxPriorityFeePerGas: string;
1546
+ paymaster?: Address;
1547
+ paymasterVerificationGasLimit?: string;
1548
+ paymasterPostOpGasLimit?: string;
1549
+ paymasterData?: Hex;
1550
+ chainId: number;
1551
+ /** Hex-encoded userOpHash the value the user signed via personal_sign. */
1552
+ userOpHash: Hex;
1550
1553
  /**
1551
- * Optional clock override for tests.
1554
+ * Fee-stripped fallback variant. Set by `/claim/prepare` and
1555
+ * `/redeem/prepare` when a PT operator fee is configured AND the
1556
+ * paymaster sponsorship attached successfully — i.e. the user might
1557
+ * still want to submit without paymaster (paying ETH gas), and in
1558
+ * that case shouldn't be charged the PT fee. `/claim/submit` reads
1559
+ * this branch when its request body specifies
1560
+ * `variant: "fallback"`.
1561
+ *
1562
+ * Has a different `callData` (no PT.transfer prepended) and
1563
+ * therefore a different `userOpHash`. Paymaster fields are NOT
1564
+ * present — the fallback is by definition unsponsored.
1552
1565
  */
1553
- now?: () => number;
1566
+ fallback?: {
1567
+ callData: Hex;
1568
+ callGasLimit: string;
1569
+ verificationGasLimit: string;
1570
+ preVerificationGas: string;
1571
+ userOpHash: Hex;
1572
+ };
1554
1573
  }
1555
1574
  /**
1556
- * Create a `PoolsProvider` backed by the PAFI subgraph.
1557
- *
1558
- * Queries the `pafiTokens` entity for the given `pointTokenAddress`,
1559
- * reads its linked `Pool`, and returns a single-element `PoolKey[]`.
1560
- * Multiple pools per token would require a subgraph schema change.
1561
- *
1562
- * The result is cached in-process with a short TTL (default 30s). Pool
1563
- * discovery is near-static so this avoids hammering the subgraph without
1564
- * blocking config changes for long.
1565
- *
1566
- * Returns `{ pools: [] }` if:
1567
- * - the token is not registered on PAFI yet (no PafiToken entity)
1568
- * - the token is registered but its pool has not been initialised
1569
- * - the subgraph is unreachable or returns an error (logs to console,
1570
- * does not throw — callers should handle empty pool list gracefully)
1571
- *
1572
- * Assumes the PAFI subgraph schema. Issuers with a custom subgraph must
1573
- * implement `PoolsProvider` themselves instead of using this helper.
1575
+ * Storage backend for pending UserOps in the mobile prepare/submit pattern.
1574
1576
  *
1575
- * @example
1576
- * ```ts
1577
- * import { createSubgraphPoolsProvider, createIssuerService } from "@pafi/issuer";
1577
+ * Implement this interface and wire it into your issuer backend:
1578
+ * - `save()` — called by `POST /claim/prepare` and `POST /redeem/prepare`
1579
+ * - `get()` — called by `POST /claim/submit` and `POST /redeem/submit`
1580
+ * - `delete()` — called after successful submit or explicit cancellation
1578
1581
  *
1579
- * const service = createIssuerService({
1580
- * // ...other config
1581
- * poolsProvider: createSubgraphPoolsProvider({
1582
- * subgraphUrl: "https://graph.pacificfinance.org/subgraphs/name/pafi",
1583
- * }),
1584
- * });
1585
- * ```
1586
- */
1587
- declare function createSubgraphPoolsProvider(config?: SubgraphPoolsProviderConfig): PoolsProvider;
1588
-
1589
- /**
1590
- * Config for `createSubgraphNativeUsdtQuoter`.
1582
+ * The default implementation in the gg56 boilerplate uses Redis with
1583
+ * a short TTL matching the MintRequest / BurnRequest deadline.
1591
1584
  */
1592
- interface SubgraphNativeUsdtQuoterConfig {
1593
- /**
1594
- * Fully qualified subgraph GraphQL endpoint.
1595
- * Defaults to the PAFI-hosted subgraph (`PAFI_SUBGRAPH_URL`).
1596
- * Override only when pointing at a staging or custom deployment.
1597
- */
1598
- subgraphUrl?: string;
1599
- /**
1600
- * Decimals of the USDT token. Defaults to 6 (standard USDT/USDC on
1601
- * Base, Ethereum, Polygon). Override for chains where USDT uses a
1602
- * different decimals value.
1603
- */
1604
- usdtDecimals?: number;
1605
- /**
1606
- * Decimals of the native token (ETH on Base/mainnet/Arbitrum/Optimism,
1607
- * MATIC on Polygon). Default: 18.
1608
- */
1609
- nativeDecimals?: number;
1610
- /**
1611
- * Cache TTL in milliseconds. ETH price drifts slowly relative to gas
1612
- * estimation needs — a 30s cache keeps fees stable across bursts of
1613
- * requests without letting them go stale during volatile markets.
1614
- * Set to 0 to disable caching. Default: 30_000.
1615
- */
1616
- cacheTtlMs?: number;
1617
- /**
1618
- * Fallback price (USDT per native token, human-readable float) used
1619
- * when the subgraph is unreachable. This keeps the backend operational
1620
- * during subgraph outages rather than bricking cashouts. The fee will
1621
- * be slightly off but the operator still gets paid. Default: 3000.
1622
- */
1623
- fallbackEthPriceUsd?: number;
1624
- /** Optional fetch override for test harnesses. */
1625
- fetchImpl?: typeof fetch;
1626
- /** Optional clock override for tests. */
1627
- now?: () => number;
1585
+ interface IPendingUserOpStore {
1586
+ save(lockId: string, entry: PendingUserOpEntry, ttlSeconds: number): Promise<void>;
1587
+ get(lockId: string): Promise<PendingUserOpEntry | null>;
1588
+ delete(lockId: string): Promise<void>;
1628
1589
  }
1590
+
1629
1591
  /**
1630
- * Create a native→USDT quoter backed by the PAFI subgraph's
1631
- * `Bundle.ethPriceUSD`. The returned function has the shape
1632
- * `(amountNative: bigint) => Promise<bigint>` and can be passed as
1633
- * `quoteNativeToFee` to `FeeManager` — in v1.4 the fee currency
1634
- * is configured at the integration layer, not hardcoded here.
1635
- *
1636
- * Used by `FeeManager.estimateGasFee()` to convert the gas cost into
1637
- * an ERC-20 amount charged as part of the sponsored UserOp batch.
1638
- * Price precision is not critical — a 1-2% drift is acceptable since
1639
- * the fee manager applies a `gasPremiumBps` buffer.
1640
- *
1641
- * The result is cached in-process with a short TTL (default 30s). If
1642
- * the subgraph is unreachable, falls back to `fallbackEthPriceUsd` so
1643
- * gas estimation doesn't block user flow during a subgraph outage.
1644
- *
1645
- * @example
1646
- * ```ts
1647
- * import { createSubgraphNativeUsdtQuoter, createIssuerService } from "@pafi-dev/issuer";
1592
+ * Convert a stored `PendingUserOpEntry` (decimal-string fields) plus a
1593
+ * signature into the JSON-RPC wire format for `eth_sendUserOperation`.
1648
1594
  *
1649
- * const service = createIssuerService({
1650
- * // ...other config
1651
- * fee: {
1652
- * quoteNativeToFee: createSubgraphNativeUsdtQuoter({
1653
- * subgraphUrl: "https://graph.pacificfinance.org/subgraphs/name/pafi",
1654
- * }),
1655
- * },
1656
- * });
1657
- * ```
1595
+ * Bridges the gap between the serialized storage format (decimal strings,
1596
+ * safe for JSON/Redis) and `serializeUserOpToJsonRpc` which expects bigints.
1658
1597
  */
1659
- declare function createSubgraphNativeUsdtQuoter(config?: SubgraphNativeUsdtQuoterConfig): (amountNative: bigint) => Promise<bigint>;
1598
+ declare function serializeEntryToJsonRpc(entry: PendingUserOpEntry, signature: Hex, variant?: "sponsored" | "fallback"): Record<string, string | null>;
1660
1599
 
1661
- interface NativePtQuoterConfig {
1662
- /** Viem PublicClient — used to call Chainlink on-chain. */
1663
- provider: PublicClient;
1664
- /** Address of the PointToken being traded. */
1665
- pointTokenAddress: Address;
1666
- /** Chainlink ETH/USD feed address. Defaults to Base mainnet feed. */
1667
- chainlinkFeedAddress?: Address;
1668
- /** PAFI subgraph GraphQL endpoint. */
1669
- subgraphUrl?: string;
1670
- /** Cache TTL in ms. Default: 30_000. */
1671
- cacheTtlMs?: number;
1672
- /** Fallback ETH price (USD) when Chainlink is unreachable. Default: 3000. */
1673
- fallbackEthPriceUsd?: number;
1674
- /** Fallback PT price (USDT per 1 PT) when subgraph is unreachable. Default: 0.1. */
1675
- fallbackPtPriceUsdt?: number;
1676
- fetchImpl?: typeof fetch;
1677
- now?: () => number;
1678
- }
1679
1600
  /**
1680
- * Create a native→PT quoter for use as `FeeManager.quoteNativeToFee`.
1681
- *
1682
- * Converts ETH gas cost USDT (via Chainlink ETH/USD) → PT (via subgraph
1683
- * pool price), returning the fee amount in PT raw units (18 decimals).
1684
- *
1685
- * Formula:
1686
- * feeInPT = amountNative × ethPrice_8dec × ptPerUsdt_18dec / 10^26
1687
- *
1688
- * Both prices are cached in-process (default 30s TTL).
1689
- *
1690
- * @example
1691
- * ```ts
1692
- * fee: {
1693
- * quoteNativeToFee: createNativePtQuoter({
1694
- * provider,
1695
- * pointTokenAddress: "0x...",
1696
- * chainlinkFeedAddress: getContractAddresses(chainId).chainlinkEthUsd,
1697
- * }),
1698
- * }
1699
- * ```
1601
+ * Re-shape `UserOpTypedData` so all `bigint` fields become hex strings —
1602
+ * required for JSON transport over HTTP. Mirrors the inverse of what
1603
+ * `walletClient.signTypedData` accepts on the client (it auto-coerces hex
1604
+ * strings back to bigints for `uint256` fields).
1700
1605
  */
1701
- declare function createNativePtQuoter(config: NativePtQuoterConfig): (amountNative: bigint) => Promise<bigint>;
1702
-
1606
+ type SerializedUserOpTypedData = {
1607
+ domain: UserOpTypedData["domain"];
1608
+ types: UserOpTypedData["types"];
1609
+ primaryType: UserOpTypedData["primaryType"];
1610
+ message: {
1611
+ sender: Address;
1612
+ nonce: Hex;
1613
+ initCode: Hex;
1614
+ callData: Hex;
1615
+ accountGasLimits: Hex;
1616
+ preVerificationGas: Hex;
1617
+ gasFees: Hex;
1618
+ paymasterAndData: Hex;
1619
+ };
1620
+ };
1703
1621
  /**
1704
- * Combined off-chain + on-chain balance for a single user / token pair.
1705
- *
1706
- * - `offChain` the issuer's ledger balance (available, excluding locks)
1707
- * - `onChain` — the user's ERC-20 balance from `PointToken.balanceOf`
1708
- * - `total` — `offChain + onChain` (what the Issuer App displays)
1622
+ * Convert a `UserOpTypedData` payload into the JSON-safe wire form
1623
+ * (bigint → hex string). The mobile client passes this directly to
1624
+ * `eth_signTypedData_v4` / viem's `signTypedData`.
1709
1625
  */
1710
- interface CombinedBalance {
1711
- offChain: bigint;
1712
- onChain: bigint;
1713
- total: bigint;
1714
- }
1715
- interface BalanceAggregatorConfig {
1716
- provider: PublicClient;
1717
- ledger: IPointLedger;
1718
- }
1626
+ declare function serializeUserOpTypedData(td: UserOpTypedData): SerializedUserOpTypedData;
1719
1627
  /**
1720
- * v1.4 utility — aggregates off-chain + on-chain point balances into a
1721
- * single view for the "combined balance" UI in the Issuer App.
1722
- *
1723
- * The `/user` API handler uses this internally; the helper is exposed
1724
- * publicly so Issuer Apps can call it directly without going through
1725
- * the HTTP layer (e.g., for server-rendered pages or admin dashboards).
1726
- *
1727
- * See [REQUIREMENTS_V2.md] §1 — "The Issuer App displays a combined
1728
- * balance (off-chain points + on-chain PT) and does not surface USDT."
1729
- */
1730
- declare class BalanceAggregator {
1731
- private readonly provider;
1732
- private readonly ledger;
1733
- constructor(config: BalanceAggregatorConfig);
1734
- /**
1735
- * Combined balance for a single (user, token) pair. Fetches off-chain
1736
- * + on-chain in parallel.
1737
- */
1738
- getCombinedBalance(user: Address, pointToken: Address): Promise<CombinedBalance>;
1739
- /**
1740
- * Combined balance for multiple tokens owned by the same user. Runs
1741
- * all lookups in parallel. Returns a Map keyed by the token address
1742
- * (same casing as supplied — caller should normalize if needed).
1743
- */
1744
- getCombinedBalanceMulti(user: Address, pointTokens: Address[]): Promise<Map<Address, CombinedBalance>>;
1745
- }
1746
-
1747
- /**
1748
- * Top-level configuration for `createIssuerService`.
1749
- *
1750
- * In v1.4 the SDK is HTTP-client-free: it signs EIP-712 messages, reads
1751
- * on-chain state, builds unsigned UserOperations, and maintains the
1752
- * off-chain ledger. It never broadcasts transactions — that's the
1753
- * frontend's responsibility via Bundler + Paymaster.
1754
- *
1755
- * **Multi-token (0.2.0+):** Pass `pointTokenAddresses: Address[]` to
1756
- * support multiple PointTokens under a single issuer backend. Legacy
1757
- * `pointTokenAddress: Address` still works for single-token deployments.
1758
- * When both are provided, `pointTokenAddresses` takes precedence.
1759
- */
1760
- interface IssuerServiceConfig {
1761
- chainId: number;
1762
- /** Legacy single-token shortcut; prefer `pointTokenAddresses`. */
1763
- pointTokenAddress?: Address;
1764
- /** All PointToken addresses this issuer supports. */
1765
- pointTokenAddresses?: Address[];
1766
- /**
1767
- * Issuer-specific contract addresses merged into the `/config` response.
1768
- * PAFI-owned addresses (batchExecutor, usdt, issuerRegistry, mintingOracle,
1769
- * pafiHook) are auto-resolved from `getContractAddresses(chainId)` and
1770
- * can be omitted. Only `pointToken` / `pointTokens` must be provided.
1771
- */
1772
- contracts?: Pick<ApiConfigResponse["contracts"], "pointToken" | "pointTokens" | "relay">;
1773
- /**
1774
- * Shared `PublicClient` used for on-chain reads, simulation, indexer
1775
- * polling, and gas-price lookups. Must be pointed at the target chain.
1776
- */
1777
- provider: PublicClient;
1778
- auth: {
1779
- jwtSecret: string;
1780
- /** SIWE-style login-message domain, e.g. `"app.example.com"`. */
1781
- domain: string;
1782
- /** Passed straight to `jose` (`"24h"`, `"7d"`, …). Default `"24h"`. */
1783
- jwtExpiresIn?: string;
1784
- };
1785
- /**
1786
- * Off-chain point ledger — the source of truth for user balances and
1787
- * in-flight minting locks. Every issuer provides their own database-backed
1788
- * implementation (Postgres, Redis, etc.) that implements `IPointLedger`.
1789
- * The SDK does not ship a production ledger; each issuer's data model and
1790
- * infrastructure are different.
1791
- */
1792
- ledger: IPointLedger;
1793
- /**
1794
- * Policy engine — optional, defaults to `DefaultPolicyEngine` which checks
1795
- * off-chain balance. Extend or replace to add KYC, volume caps, etc.
1796
- */
1797
- policy?: IPolicyEngine;
1798
- /** Session store — optional, defaults to `MemorySessionStore` (dev/test only). */
1799
- sessionStore?: ISessionStore;
1800
- /**
1801
- * Fee management config. If omitted the `handleGasFee` endpoint will
1802
- * throw "not configured" at request time.
1803
- */
1804
- fee?: Omit<FeeManagerConfig, "provider">;
1805
- /**
1806
- * Pool discovery function for `handlePools`. If omitted the endpoint
1807
- * throws "not configured" at request time.
1808
- */
1809
- poolsProvider?: PoolsProvider;
1810
- /**
1811
- * Enables `handleClaim`. The factory combines these with the shared
1812
- * `policy` + `relayService` instances already wired by the factory.
1813
- * Omit to disable the `/claim` endpoint.
1814
- */
1815
- claim?: {
1816
- issuerSignerWallet: WalletClient;
1817
- /** Defaults to the PAFI-deployed BatchExecutor for the target chain. */
1818
- batchExecutorAddress?: Address;
1819
- lockDurationMs?: number;
1820
- };
1821
- indexer?: {
1822
- fromBlock?: bigint;
1823
- cursorStore?: IIndexerCursorStore;
1824
- confirmations?: number;
1825
- batchSize?: number;
1826
- pollIntervalMs?: number;
1827
- /**
1828
- * If `true`, the factory calls `indexer.start()` before returning.
1829
- * Default: `false` — the caller decides when to begin polling.
1830
- */
1831
- autoStart?: boolean;
1832
- };
1833
- }
1834
- interface IssuerService {
1835
- /** AuthService — login, logout, nonce management. */
1836
- auth: AuthService;
1837
- /** Session store — nonce + JWT session persistence. */
1838
- session: ISessionStore;
1839
- ledger: IPointLedger;
1840
- policy: IPolicyEngine;
1841
- /** RelayService — prepareMint / prepareBurn UserOp builders. */
1842
- relay: RelayService;
1843
- /** FeeManager — gas fee estimation. Undefined if not configured. */
1844
- fee: FeeManager | undefined;
1845
- /** All indexers keyed by PointToken address. */
1846
- indexers: Map<Address, PointIndexer>;
1847
- /**
1848
- * First indexer. Kept for backward compat with 0.1.x callers.
1849
- * @deprecated use `indexers.get(tokenAddress)` for multi-token.
1850
- */
1851
- indexer: PointIndexer;
1852
- /** Framework-agnostic HTTP handlers — wire into Express / Fastify / Hono. */
1853
- api: IssuerApiHandlers;
1854
- }
1855
- /**
1856
- * Wire a fully-functional issuer service from a single config object.
1857
- *
1858
- * Defaults:
1859
- * - `sessionStore` → `MemorySessionStore` (dev/test only — replace in prod)
1860
- * - `policy` → `DefaultPolicyEngine({ ledger })`
1861
- * - `feeManager` → undefined (handleGasFee throws until configured)
1862
- * - `poolsProvider` → undefined (handlePools throws until configured)
1863
- * - `indexer.autoStart` → false
1864
- *
1865
- * Throws synchronously if any required field is missing.
1866
- */
1867
- declare function createIssuerService(config: IssuerServiceConfig): IssuerService;
1868
-
1869
- /**
1870
- * A pending UserOp serialized for persistent storage (Redis, Postgres, memory).
1871
- *
1872
- * All bigint fields are stored as decimal strings so the entry can be
1873
- * JSON-serialized without precision loss. Convert back to bigint before
1874
- * calling `computeUserOpHash` or `serializeUserOpToJsonRpc`.
1875
- */
1876
- interface PendingUserOpEntry {
1877
- sender: Address;
1878
- nonce: string;
1879
- callData: Hex;
1880
- callGasLimit: string;
1881
- verificationGasLimit: string;
1882
- preVerificationGas: string;
1883
- maxFeePerGas: string;
1884
- maxPriorityFeePerGas: string;
1885
- paymaster?: Address;
1886
- paymasterVerificationGasLimit?: string;
1887
- paymasterPostOpGasLimit?: string;
1888
- paymasterData?: Hex;
1889
- chainId: number;
1890
- /** Hex-encoded userOpHash — the value the user signed via personal_sign. */
1891
- userOpHash: Hex;
1892
- /**
1893
- * Fee-stripped fallback variant. Set by `/claim/prepare` and
1894
- * `/redeem/prepare` when a PT operator fee is configured AND the
1895
- * paymaster sponsorship attached successfully — i.e. the user might
1896
- * still want to submit without paymaster (paying ETH gas), and in
1897
- * that case shouldn't be charged the PT fee. `/claim/submit` reads
1898
- * this branch when its request body specifies
1899
- * `variant: "fallback"`.
1900
- *
1901
- * Has a different `callData` (no PT.transfer prepended) and
1902
- * therefore a different `userOpHash`. Paymaster fields are NOT
1903
- * present — the fallback is by definition unsponsored.
1904
- */
1905
- fallback?: {
1906
- callData: Hex;
1907
- callGasLimit: string;
1908
- verificationGasLimit: string;
1909
- preVerificationGas: string;
1910
- userOpHash: Hex;
1911
- };
1912
- }
1913
- /**
1914
- * Storage backend for pending UserOps in the mobile prepare/submit pattern.
1915
- *
1916
- * Implement this interface and wire it into your issuer backend:
1917
- * - `save()` — called by `POST /claim/prepare` and `POST /redeem/prepare`
1918
- * - `get()` — called by `POST /claim/submit` and `POST /redeem/submit`
1919
- * - `delete()` — called after successful submit or explicit cancellation
1920
- *
1921
- * The default implementation in the gg56 boilerplate uses Redis with
1922
- * a short TTL matching the MintRequest / BurnRequest deadline.
1923
- */
1924
- interface IPendingUserOpStore {
1925
- save(lockId: string, entry: PendingUserOpEntry, ttlSeconds: number): Promise<void>;
1926
- get(lockId: string): Promise<PendingUserOpEntry | null>;
1927
- delete(lockId: string): Promise<void>;
1928
- }
1929
-
1930
- /**
1931
- * Convert a stored `PendingUserOpEntry` (decimal-string fields) plus a
1932
- * signature into the JSON-RPC wire format for `eth_sendUserOperation`.
1933
- *
1934
- * Bridges the gap between the serialized storage format (decimal strings,
1935
- * safe for JSON/Redis) and `serializeUserOpToJsonRpc` which expects bigints.
1936
- */
1937
- declare function serializeEntryToJsonRpc(entry: PendingUserOpEntry, signature: Hex, variant?: "sponsored" | "fallback"): Record<string, string | null>;
1938
-
1939
- /**
1940
- * Re-shape `UserOpTypedData` so all `bigint` fields become hex strings —
1941
- * required for JSON transport over HTTP. Mirrors the inverse of what
1942
- * `walletClient.signTypedData` accepts on the client (it auto-coerces hex
1943
- * strings back to bigints for `uint256` fields).
1944
- */
1945
- type SerializedUserOpTypedData = {
1946
- domain: UserOpTypedData["domain"];
1947
- types: UserOpTypedData["types"];
1948
- primaryType: UserOpTypedData["primaryType"];
1949
- message: {
1950
- sender: Address;
1951
- nonce: Hex;
1952
- initCode: Hex;
1953
- callData: Hex;
1954
- accountGasLimits: Hex;
1955
- preVerificationGas: Hex;
1956
- gasFees: Hex;
1957
- paymasterAndData: Hex;
1958
- };
1959
- };
1960
- /**
1961
- * Convert a `UserOpTypedData` payload into the JSON-safe wire form
1962
- * (bigint → hex string). The mobile client passes this directly to
1963
- * `eth_signTypedData_v4` / viem's `signTypedData`.
1964
- */
1965
- declare function serializeUserOpTypedData(td: UserOpTypedData): SerializedUserOpTypedData;
1966
- /**
1967
- * Merge Pimlico's paymaster-sponsorship response into a UserOp
1968
- * skeleton, applying only fields that are actually defined.
1628
+ * Merge Pimlico's paymaster-sponsorship response into a UserOp
1629
+ * skeleton, applying only fields that are actually defined.
1969
1630
  *
1970
1631
  * `pm_sponsorUserOperation` returns:
1971
1632
  * - `paymaster` / `paymasterData` — required for the sponsored sig
@@ -2052,129 +1713,875 @@ interface PrepareMobileUserOpParams {
2052
1713
  /** TTL the store entry should outlive — typically the MintRequest deadline. */
2053
1714
  ttlSeconds: number;
2054
1715
  }
2055
- interface PrepareMobileUserOpResult {
2056
- sponsored: PreparedUserOp;
2057
- /**
2058
- * Set when `partialUserOpFallback` was supplied AND the PT fee was
2059
- * non-zero (i.e. sponsored ≠ fallback). Mobile client picks which
2060
- * variant to sign + submit; SDK's `serializeEntryToJsonRpc` reads
2061
- * the `variant` flag to dispatch.
2062
- */
2063
- fallback?: PreparedUserOp;
2064
- /** What got persisted into the pending-userop store. */
2065
- entry: PendingUserOpEntry;
1716
+ interface PrepareMobileUserOpResult {
1717
+ sponsored: PreparedUserOp;
1718
+ /**
1719
+ * Set when `partialUserOpFallback` was supplied AND the PT fee was
1720
+ * non-zero (i.e. sponsored ≠ fallback). Mobile client picks which
1721
+ * variant to sign + submit; SDK's `serializeEntryToJsonRpc` reads
1722
+ * the `variant` flag to dispatch.
1723
+ */
1724
+ fallback?: PreparedUserOp;
1725
+ /** What got persisted into the pending-userop store. */
1726
+ entry: PendingUserOpEntry;
1727
+ }
1728
+ /**
1729
+ * Build the sponsored UserOp + (optional) fee-stripped fallback for the
1730
+ * mobile prepare/submit flow.
1731
+ *
1732
+ * What this does, end-to-end:
1733
+ * 1. Merge Pimlico's paymaster sponsorship + re-quoted gas into the
1734
+ * caller's `partialUserOp` skeleton.
1735
+ * 2. Compute the EIP-712 userOpHash + the typed-data payload (in
1736
+ * JSON-safe form for HTTP transport).
1737
+ * 3. (Optional) Repeat for the `partialUserOpFallback` skeleton with
1738
+ * no paymaster fields — produces a separate hash + typed-data so
1739
+ * the client can re-sign if it falls back.
1740
+ * 4. Persist a single store entry containing BOTH callData variants
1741
+ * keyed by lockId. `serializeEntryToJsonRpc` reads the `variant`
1742
+ * param at submit time.
1743
+ *
1744
+ * Replaces ~100 LoC of glue per scenario in issuer controllers.
1745
+ */
1746
+ declare function prepareMobileUserOp(params: PrepareMobileUserOpParams): Promise<PrepareMobileUserOpResult>;
1747
+
1748
+ /**
1749
+ * Mobile prepare/submit orchestrators — abstract the duplicate glue
1750
+ * between `/claim/prepare`+`/claim/submit` and `/redeem/prepare`+
1751
+ * `/redeem/submit`. Both share the same shape:
1752
+ *
1753
+ * prepare: fees + delegation check → paymaster → prepareMobileUserOp
1754
+ * submit: fetch entry → serialize+sign → relay → bind hash → delete
1755
+ *
1756
+ * Issuer controllers shrink to ~30 LoC each — wire the handler that
1757
+ * produces `partialUserOp[+ fallback]`, hand off to these.
1758
+ */
1759
+ declare class PendingUserOpNotFoundError extends Error {
1760
+ readonly code = "PENDING_USEROP_NOT_FOUND";
1761
+ constructor(lockId: string);
1762
+ }
1763
+ interface HandleMobilePrepareParams {
1764
+ /** User EOA — used for the delegation check. */
1765
+ userAddress: Address;
1766
+ chainId: number;
1767
+ /** Lock id (issuer-generated) keying both store entry + ledger row. */
1768
+ lockId: string;
1769
+ /**
1770
+ * Partial UserOp from the upstream handler (PTClaimHandler /
1771
+ * PTRedeemHandler). The orchestrator will top up `maxFeePerGas` /
1772
+ * `maxPriorityFeePerGas` from `provider.estimateFeesPerGas()` if the
1773
+ * fields aren't already set, then request paymaster sponsorship.
1774
+ */
1775
+ partialUserOp: PartialUserOperation;
1776
+ /** Optional fee-stripped fallback variant. */
1777
+ partialUserOpFallback?: PartialUserOperation;
1778
+ /**
1779
+ * Scenario tag — passed to `requestSponsorship` so the relayer can
1780
+ * apply per-scenario L1 enforcement (`mint`, `burn`, etc.).
1781
+ */
1782
+ scenario: string;
1783
+ /** Target contract for the paymaster intent. */
1784
+ pointTokenAddress: Address;
1785
+ /** TTL the store entry should outlive. Typically the lock duration in seconds. */
1786
+ ttlSeconds: number;
1787
+ store: IPendingUserOpStore;
1788
+ provider: PublicClient;
1789
+ /** Optional — when omitted, paymaster is skipped and `sponsored: false` is returned. */
1790
+ pafiBackendClient?: PafiBackendClient | null;
1791
+ onWarning?: (msg: string) => void;
1792
+ }
1793
+ interface HandleMobilePrepareResult extends PrepareMobileUserOpResult {
1794
+ /**
1795
+ * True when paymaster sponsorship was applied to the sponsored variant.
1796
+ * (Renamed from `sponsored` to avoid clashing with
1797
+ * `PrepareMobileUserOpResult.sponsored` which is the PreparedUserOp object.)
1798
+ */
1799
+ isSponsored: boolean;
1800
+ /**
1801
+ * True when the user's EOA has no EIP-7702 delegation set on-chain.
1802
+ * The mobile client must run the `/delegate/*` flow first.
1803
+ */
1804
+ needsDelegation: boolean;
1805
+ }
1806
+ /**
1807
+ * Build the mobile prepare response. End-to-end:
1808
+ *
1809
+ * 1. Pull `estimateFeesPerGas` + `getCode(user)` in parallel.
1810
+ * 2. Top up sponsored UserOp fees if the upstream handler left them
1811
+ * unset.
1812
+ * 3. `requestPaymaster` — non-fatal: if PAFI declines, the sponsored
1813
+ * variant still works, the FE just falls back to the unsponsored
1814
+ * response.
1815
+ * 4. `prepareMobileUserOp` — merge + hash + persist.
1816
+ */
1817
+ declare function handleMobilePrepare(params: HandleMobilePrepareParams): Promise<HandleMobilePrepareResult>;
1818
+ interface HandleMobileSubmitParams {
1819
+ lockId: string;
1820
+ /** User signature over `userOpHash` (or `userOpHashFallback`). */
1821
+ signature: Hex;
1822
+ /** Which variant the user actually signed. Defaults to `'sponsored'`. */
1823
+ variant?: "sponsored" | "fallback";
1824
+ store: IPendingUserOpStore;
1825
+ /**
1826
+ * Bind the bundler-returned hash to the lock so `claim/redeem status`
1827
+ * can fall back to the bundler receipt when the indexer's
1828
+ * amount-based match races and resolves a sibling. Different ledgers
1829
+ * use different methods (`bindMintUserOpHash` vs `bindCreditUserOpHash`),
1830
+ * so the caller passes the correct one.
1831
+ */
1832
+ bindUserOpHash: (lockId: string, userOpHash: Hex) => Promise<void>;
1833
+ pafiBackendClient?: PafiBackendClient | null;
1834
+ /** Defaults to `ENTRY_POINT_V08`. */
1835
+ entryPoint?: string;
1836
+ }
1837
+ /**
1838
+ * Submit a previously-prepared mobile UserOp to the bundler.
1839
+ *
1840
+ * Throws:
1841
+ * - `PendingUserOpNotFoundError` — entry expired or already submitted.
1842
+ * Map to 404.
1843
+ * - `BundlerNotConfiguredError` — `pafiBackendClient` missing. Map to 503.
1844
+ * - `BundlerRejectedError` — bundler rejected the UserOp. Map to 422.
1845
+ */
1846
+ declare function handleMobileSubmit(params: HandleMobileSubmitParams): Promise<{
1847
+ userOpHash: Hex;
1848
+ }>;
1849
+
1850
+ interface IssuerRegistryRecord {
1851
+ issuerAddress: Address;
1852
+ signerAddress: Address;
1853
+ name: string;
1854
+ symbol: string;
1855
+ declaredTotalSupply: bigint;
1856
+ capBasisPoints: number;
1857
+ active: boolean;
1858
+ pointToken: Address;
1859
+ mintingOracle: Address;
1860
+ }
1861
+ interface PreValidateMintResult {
1862
+ /** Registry record read at pre-validation time. */
1863
+ issuer: IssuerRegistryRecord;
1864
+ /** Current on-chain PointToken.totalSupply(). */
1865
+ totalSupply: bigint;
1866
+ /** declaredTotalSupply × capBasisPoints / 10000. */
1867
+ hardCap: bigint;
1868
+ /** hardCap − totalSupply (clamped to 0). */
1869
+ remaining: bigint;
1870
+ }
1871
+ /**
1872
+ * Thrown by `IssuerStateValidator.preValidateMint()`.
1873
+ * `code` maps 1:1 to the HTTP error the issuer API surfaces to clients.
1874
+ */
1875
+ declare class IssuerStateError extends Error {
1876
+ readonly code: "ISSUER_NOT_REGISTERED" | "ISSUER_INACTIVE" | "MINT_CAP_EXCEEDED";
1877
+ readonly details?: Record<string, unknown> | undefined;
1878
+ constructor(code: "ISSUER_NOT_REGISTERED" | "ISSUER_INACTIVE" | "MINT_CAP_EXCEEDED", message: string, details?: Record<string, unknown> | undefined);
1879
+ }
1880
+
1881
+ /**
1882
+ * Pure (framework-agnostic) validator for issuer state.
1883
+ *
1884
+ * Reads IssuerRegistry + PointToken on-chain state and pre-validates
1885
+ * mint requests before the user submits a UserOp. Catching these
1886
+ * off-chain lets issuers fail fast with a clear error rather than
1887
+ * wasting gas on a revert.
1888
+ *
1889
+ * Caching:
1890
+ * - `PointToken.issuer()` — memoized for the process lifetime (immutable)
1891
+ * - Full state (registry + totalSupply) — 30s TTL per PointToken
1892
+ * - Burst calls while a fetch is in-flight share the same Promise
1893
+ * (thundering-herd protection)
1894
+ *
1895
+ * Usage in NestJS: wrap this in an `@Injectable()` service; pass
1896
+ * `PublicClient` and `registryAddress` from your DI container.
1897
+ */
1898
+ declare class IssuerStateValidator {
1899
+ private readonly provider;
1900
+ private readonly registryAddress;
1901
+ private readonly pointTokenIssuerCache;
1902
+ private readonly stateCache;
1903
+ private readonly inflight;
1904
+ constructor(provider: PublicClient, registryAddress: Address);
1905
+ /**
1906
+ * Convenience factory — reads `registryAddress` from the SDK
1907
+ * `CONTRACT_ADDRESSES` map for the given chain.
1908
+ */
1909
+ static forChain(provider: PublicClient, chainId: number): IssuerStateValidator;
1910
+ /**
1911
+ * Invalidate cached state for one PointToken, or everything if omitted.
1912
+ * Call after admin txs that change registry or cap settings.
1913
+ */
1914
+ invalidate(pointToken?: Address): void;
1915
+ /**
1916
+ * Resolve `PointToken.issuer()` once per token and memoize.
1917
+ * The issuer field is set at `initialize()` and never changes.
1918
+ */
1919
+ getIssuerAddressForPointToken(pointToken: Address): Promise<Address>;
1920
+ /**
1921
+ * Read registry record + totalSupply, with 30s cache and in-flight
1922
+ * deduplication. Does NOT throw on inactive/missing — returns raw state.
1923
+ */
1924
+ getIssuerState(pointToken: Address): Promise<PreValidateMintResult>;
1925
+ /**
1926
+ * Validate that `amount` PT can be minted on `pointToken` right now.
1927
+ *
1928
+ * Throws `IssuerStateError` with:
1929
+ * - `ISSUER_NOT_REGISTERED` — registry has no record for this issuer
1930
+ * - `ISSUER_INACTIVE` — issuer.active is false
1931
+ * - `MINT_CAP_EXCEEDED` — totalSupply + amount would exceed hardCap
1932
+ *
1933
+ * Returns the fetched state on success so callers can log without a
1934
+ * second RPC round-trip.
1935
+ */
1936
+ preValidateMint(pointToken: Address, amount: bigint): Promise<PreValidateMintResult>;
1937
+ private fetchIssuerState;
1938
+ }
1939
+
1940
+ type DecodedCall$2 = ReturnType<typeof decodeBatchExecuteCalls>[number];
1941
+
1942
+ /**
1943
+ * v1.4 sig-gated mint handler — mirrors `PTRedeemHandler` on the mint side.
1944
+ *
1945
+ * Pre-validates against IssuerRegistry + on-chain totalSupply, locks the
1946
+ * off-chain balance, builds the sponsored UserOp (mint + PT fee
1947
+ * transfer) plus an optional fallback variant (mint only — for
1948
+ * paymaster-refused fallback).
1949
+ *
1950
+ * Caller fetches AA + mintRequest nonces (so issuers can plug in their
1951
+ * own composer — gg56 uses a timestamp-key 2D nonce). Caller layers
1952
+ * paymaster sponsorship + sponsorAuth on top of the returned UserOps.
1953
+ */
1954
+ declare class PTClaimError extends Error {
1955
+ code: "INVALID_AMOUNT" | "VALIDATION_FAILED" | "BUILD_FAILED";
1956
+ details?: Record<string, unknown> | undefined;
1957
+ constructor(code: "INVALID_AMOUNT" | "VALIDATION_FAILED" | "BUILD_FAILED", message: string, details?: Record<string, unknown> | undefined);
1958
+ }
1959
+ interface PTClaimHandlerConfig {
1960
+ ledger: IPointLedger;
1961
+ relayService: RelayService;
1962
+ provider: PublicClient;
1963
+ /** Issuer minter signer wallet — passed through to RelayService.prepareMint. */
1964
+ issuerSignerWallet: WalletClient;
1965
+ /**
1966
+ * EIP-712 domain `name` for `MintRequest`. Typically the PointToken
1967
+ * ERC-20 name. RelayService will set chainId + verifyingContract from
1968
+ * the request.
1969
+ */
1970
+ pointTokenDomainName: string;
1971
+ /** Optional — when wired, used to estimate the PT gas-reimbursement fee. */
1972
+ feeService?: FeeManager;
1973
+ /** Optional — pre-validates issuer status + cap before locking balance. */
1974
+ issuerStateValidator?: IssuerStateValidator;
1975
+ /** How long the off-chain balance lock survives if the mint never lands. Default 15 min. */
1976
+ lockDurationMs?: number;
1977
+ /** How far ahead of `now` to set the MintRequest deadline. Default 15 min. */
1978
+ signatureDeadlineSeconds?: number;
1979
+ now?: () => number;
1980
+ }
1981
+ interface PTClaimRequest {
1982
+ authenticatedAddress: Address;
1983
+ userAddress: Address;
1984
+ amount: bigint;
1985
+ pointTokenAddress: Address;
1986
+ chainId: number;
1987
+ /** ERC-4337 account nonce for the user's EOA. */
1988
+ aaNonce: bigint;
1989
+ /** Current `mintRequestNonces[userAddress]` from PointToken. */
1990
+ mintRequestNonce: bigint;
1991
+ }
1992
+ interface PTClaimResponse {
1993
+ /** Sponsored UserOp — mint + PT fee transfer (when feeAmount > 0). */
1994
+ userOp: PartialUserOperation;
1995
+ /**
1996
+ * Fallback UserOp — mint only, no PT fee transfer. Present only when
1997
+ * `feeAmount > 0`. User pays gas in ETH directly.
1998
+ */
1999
+ fallback?: PartialUserOperation;
2000
+ lockId: string;
2001
+ feeAmount: bigint;
2002
+ signatureDeadline: bigint;
2003
+ expiresInSeconds: number;
2004
+ /** Decoded calls for the sponsored UserOp (convenience for FE-submit path). */
2005
+ calls: DecodedCall$2[];
2006
+ /** Decoded calls for the fallback UserOp (when present). */
2007
+ callsFallback?: DecodedCall$2[];
2008
+ }
2009
+ declare class PTClaimHandler {
2010
+ private readonly cfg;
2011
+ constructor(config: PTClaimHandlerConfig);
2012
+ handle(request: PTClaimRequest): Promise<PTClaimResponse>;
2013
+ }
2014
+
2015
+ type DecodedCall$1 = ReturnType<typeof decodeBatchExecuteCalls>[number];
2016
+
2017
+ /**
2018
+ * PT → USDT swap handler.
2019
+ *
2020
+ * Quotes via V4 on-chain Quoter (`findBestQuote`), applies slippage,
2021
+ * computes the PT gas-reimbursement fee from `FeeManager`, and builds
2022
+ * two UserOps:
2023
+ *
2024
+ * - **sponsored** — swap (amountIn − fee) + transfer(fee, PAFI). User
2025
+ * holds exactly `amountIn` PT.
2026
+ * - **fallback** — swap full `amountIn`, no PT fee transfer. Built
2027
+ * only when `feeAmount > 0`. User pays gas in ETH directly.
2028
+ *
2029
+ * Re-quotes the sponsored path on `(amountIn − feeAmount)` because the
2030
+ * sponsored path actually swaps that much; the fallback uses the
2031
+ * original quote.
2032
+ *
2033
+ * Caller (controller) layers `sponsorAuth` on top of the returned
2034
+ * userOp. No off-chain ledger state is touched — swap is purely
2035
+ * on-chain.
2036
+ */
2037
+ declare class SwapError extends Error {
2038
+ code: "QUOTE_UNAVAILABLE" | "FEE_EXCEEDS_AMOUNT" | "INVALID_AMOUNT";
2039
+ constructor(code: "QUOTE_UNAVAILABLE" | "FEE_EXCEEDS_AMOUNT" | "INVALID_AMOUNT", message: string);
2040
+ }
2041
+ interface SwapHandlerConfig {
2042
+ provider: PublicClient;
2043
+ poolsProvider: PoolsProvider;
2044
+ /** Optional — when wired, used to estimate the PT gas-reimbursement fee. */
2045
+ feeService?: FeeManager;
2046
+ /**
2047
+ * Default slippage applied to the V4 quote when the request omits
2048
+ * `slippageBps`. 50 bps = 0.5%.
2049
+ */
2050
+ defaultSlippageBps?: number;
2051
+ /** Default deadline window in seconds applied when not passed in request. Default 300. */
2052
+ defaultSwapDeadlineSeconds?: number;
2053
+ now?: () => number;
2054
+ }
2055
+ interface SwapRequest {
2056
+ userAddress: Address;
2057
+ chainId: number;
2058
+ pointTokenAddress: Address;
2059
+ amountIn: bigint;
2060
+ /** ERC-4337 account nonce for the user's EOA. */
2061
+ aaNonce: bigint;
2062
+ /** Optional override; falls back to `defaultSlippageBps`. */
2063
+ slippageBps?: number;
2064
+ /** Optional override; falls back to `now() + defaultSwapDeadlineSeconds`. */
2065
+ deadline?: bigint;
2066
+ }
2067
+ interface SwapResponse {
2068
+ /** Sponsored UserOp — swap (amountIn − fee) + PT.transfer(fee). */
2069
+ userOp: PartialUserOperation;
2070
+ /** Fallback UserOp — swap full amountIn, no PT fee transfer. Present only when fee > 0. */
2071
+ fallback?: PartialUserOperation;
2072
+ feeAmount: bigint;
2073
+ /** Quote for the sponsored path (after fee deduction). */
2074
+ estimatedUsdtOut: bigint;
2075
+ minAmountOut: bigint;
2076
+ /** Quote for the fallback path (full amountIn). Present only when fee > 0. */
2077
+ estimatedUsdtOutFallback?: bigint;
2078
+ minAmountOutFallback?: bigint;
2079
+ deadline: bigint;
2080
+ calls: DecodedCall$1[];
2081
+ callsFallback?: DecodedCall$1[];
2082
+ }
2083
+ declare class SwapHandler {
2084
+ private readonly cfg;
2085
+ constructor(config: SwapHandlerConfig);
2086
+ handle(request: SwapRequest): Promise<SwapResponse>;
2087
+ }
2088
+
2089
+ type DecodedCall = ReturnType<typeof decodeBatchExecuteCalls>[number];
2090
+
2091
+ /**
2092
+ * Orderly perp-deposit handler — builds the sponsored + fallback
2093
+ * UserOps for the PAFI Relay path.
2094
+ *
2095
+ * Resolves USDC + verifies the broker is whitelisted on the Vault,
2096
+ * quotes the Relay's USDC fee (covers LayerZero msg.value out of the
2097
+ * Relay's ETH reserve), then builds two UserOps:
2098
+ *
2099
+ * - **sponsored** — PT.transfer(fee, PAFI) + USDC.approve(relay,
2100
+ * total) + Relay.deposit(req). User reimburses gas in PT.
2101
+ * - **fallback** — USDC.approve + Relay.deposit only. User pays
2102
+ * ERC-4337 gas in ETH. Built only when `feeAmount > 0`.
2103
+ *
2104
+ * `maxFee` is set to `quote × 1.5` — slippage cap on the Relay's
2105
+ * USD-pricing during the inclusion window. If the actual fee at
2106
+ * execution exceeds maxFee the Relay reverts (`FeeExceedsMax`).
2107
+ */
2108
+ declare class PerpDepositError extends Error {
2109
+ code: "PERP_DEPOSIT_UNAVAILABLE" | "BROKER_NOT_WHITELISTED" | "RELAY_FEE_EXCEEDS_AMOUNT" | "FEE_EXCEEDS_AMOUNT" | "INVALID_AMOUNT";
2110
+ constructor(code: "PERP_DEPOSIT_UNAVAILABLE" | "BROKER_NOT_WHITELISTED" | "RELAY_FEE_EXCEEDS_AMOUNT" | "FEE_EXCEEDS_AMOUNT" | "INVALID_AMOUNT", message: string);
2111
+ }
2112
+ interface PerpDepositHandlerConfig {
2113
+ provider: PublicClient;
2114
+ /** Optional — when wired, used to estimate the PT gas-reimbursement fee. */
2115
+ feeService?: FeeManager;
2116
+ /** PointToken address used for the sponsored PT.transfer. */
2117
+ pointTokenAddress: Address;
2118
+ /**
2119
+ * Slippage premium applied on top of the Relay quote when computing
2120
+ * `maxFee`. Default 50% (factor 150). The Relay reverts if actual fee
2121
+ * exceeds `maxFee` so a generous cap reduces re-quote churn.
2122
+ */
2123
+ maxFeePremiumBps?: number;
2124
+ }
2125
+ interface PerpDepositRequest {
2126
+ userAddress: Address;
2127
+ chainId: number;
2128
+ amount: bigint;
2129
+ brokerId: keyof typeof BROKER_HASHES;
2130
+ /** ERC-4337 account nonce. */
2131
+ aaNonce: bigint;
2132
+ }
2133
+ interface PerpDepositResponse {
2134
+ userOp: PartialUserOperation;
2135
+ fallback?: PartialUserOperation;
2136
+ feeAmount: bigint;
2137
+ relayTokenFee: bigint;
2138
+ maxFee: bigint;
2139
+ netDeposit: bigint;
2140
+ accountId: `0x${string}`;
2141
+ brokerHash: `0x${string}`;
2142
+ usdcAddress: Address;
2143
+ relayAddress: Address;
2144
+ calls: DecodedCall[];
2145
+ callsFallback?: DecodedCall[];
2146
+ }
2147
+ declare class PerpDepositHandler {
2148
+ private readonly cfg;
2149
+ constructor(config: PerpDepositHandlerConfig);
2150
+ handle(request: PerpDepositRequest): Promise<PerpDepositResponse>;
2151
+ }
2152
+
2153
+ /**
2154
+ * Config for `createSubgraphPoolsProvider`.
2155
+ */
2156
+ interface SubgraphPoolsProviderConfig {
2157
+ /**
2158
+ * Fully qualified subgraph GraphQL endpoint.
2159
+ * Defaults to the PAFI-hosted subgraph (`PAFI_SUBGRAPH_URL`).
2160
+ * Override only when pointing at a staging or custom deployment.
2161
+ */
2162
+ subgraphUrl?: string;
2163
+ /**
2164
+ * Cache TTL in milliseconds. Pool discovery is near-static — a 30s
2165
+ * cache removes subgraph load without meaningfully delaying UX.
2166
+ * Set to 0 to disable caching. Default: 30_000.
2167
+ */
2168
+ cacheTtlMs?: number;
2169
+ /**
2170
+ * Optional fetch override for test harnesses. Defaults to global `fetch`.
2171
+ */
2172
+ fetchImpl?: typeof fetch;
2173
+ /**
2174
+ * Optional clock override for tests.
2175
+ */
2176
+ now?: () => number;
2177
+ }
2178
+ /**
2179
+ * Create a `PoolsProvider` backed by the PAFI subgraph.
2180
+ *
2181
+ * Queries the `pafiTokens` entity for the given `pointTokenAddress`,
2182
+ * reads its linked `Pool`, and returns a single-element `PoolKey[]`.
2183
+ * Multiple pools per token would require a subgraph schema change.
2184
+ *
2185
+ * The result is cached in-process with a short TTL (default 30s). Pool
2186
+ * discovery is near-static so this avoids hammering the subgraph without
2187
+ * blocking config changes for long.
2188
+ *
2189
+ * Returns `{ pools: [] }` if:
2190
+ * - the token is not registered on PAFI yet (no PafiToken entity)
2191
+ * - the token is registered but its pool has not been initialised
2192
+ * - the subgraph is unreachable or returns an error (logs to console,
2193
+ * does not throw — callers should handle empty pool list gracefully)
2194
+ *
2195
+ * Assumes the PAFI subgraph schema. Issuers with a custom subgraph must
2196
+ * implement `PoolsProvider` themselves instead of using this helper.
2197
+ *
2198
+ * @example
2199
+ * ```ts
2200
+ * import { createSubgraphPoolsProvider, createIssuerService } from "@pafi/issuer";
2201
+ *
2202
+ * const service = createIssuerService({
2203
+ * // ...other config
2204
+ * poolsProvider: createSubgraphPoolsProvider({
2205
+ * subgraphUrl: "https://graph.pacificfinance.org/subgraphs/name/pafi",
2206
+ * }),
2207
+ * });
2208
+ * ```
2209
+ */
2210
+ declare function createSubgraphPoolsProvider(config?: SubgraphPoolsProviderConfig): PoolsProvider;
2211
+
2212
+ /**
2213
+ * Config for `createSubgraphNativeUsdtQuoter`.
2214
+ */
2215
+ interface SubgraphNativeUsdtQuoterConfig {
2216
+ /**
2217
+ * Fully qualified subgraph GraphQL endpoint.
2218
+ * Defaults to the PAFI-hosted subgraph (`PAFI_SUBGRAPH_URL`).
2219
+ * Override only when pointing at a staging or custom deployment.
2220
+ */
2221
+ subgraphUrl?: string;
2222
+ /**
2223
+ * Decimals of the USDT token. Defaults to 6 (standard USDT/USDC on
2224
+ * Base, Ethereum, Polygon). Override for chains where USDT uses a
2225
+ * different decimals value.
2226
+ */
2227
+ usdtDecimals?: number;
2228
+ /**
2229
+ * Decimals of the native token (ETH on Base/mainnet/Arbitrum/Optimism,
2230
+ * MATIC on Polygon). Default: 18.
2231
+ */
2232
+ nativeDecimals?: number;
2233
+ /**
2234
+ * Cache TTL in milliseconds. ETH price drifts slowly relative to gas
2235
+ * estimation needs — a 30s cache keeps fees stable across bursts of
2236
+ * requests without letting them go stale during volatile markets.
2237
+ * Set to 0 to disable caching. Default: 30_000.
2238
+ */
2239
+ cacheTtlMs?: number;
2240
+ /**
2241
+ * Fallback price (USDT per native token, human-readable float) used
2242
+ * when the subgraph is unreachable. This keeps the backend operational
2243
+ * during subgraph outages rather than bricking cashouts. The fee will
2244
+ * be slightly off but the operator still gets paid. Default: 3000.
2245
+ */
2246
+ fallbackEthPriceUsd?: number;
2247
+ /** Optional fetch override for test harnesses. */
2248
+ fetchImpl?: typeof fetch;
2249
+ /** Optional clock override for tests. */
2250
+ now?: () => number;
2251
+ }
2252
+ /**
2253
+ * Create a native→USDT quoter backed by the PAFI subgraph's
2254
+ * `Bundle.ethPriceUSD`. The returned function has the shape
2255
+ * `(amountNative: bigint) => Promise<bigint>` and can be passed as
2256
+ * `quoteNativeToFee` to `FeeManager` — in v1.4 the fee currency
2257
+ * is configured at the integration layer, not hardcoded here.
2258
+ *
2259
+ * Used by `FeeManager.estimateGasFee()` to convert the gas cost into
2260
+ * an ERC-20 amount charged as part of the sponsored UserOp batch.
2261
+ * Price precision is not critical — a 1-2% drift is acceptable since
2262
+ * the fee manager applies a `gasPremiumBps` buffer.
2263
+ *
2264
+ * The result is cached in-process with a short TTL (default 30s). If
2265
+ * the subgraph is unreachable, falls back to `fallbackEthPriceUsd` so
2266
+ * gas estimation doesn't block user flow during a subgraph outage.
2267
+ *
2268
+ * @example
2269
+ * ```ts
2270
+ * import { createSubgraphNativeUsdtQuoter, createIssuerService } from "@pafi-dev/issuer";
2271
+ *
2272
+ * const service = createIssuerService({
2273
+ * // ...other config
2274
+ * fee: {
2275
+ * quoteNativeToFee: createSubgraphNativeUsdtQuoter({
2276
+ * subgraphUrl: "https://graph.pacificfinance.org/subgraphs/name/pafi",
2277
+ * }),
2278
+ * },
2279
+ * });
2280
+ * ```
2281
+ */
2282
+ declare function createSubgraphNativeUsdtQuoter(config?: SubgraphNativeUsdtQuoterConfig): (amountNative: bigint) => Promise<bigint>;
2283
+
2284
+ interface NativePtQuoterConfig {
2285
+ /** Viem PublicClient — used to call Chainlink on-chain. */
2286
+ provider: PublicClient;
2287
+ /** Address of the PointToken being traded. */
2288
+ pointTokenAddress: Address;
2289
+ /** Chainlink ETH/USD feed address. Defaults to Base mainnet feed. */
2290
+ chainlinkFeedAddress?: Address;
2291
+ /** PAFI subgraph GraphQL endpoint. */
2292
+ subgraphUrl?: string;
2293
+ /** Cache TTL in ms. Default: 30_000. */
2294
+ cacheTtlMs?: number;
2295
+ /** Fallback ETH price (USD) when Chainlink is unreachable. Default: 3000. */
2296
+ fallbackEthPriceUsd?: number;
2297
+ /** Fallback PT price (USDT per 1 PT) when subgraph is unreachable. Default: 0.1. */
2298
+ fallbackPtPriceUsdt?: number;
2299
+ fetchImpl?: typeof fetch;
2300
+ now?: () => number;
2301
+ }
2302
+ /**
2303
+ * Create a native→PT quoter for use as `FeeManager.quoteNativeToFee`.
2304
+ *
2305
+ * Converts ETH gas cost → USDT (via Chainlink ETH/USD) → PT (via subgraph
2306
+ * pool price), returning the fee amount in PT raw units (18 decimals).
2307
+ *
2308
+ * Formula:
2309
+ * feeInPT = amountNative × ethPrice_8dec × ptPerUsdt_18dec / 10^26
2310
+ *
2311
+ * Both prices are cached in-process (default 30s TTL).
2312
+ *
2313
+ * @example
2314
+ * ```ts
2315
+ * fee: {
2316
+ * quoteNativeToFee: createNativePtQuoter({
2317
+ * provider,
2318
+ * pointTokenAddress: "0x...",
2319
+ * chainlinkFeedAddress: getContractAddresses(chainId).chainlinkEthUsd,
2320
+ * }),
2321
+ * }
2322
+ * ```
2323
+ */
2324
+ declare function createNativePtQuoter(config: NativePtQuoterConfig): (amountNative: bigint) => Promise<bigint>;
2325
+
2326
+ /**
2327
+ * Combined off-chain + on-chain balance for a single user / token pair.
2328
+ *
2329
+ * - `offChain` — the issuer's ledger balance (available, excluding locks)
2330
+ * - `onChain` — the user's ERC-20 balance from `PointToken.balanceOf`
2331
+ * - `total` — `offChain + onChain` (what the Issuer App displays)
2332
+ */
2333
+ interface CombinedBalance {
2334
+ offChain: bigint;
2335
+ onChain: bigint;
2336
+ total: bigint;
2337
+ }
2338
+ interface BalanceAggregatorConfig {
2339
+ provider: PublicClient;
2340
+ ledger: IPointLedger;
2066
2341
  }
2067
2342
  /**
2068
- * Build the sponsored UserOp + (optional) fee-stripped fallback for the
2069
- * mobile prepare/submit flow.
2343
+ * v1.4 utility aggregates off-chain + on-chain point balances into a
2344
+ * single view for the "combined balance" UI in the Issuer App.
2070
2345
  *
2071
- * What this does, end-to-end:
2072
- * 1. Merge Pimlico's paymaster sponsorship + re-quoted gas into the
2073
- * caller's `partialUserOp` skeleton.
2074
- * 2. Compute the EIP-712 userOpHash + the typed-data payload (in
2075
- * JSON-safe form for HTTP transport).
2076
- * 3. (Optional) Repeat for the `partialUserOpFallback` skeleton with
2077
- * no paymaster fields — produces a separate hash + typed-data so
2078
- * the client can re-sign if it falls back.
2079
- * 4. Persist a single store entry containing BOTH callData variants
2080
- * keyed by lockId. `serializeEntryToJsonRpc` reads the `variant`
2081
- * param at submit time.
2346
+ * The `/user` API handler uses this internally; the helper is exposed
2347
+ * publicly so Issuer Apps can call it directly without going through
2348
+ * the HTTP layer (e.g., for server-rendered pages or admin dashboards).
2082
2349
  *
2083
- * Replaces ~100 LoC of glue per scenario in issuer controllers.
2350
+ * See [REQUIREMENTS_V2.md] §1 "The Issuer App displays a combined
2351
+ * balance (off-chain points + on-chain PT) and does not surface USDT."
2084
2352
  */
2085
- declare function prepareMobileUserOp(params: PrepareMobileUserOpParams): Promise<PrepareMobileUserOpResult>;
2353
+ declare class BalanceAggregator {
2354
+ private readonly provider;
2355
+ private readonly ledger;
2356
+ constructor(config: BalanceAggregatorConfig);
2357
+ /**
2358
+ * Combined balance for a single (user, token) pair. Fetches off-chain
2359
+ * + on-chain in parallel.
2360
+ */
2361
+ getCombinedBalance(user: Address, pointToken: Address): Promise<CombinedBalance>;
2362
+ /**
2363
+ * Combined balance for multiple tokens owned by the same user. Runs
2364
+ * all lookups in parallel. Returns a Map keyed by the token address
2365
+ * (same casing as supplied — caller should normalize if needed).
2366
+ */
2367
+ getCombinedBalanceMulti(user: Address, pointTokens: Address[]): Promise<Map<Address, CombinedBalance>>;
2368
+ }
2086
2369
 
2087
- interface IssuerRegistryRecord {
2088
- issuerAddress: Address;
2089
- signerAddress: Address;
2090
- name: string;
2091
- symbol: string;
2092
- declaredTotalSupply: bigint;
2093
- capBasisPoints: number;
2094
- active: boolean;
2095
- pointToken: Address;
2096
- mintingOracle: Address;
2370
+ /**
2371
+ * Typed errors thrown by the helpers below — issuer controllers map
2372
+ * these to the appropriate HTTP status. We don't depend on @nestjs/common
2373
+ * here because the SDK is framework-agnostic; the consuming controller
2374
+ * wraps each one in its preferred exception class.
2375
+ */
2376
+ declare class BundlerNotConfiguredError extends Error {
2377
+ readonly code = "BUNDLER_NOT_CONFIGURED";
2378
+ constructor();
2097
2379
  }
2098
- interface PreValidateMintResult {
2099
- /** Registry record read at pre-validation time. */
2100
- issuer: IssuerRegistryRecord;
2101
- /** Current on-chain PointToken.totalSupply(). */
2102
- totalSupply: bigint;
2103
- /** declaredTotalSupply × capBasisPoints / 10000. */
2104
- hardCap: bigint;
2105
- /** hardCap totalSupply (clamped to 0). */
2106
- remaining: bigint;
2380
+ declare class BundlerRejectedError extends Error {
2381
+ readonly code = "BUNDLER_REJECTED";
2382
+ readonly cause: unknown;
2383
+ constructor(message: string, cause: unknown);
2384
+ }
2385
+ interface RequestPaymasterParams {
2386
+ /** PAFI backend client. When `null` / `undefined` → returns `undefined`. */
2387
+ client: PafiBackendClient | null | undefined;
2388
+ chainId: number;
2389
+ scenario: string;
2390
+ /** UserOp skeleton — must have all gas + fee fields set. */
2391
+ userOp: {
2392
+ sender: Address;
2393
+ nonce: bigint;
2394
+ callData: Hex;
2395
+ callGasLimit: bigint;
2396
+ verificationGasLimit: bigint;
2397
+ preVerificationGas: bigint;
2398
+ maxFeePerGas: bigint;
2399
+ maxPriorityFeePerGas: bigint;
2400
+ };
2401
+ /** Target contract (typically the PointToken). */
2402
+ pointTokenAddress: Address;
2403
+ /**
2404
+ * Function name to surface in audit / paymaster context. Defaults to
2405
+ * a per-scenario sensible value (`mint` / `burn` / `swap` / generic
2406
+ * scenario name).
2407
+ */
2408
+ functionName?: string;
2409
+ /** Optional logger for the "sponsorship declined" warning. */
2410
+ onWarning?: (msg: string) => void;
2107
2411
  }
2108
2412
  /**
2109
- * Thrown by `IssuerStateValidator.preValidateMint()`.
2110
- * `code` maps 1:1 to the HTTP error the issuer API surfaces to clients.
2413
+ * Thin wrapper around `PafiBackendClient.requestSponsorship` with the
2414
+ * "non-fatal on failure" semantics every issuer wants:
2415
+ *
2416
+ * - When the client is missing → returns `undefined` (the caller falls
2417
+ * back to a self-funded UserOp).
2418
+ * - When the network call throws OR PAFI declines (rate limit, intent
2419
+ * rejection, paymaster outage) → returns `undefined` after logging,
2420
+ * so the controller doesn't hard-fail. The caller's
2421
+ * `prepareMobileUserOp` / `mergePaymasterFields` will gracefully
2422
+ * produce the unsponsored variant.
2423
+ *
2424
+ * Replaces ~30 LoC of try/catch + scenario-to-function mapping every
2425
+ * issuer would copy.
2111
2426
  */
2112
- declare class IssuerStateError extends Error {
2113
- readonly code: "ISSUER_NOT_REGISTERED" | "ISSUER_INACTIVE" | "MINT_CAP_EXCEEDED";
2114
- readonly details?: Record<string, unknown> | undefined;
2115
- constructor(code: "ISSUER_NOT_REGISTERED" | "ISSUER_INACTIVE" | "MINT_CAP_EXCEEDED", message: string, details?: Record<string, unknown> | undefined);
2427
+ declare function requestPaymaster(params: RequestPaymasterParams): Promise<Awaited<ReturnType<PafiBackendClient["requestSponsorship"]>> | undefined>;
2428
+ interface RelayUserOpParams {
2429
+ client: PafiBackendClient | null | undefined;
2430
+ /** EntryPoint address typically `ENTRY_POINT_V08` from core. */
2431
+ entryPoint: typeof ENTRY_POINT_V08 | string;
2432
+ userOp: Record<string, string | null>;
2433
+ /** EIP-7702 authorization (delegation UserOps only). */
2434
+ eip7702Auth?: {
2435
+ chainId: string;
2436
+ address: string;
2437
+ nonce: string;
2438
+ r: string;
2439
+ s: string;
2440
+ yParity: string;
2441
+ };
2116
2442
  }
2443
+ /**
2444
+ * Submit a serialized UserOp to the Pimlico bundler via PAFI's
2445
+ * sponsor-relayer. Handles the "client missing" / "bundler rejected"
2446
+ * branches as typed errors so the controller can map to HTTP cleanly.
2447
+ *
2448
+ * Every issuer mobile flow has this exact wrapper — moved into SDK
2449
+ * to drop ~30 LoC of try/catch + error-shape boilerplate per
2450
+ * controller.
2451
+ *
2452
+ * Throws:
2453
+ * - `BundlerNotConfiguredError` — caller didn't configure
2454
+ * `PafiBackendClient`. Map to 503.
2455
+ * - `BundlerRejectedError` — bundler returned an error. Map to 422
2456
+ * (the FE can show the reason — usually `AA21` / `AA34` / etc.).
2457
+ */
2458
+ declare function relayUserOp(params: RelayUserOpParams): Promise<{
2459
+ userOpHash: Hex;
2460
+ }>;
2117
2461
 
2118
2462
  /**
2119
- * Pure (framework-agnostic) validator for issuer state.
2120
- *
2121
- * Reads IssuerRegistry + PointToken on-chain state and pre-validates
2122
- * mint requests before the user submits a UserOp. Catching these
2123
- * off-chain lets issuers fail fast with a clear error rather than
2124
- * wasting gas on a revert.
2463
+ * Top-level configuration for `createIssuerService`.
2125
2464
  *
2126
- * Caching:
2127
- * - `PointToken.issuer()` memoized for the process lifetime (immutable)
2128
- * - Full state (registry + totalSupply)30s TTL per PointToken
2129
- * - Burst calls while a fetch is in-flight share the same Promise
2130
- * (thundering-herd protection)
2465
+ * In v1.4 the SDK is HTTP-client-free: it signs EIP-712 messages, reads
2466
+ * on-chain state, builds unsigned UserOperations, and maintains the
2467
+ * off-chain ledger. It never broadcasts transactionsthat's the
2468
+ * frontend's responsibility via Bundler + Paymaster.
2131
2469
  *
2132
- * Usage in NestJS: wrap this in an `@Injectable()` service; pass
2133
- * `PublicClient` and `registryAddress` from your DI container.
2470
+ * **Multi-token (0.2.0+):** Pass `pointTokenAddresses: Address[]` to
2471
+ * support multiple PointTokens under a single issuer backend. Legacy
2472
+ * `pointTokenAddress: Address` still works for single-token deployments.
2473
+ * When both are provided, `pointTokenAddresses` takes precedence.
2134
2474
  */
2135
- declare class IssuerStateValidator {
2136
- private readonly provider;
2137
- private readonly registryAddress;
2138
- private readonly pointTokenIssuerCache;
2139
- private readonly stateCache;
2140
- private readonly inflight;
2141
- constructor(provider: PublicClient, registryAddress: Address);
2475
+ interface IssuerServiceConfig {
2476
+ chainId: number;
2477
+ /** Legacy single-token shortcut; prefer `pointTokenAddresses`. */
2478
+ pointTokenAddress?: Address;
2479
+ /** All PointToken addresses this issuer supports. */
2480
+ pointTokenAddresses?: Address[];
2142
2481
  /**
2143
- * Convenience factory reads `registryAddress` from the SDK
2144
- * `CONTRACT_ADDRESSES` map for the given chain.
2482
+ * Issuer-specific contract addresses merged into the `/config` response.
2483
+ * PAFI-owned addresses (batchExecutor, usdt, issuerRegistry, mintingOracle,
2484
+ * pafiHook) are auto-resolved from `getContractAddresses(chainId)` and
2485
+ * can be omitted. Only `pointToken` / `pointTokens` must be provided.
2145
2486
  */
2146
- static forChain(provider: PublicClient, chainId: number): IssuerStateValidator;
2487
+ contracts?: Pick<ApiConfigResponse["contracts"], "pointToken" | "pointTokens" | "relay">;
2147
2488
  /**
2148
- * Invalidate cached state for one PointToken, or everything if omitted.
2149
- * Call after admin txs that change registry or cap settings.
2489
+ * Shared `PublicClient` used for on-chain reads, simulation, indexer
2490
+ * polling, and gas-price lookups. Must be pointed at the target chain.
2150
2491
  */
2151
- invalidate(pointToken?: Address): void;
2492
+ provider: PublicClient;
2493
+ auth: {
2494
+ jwtSecret: string;
2495
+ /** SIWE-style login-message domain, e.g. `"app.example.com"`. */
2496
+ domain: string;
2497
+ /** Passed straight to `jose` (`"24h"`, `"7d"`, …). Default `"24h"`. */
2498
+ jwtExpiresIn?: string;
2499
+ };
2152
2500
  /**
2153
- * Resolve `PointToken.issuer()` once per token and memoize.
2154
- * The issuer field is set at `initialize()` and never changes.
2501
+ * Off-chain point ledger the source of truth for user balances and
2502
+ * in-flight minting locks. Every issuer provides their own database-backed
2503
+ * implementation (Postgres, Redis, etc.) that implements `IPointLedger`.
2504
+ * The SDK does not ship a production ledger; each issuer's data model and
2505
+ * infrastructure are different.
2155
2506
  */
2156
- getIssuerAddressForPointToken(pointToken: Address): Promise<Address>;
2507
+ ledger: IPointLedger;
2157
2508
  /**
2158
- * Read registry record + totalSupply, with 30s cache and in-flight
2159
- * deduplication. Does NOT throw on inactive/missing returns raw state.
2509
+ * Policy engine optional, defaults to `DefaultPolicyEngine` which checks
2510
+ * off-chain balance. Extend or replace to add KYC, volume caps, etc.
2160
2511
  */
2161
- getIssuerState(pointToken: Address): Promise<PreValidateMintResult>;
2512
+ policy?: IPolicyEngine;
2513
+ /** Session store — optional, defaults to `MemorySessionStore` (dev/test only). */
2514
+ sessionStore?: ISessionStore;
2162
2515
  /**
2163
- * Validate that `amount` PT can be minted on `pointToken` right now.
2164
- *
2165
- * Throws `IssuerStateError` with:
2166
- * - `ISSUER_NOT_REGISTERED` — registry has no record for this issuer
2167
- * - `ISSUER_INACTIVE` — issuer.active is false
2168
- * - `MINT_CAP_EXCEEDED` — totalSupply + amount would exceed hardCap
2169
- *
2170
- * Returns the fetched state on success so callers can log without a
2171
- * second RPC round-trip.
2516
+ * Fee management config. If omitted the `handleGasFee` endpoint will
2517
+ * throw "not configured" at request time.
2172
2518
  */
2173
- preValidateMint(pointToken: Address, amount: bigint): Promise<PreValidateMintResult>;
2174
- private fetchIssuerState;
2519
+ fee?: Omit<FeeManagerConfig, "provider">;
2520
+ /**
2521
+ * Pool discovery function for `handlePools`. If omitted the endpoint
2522
+ * throws "not configured" at request time.
2523
+ */
2524
+ poolsProvider?: PoolsProvider;
2525
+ /**
2526
+ * Enables `handleClaim`. The factory combines these with the shared
2527
+ * `policy` + `relayService` instances already wired by the factory.
2528
+ * Omit to disable the `/claim` endpoint.
2529
+ */
2530
+ claim?: {
2531
+ issuerSignerWallet: WalletClient;
2532
+ /** Defaults to the PAFI-deployed BatchExecutor for the target chain. */
2533
+ batchExecutorAddress?: Address;
2534
+ lockDurationMs?: number;
2535
+ };
2536
+ indexer?: {
2537
+ fromBlock?: bigint;
2538
+ cursorStore?: IIndexerCursorStore;
2539
+ confirmations?: number;
2540
+ batchSize?: number;
2541
+ pollIntervalMs?: number;
2542
+ /**
2543
+ * If `true`, the factory calls `indexer.start()` before returning.
2544
+ * Default: `false` — the caller decides when to begin polling.
2545
+ */
2546
+ autoStart?: boolean;
2547
+ };
2548
+ }
2549
+ interface IssuerService {
2550
+ /** AuthService — login, logout, nonce management. */
2551
+ auth: AuthService;
2552
+ /** Session store — nonce + JWT session persistence. */
2553
+ session: ISessionStore;
2554
+ ledger: IPointLedger;
2555
+ policy: IPolicyEngine;
2556
+ /** RelayService — prepareMint / prepareBurn UserOp builders. */
2557
+ relay: RelayService;
2558
+ /** FeeManager — gas fee estimation. Undefined if not configured. */
2559
+ fee: FeeManager | undefined;
2560
+ /** All indexers keyed by PointToken address. */
2561
+ indexers: Map<Address, PointIndexer>;
2562
+ /**
2563
+ * First indexer. Kept for backward compat with 0.1.x callers.
2564
+ * @deprecated use `indexers.get(tokenAddress)` for multi-token.
2565
+ */
2566
+ indexer: PointIndexer;
2567
+ /** Framework-agnostic HTTP handlers — wire into Express / Fastify / Hono. */
2568
+ api: IssuerApiHandlers;
2175
2569
  }
2570
+ /**
2571
+ * Wire a fully-functional issuer service from a single config object.
2572
+ *
2573
+ * Defaults:
2574
+ * - `sessionStore` → `MemorySessionStore` (dev/test only — replace in prod)
2575
+ * - `policy` → `DefaultPolicyEngine({ ledger })`
2576
+ * - `feeManager` → undefined (handleGasFee throws until configured)
2577
+ * - `poolsProvider` → undefined (handlePools throws until configured)
2578
+ * - `indexer.autoStart` → false
2579
+ *
2580
+ * Throws synchronously if any required field is missing.
2581
+ */
2582
+ declare function createIssuerService(config: IssuerServiceConfig): IssuerService;
2176
2583
 
2177
2584
  /** SDK package version — bumped on every release */
2178
2585
  declare const PAFI_ISSUER_SDK_VERSION = "0.4.0";
2179
2586
 
2180
- export { type ApiClaimRequest, type ApiClaimResponse, type ApiConfigResponse, type ApiGasFeeResponse, type ApiLoginRequest, type ApiLoginResponse, type ApiNonceResponse, type ApiPoolsRequest, type ApiPoolsResponse, type ApiRedeemRequest, type ApiRedeemResponse, type ApiTopUpRequest, type ApiTopUpResponse, type ApiUserRequest, type ApiUserResponse, type AuthContext, AuthError, type AuthErrorCode, AuthService, type AuthServiceConfig, BalanceAggregator, type BalanceAggregatorConfig, type BurnEvent, BurnIndexer, type BurnIndexerConfig, type BurnStatusParams, type BurnStatusResponse, type CombinedBalance, DefaultPolicyEngine, type DefaultPolicyEngineOptions, FeeManager, type FeeManagerConfig, type IIndexerCursorStore, type IPendingUserOpStore, type IPointLedger, type IPolicyEngine, type ISessionStore, InMemoryCursorStore, IssuerApiHandlers, type IssuerApiHandlersConfig, type IssuerRegistryRecord, type IssuerService, type IssuerServiceConfig, IssuerStateError, IssuerStateValidator, LockNotFoundError, type LockedMintRequest, type LoginResult, MemorySessionStore, type MemorySessionStoreOptions, type MintEvent, type MintStatusParams, type MintStatusResponse, type MintingStatus, type NativePtQuoterConfig, NonceManager, PAFI_ISSUER_SDK_VERSION, PTRedeemError, PTRedeemHandler, type PTRedeemHandlerConfig, type PTRedeemRequest, type PTRedeemResponse, PafiBackendClient, type PafiBackendConfig, PafiBackendError, type PafiBackendErrorCode, type PendingCredit, type PendingUserOpEntry, PointIndexer, type PointIndexerConfig, type PolicyDecision, type PolicyEvalRequest, type PoolsProvider, type PreValidateMintResult, type PrepareBurnDirectParams, type PrepareBurnParams, type PrepareBurnWithSigParams, type PrepareMintParams, type PrepareMobileUserOpParams, type PrepareMobileUserOpResult, type PreparedUserOp, RelayError, type RelayErrorCode, RelayService, type RelayUserOpRequest, type RelayUserOpResponse, type RetryConfig, type SerializedUserOpTypedData, type Session, type SponsorshipRequest, type SponsorshipResponse, type SponsorshipTarget, type SponsorshipUserOp, type SubgraphNativeUsdtQuoterConfig, type SubgraphPoolsProviderConfig, TopUpRedemptionError, TopUpRedemptionHandler, type TopUpRedemptionHandlerConfig, type TopUpRedemptionRequest, type TopUpRedemptionResponse, authenticateRequest, createIssuerService, createNativePtQuoter, createSubgraphNativeUsdtQuoter, createSubgraphPoolsProvider, handleClaimStatus, handleRedeemStatus, mergePaymasterFields, prepareMobileUserOp, serializeEntryToJsonRpc, serializeUserOpTypedData };
2587
+ export { type ApiClaimRequest, type ApiClaimResponse, type ApiConfigResponse, type ApiGasFeeResponse, type ApiLoginRequest, type ApiLoginResponse, type ApiNonceResponse, type ApiPoolsRequest, type ApiPoolsResponse, type ApiRedeemRequest, type ApiRedeemResponse, type ApiTopUpRequest, type ApiTopUpResponse, type ApiUserRequest, type ApiUserResponse, type AuthContext, AuthError, type AuthErrorCode, AuthService, type AuthServiceConfig, BalanceAggregator, type BalanceAggregatorConfig, BundlerNotConfiguredError, BundlerRejectedError, type BurnEvent, BurnIndexer, type BurnIndexerConfig, type BurnStatusParams, type BurnStatusResponse, type CombinedBalance, DefaultPolicyEngine, type DefaultPolicyEngineOptions, FeeManager, type FeeManagerConfig, type HandleMobilePrepareParams, type HandleMobilePrepareResult, type HandleMobileSubmitParams, type IIndexerCursorStore, type IPendingUserOpStore, type IPointLedger, type IPolicyEngine, type ISessionStore, InMemoryCursorStore, IssuerApiHandlers, type IssuerApiHandlersConfig, type IssuerRegistryRecord, type IssuerService, type IssuerServiceConfig, IssuerStateError, IssuerStateValidator, LockNotFoundError, type LockedMintRequest, type LoginResult, MemorySessionStore, type MemorySessionStoreOptions, type MintEvent, type MintStatusParams, type MintStatusResponse, type MintingStatus, type NativePtQuoterConfig, NonceManager, PAFI_ISSUER_SDK_VERSION, PTClaimError, PTClaimHandler, type PTClaimHandlerConfig, type PTClaimRequest, type PTClaimResponse, PTRedeemError, PTRedeemHandler, type PTRedeemHandlerConfig, type PTRedeemRequest, type PTRedeemResponse, PafiBackendClient, type PafiBackendConfig, PafiBackendError, type PafiBackendErrorCode, type PendingCredit, type PendingUserOpEntry, PendingUserOpNotFoundError, PerpDepositError, PerpDepositHandler, type PerpDepositHandlerConfig, type PerpDepositRequest, type PerpDepositResponse, PointIndexer, type PointIndexerConfig, type PolicyDecision, type PolicyEvalRequest, type PoolsProvider, type PreValidateMintResult, type PrepareBurnDirectParams, type PrepareBurnParams, type PrepareBurnWithSigParams, type PrepareMintParams, type PrepareMobileUserOpParams, type PrepareMobileUserOpResult, type PreparedUserOp, RelayError, type RelayErrorCode, RelayService, type RelayUserOpParams, type RelayUserOpRequest, type RelayUserOpResponse, type RequestPaymasterParams, type RetryConfig, type SerializedUserOpTypedData, type Session, type SponsorshipRequest, type SponsorshipResponse, type SponsorshipTarget, type SponsorshipUserOp, type SubgraphNativeUsdtQuoterConfig, type SubgraphPoolsProviderConfig, SwapError, SwapHandler, type SwapHandlerConfig, type SwapRequest, type SwapResponse, TopUpRedemptionError, TopUpRedemptionHandler, type TopUpRedemptionHandlerConfig, type TopUpRedemptionRequest, type TopUpRedemptionResponse, authenticateRequest, createIssuerService, createNativePtQuoter, createSubgraphNativeUsdtQuoter, createSubgraphPoolsProvider, handleClaimStatus, handleMobilePrepare, handleMobileSubmit, handleRedeemStatus, mergePaymasterFields, prepareMobileUserOp, relayUserOp, requestPaymaster, serializeEntryToJsonRpc, serializeUserOpTypedData };