@pafi-dev/issuer 0.5.34 → 0.5.36

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, ENTRY_POINT_V08, 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
  /**
@@ -1527,6 +1527,548 @@ declare function handleClaimStatus(params: MintStatusParams): Promise<MintStatus
1527
1527
  */
1528
1528
  declare function handleRedeemStatus(params: BurnStatusParams): Promise<BurnStatusResponse>;
1529
1529
 
1530
+ /**
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`.
1536
+ */
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;
1553
+ /**
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.
1565
+ */
1566
+ fallback?: {
1567
+ callData: Hex;
1568
+ callGasLimit: string;
1569
+ verificationGasLimit: string;
1570
+ preVerificationGas: string;
1571
+ userOpHash: Hex;
1572
+ };
1573
+ }
1574
+ /**
1575
+ * Storage backend for pending UserOps in the mobile prepare/submit pattern.
1576
+ *
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
1581
+ *
1582
+ * The default implementation in the gg56 boilerplate uses Redis with
1583
+ * a short TTL matching the MintRequest / BurnRequest deadline.
1584
+ */
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>;
1589
+ }
1590
+
1591
+ /**
1592
+ * Convert a stored `PendingUserOpEntry` (decimal-string fields) plus a
1593
+ * signature into the JSON-RPC wire format for `eth_sendUserOperation`.
1594
+ *
1595
+ * Bridges the gap between the serialized storage format (decimal strings,
1596
+ * safe for JSON/Redis) and `serializeUserOpToJsonRpc` which expects bigints.
1597
+ */
1598
+ declare function serializeEntryToJsonRpc(entry: PendingUserOpEntry, signature: Hex, variant?: "sponsored" | "fallback"): Record<string, string | null>;
1599
+
1600
+ /**
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).
1605
+ */
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
+ };
1621
+ /**
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`.
1625
+ */
1626
+ declare function serializeUserOpTypedData(td: UserOpTypedData): SerializedUserOpTypedData;
1627
+ /**
1628
+ * Merge Pimlico's paymaster-sponsorship response into a UserOp
1629
+ * skeleton, applying only fields that are actually defined.
1630
+ *
1631
+ * `pm_sponsorUserOperation` returns:
1632
+ * - `paymaster` / `paymasterData` — required for the sponsored sig
1633
+ * - `paymasterVerificationGasLimit` / `paymasterPostOpGasLimit`
1634
+ * - **re-estimated** `callGasLimit` / `verificationGasLimit` /
1635
+ * `preVerificationGas` — the paymaster signature is computed over
1636
+ * these new values
1637
+ * - **bundler-required** `maxFeePerGas` / `maxPriorityFeePerGas` —
1638
+ * Base RPC's `eth_feeHistory` underestimates, bundler rejects
1639
+ *
1640
+ * Callers MUST re-merge ALL of these into the userOp BEFORE computing
1641
+ * the EIP-712 userOpHash — otherwise both the paymaster signature
1642
+ * (AA34) and the user signature (AA24, recovered against a different
1643
+ * hash) fail at validation.
1644
+ *
1645
+ * Skips fields that are undefined so legacy paymaster responses
1646
+ * (without re-estimated gas) don't accidentally zero out the original
1647
+ * estimates.
1648
+ */
1649
+ declare function mergePaymasterFields<T extends object>(userOp: T, paymasterFields: {
1650
+ paymaster: Address;
1651
+ paymasterData: Hex;
1652
+ paymasterVerificationGasLimit: bigint;
1653
+ paymasterPostOpGasLimit: bigint;
1654
+ callGasLimit?: bigint;
1655
+ verificationGasLimit?: bigint;
1656
+ preVerificationGas?: bigint;
1657
+ maxFeePerGas?: bigint;
1658
+ maxPriorityFeePerGas?: bigint;
1659
+ } | undefined): T;
1660
+ interface PreparedUserOp {
1661
+ /** The bundler-ready UserOp (with paymaster + Pimlico-quoted gas). */
1662
+ userOp: PartialUserOperation & {
1663
+ maxFeePerGas: bigint;
1664
+ maxPriorityFeePerGas: bigint;
1665
+ paymaster?: Address;
1666
+ paymasterData?: Hex;
1667
+ paymasterVerificationGasLimit?: bigint;
1668
+ paymasterPostOpGasLimit?: bigint;
1669
+ };
1670
+ /** Hex-encoded EIP-712 digest. Equals `EntryPoint.getUserOpHash`. */
1671
+ userOpHash: Hex;
1672
+ /** Typed-data payload — pass directly to `eth_signTypedData_v4`. */
1673
+ typedData: SerializedUserOpTypedData;
1674
+ }
1675
+ interface PrepareMobileUserOpParams {
1676
+ /** Lock id (issuer-generated) keying both store entry + ledger row. */
1677
+ lockId: string;
1678
+ /**
1679
+ * Sponsored variant — built with the PT operator-fee transfer
1680
+ * included. SDK or caller should set `partialUserOp.maxFeePerGas` /
1681
+ * `maxPriorityFeePerGas` from `provider.estimateFeesPerGas()` before
1682
+ * calling — they get overridden by Pimlico's quote anyway, but
1683
+ * they must be valid bigints for the merge.
1684
+ */
1685
+ partialUserOp: PartialUserOperation & {
1686
+ maxFeePerGas: bigint;
1687
+ maxPriorityFeePerGas: bigint;
1688
+ };
1689
+ /**
1690
+ * Optional fee-stripped fallback variant — built with `gasFeePt: 0n`
1691
+ * (no PT operator-fee transfer). Submitted when paymaster refuses
1692
+ * sponsorship and the user pays ETH gas directly.
1693
+ */
1694
+ partialUserOpFallback?: PartialUserOperation & {
1695
+ maxFeePerGas?: bigint;
1696
+ maxPriorityFeePerGas?: bigint;
1697
+ };
1698
+ /** Paymaster sponsorship response, or `undefined` if PAFI declined. */
1699
+ paymasterFields?: {
1700
+ paymaster: Address;
1701
+ paymasterData: Hex;
1702
+ paymasterVerificationGasLimit: bigint;
1703
+ paymasterPostOpGasLimit: bigint;
1704
+ callGasLimit?: bigint;
1705
+ verificationGasLimit?: bigint;
1706
+ preVerificationGas?: bigint;
1707
+ maxFeePerGas?: bigint;
1708
+ maxPriorityFeePerGas?: bigint;
1709
+ };
1710
+ chainId: number;
1711
+ /** Pending-userop store implementation (Redis/Postgres/Memory). */
1712
+ store: IPendingUserOpStore;
1713
+ /** TTL the store entry should outlive — typically the MintRequest deadline. */
1714
+ ttlSeconds: number;
1715
+ }
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
+ type DecodedCall$2 = ReturnType<typeof decodeBatchExecuteCalls>[number];
1851
+
1852
+ /**
1853
+ * Structural shape — accepts both the raw `IssuerStateValidator` from
1854
+ * `@pafi-dev/issuer/issuer-state` and any host-framework wrapper (e.g.
1855
+ * a NestJS Injectable that delegates to it). Only `preValidateMint`
1856
+ * is needed on this surface.
1857
+ */
1858
+ interface IssuerStateValidatorLike {
1859
+ preValidateMint(pointToken: Address, amount: bigint): Promise<unknown>;
1860
+ }
1861
+ /**
1862
+ * v1.4 sig-gated mint handler — mirrors `PTRedeemHandler` on the mint side.
1863
+ *
1864
+ * Pre-validates against IssuerRegistry + on-chain totalSupply, locks the
1865
+ * off-chain balance, builds the sponsored UserOp (mint + PT fee
1866
+ * transfer) plus an optional fallback variant (mint only — for
1867
+ * paymaster-refused fallback).
1868
+ *
1869
+ * Caller fetches AA + mintRequest nonces (so issuers can plug in their
1870
+ * own composer — gg56 uses a timestamp-key 2D nonce). Caller layers
1871
+ * paymaster sponsorship + sponsorAuth on top of the returned UserOps.
1872
+ */
1873
+ declare class PTClaimError extends Error {
1874
+ code: "INVALID_AMOUNT" | "VALIDATION_FAILED" | "BUILD_FAILED";
1875
+ details?: Record<string, unknown> | undefined;
1876
+ constructor(code: "INVALID_AMOUNT" | "VALIDATION_FAILED" | "BUILD_FAILED", message: string, details?: Record<string, unknown> | undefined);
1877
+ }
1878
+ interface PTClaimHandlerConfig {
1879
+ ledger: IPointLedger;
1880
+ relayService: RelayService;
1881
+ provider: PublicClient;
1882
+ /** Issuer minter signer wallet — passed through to RelayService.prepareMint. */
1883
+ issuerSignerWallet: WalletClient;
1884
+ /**
1885
+ * EIP-712 domain `name` for `MintRequest`. Typically the PointToken
1886
+ * ERC-20 name. RelayService will set chainId + verifyingContract from
1887
+ * the request.
1888
+ */
1889
+ pointTokenDomainName: string;
1890
+ /** Optional — when wired, used to estimate the PT gas-reimbursement fee. */
1891
+ feeService?: FeeManager;
1892
+ /** Optional — pre-validates issuer status + cap before locking balance. */
1893
+ issuerStateValidator?: IssuerStateValidatorLike;
1894
+ /** How long the off-chain balance lock survives if the mint never lands. Default 15 min. */
1895
+ lockDurationMs?: number;
1896
+ /** How far ahead of `now` to set the MintRequest deadline. Default 15 min. */
1897
+ signatureDeadlineSeconds?: number;
1898
+ now?: () => number;
1899
+ }
1900
+ interface PTClaimRequest {
1901
+ authenticatedAddress: Address;
1902
+ userAddress: Address;
1903
+ amount: bigint;
1904
+ pointTokenAddress: Address;
1905
+ chainId: number;
1906
+ /** ERC-4337 account nonce for the user's EOA. */
1907
+ aaNonce: bigint;
1908
+ /** Current `mintRequestNonces[userAddress]` from PointToken. */
1909
+ mintRequestNonce: bigint;
1910
+ }
1911
+ interface PTClaimResponse {
1912
+ /** Sponsored UserOp — mint + PT fee transfer (when feeAmount > 0). */
1913
+ userOp: PartialUserOperation;
1914
+ /**
1915
+ * Fallback UserOp — mint only, no PT fee transfer. Present only when
1916
+ * `feeAmount > 0`. User pays gas in ETH directly.
1917
+ */
1918
+ fallback?: PartialUserOperation;
1919
+ lockId: string;
1920
+ feeAmount: bigint;
1921
+ signatureDeadline: bigint;
1922
+ expiresInSeconds: number;
1923
+ /** Decoded calls for the sponsored UserOp (convenience for FE-submit path). */
1924
+ calls: DecodedCall$2[];
1925
+ /** Decoded calls for the fallback UserOp (when present). */
1926
+ callsFallback?: DecodedCall$2[];
1927
+ }
1928
+ declare class PTClaimHandler {
1929
+ private readonly cfg;
1930
+ constructor(config: PTClaimHandlerConfig);
1931
+ handle(request: PTClaimRequest): Promise<PTClaimResponse>;
1932
+ }
1933
+
1934
+ type DecodedCall$1 = ReturnType<typeof decodeBatchExecuteCalls>[number];
1935
+
1936
+ /**
1937
+ * PT → USDT swap handler.
1938
+ *
1939
+ * Quotes via V4 on-chain Quoter (`findBestQuote`), applies slippage,
1940
+ * computes the PT gas-reimbursement fee from `FeeManager`, and builds
1941
+ * two UserOps:
1942
+ *
1943
+ * - **sponsored** — swap (amountIn − fee) + transfer(fee, PAFI). User
1944
+ * holds exactly `amountIn` PT.
1945
+ * - **fallback** — swap full `amountIn`, no PT fee transfer. Built
1946
+ * only when `feeAmount > 0`. User pays gas in ETH directly.
1947
+ *
1948
+ * Re-quotes the sponsored path on `(amountIn − feeAmount)` because the
1949
+ * sponsored path actually swaps that much; the fallback uses the
1950
+ * original quote.
1951
+ *
1952
+ * Caller (controller) layers `sponsorAuth` on top of the returned
1953
+ * userOp. No off-chain ledger state is touched — swap is purely
1954
+ * on-chain.
1955
+ */
1956
+ declare class SwapError extends Error {
1957
+ code: "QUOTE_UNAVAILABLE" | "FEE_EXCEEDS_AMOUNT" | "INVALID_AMOUNT";
1958
+ constructor(code: "QUOTE_UNAVAILABLE" | "FEE_EXCEEDS_AMOUNT" | "INVALID_AMOUNT", message: string);
1959
+ }
1960
+ interface SwapHandlerConfig {
1961
+ provider: PublicClient;
1962
+ poolsProvider: PoolsProvider;
1963
+ /** Optional — when wired, used to estimate the PT gas-reimbursement fee. */
1964
+ feeService?: FeeManager;
1965
+ /**
1966
+ * Default slippage applied to the V4 quote when the request omits
1967
+ * `slippageBps`. 50 bps = 0.5%.
1968
+ */
1969
+ defaultSlippageBps?: number;
1970
+ /** Default deadline window in seconds applied when not passed in request. Default 300. */
1971
+ defaultSwapDeadlineSeconds?: number;
1972
+ now?: () => number;
1973
+ }
1974
+ interface SwapRequest {
1975
+ userAddress: Address;
1976
+ chainId: number;
1977
+ pointTokenAddress: Address;
1978
+ amountIn: bigint;
1979
+ /** ERC-4337 account nonce for the user's EOA. */
1980
+ aaNonce: bigint;
1981
+ /** Optional override; falls back to `defaultSlippageBps`. */
1982
+ slippageBps?: number;
1983
+ /** Optional override; falls back to `now() + defaultSwapDeadlineSeconds`. */
1984
+ deadline?: bigint;
1985
+ }
1986
+ interface SwapResponse {
1987
+ /** Sponsored UserOp — swap (amountIn − fee) + PT.transfer(fee). */
1988
+ userOp: PartialUserOperation;
1989
+ /** Fallback UserOp — swap full amountIn, no PT fee transfer. Present only when fee > 0. */
1990
+ fallback?: PartialUserOperation;
1991
+ feeAmount: bigint;
1992
+ /** Quote for the sponsored path (after fee deduction). */
1993
+ estimatedUsdtOut: bigint;
1994
+ minAmountOut: bigint;
1995
+ /** Quote for the fallback path (full amountIn). Present only when fee > 0. */
1996
+ estimatedUsdtOutFallback?: bigint;
1997
+ minAmountOutFallback?: bigint;
1998
+ deadline: bigint;
1999
+ calls: DecodedCall$1[];
2000
+ callsFallback?: DecodedCall$1[];
2001
+ }
2002
+ declare class SwapHandler {
2003
+ private readonly cfg;
2004
+ constructor(config: SwapHandlerConfig);
2005
+ handle(request: SwapRequest): Promise<SwapResponse>;
2006
+ }
2007
+
2008
+ type DecodedCall = ReturnType<typeof decodeBatchExecuteCalls>[number];
2009
+
2010
+ /**
2011
+ * Orderly perp-deposit handler — builds the sponsored + fallback
2012
+ * UserOps for the PAFI Relay path.
2013
+ *
2014
+ * Resolves USDC + verifies the broker is whitelisted on the Vault,
2015
+ * quotes the Relay's USDC fee (covers LayerZero msg.value out of the
2016
+ * Relay's ETH reserve), then builds two UserOps:
2017
+ *
2018
+ * - **sponsored** — PT.transfer(fee, PAFI) + USDC.approve(relay,
2019
+ * total) + Relay.deposit(req). User reimburses gas in PT.
2020
+ * - **fallback** — USDC.approve + Relay.deposit only. User pays
2021
+ * ERC-4337 gas in ETH. Built only when `feeAmount > 0`.
2022
+ *
2023
+ * `maxFee` is set to `quote × 1.5` — slippage cap on the Relay's
2024
+ * USD-pricing during the inclusion window. If the actual fee at
2025
+ * execution exceeds maxFee the Relay reverts (`FeeExceedsMax`).
2026
+ */
2027
+ declare class PerpDepositError extends Error {
2028
+ code: "PERP_DEPOSIT_UNAVAILABLE" | "BROKER_NOT_WHITELISTED" | "RELAY_FEE_EXCEEDS_AMOUNT" | "FEE_EXCEEDS_AMOUNT" | "INVALID_AMOUNT";
2029
+ constructor(code: "PERP_DEPOSIT_UNAVAILABLE" | "BROKER_NOT_WHITELISTED" | "RELAY_FEE_EXCEEDS_AMOUNT" | "FEE_EXCEEDS_AMOUNT" | "INVALID_AMOUNT", message: string);
2030
+ }
2031
+ interface PerpDepositHandlerConfig {
2032
+ provider: PublicClient;
2033
+ /** Optional — when wired, used to estimate the PT gas-reimbursement fee. */
2034
+ feeService?: FeeManager;
2035
+ /** PointToken address used for the sponsored PT.transfer. */
2036
+ pointTokenAddress: Address;
2037
+ /**
2038
+ * Slippage premium applied on top of the Relay quote when computing
2039
+ * `maxFee`. Default 50% (factor 150). The Relay reverts if actual fee
2040
+ * exceeds `maxFee` so a generous cap reduces re-quote churn.
2041
+ */
2042
+ maxFeePremiumBps?: number;
2043
+ }
2044
+ interface PerpDepositRequest {
2045
+ userAddress: Address;
2046
+ chainId: number;
2047
+ amount: bigint;
2048
+ brokerId: keyof typeof BROKER_HASHES;
2049
+ /** ERC-4337 account nonce. */
2050
+ aaNonce: bigint;
2051
+ }
2052
+ interface PerpDepositResponse {
2053
+ userOp: PartialUserOperation;
2054
+ fallback?: PartialUserOperation;
2055
+ feeAmount: bigint;
2056
+ relayTokenFee: bigint;
2057
+ maxFee: bigint;
2058
+ netDeposit: bigint;
2059
+ accountId: `0x${string}`;
2060
+ brokerHash: `0x${string}`;
2061
+ usdcAddress: Address;
2062
+ relayAddress: Address;
2063
+ calls: DecodedCall[];
2064
+ callsFallback?: DecodedCall[];
2065
+ }
2066
+ declare class PerpDepositHandler {
2067
+ private readonly cfg;
2068
+ constructor(config: PerpDepositHandlerConfig);
2069
+ handle(request: PerpDepositRequest): Promise<PerpDepositResponse>;
2070
+ }
2071
+
1530
2072
  /**
1531
2073
  * Config for `createSubgraphPoolsProvider`.
1532
2074
  */
@@ -1958,224 +2500,6 @@ interface IssuerService {
1958
2500
  */
1959
2501
  declare function createIssuerService(config: IssuerServiceConfig): IssuerService;
1960
2502
 
1961
- /**
1962
- * A pending UserOp serialized for persistent storage (Redis, Postgres, memory).
1963
- *
1964
- * All bigint fields are stored as decimal strings so the entry can be
1965
- * JSON-serialized without precision loss. Convert back to bigint before
1966
- * calling `computeUserOpHash` or `serializeUserOpToJsonRpc`.
1967
- */
1968
- interface PendingUserOpEntry {
1969
- sender: Address;
1970
- nonce: string;
1971
- callData: Hex;
1972
- callGasLimit: string;
1973
- verificationGasLimit: string;
1974
- preVerificationGas: string;
1975
- maxFeePerGas: string;
1976
- maxPriorityFeePerGas: string;
1977
- paymaster?: Address;
1978
- paymasterVerificationGasLimit?: string;
1979
- paymasterPostOpGasLimit?: string;
1980
- paymasterData?: Hex;
1981
- chainId: number;
1982
- /** Hex-encoded userOpHash — the value the user signed via personal_sign. */
1983
- userOpHash: Hex;
1984
- /**
1985
- * Fee-stripped fallback variant. Set by `/claim/prepare` and
1986
- * `/redeem/prepare` when a PT operator fee is configured AND the
1987
- * paymaster sponsorship attached successfully — i.e. the user might
1988
- * still want to submit without paymaster (paying ETH gas), and in
1989
- * that case shouldn't be charged the PT fee. `/claim/submit` reads
1990
- * this branch when its request body specifies
1991
- * `variant: "fallback"`.
1992
- *
1993
- * Has a different `callData` (no PT.transfer prepended) and
1994
- * therefore a different `userOpHash`. Paymaster fields are NOT
1995
- * present — the fallback is by definition unsponsored.
1996
- */
1997
- fallback?: {
1998
- callData: Hex;
1999
- callGasLimit: string;
2000
- verificationGasLimit: string;
2001
- preVerificationGas: string;
2002
- userOpHash: Hex;
2003
- };
2004
- }
2005
- /**
2006
- * Storage backend for pending UserOps in the mobile prepare/submit pattern.
2007
- *
2008
- * Implement this interface and wire it into your issuer backend:
2009
- * - `save()` — called by `POST /claim/prepare` and `POST /redeem/prepare`
2010
- * - `get()` — called by `POST /claim/submit` and `POST /redeem/submit`
2011
- * - `delete()` — called after successful submit or explicit cancellation
2012
- *
2013
- * The default implementation in the gg56 boilerplate uses Redis with
2014
- * a short TTL matching the MintRequest / BurnRequest deadline.
2015
- */
2016
- interface IPendingUserOpStore {
2017
- save(lockId: string, entry: PendingUserOpEntry, ttlSeconds: number): Promise<void>;
2018
- get(lockId: string): Promise<PendingUserOpEntry | null>;
2019
- delete(lockId: string): Promise<void>;
2020
- }
2021
-
2022
- /**
2023
- * Convert a stored `PendingUserOpEntry` (decimal-string fields) plus a
2024
- * signature into the JSON-RPC wire format for `eth_sendUserOperation`.
2025
- *
2026
- * Bridges the gap between the serialized storage format (decimal strings,
2027
- * safe for JSON/Redis) and `serializeUserOpToJsonRpc` which expects bigints.
2028
- */
2029
- declare function serializeEntryToJsonRpc(entry: PendingUserOpEntry, signature: Hex, variant?: "sponsored" | "fallback"): Record<string, string | null>;
2030
-
2031
- /**
2032
- * Re-shape `UserOpTypedData` so all `bigint` fields become hex strings —
2033
- * required for JSON transport over HTTP. Mirrors the inverse of what
2034
- * `walletClient.signTypedData` accepts on the client (it auto-coerces hex
2035
- * strings back to bigints for `uint256` fields).
2036
- */
2037
- type SerializedUserOpTypedData = {
2038
- domain: UserOpTypedData["domain"];
2039
- types: UserOpTypedData["types"];
2040
- primaryType: UserOpTypedData["primaryType"];
2041
- message: {
2042
- sender: Address;
2043
- nonce: Hex;
2044
- initCode: Hex;
2045
- callData: Hex;
2046
- accountGasLimits: Hex;
2047
- preVerificationGas: Hex;
2048
- gasFees: Hex;
2049
- paymasterAndData: Hex;
2050
- };
2051
- };
2052
- /**
2053
- * Convert a `UserOpTypedData` payload into the JSON-safe wire form
2054
- * (bigint → hex string). The mobile client passes this directly to
2055
- * `eth_signTypedData_v4` / viem's `signTypedData`.
2056
- */
2057
- declare function serializeUserOpTypedData(td: UserOpTypedData): SerializedUserOpTypedData;
2058
- /**
2059
- * Merge Pimlico's paymaster-sponsorship response into a UserOp
2060
- * skeleton, applying only fields that are actually defined.
2061
- *
2062
- * `pm_sponsorUserOperation` returns:
2063
- * - `paymaster` / `paymasterData` — required for the sponsored sig
2064
- * - `paymasterVerificationGasLimit` / `paymasterPostOpGasLimit`
2065
- * - **re-estimated** `callGasLimit` / `verificationGasLimit` /
2066
- * `preVerificationGas` — the paymaster signature is computed over
2067
- * these new values
2068
- * - **bundler-required** `maxFeePerGas` / `maxPriorityFeePerGas` —
2069
- * Base RPC's `eth_feeHistory` underestimates, bundler rejects
2070
- *
2071
- * Callers MUST re-merge ALL of these into the userOp BEFORE computing
2072
- * the EIP-712 userOpHash — otherwise both the paymaster signature
2073
- * (AA34) and the user signature (AA24, recovered against a different
2074
- * hash) fail at validation.
2075
- *
2076
- * Skips fields that are undefined so legacy paymaster responses
2077
- * (without re-estimated gas) don't accidentally zero out the original
2078
- * estimates.
2079
- */
2080
- declare function mergePaymasterFields<T extends object>(userOp: T, paymasterFields: {
2081
- paymaster: Address;
2082
- paymasterData: Hex;
2083
- paymasterVerificationGasLimit: bigint;
2084
- paymasterPostOpGasLimit: bigint;
2085
- callGasLimit?: bigint;
2086
- verificationGasLimit?: bigint;
2087
- preVerificationGas?: bigint;
2088
- maxFeePerGas?: bigint;
2089
- maxPriorityFeePerGas?: bigint;
2090
- } | undefined): T;
2091
- interface PreparedUserOp {
2092
- /** The bundler-ready UserOp (with paymaster + Pimlico-quoted gas). */
2093
- userOp: PartialUserOperation & {
2094
- maxFeePerGas: bigint;
2095
- maxPriorityFeePerGas: bigint;
2096
- paymaster?: Address;
2097
- paymasterData?: Hex;
2098
- paymasterVerificationGasLimit?: bigint;
2099
- paymasterPostOpGasLimit?: bigint;
2100
- };
2101
- /** Hex-encoded EIP-712 digest. Equals `EntryPoint.getUserOpHash`. */
2102
- userOpHash: Hex;
2103
- /** Typed-data payload — pass directly to `eth_signTypedData_v4`. */
2104
- typedData: SerializedUserOpTypedData;
2105
- }
2106
- interface PrepareMobileUserOpParams {
2107
- /** Lock id (issuer-generated) keying both store entry + ledger row. */
2108
- lockId: string;
2109
- /**
2110
- * Sponsored variant — built with the PT operator-fee transfer
2111
- * included. SDK or caller should set `partialUserOp.maxFeePerGas` /
2112
- * `maxPriorityFeePerGas` from `provider.estimateFeesPerGas()` before
2113
- * calling — they get overridden by Pimlico's quote anyway, but
2114
- * they must be valid bigints for the merge.
2115
- */
2116
- partialUserOp: PartialUserOperation & {
2117
- maxFeePerGas: bigint;
2118
- maxPriorityFeePerGas: bigint;
2119
- };
2120
- /**
2121
- * Optional fee-stripped fallback variant — built with `gasFeePt: 0n`
2122
- * (no PT operator-fee transfer). Submitted when paymaster refuses
2123
- * sponsorship and the user pays ETH gas directly.
2124
- */
2125
- partialUserOpFallback?: PartialUserOperation & {
2126
- maxFeePerGas?: bigint;
2127
- maxPriorityFeePerGas?: bigint;
2128
- };
2129
- /** Paymaster sponsorship response, or `undefined` if PAFI declined. */
2130
- paymasterFields?: {
2131
- paymaster: Address;
2132
- paymasterData: Hex;
2133
- paymasterVerificationGasLimit: bigint;
2134
- paymasterPostOpGasLimit: bigint;
2135
- callGasLimit?: bigint;
2136
- verificationGasLimit?: bigint;
2137
- preVerificationGas?: bigint;
2138
- maxFeePerGas?: bigint;
2139
- maxPriorityFeePerGas?: bigint;
2140
- };
2141
- chainId: number;
2142
- /** Pending-userop store implementation (Redis/Postgres/Memory). */
2143
- store: IPendingUserOpStore;
2144
- /** TTL the store entry should outlive — typically the MintRequest deadline. */
2145
- ttlSeconds: number;
2146
- }
2147
- interface PrepareMobileUserOpResult {
2148
- sponsored: PreparedUserOp;
2149
- /**
2150
- * Set when `partialUserOpFallback` was supplied AND the PT fee was
2151
- * non-zero (i.e. sponsored ≠ fallback). Mobile client picks which
2152
- * variant to sign + submit; SDK's `serializeEntryToJsonRpc` reads
2153
- * the `variant` flag to dispatch.
2154
- */
2155
- fallback?: PreparedUserOp;
2156
- /** What got persisted into the pending-userop store. */
2157
- entry: PendingUserOpEntry;
2158
- }
2159
- /**
2160
- * Build the sponsored UserOp + (optional) fee-stripped fallback for the
2161
- * mobile prepare/submit flow.
2162
- *
2163
- * What this does, end-to-end:
2164
- * 1. Merge Pimlico's paymaster sponsorship + re-quoted gas into the
2165
- * caller's `partialUserOp` skeleton.
2166
- * 2. Compute the EIP-712 userOpHash + the typed-data payload (in
2167
- * JSON-safe form for HTTP transport).
2168
- * 3. (Optional) Repeat for the `partialUserOpFallback` skeleton with
2169
- * no paymaster fields — produces a separate hash + typed-data so
2170
- * the client can re-sign if it falls back.
2171
- * 4. Persist a single store entry containing BOTH callData variants
2172
- * keyed by lockId. `serializeEntryToJsonRpc` reads the `variant`
2173
- * param at submit time.
2174
- *
2175
- * Replaces ~100 LoC of glue per scenario in issuer controllers.
2176
- */
2177
- declare function prepareMobileUserOp(params: PrepareMobileUserOpParams): Promise<PrepareMobileUserOpResult>;
2178
-
2179
2503
  interface IssuerRegistryRecord {
2180
2504
  issuerAddress: Address;
2181
2505
  signerAddress: Address;
@@ -2269,4 +2593,4 @@ declare class IssuerStateValidator {
2269
2593
  /** SDK package version — bumped on every release */
2270
2594
  declare const PAFI_ISSUER_SDK_VERSION = "0.4.0";
2271
2595
 
2272
- 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 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 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, TopUpRedemptionError, TopUpRedemptionHandler, type TopUpRedemptionHandlerConfig, type TopUpRedemptionRequest, type TopUpRedemptionResponse, authenticateRequest, createIssuerService, createNativePtQuoter, createSubgraphNativeUsdtQuoter, createSubgraphPoolsProvider, handleClaimStatus, handleRedeemStatus, mergePaymasterFields, prepareMobileUserOp, relayUserOp, requestPaymaster, serializeEntryToJsonRpc, serializeUserOpTypedData };
2596
+ 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 };