@pafi-dev/issuer 0.23.0 → 0.25.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
@@ -579,12 +579,14 @@ import {
579
579
  erc20Abi
580
580
  } from "viem";
581
581
  import {
582
- POINT_TOKEN_ABI,
582
+ POINT_TOKEN_MINT_SIG_ABI,
583
+ POINT_TOKEN_BURN_SIG_ABI,
583
584
  mintFeeWrapperAbi,
584
585
  buildPartialUserOperation,
585
586
  signMintRequest,
586
587
  getContractAddresses,
587
- quoteOperatorFeePt
588
+ quoteOperatorFeePt,
589
+ Source
588
590
  } from "@pafi-dev/core";
589
591
  var RelayService = class {
590
592
  provider;
@@ -673,6 +675,7 @@ var RelayService = class {
673
675
  "prepareMint: deadline exceeds maximum allowed window (1 hour)"
674
676
  );
675
677
  }
678
+ const MINT_SOURCE = Source.EQUITY;
676
679
  const useWrapper = params.mintFeeWrapperAddress !== void 0;
677
680
  const receiverForSig = useWrapper ? params.mintFeeWrapperAddress : params.userAddress;
678
681
  let minterSig;
@@ -684,6 +687,7 @@ var RelayService = class {
684
687
  user: params.userAddress,
685
688
  receiver: receiverForSig,
686
689
  amount: params.amount,
690
+ source: MINT_SOURCE,
687
691
  nonce: params.mintRequestNonce,
688
692
  deadline: params.deadline
689
693
  }
@@ -714,9 +718,15 @@ var RelayService = class {
714
718
  mintTarget = params.mintFeeWrapperAddress;
715
719
  } else {
716
720
  mintCallData = encodeFunctionData({
717
- abi: POINT_TOKEN_ABI,
721
+ abi: POINT_TOKEN_MINT_SIG_ABI,
718
722
  functionName: "mint",
719
- args: [params.userAddress, params.amount, params.deadline, minterSig]
723
+ args: [
724
+ params.userAddress,
725
+ params.amount,
726
+ MINT_SOURCE,
727
+ params.deadline,
728
+ minterSig
729
+ ]
720
730
  });
721
731
  mintTarget = params.pointTokenAddress;
722
732
  }
@@ -801,11 +811,12 @@ var RelayService = class {
801
811
  let burnCallData;
802
812
  try {
803
813
  burnCallData = encodeFunctionData({
804
- abi: POINT_TOKEN_ABI,
814
+ abi: POINT_TOKEN_BURN_SIG_ABI,
805
815
  functionName: "burn",
806
816
  args: [
807
817
  params.burnRequest.from,
808
818
  params.burnRequest.amount,
819
+ params.burnRequest.source,
809
820
  params.burnRequest.deadline,
810
821
  params.burnerSignature
811
822
  ]
@@ -900,11 +911,12 @@ var RelayService = class {
900
911
  mintTarget = params.mintFeeWrapperAddress;
901
912
  } else {
902
913
  mintCallData = encodeFunctionData({
903
- abi: POINT_TOKEN_ABI,
914
+ abi: POINT_TOKEN_MINT_SIG_ABI,
904
915
  functionName: "mint",
905
916
  args: [
906
917
  params.userAddress,
907
918
  params.amount,
919
+ Source.EQUITY,
908
920
  params.deadline,
909
921
  PLACEHOLDER_SIG_65
910
922
  ]
@@ -926,11 +938,12 @@ var RelayService = class {
926
938
  /** Burn-side mirror of `previewMintUserOp`. */
927
939
  previewBurnUserOp(params) {
928
940
  const burnCallData = encodeFunctionData({
929
- abi: POINT_TOKEN_ABI,
941
+ abi: POINT_TOKEN_BURN_SIG_ABI,
930
942
  functionName: "burn",
931
943
  args: [
932
944
  params.userAddress,
933
945
  params.amount,
946
+ Source.EQUITY,
934
947
  params.deadline,
935
948
  PLACEHOLDER_SIG_65
936
949
  ]
@@ -1148,6 +1161,16 @@ var InMemoryCursorStore = class _InMemoryCursorStore {
1148
1161
 
1149
1162
  // src/indexer/pointIndexer.ts
1150
1163
  import { getAddress as getAddress3, parseAbiItem } from "viem";
1164
+ var PointIndexerFinalizeError = class extends Error {
1165
+ constructor(message, context, cause) {
1166
+ super(message);
1167
+ this.context = context;
1168
+ this.cause = cause;
1169
+ this.name = "PointIndexerFinalizeError";
1170
+ }
1171
+ context;
1172
+ cause;
1173
+ };
1151
1174
  var TRANSFER_EVENT = parseAbiItem(
1152
1175
  "event Transfer(address indexed from, address indexed to, uint256 value)"
1153
1176
  );
@@ -1373,8 +1396,18 @@ var PointIndexer = class {
1373
1396
  evt.txHash,
1374
1397
  this.pointTokenAddress
1375
1398
  );
1376
- } catch {
1377
- return;
1399
+ } catch (err) {
1400
+ throw new PointIndexerFinalizeError(
1401
+ `PointIndexer.deductBalance failed for tx ${evt.txHash} (to=${evt.to}, amount=${evt.amount}, block=${evt.blockNumber}); cursor will NOT advance, next tick retries.`,
1402
+ {
1403
+ pointToken: this.pointTokenAddress,
1404
+ to: evt.to,
1405
+ amount: evt.amount,
1406
+ txHash: evt.txHash,
1407
+ blockNumber: evt.blockNumber
1408
+ },
1409
+ err
1410
+ );
1378
1411
  }
1379
1412
  try {
1380
1413
  await this.ledger.updateMintStatus(match.lockId, "MINTED", evt.txHash);
@@ -1396,6 +1429,16 @@ function pickMatchingLock(locks, amount) {
1396
1429
 
1397
1430
  // src/indexer/burnIndexer.ts
1398
1431
  import { getAddress as getAddress4, parseAbiItem as parseAbiItem2 } from "viem";
1432
+ var BurnIndexerFinalizeError = class extends Error {
1433
+ constructor(message, context, cause) {
1434
+ super(message);
1435
+ this.context = context;
1436
+ this.cause = cause;
1437
+ this.name = "BurnIndexerFinalizeError";
1438
+ }
1439
+ context;
1440
+ cause;
1441
+ };
1399
1442
  var TRANSFER_EVENT2 = parseAbiItem2(
1400
1443
  "event Transfer(address indexed from, address indexed to, uint256 value)"
1401
1444
  );
@@ -1563,7 +1606,18 @@ var BurnIndexer = class {
1563
1606
  try {
1564
1607
  await this.ledger.resolveCreditByBurnTx(lockId, evt.txHash);
1565
1608
  } catch (err) {
1566
- console.error("[PAFI] BurnIndexer finalize error \u2014 credit may be lost:", err);
1609
+ throw new BurnIndexerFinalizeError(
1610
+ `BurnIndexer.resolveCreditByBurnTx failed for tx ${evt.txHash} (lockId=${lockId}, from=${evt.from}, amount=${evt.amount}, block=${evt.blockNumber}); cursor will NOT advance, next tick retries.`,
1611
+ {
1612
+ pointToken: this.pointTokenAddress,
1613
+ from: evt.from,
1614
+ amount: evt.amount,
1615
+ txHash: evt.txHash,
1616
+ blockNumber: evt.blockNumber,
1617
+ lockId
1618
+ },
1619
+ err
1620
+ );
1567
1621
  }
1568
1622
  }
1569
1623
  };
@@ -1611,7 +1665,6 @@ function hashKeyToInt64(key) {
1611
1665
  import { getAddress as getAddress5 } from "viem";
1612
1666
  import {
1613
1667
  getMintFeeBps,
1614
- getMintRequestNonce,
1615
1668
  getPointTokenBalance,
1616
1669
  isMinter
1617
1670
  } from "@pafi-dev/core";
@@ -1883,14 +1936,12 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
1883
1936
  { requested: pointToken }
1884
1937
  );
1885
1938
  }
1886
- const [mintRequestNonce, offChainBalance, onChainBalance, minter] = await Promise.all([
1887
- getMintRequestNonce(this.provider, pointToken, normalizedAuthed),
1939
+ const [offChainBalance, onChainBalance, minter] = await Promise.all([
1888
1940
  this.ledger.getBalance(normalizedAuthed, pointToken),
1889
1941
  getPointTokenBalance(this.provider, pointToken, normalizedAuthed),
1890
1942
  isMinter(this.provider, pointToken, normalizedAuthed)
1891
1943
  ]);
1892
1944
  return {
1893
- mintRequestNonce,
1894
1945
  offChainBalance,
1895
1946
  onChainBalance,
1896
1947
  totalBalance: offChainBalance + onChainBalance,
@@ -2027,12 +2078,14 @@ var PointTokenDomainResolver = class {
2027
2078
  import { getAddress as getAddress7 } from "viem";
2028
2079
  import {
2029
2080
  signBurnRequest,
2030
- POINT_TOKEN_ABI as POINT_TOKEN_ABI2,
2081
+ POINT_TOKEN_ABI,
2031
2082
  getPointTokenBalance as getPointTokenBalance2,
2032
- getContractAddresses as getContractAddresses2
2083
+ getContractAddresses as getContractAddresses2,
2084
+ Source as Source2
2033
2085
  } from "@pafi-dev/core";
2034
2086
  var DEFAULT_REDEEM_LOCK_MS = 15 * 60 * 1e3;
2035
- var DEFAULT_SIG_DEADLINE_SEC = 15 * 60;
2087
+ var M11_SAFETY_MARGIN_MS = 30 * 1e3;
2088
+ var DEFAULT_SIG_DEADLINE_SEC = (DEFAULT_REDEEM_LOCK_MS - M11_SAFETY_MARGIN_MS) / 1e3;
2036
2089
  var PTRedeemError = class extends PafiSdkError {
2037
2090
  httpStatus = "unprocessable";
2038
2091
  code;
@@ -2099,6 +2152,13 @@ var PTRedeemHandler = class {
2099
2152
  this.redeemLockDurationMs = config.redeemLockDurationMs ?? DEFAULT_REDEEM_LOCK_MS;
2100
2153
  this.signatureDeadlineSeconds = config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC;
2101
2154
  this.now = config.now ?? (() => Date.now());
2155
+ const maxAllowedSignatureMs = this.redeemLockDurationMs - M11_SAFETY_MARGIN_MS;
2156
+ if (this.signatureDeadlineSeconds * 1e3 > maxAllowedSignatureMs) {
2157
+ throw new PTRedeemError(
2158
+ "INVALID_AMOUNT",
2159
+ `PTRedeemHandler config: signatureDeadlineSeconds (${this.signatureDeadlineSeconds}s) must be at most redeemLockDurationMs - safety margin = ${maxAllowedSignatureMs / 1e3}s (redeemLockDurationMs=${this.redeemLockDurationMs / 1e3}s, safety=${M11_SAFETY_MARGIN_MS / 1e3}s). See audit M-11.`
2160
+ );
2161
+ }
2102
2162
  if (config.redemptionService) {
2103
2163
  this.redemptionService = config.redemptionService;
2104
2164
  }
@@ -2133,7 +2193,7 @@ var PTRedeemHandler = class {
2133
2193
  try {
2134
2194
  burnNonce = await this.provider.readContract({
2135
2195
  address: pointTokenAddress,
2136
- abi: POINT_TOKEN_ABI2,
2196
+ abi: POINT_TOKEN_ABI,
2137
2197
  functionName: "burnRequestNonces",
2138
2198
  args: [request.userAddress]
2139
2199
  });
@@ -2164,8 +2224,14 @@ var PTRedeemHandler = class {
2164
2224
  }
2165
2225
  }
2166
2226
  async _handleAfterNonceLock(request, burnNonce, pointTokenAddress) {
2227
+ const referenceMs = this.now();
2228
+ const projectedLockExpiresAtMs = referenceMs + this.redeemLockDurationMs;
2229
+ const requestedDeadlineSec = Math.floor(referenceMs / 1e3) + this.signatureDeadlineSeconds;
2230
+ const lockBoundedDeadlineSec = Math.floor(
2231
+ (projectedLockExpiresAtMs - M11_SAFETY_MARGIN_MS) / 1e3
2232
+ );
2167
2233
  const previewDeadline = BigInt(
2168
- Math.floor(this.now() / 1e3) + this.signatureDeadlineSeconds
2234
+ Math.min(requestedDeadlineSec, lockBoundedDeadlineSec)
2169
2235
  );
2170
2236
  let fee;
2171
2237
  if (request.feeAmount !== void 0) {
@@ -2173,7 +2239,7 @@ var PTRedeemHandler = class {
2173
2239
  } else if (this.feeService) {
2174
2240
  const previewUserOp = this.relayService.previewBurnUserOp({
2175
2241
  userAddress: request.userAddress,
2176
- aaNonce: burnNonce,
2242
+ aaNonce: request.aaNonce,
2177
2243
  pointTokenAddress,
2178
2244
  amount: request.amount,
2179
2245
  deadline: previewDeadline
@@ -2215,10 +2281,12 @@ var PTRedeemHandler = class {
2215
2281
  chainId: this.chainId,
2216
2282
  verifyingContract: pointTokenAddress
2217
2283
  };
2284
+ const BURN_SOURCE = Source2.EQUITY;
2218
2285
  const sponsoredBurnAmount = request.amount - fee;
2219
2286
  const sponsoredBurnRequest = {
2220
2287
  from: request.userAddress,
2221
2288
  amount: sponsoredBurnAmount,
2289
+ source: BURN_SOURCE,
2222
2290
  nonce: burnNonce,
2223
2291
  deadline
2224
2292
  };
@@ -2254,6 +2322,7 @@ var PTRedeemHandler = class {
2254
2322
  const fallbackBurnRequest = {
2255
2323
  from: request.userAddress,
2256
2324
  amount: request.amount,
2325
+ source: BURN_SOURCE,
2257
2326
  nonce: burnNonce,
2258
2327
  deadline
2259
2328
  };
@@ -2796,6 +2865,7 @@ async function handleMobileSubmit(params) {
2796
2865
  // src/api/handlers/ptClaimHandler.ts
2797
2866
  import { getAddress as getAddress9 } from "viem";
2798
2867
  import {
2868
+ POINT_TOKEN_ABI as POINT_TOKEN_ABI2,
2799
2869
  decodeBatchExecuteCalls,
2800
2870
  getContractAddresses as getContractAddresses3
2801
2871
  } from "@pafi-dev/core";
@@ -2831,14 +2901,30 @@ function isNoWrapper2(address) {
2831
2901
  return lower === "0x0000000000000000000000000000000000000000" || lower === "0x000000000000000000000000000000000000dead";
2832
2902
  }
2833
2903
  var DEFAULT_LOCK_MS = 15 * 60 * 1e3;
2834
- var DEFAULT_SIG_DEADLINE_SEC2 = 15 * 60;
2904
+ var M11_SAFETY_MARGIN_MS2 = 30 * 1e3;
2905
+ var DEFAULT_SIG_DEADLINE_SEC2 = (DEFAULT_LOCK_MS - M11_SAFETY_MARGIN_MS2) / 1e3;
2835
2906
  var PTClaimHandler = class {
2836
2907
  cfg;
2908
+ inFlightNonces = /* @__PURE__ */ new Map();
2837
2909
  constructor(config) {
2910
+ const lockDurationMs = config.lockDurationMs ?? DEFAULT_LOCK_MS;
2911
+ const signatureDeadlineSeconds = config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC2;
2912
+ const maxAllowedSignatureMs = lockDurationMs - M11_SAFETY_MARGIN_MS2;
2913
+ if (signatureDeadlineSeconds * 1e3 > maxAllowedSignatureMs) {
2914
+ throw new PTClaimError(
2915
+ "VALIDATION_FAILED",
2916
+ `PTClaimHandler config: signatureDeadlineSeconds (${signatureDeadlineSeconds}s) must be at most lockDurationMs - safety margin = ${maxAllowedSignatureMs / 1e3}s (lockDurationMs=${lockDurationMs / 1e3}s, safety=${M11_SAFETY_MARGIN_MS2 / 1e3}s). See audit M-11.`,
2917
+ {
2918
+ lockDurationMs,
2919
+ signatureDeadlineSeconds,
2920
+ maxAllowedSignatureSec: maxAllowedSignatureMs / 1e3
2921
+ }
2922
+ );
2923
+ }
2838
2924
  this.cfg = {
2839
2925
  ...config,
2840
- lockDurationMs: config.lockDurationMs ?? DEFAULT_LOCK_MS,
2841
- signatureDeadlineSeconds: config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC2,
2926
+ lockDurationMs,
2927
+ signatureDeadlineSeconds,
2842
2928
  now: config.now ?? (() => Date.now())
2843
2929
  };
2844
2930
  }
@@ -2868,77 +2954,82 @@ var PTClaimHandler = class {
2868
2954
  }
2869
2955
  const chainAddresses = getContractAddresses3(request.chainId);
2870
2956
  const { batchExecutor: batchExecutorAddress } = chainAddresses;
2957
+ let mintRequestNonce;
2958
+ try {
2959
+ mintRequestNonce = await this.cfg.provider.readContract({
2960
+ address: request.pointTokenAddress,
2961
+ abi: POINT_TOKEN_ABI2,
2962
+ functionName: "mintRequestNonces",
2963
+ args: [request.userAddress]
2964
+ });
2965
+ } catch (err) {
2966
+ throw new PTClaimError(
2967
+ "NONCE_READ_FAILED",
2968
+ `failed to read mintRequestNonces(${request.userAddress}): ${err instanceof Error ? err.message : String(err)}`
2969
+ );
2970
+ }
2971
+ const nonceKey = `${getAddress9(request.userAddress).toLowerCase()}:${request.pointTokenAddress.toLowerCase()}`;
2972
+ let userNonces = this.inFlightNonces.get(nonceKey);
2973
+ if (!userNonces) {
2974
+ userNonces = /* @__PURE__ */ new Set();
2975
+ this.inFlightNonces.set(nonceKey, userNonces);
2976
+ }
2977
+ if (userNonces.has(mintRequestNonce)) {
2978
+ throw new PTClaimError(
2979
+ "NONCE_IN_FLIGHT",
2980
+ `concurrent claim for nonce ${mintRequestNonce} in progress; retry after the prior request completes`,
2981
+ { userAddress: request.userAddress, pointToken: request.pointTokenAddress, nonce: mintRequestNonce.toString() }
2982
+ );
2983
+ }
2984
+ userNonces.add(mintRequestNonce);
2871
2985
  const wrapperOverride = this.cfg.mintFeeWrapperAddress;
2872
2986
  const wrapperFromSdk = chainAddresses.mintFeeWrapper;
2873
2987
  const resolvedWrapper = wrapperOverride !== void 0 ? isNoWrapper2(wrapperOverride) ? void 0 : wrapperOverride : isNoWrapper2(wrapperFromSdk) ? void 0 : wrapperFromSdk;
2874
- const lockId = await this.cfg.ledger.lockForMinting(
2875
- request.userAddress,
2876
- request.amount,
2877
- this.cfg.lockDurationMs,
2878
- request.pointTokenAddress
2879
- );
2880
2988
  try {
2881
- const signatureDeadline = BigInt(
2882
- Math.floor(this.cfg.now() / 1e3) + this.cfg.signatureDeadlineSeconds
2883
- );
2884
- const previewUserOp = this.cfg.relayService.previewMintUserOp({
2885
- userAddress: request.userAddress,
2886
- aaNonce: request.aaNonce,
2887
- pointTokenAddress: request.pointTokenAddress,
2888
- amount: request.amount,
2889
- deadline: signatureDeadline,
2890
- mintFeeWrapperAddress: resolvedWrapper
2891
- });
2892
- const feeAmount = this.cfg.feeService ? await this.cfg.feeService.estimateGasFee({
2893
- scenario: resolvedWrapper ? "mint-wrapped" : "mint",
2894
- contractAddress: request.pointTokenAddress,
2895
- partialUserOp: {
2896
- sender: previewUserOp.sender,
2897
- nonce: previewUserOp.nonce,
2898
- callData: previewUserOp.callData
2899
- }
2900
- }) : 0n;
2901
- const domainName = await this.cfg.domainResolver.resolve(
2989
+ const lockCreatedAtMs = this.cfg.now();
2990
+ const lockExpiresAtMs = lockCreatedAtMs + this.cfg.lockDurationMs;
2991
+ const lockId = await this.cfg.ledger.lockForMinting(
2992
+ request.userAddress,
2993
+ request.amount,
2994
+ this.cfg.lockDurationMs,
2902
2995
  request.pointTokenAddress
2903
2996
  );
2904
- const domain = {
2905
- name: domainName,
2906
- chainId: request.chainId,
2907
- verifyingContract: request.pointTokenAddress
2908
- };
2909
- let userOp;
2910
2997
  try {
2911
- userOp = await this.cfg.relayService.prepareMint({
2998
+ const requestedDeadlineSec = Math.floor(lockCreatedAtMs / 1e3) + this.cfg.signatureDeadlineSeconds;
2999
+ const lockBoundedDeadlineSec = Math.floor(
3000
+ (lockExpiresAtMs - M11_SAFETY_MARGIN_MS2) / 1e3
3001
+ );
3002
+ const signatureDeadline = BigInt(
3003
+ Math.min(requestedDeadlineSec, lockBoundedDeadlineSec)
3004
+ );
3005
+ const previewUserOp = this.cfg.relayService.previewMintUserOp({
2912
3006
  userAddress: request.userAddress,
2913
3007
  aaNonce: request.aaNonce,
2914
- batchExecutorAddress,
2915
3008
  pointTokenAddress: request.pointTokenAddress,
2916
3009
  amount: request.amount,
2917
- issuerSignerWallet: this.cfg.issuerSignerWallet,
2918
- domain,
2919
- mintRequestNonce: request.mintRequestNonce,
2920
3010
  deadline: signatureDeadline,
2921
- mintFeeWrapperAddress: resolvedWrapper,
2922
- // Pass the bundler-estimated `feeAmount` explicitly so the
2923
- // RelayService skips its legacy `quoteOperatorFeePt` path
2924
- // (which uses the SDK's old 12_000 bps premium default).
2925
- // Without this, the response's `feeAmount` (from FeeManager,
2926
- // 100% premium on top of PAFI's 110% server-side estimate)
2927
- // would diverge from the actual PT.transfer amount in the
2928
- // UserOp batch (`quoteOperatorFeePt`'s 120%), and the user
2929
- // would see one value while the wallet transferred another.
2930
- feeAmount
3011
+ mintFeeWrapperAddress: resolvedWrapper
2931
3012
  });
2932
- } catch (err) {
2933
- throw new PTClaimError(
2934
- "BUILD_FAILED",
2935
- `prepareMint failed: ${err instanceof Error ? err.message : String(err)}`
3013
+ const feeAmount = this.cfg.feeService ? await this.cfg.feeService.estimateGasFee({
3014
+ scenario: resolvedWrapper ? "mint-wrapped" : "mint",
3015
+ contractAddress: request.pointTokenAddress,
3016
+ partialUserOp: {
3017
+ sender: previewUserOp.sender,
3018
+ nonce: previewUserOp.nonce,
3019
+ callData: previewUserOp.callData
3020
+ }
3021
+ }) : 0n;
3022
+ const domainName = await this.cfg.domainResolver.resolve(
3023
+ request.pointTokenAddress
2936
3024
  );
2937
- }
2938
- let fallback;
2939
- if (feeAmount > 0n) {
3025
+ const domain = {
3026
+ name: domainName,
3027
+ chainId: request.chainId,
3028
+ verifyingContract: request.pointTokenAddress
3029
+ };
3030
+ let userOp;
2940
3031
  try {
2941
- fallback = await this.cfg.relayService.prepareMint({
3032
+ userOp = await this.cfg.relayService.prepareMint({
2942
3033
  userAddress: request.userAddress,
2943
3034
  aaNonce: request.aaNonce,
2944
3035
  batchExecutorAddress,
@@ -2946,34 +3037,68 @@ var PTClaimHandler = class {
2946
3037
  amount: request.amount,
2947
3038
  issuerSignerWallet: this.cfg.issuerSignerWallet,
2948
3039
  domain,
2949
- mintRequestNonce: request.mintRequestNonce,
3040
+ mintRequestNonce,
2950
3041
  deadline: signatureDeadline,
2951
- feeAmount: 0n,
2952
- mintFeeWrapperAddress: resolvedWrapper
3042
+ mintFeeWrapperAddress: resolvedWrapper,
3043
+ // Pass the bundler-estimated `feeAmount` explicitly so the
3044
+ // RelayService skips its legacy `quoteOperatorFeePt` path
3045
+ // (which uses the SDK's old 12_000 bps premium default).
3046
+ // Without this, the response's `feeAmount` (from FeeManager,
3047
+ // 100% premium on top of PAFI's 110% server-side estimate)
3048
+ // would diverge from the actual PT.transfer amount in the
3049
+ // UserOp batch (`quoteOperatorFeePt`'s 120%), and the user
3050
+ // would see one value while the wallet transferred another.
3051
+ feeAmount
2953
3052
  });
2954
3053
  } catch (err) {
2955
3054
  throw new PTClaimError(
2956
3055
  "BUILD_FAILED",
2957
- `prepareMint (fallback) failed: ${err instanceof Error ? err.message : String(err)}`
3056
+ `prepareMint failed: ${err instanceof Error ? err.message : String(err)}`
2958
3057
  );
2959
3058
  }
3059
+ let fallback;
3060
+ if (feeAmount > 0n) {
3061
+ try {
3062
+ fallback = await this.cfg.relayService.prepareMint({
3063
+ userAddress: request.userAddress,
3064
+ aaNonce: request.aaNonce,
3065
+ batchExecutorAddress,
3066
+ pointTokenAddress: request.pointTokenAddress,
3067
+ amount: request.amount,
3068
+ issuerSignerWallet: this.cfg.issuerSignerWallet,
3069
+ domain,
3070
+ mintRequestNonce,
3071
+ deadline: signatureDeadline,
3072
+ feeAmount: 0n,
3073
+ mintFeeWrapperAddress: resolvedWrapper
3074
+ });
3075
+ } catch (err) {
3076
+ throw new PTClaimError(
3077
+ "BUILD_FAILED",
3078
+ `prepareMint (fallback) failed: ${err instanceof Error ? err.message : String(err)}`
3079
+ );
3080
+ }
3081
+ }
3082
+ const calls = decodeBatchExecuteCalls(userOp.callData);
3083
+ const callsFallback = fallback ? decodeBatchExecuteCalls(fallback.callData) : void 0;
3084
+ return {
3085
+ userOp,
3086
+ fallback,
3087
+ lockId,
3088
+ feeAmount,
3089
+ signatureDeadline,
3090
+ expiresInSeconds: Math.floor(this.cfg.lockDurationMs / 1e3),
3091
+ calls,
3092
+ callsFallback
3093
+ };
3094
+ } catch (err) {
3095
+ await this.cfg.ledger.releaseLock(lockId).catch(() => {
3096
+ });
3097
+ throw err;
2960
3098
  }
2961
- const calls = decodeBatchExecuteCalls(userOp.callData);
2962
- const callsFallback = fallback ? decodeBatchExecuteCalls(fallback.callData) : void 0;
2963
- return {
2964
- userOp,
2965
- fallback,
2966
- lockId,
2967
- feeAmount,
2968
- signatureDeadline,
2969
- expiresInSeconds: Math.floor(this.cfg.lockDurationMs / 1e3),
2970
- calls,
2971
- callsFallback
2972
- };
2973
- } catch (err) {
2974
- await this.cfg.ledger.releaseLock(lockId).catch(() => {
2975
- });
2976
- throw err;
3099
+ } finally {
3100
+ userNonces.delete(mintRequestNonce);
3101
+ if (userNonces.size === 0) this.inFlightNonces.delete(nonceKey);
2977
3102
  }
2978
3103
  }
2979
3104
  };
@@ -3312,7 +3437,6 @@ var IssuerApiAdapter = class {
3312
3437
  }
3313
3438
  );
3314
3439
  return {
3315
- mintRequestNonce: result.mintRequestNonce.toString(),
3316
3440
  offChainBalance: result.offChainBalance.toString(),
3317
3441
  onChainBalance: result.onChainBalance.toString(),
3318
3442
  totalBalance: result.totalBalance.toString(),
@@ -3336,8 +3460,7 @@ var IssuerApiAdapter = class {
3336
3460
  amount: input.amount,
3337
3461
  pointTokenAddress,
3338
3462
  chainId: input.chainId,
3339
- aaNonce: input.aaNonce,
3340
- mintRequestNonce: input.mintRequestNonce
3463
+ aaNonce: input.aaNonce
3341
3464
  });
3342
3465
  const sponsorAuth = await this.buildSponsorAuth(
3343
3466
  input.authenticatedAddress,
@@ -3433,8 +3556,7 @@ var IssuerApiAdapter = class {
3433
3556
  amount: input.amount,
3434
3557
  pointTokenAddress,
3435
3558
  chainId: input.chainId,
3436
- aaNonce: input.aaNonce,
3437
- mintRequestNonce: input.mintRequestNonce
3559
+ aaNonce: input.aaNonce
3438
3560
  });
3439
3561
  const prepared = await this.runMobilePrepare(
3440
3562
  input.authenticatedAddress,
@@ -4703,8 +4825,7 @@ import { getAddress as getAddress13 } from "viem";
4703
4825
  import {
4704
4826
  POINT_TOKEN_ABI as POINT_TOKEN_ABI3,
4705
4827
  issuerRegistryAbi,
4706
- getContractAddresses as getContractAddresses8,
4707
- getTokenCap
4828
+ getContractAddresses as getContractAddresses8
4708
4829
  } from "@pafi-dev/core";
4709
4830
  var ISSUER_RECORD_TTL_MS = 3e4;
4710
4831
  var IssuerStateValidator = class _IssuerStateValidator {
@@ -4805,22 +4926,22 @@ var IssuerStateValidator = class _IssuerStateValidator {
4805
4926
  }
4806
4927
  throw err;
4807
4928
  }
4808
- const { issuer, totalSupply, hardCap, remaining } = state;
4929
+ const { issuer, equityCap, equitySupply, remaining } = state;
4809
4930
  if (!issuer.active) {
4810
4931
  throw new IssuerStateError(
4811
4932
  "ISSUER_INACTIVE",
4812
- `Issuer ${issuer.issuerAddress} is deactivated on IssuerRegistry`,
4813
- { issuer: issuer.issuerAddress, pointToken: issuer.pointToken }
4933
+ `Issuer "${issuer.name}" is deactivated on IssuerRegistry`,
4934
+ { pointToken }
4814
4935
  );
4815
4936
  }
4816
- if (totalSupply + amount > hardCap) {
4937
+ if (equitySupply + amount > equityCap.hardCap) {
4817
4938
  throw new IssuerStateError(
4818
4939
  "MINT_CAP_EXCEEDED",
4819
- `Requested ${amount} PT would exceed mint cap. Cap=${hardCap}, minted=${totalSupply}, remaining=${remaining}`,
4940
+ `Requested ${amount} PT would exceed EQUITY mint cap. Cap=${equityCap.hardCap}, equityMinted=${equitySupply}, remaining=${remaining}`,
4820
4941
  {
4821
4942
  requested: amount.toString(),
4822
- cap: hardCap.toString(),
4823
- minted: totalSupply.toString(),
4943
+ cap: equityCap.hardCap.toString(),
4944
+ equityMinted: equitySupply.toString(),
4824
4945
  remaining: remaining.toString()
4825
4946
  }
4826
4947
  );
@@ -4836,33 +4957,28 @@ var IssuerStateValidator = class _IssuerStateValidator {
4836
4957
  args: [issuerAddr]
4837
4958
  });
4838
4959
  const issuer = {
4839
- issuerAddress: issuerStruct.issuerAddress,
4840
4960
  signerAddress: issuerStruct.signerAddress,
4841
4961
  name: issuerStruct.name,
4842
- symbol: issuerStruct.symbol,
4843
4962
  active: issuerStruct.active,
4844
- pointToken: issuerStruct.pointToken,
4845
- mintingOracle: issuerStruct.mintingOracle
4963
+ capitalBase: issuerStruct.capitalBase,
4964
+ basisPoints: Number(issuerStruct.basisPoints)
4846
4965
  };
4847
- const [tokenCap, totalSupply] = await Promise.all([
4848
- getTokenCap(this.provider, issuer.mintingOracle, tokenAddr),
4849
- this.provider.readContract({
4850
- address: tokenAddr,
4851
- abi: POINT_TOKEN_ABI3,
4852
- functionName: "totalSupply"
4853
- })
4854
- ]);
4855
- const tokenCapRecord = {
4856
- declaredTotalSupply: tokenCap.declaredTotalSupply,
4857
- capBasisPoints: tokenCap.capBasisPoints
4966
+ const equitySupply = await this.provider.readContract({
4967
+ address: tokenAddr,
4968
+ abi: POINT_TOKEN_ABI3,
4969
+ functionName: "equitySupply"
4970
+ });
4971
+ const hardCap = issuer.capitalBase * BigInt(issuer.basisPoints) / 10000n;
4972
+ const equityCap = {
4973
+ capitalBase: issuer.capitalBase,
4974
+ basisPoints: issuer.basisPoints,
4975
+ hardCap
4858
4976
  };
4859
- const hardCap = tokenCapRecord.declaredTotalSupply * BigInt(tokenCapRecord.capBasisPoints) / 10000n;
4860
- const remaining = hardCap > totalSupply ? hardCap - totalSupply : 0n;
4977
+ const remaining = hardCap > equitySupply ? hardCap - equitySupply : 0n;
4861
4978
  return {
4862
4979
  issuer,
4863
- tokenCap: tokenCapRecord,
4864
- totalSupply,
4865
- hardCap,
4980
+ equityCap,
4981
+ equitySupply,
4866
4982
  remaining
4867
4983
  };
4868
4984
  }
@@ -4905,7 +5021,7 @@ var MemoryRedemptionHistoryStore = class {
4905
5021
  };
4906
5022
 
4907
5023
  // src/index.ts
4908
- var PAFI_ISSUER_SDK_VERSION = true ? "0.23.0" : "dev";
5024
+ var PAFI_ISSUER_SDK_VERSION = true ? "0.25.0" : "dev";
4909
5025
  export {
4910
5026
  AdapterMisconfiguredError,
4911
5027
  AuthError,
@@ -4913,6 +5029,7 @@ export {
4913
5029
  BundlerNotConfiguredError,
4914
5030
  BundlerRejectedError,
4915
5031
  BurnIndexer,
5032
+ BurnIndexerFinalizeError,
4916
5033
  ConfigurationError,
4917
5034
  DEFAULT_REDEMPTION_POLICY,
4918
5035
  DefaultPolicyEngine,
@@ -4944,6 +5061,7 @@ export {
4944
5061
  PerpDepositError,
4945
5062
  PerpDepositHandler,
4946
5063
  PointIndexer,
5064
+ PointIndexerFinalizeError,
4947
5065
  PointTokenDomainResolver,
4948
5066
  PolicyProvider,
4949
5067
  REDEMPTION_HISTORY_WINDOW_SEC,