@pafi-dev/issuer 0.19.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -862,58 +862,265 @@ var RelayService = class {
862
862
  }
863
863
  });
864
864
  }
865
+ // =========================================================================
866
+ // Preview methods — produce bundler-ready partial UserOps WITHOUT signing.
867
+ //
868
+ // These exist so callers can fetch an accurate gas estimate from PAFI's
869
+ // `/v1/estimate-gas-fee` BEFORE committing to a signed MintRequest /
870
+ // BurnRequest (HSM/KMS signing is expensive in production). The
871
+ // returned `callData` matches the SHAPE of the real call; the EIP-712
872
+ // signature bytes are a placeholder. Bundler simulation doesn't
873
+ // validate the signature, so the gas estimate comes back accurate.
874
+ //
875
+ // Cache-wise: same SC version + same scenario produce identical
876
+ // calldata shape → identical bundler-returned gas units. The first
877
+ // call seeds the cache; subsequent ones hit it.
878
+ // =========================================================================
879
+ /**
880
+ * Build a dummy `PartialUserOperation` for the mint scenario, suitable
881
+ * for `feeManager.estimateGasFee({ partialUserOp, ... })`. NO signing —
882
+ * uses a 65-byte zero signature placeholder.
883
+ */
884
+ previewMintUserOp(params) {
885
+ const useWrapper = params.mintFeeWrapperAddress !== void 0;
886
+ let mintCallData;
887
+ let mintTarget;
888
+ if (useWrapper) {
889
+ mintCallData = encodeFunctionData({
890
+ abi: mintFeeWrapperAbi,
891
+ functionName: "mintWithFee",
892
+ args: [
893
+ params.pointTokenAddress,
894
+ params.userAddress,
895
+ params.amount,
896
+ params.deadline,
897
+ PLACEHOLDER_SIG_65
898
+ ]
899
+ });
900
+ mintTarget = params.mintFeeWrapperAddress;
901
+ } else {
902
+ mintCallData = encodeFunctionData({
903
+ abi: POINT_TOKEN_ABI,
904
+ functionName: "mint",
905
+ args: [
906
+ params.userAddress,
907
+ params.amount,
908
+ params.deadline,
909
+ PLACEHOLDER_SIG_65
910
+ ]
911
+ });
912
+ mintTarget = params.pointTokenAddress;
913
+ }
914
+ return buildPartialUserOperation({
915
+ sender: params.userAddress,
916
+ nonce: params.aaNonce,
917
+ operations: [{ target: mintTarget, value: 0n, data: mintCallData }],
918
+ // Gas limits ignored by bundler estimate — it computes them.
919
+ gasLimits: {
920
+ callGasLimit: 1n,
921
+ verificationGasLimit: 1n,
922
+ preVerificationGas: 1n
923
+ }
924
+ });
925
+ }
926
+ /** Burn-side mirror of `previewMintUserOp`. */
927
+ previewBurnUserOp(params) {
928
+ const burnCallData = encodeFunctionData({
929
+ abi: POINT_TOKEN_ABI,
930
+ functionName: "burn",
931
+ args: [
932
+ params.userAddress,
933
+ params.amount,
934
+ params.deadline,
935
+ PLACEHOLDER_SIG_65
936
+ ]
937
+ });
938
+ return buildPartialUserOperation({
939
+ sender: params.userAddress,
940
+ nonce: params.aaNonce,
941
+ operations: [
942
+ { target: params.pointTokenAddress, value: 0n, data: burnCallData }
943
+ ],
944
+ gasLimits: {
945
+ callGasLimit: 1n,
946
+ verificationGasLimit: 1n,
947
+ preVerificationGas: 1n
948
+ }
949
+ });
950
+ }
865
951
  };
952
+ var PLACEHOLDER_SIG_65 = `0x${"00".repeat(65)}`;
866
953
  function errorMessage(err) {
867
954
  return err instanceof Error ? err.message : String(err);
868
955
  }
869
956
 
870
957
  // src/relay/feeManager.ts
871
958
  var DEFAULT_GAS_UNITS = 500000n;
872
- var DEFAULT_PREMIUM_BPS = 12e3;
959
+ var DEFAULT_PREMIUM_BPS = 1e4;
873
960
  var FeeManager = class _FeeManager {
874
961
  provider;
875
- gasUnits;
962
+ fallbackGasUnits;
876
963
  gasPremiumBps;
877
964
  quoteNativeToFee;
965
+ bundlerClient;
966
+ metrics;
967
+ // Short-lived fee-value cache. Distinct from the estimator's cache:
968
+ // this absorbs burst calls (e.g. 5 user requests in 5s all hit the
969
+ // /gas-fee endpoint) by remembering the COMPUTED PT amount, not the
970
+ // gas units. Only used by the no-opts legacy path; estimator path
971
+ // gets its caching from the PAFI side instead.
878
972
  cachedFee = null;
879
973
  cacheExpiresAt = 0;
880
- static CACHE_TTL_MS = 1e4;
974
+ static FEE_CACHE_TTL_MS = 1e4;
881
975
  constructor(config) {
882
976
  if (!config.provider) throw new Error("FeeManager: provider required");
883
977
  if (!config.quoteNativeToFee)
884
978
  throw new Error("FeeManager: quoteNativeToFee required");
885
979
  this.provider = config.provider;
886
- this.gasUnits = config.gasUnits ?? DEFAULT_GAS_UNITS;
980
+ this.fallbackGasUnits = config.gasUnits ?? DEFAULT_GAS_UNITS;
887
981
  this.gasPremiumBps = config.gasPremiumBps ?? DEFAULT_PREMIUM_BPS;
888
982
  this.quoteNativeToFee = config.quoteNativeToFee;
983
+ this.bundlerClient = config.bundlerClient;
984
+ this.metrics = config.metrics;
889
985
  }
890
986
  /**
891
- * Estimate the fee (in the caller's fee currency) to charge for the
892
- * next sponsored UserOp:
987
+ * Estimate the operator fee for the next sponsored UserOp.
893
988
  *
894
- * nativeCost = gasUnits × gasPrice
895
- * withPremium = nativeCost × premiumBps / 10_000
896
- * fee = quoteNativeToFee(withPremium)
989
+ * Without `opts` → legacy path: `gasUnits × gasPrice × premium →
990
+ * quoteNativeToFee`. Cached for 10 s to absorb bursts.
897
991
  *
898
- * For backward compatibility with v0.2.x code that reads `gasFeeUsdt`
899
- * from the response, the name `estimateGasFee` is kept but the
900
- * currency depends on how the caller wired `quoteNativeToFee`.
992
+ * With `opts` AND `bundlerClient` → estimator path. Each call may
993
+ * hit a different bundler-cached result; the SDK does NOT add its
994
+ * own value cache because the estimator's cache TTL is the source
995
+ * of truth for "how long is this estimate good for".
901
996
  */
902
- async estimateGasFee() {
997
+ async estimateGasFee(opts = {}) {
998
+ const isLegacyCall = !opts.partialUserOp && !opts.scenario && !opts.contractAddress;
903
999
  const now = Date.now();
904
- if (this.cachedFee !== null && now < this.cacheExpiresAt) {
1000
+ if (isLegacyCall && this.cachedFee !== null && now < this.cacheExpiresAt) {
905
1001
  return this.cachedFee;
906
1002
  }
1003
+ const t0 = Date.now();
1004
+ const { gasUnits, source } = await this.resolveGasUnits(opts);
1005
+ const latencyMs = Date.now() - t0;
1006
+ this.safeEmit(
1007
+ () => this.metrics?.onEstimate?.({
1008
+ source,
1009
+ scenario: opts.scenario,
1010
+ gasUnits,
1011
+ latencyMs
1012
+ })
1013
+ );
907
1014
  const gasPrice = await this.provider.getGasPrice();
908
- const nativeCost = gasPrice * this.gasUnits;
1015
+ const nativeCost = gasPrice * gasUnits;
909
1016
  const withPremium = nativeCost * BigInt(this.gasPremiumBps) / 10000n;
910
1017
  const fee = await this.quoteNativeToFee(withPremium);
911
- this.cachedFee = fee;
912
- this.cacheExpiresAt = now + _FeeManager.CACHE_TTL_MS;
1018
+ if (isLegacyCall) {
1019
+ this.cachedFee = fee;
1020
+ this.cacheExpiresAt = now + _FeeManager.FEE_CACHE_TTL_MS;
1021
+ }
913
1022
  return fee;
914
1023
  }
1024
+ /** Manually purge the legacy 10s fee cache. */
1025
+ invalidateCache() {
1026
+ this.cachedFee = null;
1027
+ this.cacheExpiresAt = 0;
1028
+ }
1029
+ async resolveGasUnits(opts) {
1030
+ if (!this.bundlerClient || !opts.partialUserOp || !opts.scenario || !opts.contractAddress) {
1031
+ return { gasUnits: this.fallbackGasUnits, source: "fallback" };
1032
+ }
1033
+ try {
1034
+ const result = await this.bundlerClient.getGasUnits({
1035
+ scenario: opts.scenario,
1036
+ contractAddress: opts.contractAddress,
1037
+ paymasterAddress: opts.paymasterAddress,
1038
+ partialUserOp: opts.partialUserOp
1039
+ });
1040
+ return { gasUnits: result.gasUnits, source: "estimator" };
1041
+ } catch (err) {
1042
+ const reason = err instanceof Error ? err.message : String(err);
1043
+ this.safeEmit(
1044
+ () => this.metrics?.onEstimatorError?.({
1045
+ scenario: opts.scenario,
1046
+ reason
1047
+ })
1048
+ );
1049
+ return { gasUnits: this.fallbackGasUnits, source: "fallback" };
1050
+ }
1051
+ }
1052
+ safeEmit(fn) {
1053
+ try {
1054
+ fn();
1055
+ } catch {
1056
+ }
1057
+ }
915
1058
  };
916
1059
 
1060
+ // src/relay/bundlerEstimator.ts
1061
+ var PafiEstimatorHttpError = class extends Error {
1062
+ status;
1063
+ body;
1064
+ constructor(status, body, message) {
1065
+ super(message ?? `PAFI estimator HTTP ${status}`);
1066
+ this.status = status;
1067
+ this.body = body;
1068
+ }
1069
+ };
1070
+ function createPafiEstimatorClient(config) {
1071
+ const { baseUrl, apiKey, issuerId } = config;
1072
+ if (!baseUrl) throw new Error("createPafiEstimatorClient: baseUrl required");
1073
+ if (!apiKey) throw new Error("createPafiEstimatorClient: apiKey required");
1074
+ if (!issuerId) throw new Error("createPafiEstimatorClient: issuerId required");
1075
+ const fetchImpl = config.fetchImpl ?? globalThis.fetch;
1076
+ if (!fetchImpl) {
1077
+ throw new Error(
1078
+ "createPafiEstimatorClient: no fetch implementation available \u2014 pass `fetchImpl`"
1079
+ );
1080
+ }
1081
+ const url = `${baseUrl.replace(/\/$/, "")}/v1/estimate-gas-fee`;
1082
+ return {
1083
+ async getGasUnits(input) {
1084
+ const body = JSON.stringify({
1085
+ partialUserOp: {
1086
+ sender: input.partialUserOp.sender,
1087
+ // Hex-encode bigint for JSON safety. Sponsor-relayer parses
1088
+ // back to bigint at the DTO layer.
1089
+ nonce: `0x${input.partialUserOp.nonce.toString(16)}`,
1090
+ callData: input.partialUserOp.callData,
1091
+ signature: input.partialUserOp.signature
1092
+ },
1093
+ scenario: input.scenario,
1094
+ contractAddress: input.contractAddress,
1095
+ paymasterAddress: input.paymasterAddress
1096
+ });
1097
+ const res = await fetchImpl(url, {
1098
+ method: "POST",
1099
+ headers: {
1100
+ "content-type": "application/json",
1101
+ authorization: `Bearer ${apiKey}`,
1102
+ "x-issuer-id": issuerId
1103
+ },
1104
+ body
1105
+ });
1106
+ if (!res.ok) {
1107
+ let errBody = null;
1108
+ try {
1109
+ errBody = await res.json();
1110
+ } catch {
1111
+ }
1112
+ throw new PafiEstimatorHttpError(res.status, errBody);
1113
+ }
1114
+ const json = await res.json();
1115
+ return {
1116
+ gasUnits: BigInt(json.gasUnits),
1117
+ source: json.source,
1118
+ expiresAt: json.expiresAt
1119
+ };
1120
+ }
1121
+ };
1122
+ }
1123
+
917
1124
  // src/indexer/types.ts
918
1125
  var InMemoryCursorStore = class {
919
1126
  cursor;
@@ -1708,8 +1915,59 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
1708
1915
  }
1709
1916
  };
1710
1917
 
1711
- // src/api/handlers/ptRedeemHandler.ts
1918
+ // src/api/pointTokenDomainResolver.ts
1712
1919
  import { getAddress as getAddress6 } from "viem";
1920
+ var NAME_ABI = [
1921
+ {
1922
+ type: "function",
1923
+ name: "name",
1924
+ stateMutability: "view",
1925
+ inputs: [],
1926
+ outputs: [{ type: "string" }]
1927
+ }
1928
+ ];
1929
+ var PointTokenDomainResolver = class {
1930
+ provider;
1931
+ overrides;
1932
+ cache = /* @__PURE__ */ new Map();
1933
+ constructor(config) {
1934
+ this.provider = config.provider;
1935
+ this.overrides = /* @__PURE__ */ new Map();
1936
+ if (config.overrides) {
1937
+ for (const [addr, name] of Object.entries(config.overrides)) {
1938
+ this.overrides.set(getAddress6(addr).toLowerCase(), name);
1939
+ }
1940
+ }
1941
+ }
1942
+ async resolve(pointTokenAddress) {
1943
+ const key = getAddress6(pointTokenAddress).toLowerCase();
1944
+ const cached = this.cache.get(key);
1945
+ if (cached !== void 0) return cached;
1946
+ const override = this.overrides.get(key);
1947
+ if (override !== void 0) {
1948
+ this.cache.set(key, override);
1949
+ return override;
1950
+ }
1951
+ const name = await this.provider.readContract({
1952
+ address: pointTokenAddress,
1953
+ abi: NAME_ABI,
1954
+ functionName: "name"
1955
+ });
1956
+ this.cache.set(key, name);
1957
+ return name;
1958
+ }
1959
+ /** Invalidate one address (after deploy / proxy upgrade) or all. */
1960
+ invalidate(pointTokenAddress) {
1961
+ if (pointTokenAddress) {
1962
+ this.cache.delete(getAddress6(pointTokenAddress).toLowerCase());
1963
+ } else {
1964
+ this.cache.clear();
1965
+ }
1966
+ }
1967
+ };
1968
+
1969
+ // src/api/handlers/ptRedeemHandler.ts
1970
+ import { getAddress as getAddress7 } from "viem";
1713
1971
  import {
1714
1972
  signBurnRequest,
1715
1973
  POINT_TOKEN_ABI as POINT_TOKEN_ABI2,
@@ -1735,10 +1993,9 @@ var PTRedeemHandler = class {
1735
1993
  relayService;
1736
1994
  provider;
1737
1995
  feeService;
1738
- pointTokenAddress;
1739
1996
  batchExecutorAddress;
1740
1997
  chainId;
1741
- domain;
1998
+ domainResolver;
1742
1999
  burnerSignerWallet;
1743
2000
  redeemLockDurationMs;
1744
2001
  signatureDeadlineSeconds;
@@ -1775,10 +2032,9 @@ var PTRedeemHandler = class {
1775
2032
  this.relayService = config.relayService;
1776
2033
  this.provider = config.provider;
1777
2034
  this.feeService = config.feeService;
1778
- this.pointTokenAddress = getAddress6(config.pointTokenAddress);
1779
- this.batchExecutorAddress = getAddress6(config.batchExecutorAddress);
2035
+ this.batchExecutorAddress = getAddress7(config.batchExecutorAddress);
1780
2036
  this.chainId = config.chainId;
1781
- this.domain = config.domain;
2037
+ this.domainResolver = config.domainResolver;
1782
2038
  this.burnerSignerWallet = config.burnerSignerWallet;
1783
2039
  if (this.burnerSignerWallet?.account?.type === "local") {
1784
2040
  console.warn("[PAFI] PTRedeemHandler: burnerSignerWallet uses a local (private key) account. Use a KMS-backed signer in production.");
@@ -1791,7 +2047,7 @@ var PTRedeemHandler = class {
1791
2047
  }
1792
2048
  }
1793
2049
  async handle(request) {
1794
- if (getAddress6(request.authenticatedAddress) !== getAddress6(request.userAddress)) {
2050
+ if (getAddress7(request.authenticatedAddress) !== getAddress7(request.userAddress)) {
1795
2051
  throw new PTRedeemError(
1796
2052
  "UNAUTHORIZED",
1797
2053
  `userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`
@@ -1800,11 +2056,12 @@ var PTRedeemHandler = class {
1800
2056
  if (request.amount <= 0n) {
1801
2057
  throw new PTRedeemError("INVALID_AMOUNT", "redeem amount must be positive");
1802
2058
  }
2059
+ const pointTokenAddress = getAddress7(request.pointTokenAddress);
1803
2060
  if (this.redemptionService) {
1804
2061
  const decision = await this.redemptionService.evaluate(
1805
2062
  request.userAddress,
1806
2063
  request.amount,
1807
- this.pointTokenAddress
2064
+ pointTokenAddress
1808
2065
  );
1809
2066
  if (!decision.allowed) {
1810
2067
  const denial = decision.denial;
@@ -1818,7 +2075,7 @@ var PTRedeemHandler = class {
1818
2075
  let burnNonce;
1819
2076
  try {
1820
2077
  burnNonce = await this.provider.readContract({
1821
- address: this.pointTokenAddress,
2078
+ address: pointTokenAddress,
1822
2079
  abi: POINT_TOKEN_ABI2,
1823
2080
  functionName: "burnRequestNonces",
1824
2081
  args: [request.userAddress]
@@ -1829,32 +2086,50 @@ var PTRedeemHandler = class {
1829
2086
  `failed to read burnRequestNonces(${request.userAddress}): ${err instanceof Error ? err.message : String(err)}`
1830
2087
  );
1831
2088
  }
1832
- const userKey = getAddress6(request.userAddress).toLowerCase();
1833
- let userNonces = this.inFlightNonces.get(userKey);
2089
+ const nonceKey = `${getAddress7(request.userAddress).toLowerCase()}:${pointTokenAddress.toLowerCase()}`;
2090
+ let userNonces = this.inFlightNonces.get(nonceKey);
1834
2091
  if (!userNonces) {
1835
2092
  userNonces = /* @__PURE__ */ new Set();
1836
- this.inFlightNonces.set(userKey, userNonces);
2093
+ this.inFlightNonces.set(nonceKey, userNonces);
1837
2094
  }
1838
2095
  if (userNonces.has(burnNonce)) {
1839
2096
  throw new PTRedeemError(
1840
2097
  "NONCE_IN_FLIGHT",
1841
- `A burn request for nonce ${burnNonce} is already in progress for ${request.userAddress}. Retry after the current request completes.`
2098
+ `A burn request for nonce ${burnNonce} is already in progress for ${request.userAddress} on ${pointTokenAddress}. Retry after the current request completes.`
1842
2099
  );
1843
2100
  }
1844
2101
  userNonces.add(burnNonce);
1845
2102
  try {
1846
- return await this._handleAfterNonceLock(request, burnNonce);
2103
+ return await this._handleAfterNonceLock(request, burnNonce, pointTokenAddress);
1847
2104
  } finally {
1848
2105
  userNonces.delete(burnNonce);
1849
- if (userNonces.size === 0) this.inFlightNonces.delete(userKey);
2106
+ if (userNonces.size === 0) this.inFlightNonces.delete(nonceKey);
1850
2107
  }
1851
2108
  }
1852
- async _handleAfterNonceLock(request, burnNonce) {
2109
+ async _handleAfterNonceLock(request, burnNonce, pointTokenAddress) {
2110
+ const previewDeadline = BigInt(
2111
+ Math.floor(this.now() / 1e3) + this.signatureDeadlineSeconds
2112
+ );
1853
2113
  let fee;
1854
2114
  if (request.feeAmount !== void 0) {
1855
2115
  fee = request.feeAmount > 0n ? request.feeAmount : 0n;
1856
2116
  } else if (this.feeService) {
1857
- fee = await this.feeService.estimateGasFee();
2117
+ const previewUserOp = this.relayService.previewBurnUserOp({
2118
+ userAddress: request.userAddress,
2119
+ aaNonce: burnNonce,
2120
+ pointTokenAddress,
2121
+ amount: request.amount,
2122
+ deadline: previewDeadline
2123
+ });
2124
+ fee = await this.feeService.estimateGasFee({
2125
+ scenario: "burn",
2126
+ contractAddress: pointTokenAddress,
2127
+ partialUserOp: {
2128
+ sender: previewUserOp.sender,
2129
+ nonce: previewUserOp.nonce,
2130
+ callData: previewUserOp.callData
2131
+ }
2132
+ });
1858
2133
  } else {
1859
2134
  fee = 0n;
1860
2135
  }
@@ -1867,7 +2142,7 @@ var PTRedeemHandler = class {
1867
2142
  }
1868
2143
  const onChainBalance = await getPointTokenBalance2(
1869
2144
  this.provider,
1870
- this.pointTokenAddress,
2145
+ pointTokenAddress,
1871
2146
  request.userAddress
1872
2147
  );
1873
2148
  if (onChainBalance < request.amount) {
@@ -1876,13 +2151,12 @@ var PTRedeemHandler = class {
1876
2151
  `insufficient on-chain PT balance: have ${onChainBalance}, need ${request.amount}`
1877
2152
  );
1878
2153
  }
1879
- const deadline = BigInt(
1880
- Math.floor(this.now() / 1e3) + this.signatureDeadlineSeconds
1881
- );
2154
+ const deadline = previewDeadline;
2155
+ const domainName = await this.domainResolver.resolve(pointTokenAddress);
1882
2156
  const domain = {
1883
- name: this.domain.name,
2157
+ name: domainName,
1884
2158
  chainId: this.chainId,
1885
- verifyingContract: this.domain.verifyingContract ?? this.pointTokenAddress
2159
+ verifyingContract: pointTokenAddress
1886
2160
  };
1887
2161
  const sponsoredBurnAmount = request.amount - fee;
1888
2162
  const sponsoredBurnRequest = {
@@ -1904,14 +2178,14 @@ var PTRedeemHandler = class {
1904
2178
  request.userAddress,
1905
2179
  sponsoredBurnAmount,
1906
2180
  this.redeemLockDurationMs,
1907
- this.pointTokenAddress
2181
+ pointTokenAddress
1908
2182
  );
1909
2183
  try {
1910
2184
  const sponsoredUserOp = await this.relayService.prepareBurn({
1911
2185
  mode: "burnWithSig",
1912
2186
  userAddress: request.userAddress,
1913
2187
  aaNonce: request.aaNonce,
1914
- pointTokenAddress: this.pointTokenAddress,
2188
+ pointTokenAddress,
1915
2189
  batchExecutorAddress: this.batchExecutorAddress,
1916
2190
  burnRequest: sponsoredBurnRequest,
1917
2191
  burnerSignature: sponsoredSig,
@@ -1939,7 +2213,7 @@ var PTRedeemHandler = class {
1939
2213
  request.userAddress,
1940
2214
  request.amount,
1941
2215
  this.redeemLockDurationMs,
1942
- this.pointTokenAddress
2216
+ pointTokenAddress
1943
2217
  );
1944
2218
  let fallbackUserOp;
1945
2219
  try {
@@ -1947,7 +2221,7 @@ var PTRedeemHandler = class {
1947
2221
  mode: "burnWithSig",
1948
2222
  userAddress: request.userAddress,
1949
2223
  aaNonce: request.aaNonce,
1950
- pointTokenAddress: this.pointTokenAddress,
2224
+ pointTokenAddress,
1951
2225
  batchExecutorAddress: this.batchExecutorAddress,
1952
2226
  burnRequest: fallbackBurnRequest,
1953
2227
  burnerSignature: fallbackSig,
@@ -1972,7 +2246,7 @@ var PTRedeemHandler = class {
1972
2246
  await this.redemptionService.recordSuccessfulInitiate({
1973
2247
  user: request.userAddress,
1974
2248
  amountPt: request.amount,
1975
- pointTokenAddress: this.pointTokenAddress,
2249
+ pointTokenAddress,
1976
2250
  reservationId: sponsoredLockId
1977
2251
  }).catch(() => {
1978
2252
  });
@@ -2095,7 +2369,7 @@ async function handleRedeemStatus(params) {
2095
2369
  }
2096
2370
 
2097
2371
  // src/api/mobileHandlers.ts
2098
- import { getAddress as getAddress7 } from "viem";
2372
+ import { getAddress as getAddress8 } from "viem";
2099
2373
  import {
2100
2374
  ENTRY_POINT_V08,
2101
2375
  parseEip7702DelegatedAddress
@@ -2447,7 +2721,7 @@ async function handleMobileSubmit(params) {
2447
2721
  if (!entry) {
2448
2722
  throw new PendingUserOpNotFoundError(params.lockId);
2449
2723
  }
2450
- if (getAddress7(entry.sender) !== getAddress7(params.authenticatedAddress)) {
2724
+ if (getAddress8(entry.sender) !== getAddress8(params.authenticatedAddress)) {
2451
2725
  throw new PendingUserOpForbiddenError(params.lockId);
2452
2726
  }
2453
2727
  const variant = params.variant ?? "sponsored";
@@ -2463,7 +2737,7 @@ async function handleMobileSubmit(params) {
2463
2737
  }
2464
2738
 
2465
2739
  // src/api/handlers/ptClaimHandler.ts
2466
- import { getAddress as getAddress8 } from "viem";
2740
+ import { getAddress as getAddress9 } from "viem";
2467
2741
  import {
2468
2742
  decodeBatchExecuteCalls,
2469
2743
  getContractAddresses as getContractAddresses3
@@ -2512,7 +2786,7 @@ var PTClaimHandler = class {
2512
2786
  };
2513
2787
  }
2514
2788
  async handle(request) {
2515
- if (getAddress8(request.authenticatedAddress) !== getAddress8(request.userAddress)) {
2789
+ if (getAddress9(request.authenticatedAddress) !== getAddress9(request.userAddress)) {
2516
2790
  throw new PTClaimError(
2517
2791
  "VALIDATION_FAILED",
2518
2792
  `userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`
@@ -2550,9 +2824,28 @@ var PTClaimHandler = class {
2550
2824
  const signatureDeadline = BigInt(
2551
2825
  Math.floor(this.cfg.now() / 1e3) + this.cfg.signatureDeadlineSeconds
2552
2826
  );
2553
- const feeAmount = this.cfg.feeService ? await this.cfg.feeService.estimateGasFee() : 0n;
2827
+ const previewUserOp = this.cfg.relayService.previewMintUserOp({
2828
+ userAddress: request.userAddress,
2829
+ aaNonce: request.aaNonce,
2830
+ pointTokenAddress: request.pointTokenAddress,
2831
+ amount: request.amount,
2832
+ deadline: signatureDeadline,
2833
+ mintFeeWrapperAddress: resolvedWrapper
2834
+ });
2835
+ const feeAmount = this.cfg.feeService ? await this.cfg.feeService.estimateGasFee({
2836
+ scenario: resolvedWrapper ? "mint-wrapped" : "mint",
2837
+ contractAddress: request.pointTokenAddress,
2838
+ partialUserOp: {
2839
+ sender: previewUserOp.sender,
2840
+ nonce: previewUserOp.nonce,
2841
+ callData: previewUserOp.callData
2842
+ }
2843
+ }) : 0n;
2844
+ const domainName = await this.cfg.domainResolver.resolve(
2845
+ request.pointTokenAddress
2846
+ );
2554
2847
  const domain = {
2555
- name: this.cfg.pointTokenDomainName,
2848
+ name: domainName,
2556
2849
  chainId: request.chainId,
2557
2850
  verifyingContract: request.pointTokenAddress
2558
2851
  };
@@ -2767,7 +3060,7 @@ import {
2767
3060
  getContractAddresses as getContractAddresses5,
2768
3061
  serializeUserOpToJsonRpc as serializeUserOpToJsonRpc2
2769
3062
  } from "@pafi-dev/core";
2770
- import { getAddress as getAddress9 } from "viem";
3063
+ import { getAddress as getAddress10 } from "viem";
2771
3064
  var DEFAULT_DELEGATE_GAS = {
2772
3065
  callGasLimit: 100000n,
2773
3066
  verificationGasLimit: 150000n,
@@ -2854,7 +3147,7 @@ async function handleDelegateSubmit(params) {
2854
3147
  if (!entry) {
2855
3148
  throw new PendingUserOpNotFoundError(params.lockId);
2856
3149
  }
2857
- if (getAddress9(entry.sender) !== getAddress9(params.authenticatedAddress)) {
3150
+ if (getAddress10(entry.sender) !== getAddress10(params.authenticatedAddress)) {
2858
3151
  throw new PendingUserOpForbiddenError(params.lockId);
2859
3152
  }
2860
3153
  if (!entry.eip7702Auth) {
@@ -2875,7 +3168,7 @@ async function handleDelegateSubmit(params) {
2875
3168
 
2876
3169
  // src/api/issuerApiAdapter.ts
2877
3170
  import { randomUUID } from "crypto";
2878
- import { getAddress as getAddress10 } from "viem";
3171
+ import { getAddress as getAddress11 } from "viem";
2879
3172
  import {
2880
3173
  buildAndSignSponsorAuth,
2881
3174
  decodeBatchExecuteCalls as decodeBatchExecuteCalls3,
@@ -2940,7 +3233,7 @@ var IssuerApiAdapter = class {
2940
3233
  async pools(authenticatedAddress, chainId, pointTokenAddress) {
2941
3234
  const result = await this.cfg.issuerService.api.handlePools(
2942
3235
  authenticatedAddress,
2943
- { chainId, pointTokenAddress: getAddress10(pointTokenAddress) }
3236
+ { chainId, pointTokenAddress: getAddress11(pointTokenAddress) }
2944
3237
  );
2945
3238
  return { pools: result.pools };
2946
3239
  }
@@ -2949,8 +3242,8 @@ var IssuerApiAdapter = class {
2949
3242
  authenticatedAddress,
2950
3243
  {
2951
3244
  chainId,
2952
- userAddress: getAddress10(userAddress),
2953
- pointTokenAddress: getAddress10(pointTokenAddress)
3245
+ userAddress: getAddress11(userAddress),
3246
+ pointTokenAddress: getAddress11(pointTokenAddress)
2954
3247
  }
2955
3248
  );
2956
3249
  return {
@@ -2971,7 +3264,7 @@ var IssuerApiAdapter = class {
2971
3264
  "ptClaimHandler",
2972
3265
  "claim"
2973
3266
  );
2974
- const pointTokenAddress = getAddress10(input.pointTokenAddress);
3267
+ const pointTokenAddress = getAddress11(input.pointTokenAddress);
2975
3268
  const result = await ptClaimHandler.handle({
2976
3269
  authenticatedAddress: input.authenticatedAddress,
2977
3270
  userAddress: input.authenticatedAddress,
@@ -2998,9 +3291,11 @@ var IssuerApiAdapter = class {
2998
3291
  }
2999
3292
  async redeem(input) {
3000
3293
  this.assertRedeemHandler();
3294
+ const pointTokenAddress = getAddress11(input.pointTokenAddress);
3001
3295
  const response = await this.cfg.ptRedeemHandler.handle({
3002
3296
  userAddress: input.authenticatedAddress,
3003
3297
  authenticatedAddress: input.authenticatedAddress,
3298
+ pointTokenAddress,
3004
3299
  amount: input.amount,
3005
3300
  aaNonce: input.aaNonce,
3006
3301
  chainId: input.chainId
@@ -3066,7 +3361,7 @@ var IssuerApiAdapter = class {
3066
3361
  "ptClaimHandler",
3067
3362
  "claimPrepare"
3068
3363
  );
3069
- const pointTokenAddress = getAddress10(input.pointTokenAddress);
3364
+ const pointTokenAddress = getAddress11(input.pointTokenAddress);
3070
3365
  const claimResult = await ptClaimHandler.handle({
3071
3366
  authenticatedAddress: input.authenticatedAddress,
3072
3367
  userAddress: input.authenticatedAddress,
@@ -3112,10 +3407,11 @@ var IssuerApiAdapter = class {
3112
3407
  }
3113
3408
  async redeemPrepare(input) {
3114
3409
  this.assertRedeemHandler();
3115
- const pointTokenAddress = getAddress10(input.pointTokenAddress);
3410
+ const pointTokenAddress = getAddress11(input.pointTokenAddress);
3116
3411
  const redeemResponse = await this.cfg.ptRedeemHandler.handle({
3117
3412
  userAddress: input.authenticatedAddress,
3118
3413
  authenticatedAddress: input.authenticatedAddress,
3414
+ pointTokenAddress,
3119
3415
  amount: input.amount,
3120
3416
  aaNonce: input.aaNonce,
3121
3417
  chainId: input.chainId
@@ -3863,7 +4159,7 @@ var PafiBackendClient = class {
3863
4159
  };
3864
4160
 
3865
4161
  // src/config.ts
3866
- import { getAddress as getAddress11 } from "viem";
4162
+ import { getAddress as getAddress12 } from "viem";
3867
4163
  import { getContractAddresses as getContractAddresses7 } from "@pafi-dev/core";
3868
4164
 
3869
4165
  // src/redemption/evaluator.ts
@@ -4192,7 +4488,7 @@ function createIssuerService(config) {
4192
4488
  "createIssuerService: at least one of pointTokenAddress / pointTokenAddresses is required"
4193
4489
  );
4194
4490
  }
4195
- const tokenAddresses = rawAddresses.map((a) => getAddress11(a));
4491
+ const tokenAddresses = rawAddresses.map((a) => getAddress12(a));
4196
4492
  const ledger = config.ledger;
4197
4493
  const sessionStore = config.sessionStore ?? new MemorySessionStore();
4198
4494
  const policy = config.policy ?? new DefaultPolicyEngine({ ledger });
@@ -4311,7 +4607,7 @@ function createIssuerService(config) {
4311
4607
  }
4312
4608
 
4313
4609
  // src/issuer-state/validator.ts
4314
- import { getAddress as getAddress12 } from "viem";
4610
+ import { getAddress as getAddress13 } from "viem";
4315
4611
  import {
4316
4612
  POINT_TOKEN_ABI as POINT_TOKEN_ABI3,
4317
4613
  issuerRegistryAbi,
@@ -4343,7 +4639,7 @@ var IssuerStateValidator = class _IssuerStateValidator {
4343
4639
  */
4344
4640
  invalidate(pointToken) {
4345
4641
  if (pointToken) {
4346
- const key = getAddress12(pointToken);
4642
+ const key = getAddress13(pointToken);
4347
4643
  this.pointTokenIssuerCache.delete(key);
4348
4644
  this.stateCache.delete(key);
4349
4645
  this.inflight.delete(key);
@@ -4358,7 +4654,7 @@ var IssuerStateValidator = class _IssuerStateValidator {
4358
4654
  * The issuer field is set at `initialize()` and never changes.
4359
4655
  */
4360
4656
  async getIssuerAddressForPointToken(pointToken) {
4361
- const key = getAddress12(pointToken);
4657
+ const key = getAddress13(pointToken);
4362
4658
  const cached = this.pointTokenIssuerCache.get(key);
4363
4659
  if (cached) return cached;
4364
4660
  const issuer = await this.provider.readContract({
@@ -4366,15 +4662,15 @@ var IssuerStateValidator = class _IssuerStateValidator {
4366
4662
  abi: POINT_TOKEN_ABI3,
4367
4663
  functionName: "issuer"
4368
4664
  });
4369
- this.pointTokenIssuerCache.set(key, getAddress12(issuer));
4370
- return getAddress12(issuer);
4665
+ this.pointTokenIssuerCache.set(key, getAddress13(issuer));
4666
+ return getAddress13(issuer);
4371
4667
  }
4372
4668
  /**
4373
4669
  * Read registry record + totalSupply, with 30s cache and in-flight
4374
4670
  * deduplication. Does NOT throw on inactive/missing — returns raw state.
4375
4671
  */
4376
4672
  async getIssuerState(pointToken) {
4377
- const tokenAddr = getAddress12(pointToken);
4673
+ const tokenAddr = getAddress13(pointToken);
4378
4674
  const now = Date.now();
4379
4675
  const cached = this.stateCache.get(tokenAddr);
4380
4676
  if (cached && cached.expiresAt > now) return cached.value;
@@ -4517,7 +4813,7 @@ var MemoryRedemptionHistoryStore = class {
4517
4813
  };
4518
4814
 
4519
4815
  // src/index.ts
4520
- var PAFI_ISSUER_SDK_VERSION = true ? "0.19.0" : "dev";
4816
+ var PAFI_ISSUER_SDK_VERSION = true ? "0.21.0" : "dev";
4521
4817
  export {
4522
4818
  AdapterMisconfiguredError,
4523
4819
  AuthError,
@@ -4549,12 +4845,14 @@ export {
4549
4845
  PTRedeemHandler,
4550
4846
  PafiBackendClient,
4551
4847
  PafiBackendError,
4848
+ PafiEstimatorHttpError,
4552
4849
  PafiSdkError,
4553
4850
  PendingUserOpForbiddenError,
4554
4851
  PendingUserOpNotFoundError,
4555
4852
  PerpDepositError,
4556
4853
  PerpDepositHandler,
4557
4854
  PointIndexer,
4855
+ PointTokenDomainResolver,
4558
4856
  PolicyProvider,
4559
4857
  REDEMPTION_HISTORY_WINDOW_SEC,
4560
4858
  RedemptionService,
@@ -4569,6 +4867,7 @@ export {
4569
4867
  buildSdkErrorBody,
4570
4868
  createIssuerService,
4571
4869
  createNativePtQuoter,
4870
+ createPafiEstimatorClient,
4572
4871
  createSdkErrorMapper,
4573
4872
  createSubgraphNativeUsdtQuoter,
4574
4873
  createSubgraphPoolsProvider,