@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.js CHANGED
@@ -1698,6 +1698,683 @@ async function handleRedeemStatus(params) {
1698
1698
  };
1699
1699
  }
1700
1700
 
1701
+ // src/api/mobileHandlers.ts
1702
+ import {
1703
+ ENTRY_POINT_V08,
1704
+ parseEip7702DelegatedAddress
1705
+ } from "@pafi-dev/core";
1706
+
1707
+ // src/userop-store/serialize.ts
1708
+ import { serializeUserOpToJsonRpc } from "@pafi-dev/core";
1709
+ function serializeEntryToJsonRpc(entry, signature, variant = "sponsored") {
1710
+ if (variant === "fallback") {
1711
+ if (!entry.fallback) {
1712
+ throw new Error(
1713
+ "serializeEntryToJsonRpc: variant=fallback requested but the stored entry has no `fallback` branch \u2014 caller should resubmit with variant='sponsored' or re-prepare with a fee configured."
1714
+ );
1715
+ }
1716
+ return serializeUserOpToJsonRpc(
1717
+ {
1718
+ sender: entry.sender,
1719
+ nonce: BigInt(entry.nonce),
1720
+ callData: entry.fallback.callData,
1721
+ callGasLimit: BigInt(entry.fallback.callGasLimit),
1722
+ verificationGasLimit: BigInt(entry.fallback.verificationGasLimit),
1723
+ preVerificationGas: BigInt(entry.fallback.preVerificationGas),
1724
+ maxFeePerGas: BigInt(entry.maxFeePerGas),
1725
+ maxPriorityFeePerGas: BigInt(entry.maxPriorityFeePerGas)
1726
+ // intentionally no paymaster — user pays ETH gas
1727
+ },
1728
+ signature
1729
+ );
1730
+ }
1731
+ return serializeUserOpToJsonRpc(
1732
+ {
1733
+ sender: entry.sender,
1734
+ nonce: BigInt(entry.nonce),
1735
+ callData: entry.callData,
1736
+ callGasLimit: BigInt(entry.callGasLimit),
1737
+ verificationGasLimit: BigInt(entry.verificationGasLimit),
1738
+ preVerificationGas: BigInt(entry.preVerificationGas),
1739
+ maxFeePerGas: BigInt(entry.maxFeePerGas),
1740
+ maxPriorityFeePerGas: BigInt(entry.maxPriorityFeePerGas),
1741
+ paymaster: entry.paymaster,
1742
+ paymasterVerificationGasLimit: entry.paymasterVerificationGasLimit != null ? BigInt(entry.paymasterVerificationGasLimit) : void 0,
1743
+ paymasterPostOpGasLimit: entry.paymasterPostOpGasLimit != null ? BigInt(entry.paymasterPostOpGasLimit) : void 0,
1744
+ paymasterData: entry.paymasterData
1745
+ },
1746
+ signature
1747
+ );
1748
+ }
1749
+
1750
+ // src/userop-store/prepareUserOp.ts
1751
+ import {
1752
+ buildUserOpTypedData,
1753
+ computeUserOpHash
1754
+ } from "@pafi-dev/core";
1755
+ function serializeUserOpTypedData(td) {
1756
+ return {
1757
+ domain: td.domain,
1758
+ types: td.types,
1759
+ primaryType: td.primaryType,
1760
+ message: {
1761
+ sender: td.message.sender,
1762
+ nonce: `0x${td.message.nonce.toString(16)}`,
1763
+ initCode: td.message.initCode,
1764
+ callData: td.message.callData,
1765
+ accountGasLimits: td.message.accountGasLimits,
1766
+ preVerificationGas: `0x${td.message.preVerificationGas.toString(
1767
+ 16
1768
+ )}`,
1769
+ gasFees: td.message.gasFees,
1770
+ paymasterAndData: td.message.paymasterAndData
1771
+ }
1772
+ };
1773
+ }
1774
+ function mergePaymasterFields(userOp, paymasterFields) {
1775
+ if (!paymasterFields) return userOp;
1776
+ const merged = {
1777
+ ...userOp
1778
+ };
1779
+ for (const [k, v] of Object.entries(paymasterFields)) {
1780
+ if (v !== void 0) merged[k] = v;
1781
+ }
1782
+ return merged;
1783
+ }
1784
+ async function prepareMobileUserOp(params) {
1785
+ const userOp = mergePaymasterFields(
1786
+ params.partialUserOp,
1787
+ params.paymasterFields
1788
+ );
1789
+ const userOpHash = computeUserOpHash(userOp, params.chainId);
1790
+ const typedData = serializeUserOpTypedData(
1791
+ buildUserOpTypedData(userOp, params.chainId)
1792
+ );
1793
+ let fallback;
1794
+ let fallbackEntry;
1795
+ if (params.partialUserOpFallback) {
1796
+ const fallbackUserOp = {
1797
+ ...params.partialUserOpFallback,
1798
+ maxFeePerGas: userOp.maxFeePerGas,
1799
+ maxPriorityFeePerGas: userOp.maxPriorityFeePerGas
1800
+ };
1801
+ const fallbackHash = computeUserOpHash(fallbackUserOp, params.chainId);
1802
+ const fallbackTypedData = serializeUserOpTypedData(
1803
+ buildUserOpTypedData(fallbackUserOp, params.chainId)
1804
+ );
1805
+ fallback = {
1806
+ userOp: fallbackUserOp,
1807
+ userOpHash: fallbackHash,
1808
+ typedData: fallbackTypedData
1809
+ };
1810
+ fallbackEntry = {
1811
+ callData: fallbackUserOp.callData,
1812
+ callGasLimit: fallbackUserOp.callGasLimit.toString(),
1813
+ verificationGasLimit: fallbackUserOp.verificationGasLimit.toString(),
1814
+ preVerificationGas: fallbackUserOp.preVerificationGas.toString(),
1815
+ userOpHash: fallbackHash
1816
+ };
1817
+ }
1818
+ const entry = {
1819
+ sender: userOp.sender,
1820
+ nonce: userOp.nonce.toString(),
1821
+ callData: userOp.callData,
1822
+ callGasLimit: userOp.callGasLimit.toString(),
1823
+ verificationGasLimit: userOp.verificationGasLimit.toString(),
1824
+ preVerificationGas: userOp.preVerificationGas.toString(),
1825
+ maxFeePerGas: userOp.maxFeePerGas.toString(),
1826
+ maxPriorityFeePerGas: userOp.maxPriorityFeePerGas.toString(),
1827
+ paymaster: userOp.paymaster,
1828
+ paymasterVerificationGasLimit: userOp.paymasterVerificationGasLimit?.toString(),
1829
+ paymasterPostOpGasLimit: userOp.paymasterPostOpGasLimit?.toString(),
1830
+ paymasterData: userOp.paymasterData,
1831
+ chainId: params.chainId,
1832
+ userOpHash,
1833
+ fallback: fallbackEntry
1834
+ };
1835
+ await params.store.save(params.lockId, entry, params.ttlSeconds);
1836
+ return {
1837
+ sponsored: { userOp, userOpHash, typedData },
1838
+ fallback,
1839
+ entry
1840
+ };
1841
+ }
1842
+
1843
+ // src/pafi-backend/helpers.ts
1844
+ var BundlerNotConfiguredError = class extends Error {
1845
+ code = "BUNDLER_NOT_CONFIGURED";
1846
+ constructor() {
1847
+ super(
1848
+ "PAFI backend client not configured \u2014 set PAFI_BACKEND_URL, PAFI_ISSUER_ID, PAFI_API_KEY to enable mobile submit."
1849
+ );
1850
+ this.name = "BundlerNotConfiguredError";
1851
+ }
1852
+ };
1853
+ var BundlerRejectedError = class extends Error {
1854
+ code = "BUNDLER_REJECTED";
1855
+ cause;
1856
+ constructor(message, cause) {
1857
+ super(message);
1858
+ this.name = "BundlerRejectedError";
1859
+ this.cause = cause;
1860
+ }
1861
+ };
1862
+ async function requestPaymaster(params) {
1863
+ if (!params.client) return void 0;
1864
+ const fn = params.functionName ?? defaultFunctionForScenario(params.scenario);
1865
+ try {
1866
+ return await params.client.requestSponsorship({
1867
+ chainId: params.chainId,
1868
+ scenario: params.scenario,
1869
+ userOp: params.userOp,
1870
+ target: {
1871
+ contract: params.pointTokenAddress,
1872
+ function: fn,
1873
+ pointToken: params.pointTokenAddress
1874
+ }
1875
+ });
1876
+ } catch (err) {
1877
+ const msg = err instanceof Error ? err.message : String(err);
1878
+ params.onWarning?.(`Paymaster sponsorship declined: ${msg}`);
1879
+ return void 0;
1880
+ }
1881
+ }
1882
+ function defaultFunctionForScenario(scenario) {
1883
+ switch (scenario) {
1884
+ case "mint":
1885
+ return "mint";
1886
+ case "burn":
1887
+ return "burn";
1888
+ case "swap":
1889
+ return "swap";
1890
+ case "perp-deposit":
1891
+ return "deposit";
1892
+ default:
1893
+ return scenario;
1894
+ }
1895
+ }
1896
+ async function relayUserOp(params) {
1897
+ if (!params.client) {
1898
+ throw new BundlerNotConfiguredError();
1899
+ }
1900
+ try {
1901
+ const result = await params.client.relayUserOperation({
1902
+ userOp: params.userOp,
1903
+ entryPoint: params.entryPoint,
1904
+ eip7702Auth: params.eip7702Auth
1905
+ });
1906
+ return { userOpHash: result.userOpHash };
1907
+ } catch (err) {
1908
+ const msg = err instanceof Error ? err.message : String(err);
1909
+ throw new BundlerRejectedError(msg, err);
1910
+ }
1911
+ }
1912
+
1913
+ // src/api/mobileHandlers.ts
1914
+ var PendingUserOpNotFoundError = class extends Error {
1915
+ code = "PENDING_USEROP_NOT_FOUND";
1916
+ constructor(lockId) {
1917
+ super(
1918
+ `No pending UserOp found for lockId ${lockId} \u2014 it may have expired or already been submitted.`
1919
+ );
1920
+ this.name = "PendingUserOpNotFoundError";
1921
+ }
1922
+ };
1923
+ async function handleMobilePrepare(params) {
1924
+ const [fees, userCode] = await Promise.all([
1925
+ params.provider.estimateFeesPerGas(),
1926
+ params.provider.getCode({ address: params.userAddress })
1927
+ ]);
1928
+ const needsDelegation = parseEip7702DelegatedAddress(userCode) === null;
1929
+ const sponsoredOp = {
1930
+ ...params.partialUserOp,
1931
+ maxFeePerGas: fees.maxFeePerGas ?? params.partialUserOp.maxFeePerGas ?? 0n,
1932
+ maxPriorityFeePerGas: fees.maxPriorityFeePerGas ?? params.partialUserOp.maxPriorityFeePerGas ?? 0n
1933
+ };
1934
+ const paymasterFields = await requestPaymaster({
1935
+ client: params.pafiBackendClient,
1936
+ chainId: params.chainId,
1937
+ scenario: params.scenario,
1938
+ userOp: sponsoredOp,
1939
+ pointTokenAddress: params.pointTokenAddress,
1940
+ onWarning: params.onWarning
1941
+ });
1942
+ const prepared = await prepareMobileUserOp({
1943
+ lockId: params.lockId,
1944
+ partialUserOp: sponsoredOp,
1945
+ partialUserOpFallback: params.partialUserOpFallback,
1946
+ paymasterFields,
1947
+ chainId: params.chainId,
1948
+ store: params.store,
1949
+ ttlSeconds: params.ttlSeconds
1950
+ });
1951
+ return {
1952
+ ...prepared,
1953
+ isSponsored: !!paymasterFields,
1954
+ needsDelegation
1955
+ };
1956
+ }
1957
+ async function handleMobileSubmit(params) {
1958
+ const entry = await params.store.get(params.lockId);
1959
+ if (!entry) {
1960
+ throw new PendingUserOpNotFoundError(params.lockId);
1961
+ }
1962
+ const variant = params.variant ?? "sponsored";
1963
+ const userOpJson = serializeEntryToJsonRpc(entry, params.signature, variant);
1964
+ const result = await relayUserOp({
1965
+ client: params.pafiBackendClient,
1966
+ userOp: userOpJson,
1967
+ entryPoint: params.entryPoint ?? ENTRY_POINT_V08
1968
+ });
1969
+ await params.bindUserOpHash(params.lockId, result.userOpHash);
1970
+ await params.store.delete(params.lockId);
1971
+ return { userOpHash: result.userOpHash };
1972
+ }
1973
+
1974
+ // src/api/handlers/ptClaimHandler.ts
1975
+ import { getAddress as getAddress8 } from "viem";
1976
+ import {
1977
+ decodeBatchExecuteCalls,
1978
+ getContractAddresses as getContractAddresses2
1979
+ } from "@pafi-dev/core";
1980
+
1981
+ // src/issuer-state/types.ts
1982
+ var IssuerStateError = class extends Error {
1983
+ constructor(code, message, details) {
1984
+ super(message);
1985
+ this.code = code;
1986
+ this.details = details;
1987
+ this.name = "IssuerStateError";
1988
+ }
1989
+ code;
1990
+ details;
1991
+ };
1992
+
1993
+ // src/api/handlers/ptClaimHandler.ts
1994
+ var PTClaimError = class extends Error {
1995
+ constructor(code, message, details) {
1996
+ super(message);
1997
+ this.code = code;
1998
+ this.details = details;
1999
+ this.name = "PTClaimError";
2000
+ }
2001
+ code;
2002
+ details;
2003
+ };
2004
+ var DEFAULT_LOCK_MS = 15 * 60 * 1e3;
2005
+ var DEFAULT_SIG_DEADLINE_SEC2 = 15 * 60;
2006
+ var PTClaimHandler = class {
2007
+ cfg;
2008
+ constructor(config) {
2009
+ this.cfg = {
2010
+ ...config,
2011
+ lockDurationMs: config.lockDurationMs ?? DEFAULT_LOCK_MS,
2012
+ signatureDeadlineSeconds: config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC2,
2013
+ now: config.now ?? (() => Date.now())
2014
+ };
2015
+ }
2016
+ async handle(request) {
2017
+ if (getAddress8(request.authenticatedAddress) !== getAddress8(request.userAddress)) {
2018
+ throw new PTClaimError(
2019
+ "VALIDATION_FAILED",
2020
+ `userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`
2021
+ );
2022
+ }
2023
+ if (request.amount <= 0n) {
2024
+ throw new PTClaimError("INVALID_AMOUNT", "claim amount must be positive");
2025
+ }
2026
+ if (this.cfg.issuerStateValidator) {
2027
+ try {
2028
+ await this.cfg.issuerStateValidator.preValidateMint(
2029
+ request.pointTokenAddress,
2030
+ request.amount
2031
+ );
2032
+ } catch (err) {
2033
+ if (err instanceof IssuerStateError) throw err;
2034
+ throw new PTClaimError(
2035
+ "VALIDATION_FAILED",
2036
+ `issuer-state pre-validate failed: ${err instanceof Error ? err.message : String(err)}`
2037
+ );
2038
+ }
2039
+ }
2040
+ const { batchExecutor: batchExecutorAddress } = getContractAddresses2(
2041
+ request.chainId
2042
+ );
2043
+ const lockId = await this.cfg.ledger.lockForMinting(
2044
+ request.userAddress,
2045
+ request.amount,
2046
+ this.cfg.lockDurationMs,
2047
+ request.pointTokenAddress
2048
+ );
2049
+ const signatureDeadline = BigInt(
2050
+ Math.floor(this.cfg.now() / 1e3) + this.cfg.signatureDeadlineSeconds
2051
+ );
2052
+ const feeAmount = this.cfg.feeService ? await this.cfg.feeService.estimateGasFee() : 0n;
2053
+ const domain = {
2054
+ name: this.cfg.pointTokenDomainName,
2055
+ chainId: request.chainId,
2056
+ verifyingContract: request.pointTokenAddress
2057
+ };
2058
+ let userOp;
2059
+ try {
2060
+ userOp = await this.cfg.relayService.prepareMint({
2061
+ userAddress: request.userAddress,
2062
+ aaNonce: request.aaNonce,
2063
+ batchExecutorAddress,
2064
+ pointTokenAddress: request.pointTokenAddress,
2065
+ amount: request.amount,
2066
+ issuerSignerWallet: this.cfg.issuerSignerWallet,
2067
+ domain,
2068
+ mintRequestNonce: request.mintRequestNonce,
2069
+ deadline: signatureDeadline
2070
+ // No feeAmount/feeRecipient — RelayService auto-resolves.
2071
+ });
2072
+ } catch (err) {
2073
+ throw new PTClaimError(
2074
+ "BUILD_FAILED",
2075
+ `prepareMint failed: ${err instanceof Error ? err.message : String(err)}`
2076
+ );
2077
+ }
2078
+ let fallback;
2079
+ if (feeAmount > 0n) {
2080
+ try {
2081
+ fallback = await this.cfg.relayService.prepareMint({
2082
+ userAddress: request.userAddress,
2083
+ aaNonce: request.aaNonce,
2084
+ batchExecutorAddress,
2085
+ pointTokenAddress: request.pointTokenAddress,
2086
+ amount: request.amount,
2087
+ issuerSignerWallet: this.cfg.issuerSignerWallet,
2088
+ domain,
2089
+ mintRequestNonce: request.mintRequestNonce,
2090
+ deadline: signatureDeadline,
2091
+ feeAmount: 0n
2092
+ });
2093
+ } catch (err) {
2094
+ throw new PTClaimError(
2095
+ "BUILD_FAILED",
2096
+ `prepareMint (fallback) failed: ${err instanceof Error ? err.message : String(err)}`
2097
+ );
2098
+ }
2099
+ }
2100
+ const calls = decodeBatchExecuteCalls(userOp.callData);
2101
+ const callsFallback = fallback ? decodeBatchExecuteCalls(fallback.callData) : void 0;
2102
+ return {
2103
+ userOp,
2104
+ fallback,
2105
+ lockId,
2106
+ feeAmount,
2107
+ signatureDeadline,
2108
+ expiresInSeconds: Math.floor(this.cfg.lockDurationMs / 1e3),
2109
+ calls,
2110
+ callsFallback
2111
+ };
2112
+ }
2113
+ };
2114
+
2115
+ // src/api/handlers/swapHandler.ts
2116
+ import {
2117
+ buildSwapWithGasDeduction,
2118
+ decodeBatchExecuteCalls as decodeBatchExecuteCalls2,
2119
+ findBestQuote,
2120
+ getContractAddresses as getContractAddresses3
2121
+ } from "@pafi-dev/core";
2122
+ var SwapError = class extends Error {
2123
+ constructor(code, message) {
2124
+ super(message);
2125
+ this.code = code;
2126
+ this.name = "SwapError";
2127
+ }
2128
+ code;
2129
+ };
2130
+ var DEFAULT_SLIPPAGE_BPS = 50;
2131
+ var DEFAULT_SWAP_DEADLINE_SEC = 5 * 60;
2132
+ var SwapHandler = class {
2133
+ cfg;
2134
+ constructor(config) {
2135
+ this.cfg = {
2136
+ ...config,
2137
+ defaultSlippageBps: config.defaultSlippageBps ?? DEFAULT_SLIPPAGE_BPS,
2138
+ defaultSwapDeadlineSeconds: config.defaultSwapDeadlineSeconds ?? DEFAULT_SWAP_DEADLINE_SEC,
2139
+ now: config.now ?? (() => Date.now())
2140
+ };
2141
+ }
2142
+ async handle(request) {
2143
+ if (request.amountIn <= 0n) {
2144
+ throw new SwapError("INVALID_AMOUNT", "amountIn must be positive");
2145
+ }
2146
+ const slippageBps = request.slippageBps ?? this.cfg.defaultSlippageBps;
2147
+ const { usdt, pafiFeeRecipient, universalRouter } = getContractAddresses3(
2148
+ request.chainId
2149
+ );
2150
+ const poolsResponse = await this.cfg.poolsProvider({
2151
+ chainId: request.chainId,
2152
+ pointTokenAddress: request.pointTokenAddress
2153
+ });
2154
+ if (poolsResponse.pools.length === 0) {
2155
+ throw new SwapError(
2156
+ "QUOTE_UNAVAILABLE",
2157
+ "no liquidity pool found for this point token"
2158
+ );
2159
+ }
2160
+ let fallbackQuote;
2161
+ try {
2162
+ fallbackQuote = await findBestQuote(
2163
+ this.cfg.provider,
2164
+ request.chainId,
2165
+ request.pointTokenAddress,
2166
+ usdt,
2167
+ request.amountIn,
2168
+ poolsResponse.pools
2169
+ );
2170
+ } catch {
2171
+ throw new SwapError(
2172
+ "QUOTE_UNAVAILABLE",
2173
+ "no swap path found for this point token"
2174
+ );
2175
+ }
2176
+ const estimatedUsdtOutFallback = fallbackQuote.bestRoute.amountOut;
2177
+ const minAmountOutFallback = estimatedUsdtOutFallback * BigInt(1e4 - slippageBps) / 10000n;
2178
+ const deadline = request.deadline ?? BigInt(
2179
+ Math.floor(this.cfg.now() / 1e3) + this.cfg.defaultSwapDeadlineSeconds
2180
+ );
2181
+ const feeAmount = this.cfg.feeService ? await this.cfg.feeService.estimateGasFee() : 0n;
2182
+ if (feeAmount > 0n && feeAmount >= request.amountIn) {
2183
+ throw new SwapError(
2184
+ "FEE_EXCEEDS_AMOUNT",
2185
+ `gas fee (${feeAmount}) must be strictly less than swap amount (${request.amountIn})`
2186
+ );
2187
+ }
2188
+ const sponsoredAmountIn = request.amountIn - feeAmount;
2189
+ let estimatedUsdtOutSponsored = estimatedUsdtOutFallback;
2190
+ let sponsoredPath = fallbackQuote.bestRoute.path;
2191
+ if (feeAmount > 0n) {
2192
+ try {
2193
+ const sponsoredQuote = await findBestQuote(
2194
+ this.cfg.provider,
2195
+ request.chainId,
2196
+ request.pointTokenAddress,
2197
+ usdt,
2198
+ sponsoredAmountIn,
2199
+ poolsResponse.pools
2200
+ );
2201
+ estimatedUsdtOutSponsored = sponsoredQuote.bestRoute.amountOut;
2202
+ sponsoredPath = sponsoredQuote.bestRoute.path;
2203
+ } catch {
2204
+ throw new SwapError(
2205
+ "QUOTE_UNAVAILABLE",
2206
+ "no swap path found for sponsored amount (after fee deduction)"
2207
+ );
2208
+ }
2209
+ }
2210
+ const minAmountOutSponsored = estimatedUsdtOutSponsored * BigInt(1e4 - slippageBps) / 10000n;
2211
+ const sponsoredOp = buildSwapWithGasDeduction({
2212
+ userAddress: request.userAddress,
2213
+ aaNonce: request.aaNonce,
2214
+ pointTokenAddress: request.pointTokenAddress,
2215
+ outputTokenAddress: usdt,
2216
+ universalRouterAddress: universalRouter,
2217
+ amountIn: sponsoredAmountIn,
2218
+ minAmountOut: minAmountOutSponsored,
2219
+ swapPath: sponsoredPath,
2220
+ deadline,
2221
+ gasFeePt: feeAmount,
2222
+ feeRecipient: pafiFeeRecipient
2223
+ });
2224
+ const fallbackOp = feeAmount > 0n ? buildSwapWithGasDeduction({
2225
+ userAddress: request.userAddress,
2226
+ aaNonce: request.aaNonce,
2227
+ pointTokenAddress: request.pointTokenAddress,
2228
+ outputTokenAddress: usdt,
2229
+ universalRouterAddress: universalRouter,
2230
+ amountIn: request.amountIn,
2231
+ minAmountOut: minAmountOutFallback,
2232
+ swapPath: fallbackQuote.bestRoute.path,
2233
+ deadline,
2234
+ gasFeePt: 0n,
2235
+ feeRecipient: pafiFeeRecipient
2236
+ }) : void 0;
2237
+ return {
2238
+ userOp: sponsoredOp,
2239
+ fallback: fallbackOp,
2240
+ feeAmount,
2241
+ estimatedUsdtOut: estimatedUsdtOutSponsored,
2242
+ minAmountOut: minAmountOutSponsored,
2243
+ estimatedUsdtOutFallback: fallbackOp ? estimatedUsdtOutFallback : void 0,
2244
+ minAmountOutFallback: fallbackOp ? minAmountOutFallback : void 0,
2245
+ deadline,
2246
+ calls: decodeBatchExecuteCalls2(sponsoredOp.callData),
2247
+ callsFallback: fallbackOp ? decodeBatchExecuteCalls2(fallbackOp.callData) : void 0
2248
+ };
2249
+ }
2250
+ };
2251
+
2252
+ // src/api/handlers/perpDepositHandler.ts
2253
+ import {
2254
+ BROKER_HASHES,
2255
+ ORDERLY_RELAY_ABI,
2256
+ ORDERLY_VAULT_ABI,
2257
+ ORDERLY_VAULT_ADDRESSES,
2258
+ TOKEN_HASHES,
2259
+ buildPerpDepositViaRelay,
2260
+ computeAccountId,
2261
+ decodeBatchExecuteCalls as decodeBatchExecuteCalls3,
2262
+ getContractAddresses as getContractAddresses4
2263
+ } from "@pafi-dev/core";
2264
+ var PerpDepositError = class extends Error {
2265
+ constructor(code, message) {
2266
+ super(message);
2267
+ this.code = code;
2268
+ this.name = "PerpDepositError";
2269
+ }
2270
+ code;
2271
+ };
2272
+ var DEFAULT_MAX_FEE_PREMIUM_BPS = 5e3;
2273
+ var PerpDepositHandler = class {
2274
+ cfg;
2275
+ constructor(config) {
2276
+ this.cfg = {
2277
+ ...config,
2278
+ maxFeePremiumBps: config.maxFeePremiumBps ?? DEFAULT_MAX_FEE_PREMIUM_BPS
2279
+ };
2280
+ }
2281
+ async handle(request) {
2282
+ if (request.amount <= 0n) {
2283
+ throw new PerpDepositError("INVALID_AMOUNT", "amount must be positive");
2284
+ }
2285
+ const brokerHash = BROKER_HASHES[request.brokerId];
2286
+ const tokenHash = TOKEN_HASHES.USDC;
2287
+ const vault = ORDERLY_VAULT_ADDRESSES[request.chainId];
2288
+ if (!vault) {
2289
+ throw new PerpDepositError(
2290
+ "PERP_DEPOSIT_UNAVAILABLE",
2291
+ `no Orderly Vault for chainId ${request.chainId}`
2292
+ );
2293
+ }
2294
+ const { orderlyRelay: relayAddress, pafiFeeRecipient } = getContractAddresses4(request.chainId);
2295
+ const [usdcAddress, brokerAllowed] = await Promise.all([
2296
+ this.cfg.provider.readContract({
2297
+ address: vault,
2298
+ abi: ORDERLY_VAULT_ABI,
2299
+ functionName: "getAllowedToken",
2300
+ args: [tokenHash]
2301
+ }),
2302
+ this.cfg.provider.readContract({
2303
+ address: vault,
2304
+ abi: ORDERLY_VAULT_ABI,
2305
+ functionName: "getAllowedBroker",
2306
+ args: [brokerHash]
2307
+ })
2308
+ ]);
2309
+ if (!brokerAllowed) {
2310
+ throw new PerpDepositError(
2311
+ "BROKER_NOT_WHITELISTED",
2312
+ `broker "${request.brokerId}" is not whitelisted on Orderly Vault`
2313
+ );
2314
+ }
2315
+ const accountId = computeAccountId(request.userAddress, brokerHash);
2316
+ const requestForQuote = {
2317
+ token: usdcAddress,
2318
+ receiver: request.userAddress,
2319
+ brokerHash,
2320
+ totalAmount: request.amount,
2321
+ maxFee: 0n
2322
+ };
2323
+ const [relayTokenFee, ptGasFee] = await Promise.all([
2324
+ this.cfg.provider.readContract({
2325
+ address: relayAddress,
2326
+ abi: ORDERLY_RELAY_ABI,
2327
+ functionName: "quoteTokenFee",
2328
+ args: [requestForQuote]
2329
+ }),
2330
+ this.cfg.feeService ? this.cfg.feeService.estimateGasFee() : Promise.resolve(0n)
2331
+ ]);
2332
+ if (relayTokenFee >= request.amount) {
2333
+ throw new PerpDepositError(
2334
+ "RELAY_FEE_EXCEEDS_AMOUNT",
2335
+ `Relay quoted fee ${relayTokenFee} >= deposit amount ${request.amount}`
2336
+ );
2337
+ }
2338
+ const maxFee = relayTokenFee * BigInt(1e4 + this.cfg.maxFeePremiumBps) / 10000n;
2339
+ const depositReq = {
2340
+ token: usdcAddress,
2341
+ receiver: request.userAddress,
2342
+ brokerHash,
2343
+ totalAmount: request.amount,
2344
+ maxFee
2345
+ };
2346
+ const sponsoredOp = buildPerpDepositViaRelay({
2347
+ userAddress: request.userAddress,
2348
+ aaNonce: request.aaNonce,
2349
+ relayAddress,
2350
+ request: depositReq,
2351
+ pointTokenAddress: this.cfg.pointTokenAddress,
2352
+ gasFeePt: ptGasFee,
2353
+ gasFeePtRecipient: pafiFeeRecipient
2354
+ });
2355
+ const fallbackOp = ptGasFee > 0n ? buildPerpDepositViaRelay({
2356
+ userAddress: request.userAddress,
2357
+ aaNonce: request.aaNonce,
2358
+ relayAddress,
2359
+ request: depositReq
2360
+ }) : void 0;
2361
+ return {
2362
+ userOp: sponsoredOp,
2363
+ fallback: fallbackOp,
2364
+ feeAmount: ptGasFee,
2365
+ relayTokenFee,
2366
+ maxFee,
2367
+ netDeposit: request.amount - relayTokenFee,
2368
+ accountId,
2369
+ brokerHash,
2370
+ usdcAddress,
2371
+ relayAddress,
2372
+ calls: decodeBatchExecuteCalls3(sponsoredOp.callData),
2373
+ callsFallback: fallbackOp ? decodeBatchExecuteCalls3(fallbackOp.callData) : void 0
2374
+ };
2375
+ }
2376
+ };
2377
+
1701
2378
  // src/pools/subgraphPoolsProvider.ts
1702
2379
  import { isAddress } from "viem";
1703
2380
  import { PAFI_SUBGRAPH_URL } from "@pafi-dev/core";
@@ -2315,79 +2992,9 @@ var PafiBackendClient = class {
2315
2992
  }
2316
2993
  };
2317
2994
 
2318
- // src/pafi-backend/helpers.ts
2319
- var BundlerNotConfiguredError = class extends Error {
2320
- code = "BUNDLER_NOT_CONFIGURED";
2321
- constructor() {
2322
- super(
2323
- "PAFI backend client not configured \u2014 set PAFI_BACKEND_URL, PAFI_ISSUER_ID, PAFI_API_KEY to enable mobile submit."
2324
- );
2325
- this.name = "BundlerNotConfiguredError";
2326
- }
2327
- };
2328
- var BundlerRejectedError = class extends Error {
2329
- code = "BUNDLER_REJECTED";
2330
- cause;
2331
- constructor(message, cause) {
2332
- super(message);
2333
- this.name = "BundlerRejectedError";
2334
- this.cause = cause;
2335
- }
2336
- };
2337
- async function requestPaymaster(params) {
2338
- if (!params.client) return void 0;
2339
- const fn = params.functionName ?? defaultFunctionForScenario(params.scenario);
2340
- try {
2341
- return await params.client.requestSponsorship({
2342
- chainId: params.chainId,
2343
- scenario: params.scenario,
2344
- userOp: params.userOp,
2345
- target: {
2346
- contract: params.pointTokenAddress,
2347
- function: fn,
2348
- pointToken: params.pointTokenAddress
2349
- }
2350
- });
2351
- } catch (err) {
2352
- const msg = err instanceof Error ? err.message : String(err);
2353
- params.onWarning?.(`Paymaster sponsorship declined: ${msg}`);
2354
- return void 0;
2355
- }
2356
- }
2357
- function defaultFunctionForScenario(scenario) {
2358
- switch (scenario) {
2359
- case "mint":
2360
- return "mint";
2361
- case "burn":
2362
- return "burn";
2363
- case "swap":
2364
- return "swap";
2365
- case "perp-deposit":
2366
- return "deposit";
2367
- default:
2368
- return scenario;
2369
- }
2370
- }
2371
- async function relayUserOp(params) {
2372
- if (!params.client) {
2373
- throw new BundlerNotConfiguredError();
2374
- }
2375
- try {
2376
- const result = await params.client.relayUserOperation({
2377
- userOp: params.userOp,
2378
- entryPoint: params.entryPoint,
2379
- eip7702Auth: params.eip7702Auth
2380
- });
2381
- return { userOpHash: result.userOpHash };
2382
- } catch (err) {
2383
- const msg = err instanceof Error ? err.message : String(err);
2384
- throw new BundlerRejectedError(msg, err);
2385
- }
2386
- }
2387
-
2388
2995
  // src/config.ts
2389
- import { getAddress as getAddress8 } from "viem";
2390
- import { getContractAddresses as getContractAddresses2 } from "@pafi-dev/core";
2996
+ import { getAddress as getAddress9 } from "viem";
2997
+ import { getContractAddresses as getContractAddresses5 } from "@pafi-dev/core";
2391
2998
  function createIssuerService(config) {
2392
2999
  if (!config.provider) {
2393
3000
  throw new Error("createIssuerService: provider is required");
@@ -2407,7 +3014,7 @@ function createIssuerService(config) {
2407
3014
  "createIssuerService: at least one of pointTokenAddress / pointTokenAddresses is required"
2408
3015
  );
2409
3016
  }
2410
- const tokenAddresses = rawAddresses.map((a) => getAddress8(a));
3017
+ const tokenAddresses = rawAddresses.map((a) => getAddress9(a));
2411
3018
  const ledger = config.ledger;
2412
3019
  const sessionStore = config.sessionStore ?? new MemorySessionStore();
2413
3020
  const policy = config.policy ?? new DefaultPolicyEngine({ ledger });
@@ -2457,7 +3064,7 @@ function createIssuerService(config) {
2457
3064
  indexers.set(tokenAddress, new PointIndexer(indexerConfig));
2458
3065
  }
2459
3066
  const firstIndexer = indexers.get(tokenAddresses[0]);
2460
- const chainAddresses = getContractAddresses2(config.chainId);
3067
+ const chainAddresses = getContractAddresses5(config.chainId);
2461
3068
  const resolvedContracts = {
2462
3069
  batchExecutor: chainAddresses.batchExecutor,
2463
3070
  usdt: chainAddresses.usdt,
@@ -2504,163 +3111,13 @@ function createIssuerService(config) {
2504
3111
  };
2505
3112
  }
2506
3113
 
2507
- // src/userop-store/serialize.ts
2508
- import { serializeUserOpToJsonRpc } from "@pafi-dev/core";
2509
- function serializeEntryToJsonRpc(entry, signature, variant = "sponsored") {
2510
- if (variant === "fallback") {
2511
- if (!entry.fallback) {
2512
- throw new Error(
2513
- "serializeEntryToJsonRpc: variant=fallback requested but the stored entry has no `fallback` branch \u2014 caller should resubmit with variant='sponsored' or re-prepare with a fee configured."
2514
- );
2515
- }
2516
- return serializeUserOpToJsonRpc(
2517
- {
2518
- sender: entry.sender,
2519
- nonce: BigInt(entry.nonce),
2520
- callData: entry.fallback.callData,
2521
- callGasLimit: BigInt(entry.fallback.callGasLimit),
2522
- verificationGasLimit: BigInt(entry.fallback.verificationGasLimit),
2523
- preVerificationGas: BigInt(entry.fallback.preVerificationGas),
2524
- maxFeePerGas: BigInt(entry.maxFeePerGas),
2525
- maxPriorityFeePerGas: BigInt(entry.maxPriorityFeePerGas)
2526
- // intentionally no paymaster — user pays ETH gas
2527
- },
2528
- signature
2529
- );
2530
- }
2531
- return serializeUserOpToJsonRpc(
2532
- {
2533
- sender: entry.sender,
2534
- nonce: BigInt(entry.nonce),
2535
- callData: entry.callData,
2536
- callGasLimit: BigInt(entry.callGasLimit),
2537
- verificationGasLimit: BigInt(entry.verificationGasLimit),
2538
- preVerificationGas: BigInt(entry.preVerificationGas),
2539
- maxFeePerGas: BigInt(entry.maxFeePerGas),
2540
- maxPriorityFeePerGas: BigInt(entry.maxPriorityFeePerGas),
2541
- paymaster: entry.paymaster,
2542
- paymasterVerificationGasLimit: entry.paymasterVerificationGasLimit != null ? BigInt(entry.paymasterVerificationGasLimit) : void 0,
2543
- paymasterPostOpGasLimit: entry.paymasterPostOpGasLimit != null ? BigInt(entry.paymasterPostOpGasLimit) : void 0,
2544
- paymasterData: entry.paymasterData
2545
- },
2546
- signature
2547
- );
2548
- }
2549
-
2550
- // src/userop-store/prepareUserOp.ts
2551
- import {
2552
- buildUserOpTypedData,
2553
- computeUserOpHash
2554
- } from "@pafi-dev/core";
2555
- function serializeUserOpTypedData(td) {
2556
- return {
2557
- domain: td.domain,
2558
- types: td.types,
2559
- primaryType: td.primaryType,
2560
- message: {
2561
- sender: td.message.sender,
2562
- nonce: `0x${td.message.nonce.toString(16)}`,
2563
- initCode: td.message.initCode,
2564
- callData: td.message.callData,
2565
- accountGasLimits: td.message.accountGasLimits,
2566
- preVerificationGas: `0x${td.message.preVerificationGas.toString(
2567
- 16
2568
- )}`,
2569
- gasFees: td.message.gasFees,
2570
- paymasterAndData: td.message.paymasterAndData
2571
- }
2572
- };
2573
- }
2574
- function mergePaymasterFields(userOp, paymasterFields) {
2575
- if (!paymasterFields) return userOp;
2576
- const merged = {
2577
- ...userOp
2578
- };
2579
- for (const [k, v] of Object.entries(paymasterFields)) {
2580
- if (v !== void 0) merged[k] = v;
2581
- }
2582
- return merged;
2583
- }
2584
- async function prepareMobileUserOp(params) {
2585
- const userOp = mergePaymasterFields(
2586
- params.partialUserOp,
2587
- params.paymasterFields
2588
- );
2589
- const userOpHash = computeUserOpHash(userOp, params.chainId);
2590
- const typedData = serializeUserOpTypedData(
2591
- buildUserOpTypedData(userOp, params.chainId)
2592
- );
2593
- let fallback;
2594
- let fallbackEntry;
2595
- if (params.partialUserOpFallback) {
2596
- const fallbackUserOp = {
2597
- ...params.partialUserOpFallback,
2598
- maxFeePerGas: userOp.maxFeePerGas,
2599
- maxPriorityFeePerGas: userOp.maxPriorityFeePerGas
2600
- };
2601
- const fallbackHash = computeUserOpHash(fallbackUserOp, params.chainId);
2602
- const fallbackTypedData = serializeUserOpTypedData(
2603
- buildUserOpTypedData(fallbackUserOp, params.chainId)
2604
- );
2605
- fallback = {
2606
- userOp: fallbackUserOp,
2607
- userOpHash: fallbackHash,
2608
- typedData: fallbackTypedData
2609
- };
2610
- fallbackEntry = {
2611
- callData: fallbackUserOp.callData,
2612
- callGasLimit: fallbackUserOp.callGasLimit.toString(),
2613
- verificationGasLimit: fallbackUserOp.verificationGasLimit.toString(),
2614
- preVerificationGas: fallbackUserOp.preVerificationGas.toString(),
2615
- userOpHash: fallbackHash
2616
- };
2617
- }
2618
- const entry = {
2619
- sender: userOp.sender,
2620
- nonce: userOp.nonce.toString(),
2621
- callData: userOp.callData,
2622
- callGasLimit: userOp.callGasLimit.toString(),
2623
- verificationGasLimit: userOp.verificationGasLimit.toString(),
2624
- preVerificationGas: userOp.preVerificationGas.toString(),
2625
- maxFeePerGas: userOp.maxFeePerGas.toString(),
2626
- maxPriorityFeePerGas: userOp.maxPriorityFeePerGas.toString(),
2627
- paymaster: userOp.paymaster,
2628
- paymasterVerificationGasLimit: userOp.paymasterVerificationGasLimit?.toString(),
2629
- paymasterPostOpGasLimit: userOp.paymasterPostOpGasLimit?.toString(),
2630
- paymasterData: userOp.paymasterData,
2631
- chainId: params.chainId,
2632
- userOpHash,
2633
- fallback: fallbackEntry
2634
- };
2635
- await params.store.save(params.lockId, entry, params.ttlSeconds);
2636
- return {
2637
- sponsored: { userOp, userOpHash, typedData },
2638
- fallback,
2639
- entry
2640
- };
2641
- }
2642
-
2643
3114
  // src/issuer-state/validator.ts
2644
- import { getAddress as getAddress9 } from "viem";
3115
+ import { getAddress as getAddress10 } from "viem";
2645
3116
  import {
2646
3117
  POINT_TOKEN_V2_ABI as POINT_TOKEN_V2_ABI3,
2647
3118
  issuerRegistryGetIssuerFlatAbi,
2648
- getContractAddresses as getContractAddresses3
3119
+ getContractAddresses as getContractAddresses6
2649
3120
  } from "@pafi-dev/core";
2650
-
2651
- // src/issuer-state/types.ts
2652
- var IssuerStateError = class extends Error {
2653
- constructor(code, message, details) {
2654
- super(message);
2655
- this.code = code;
2656
- this.details = details;
2657
- this.name = "IssuerStateError";
2658
- }
2659
- code;
2660
- details;
2661
- };
2662
-
2663
- // src/issuer-state/validator.ts
2664
3121
  var ISSUER_RECORD_TTL_MS = 3e4;
2665
3122
  var IssuerStateValidator = class _IssuerStateValidator {
2666
3123
  constructor(provider, registryAddress) {
@@ -2677,7 +3134,7 @@ var IssuerStateValidator = class _IssuerStateValidator {
2677
3134
  * `CONTRACT_ADDRESSES` map for the given chain.
2678
3135
  */
2679
3136
  static forChain(provider, chainId) {
2680
- const { issuerRegistry } = getContractAddresses3(chainId);
3137
+ const { issuerRegistry } = getContractAddresses6(chainId);
2681
3138
  return new _IssuerStateValidator(provider, issuerRegistry);
2682
3139
  }
2683
3140
  /**
@@ -2686,7 +3143,7 @@ var IssuerStateValidator = class _IssuerStateValidator {
2686
3143
  */
2687
3144
  invalidate(pointToken) {
2688
3145
  if (pointToken) {
2689
- const key = getAddress9(pointToken);
3146
+ const key = getAddress10(pointToken);
2690
3147
  this.pointTokenIssuerCache.delete(key);
2691
3148
  this.stateCache.delete(key);
2692
3149
  this.inflight.delete(key);
@@ -2701,7 +3158,7 @@ var IssuerStateValidator = class _IssuerStateValidator {
2701
3158
  * The issuer field is set at `initialize()` and never changes.
2702
3159
  */
2703
3160
  async getIssuerAddressForPointToken(pointToken) {
2704
- const key = getAddress9(pointToken);
3161
+ const key = getAddress10(pointToken);
2705
3162
  const cached = this.pointTokenIssuerCache.get(key);
2706
3163
  if (cached) return cached;
2707
3164
  const issuer = await this.provider.readContract({
@@ -2709,15 +3166,15 @@ var IssuerStateValidator = class _IssuerStateValidator {
2709
3166
  abi: POINT_TOKEN_V2_ABI3,
2710
3167
  functionName: "issuer"
2711
3168
  });
2712
- this.pointTokenIssuerCache.set(key, getAddress9(issuer));
2713
- return getAddress9(issuer);
3169
+ this.pointTokenIssuerCache.set(key, getAddress10(issuer));
3170
+ return getAddress10(issuer);
2714
3171
  }
2715
3172
  /**
2716
3173
  * Read registry record + totalSupply, with 30s cache and in-flight
2717
3174
  * deduplication. Does NOT throw on inactive/missing — returns raw state.
2718
3175
  */
2719
3176
  async getIssuerState(pointToken) {
2720
- const tokenAddr = getAddress9(pointToken);
3177
+ const tokenAddr = getAddress10(pointToken);
2721
3178
  const now = Date.now();
2722
3179
  const cached = this.stateCache.get(tokenAddr);
2723
3180
  if (cached && cached.expiresAt > now) return cached.value;
@@ -2834,13 +3291,20 @@ export {
2834
3291
  NonceManager,
2835
3292
  PAFI_ISSUER_SDK_VERSION,
2836
3293
  PAFI_SUBGRAPH_URL,
3294
+ PTClaimError,
3295
+ PTClaimHandler,
2837
3296
  PTRedeemError,
2838
3297
  PTRedeemHandler,
2839
3298
  PafiBackendClient,
2840
3299
  PafiBackendError,
3300
+ PendingUserOpNotFoundError,
3301
+ PerpDepositError,
3302
+ PerpDepositHandler,
2841
3303
  PointIndexer,
2842
3304
  RelayError,
2843
3305
  RelayService,
3306
+ SwapError,
3307
+ SwapHandler,
2844
3308
  TopUpRedemptionError,
2845
3309
  TopUpRedemptionHandler,
2846
3310
  authenticateRequest,
@@ -2849,6 +3313,8 @@ export {
2849
3313
  createSubgraphNativeUsdtQuoter,
2850
3314
  createSubgraphPoolsProvider,
2851
3315
  handleClaimStatus,
3316
+ handleMobilePrepare,
3317
+ handleMobileSubmit,
2852
3318
  handleRedeemStatus,
2853
3319
  mergePaymasterFields,
2854
3320
  prepareMobileUserOp,