@pafi-dev/issuer 0.24.1 → 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.cjs CHANGED
@@ -2167,14 +2167,12 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
2167
2167
  { requested: pointToken }
2168
2168
  );
2169
2169
  }
2170
- const [mintRequestNonce, offChainBalance, onChainBalance, minter] = await Promise.all([
2171
- (0, import_core6.getMintRequestNonce)(this.provider, pointToken, normalizedAuthed),
2170
+ const [offChainBalance, onChainBalance, minter] = await Promise.all([
2172
2171
  this.ledger.getBalance(normalizedAuthed, pointToken),
2173
2172
  (0, import_core6.getPointTokenBalance)(this.provider, pointToken, normalizedAuthed),
2174
2173
  (0, import_core6.isMinter)(this.provider, pointToken, normalizedAuthed)
2175
2174
  ]);
2176
2175
  return {
2177
- mintRequestNonce,
2178
2176
  offChainBalance,
2179
2177
  onChainBalance,
2180
2178
  totalBalance: offChainBalance + onChainBalance,
@@ -2311,7 +2309,8 @@ var PointTokenDomainResolver = class {
2311
2309
  var import_viem8 = require("viem");
2312
2310
  var import_core7 = require("@pafi-dev/core");
2313
2311
  var DEFAULT_REDEEM_LOCK_MS = 15 * 60 * 1e3;
2314
- var DEFAULT_SIG_DEADLINE_SEC = 15 * 60;
2312
+ var M11_SAFETY_MARGIN_MS = 30 * 1e3;
2313
+ var DEFAULT_SIG_DEADLINE_SEC = (DEFAULT_REDEEM_LOCK_MS - M11_SAFETY_MARGIN_MS) / 1e3;
2315
2314
  var PTRedeemError = class extends import_core.PafiSdkError {
2316
2315
  httpStatus = "unprocessable";
2317
2316
  code;
@@ -2378,6 +2377,13 @@ var PTRedeemHandler = class {
2378
2377
  this.redeemLockDurationMs = config.redeemLockDurationMs ?? DEFAULT_REDEEM_LOCK_MS;
2379
2378
  this.signatureDeadlineSeconds = config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC;
2380
2379
  this.now = config.now ?? (() => Date.now());
2380
+ const maxAllowedSignatureMs = this.redeemLockDurationMs - M11_SAFETY_MARGIN_MS;
2381
+ if (this.signatureDeadlineSeconds * 1e3 > maxAllowedSignatureMs) {
2382
+ throw new PTRedeemError(
2383
+ "INVALID_AMOUNT",
2384
+ `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.`
2385
+ );
2386
+ }
2381
2387
  if (config.redemptionService) {
2382
2388
  this.redemptionService = config.redemptionService;
2383
2389
  }
@@ -2443,8 +2449,14 @@ var PTRedeemHandler = class {
2443
2449
  }
2444
2450
  }
2445
2451
  async _handleAfterNonceLock(request, burnNonce, pointTokenAddress) {
2452
+ const referenceMs = this.now();
2453
+ const projectedLockExpiresAtMs = referenceMs + this.redeemLockDurationMs;
2454
+ const requestedDeadlineSec = Math.floor(referenceMs / 1e3) + this.signatureDeadlineSeconds;
2455
+ const lockBoundedDeadlineSec = Math.floor(
2456
+ (projectedLockExpiresAtMs - M11_SAFETY_MARGIN_MS) / 1e3
2457
+ );
2446
2458
  const previewDeadline = BigInt(
2447
- Math.floor(this.now() / 1e3) + this.signatureDeadlineSeconds
2459
+ Math.min(requestedDeadlineSec, lockBoundedDeadlineSec)
2448
2460
  );
2449
2461
  let fee;
2450
2462
  if (request.feeAmount !== void 0) {
@@ -2452,7 +2464,7 @@ var PTRedeemHandler = class {
2452
2464
  } else if (this.feeService) {
2453
2465
  const previewUserOp = this.relayService.previewBurnUserOp({
2454
2466
  userAddress: request.userAddress,
2455
- aaNonce: burnNonce,
2467
+ aaNonce: request.aaNonce,
2456
2468
  pointTokenAddress,
2457
2469
  amount: request.amount,
2458
2470
  deadline: previewDeadline
@@ -3104,14 +3116,30 @@ function isNoWrapper2(address) {
3104
3116
  return lower === "0x0000000000000000000000000000000000000000" || lower === "0x000000000000000000000000000000000000dead";
3105
3117
  }
3106
3118
  var DEFAULT_LOCK_MS = 15 * 60 * 1e3;
3107
- var DEFAULT_SIG_DEADLINE_SEC2 = 15 * 60;
3119
+ var M11_SAFETY_MARGIN_MS2 = 30 * 1e3;
3120
+ var DEFAULT_SIG_DEADLINE_SEC2 = (DEFAULT_LOCK_MS - M11_SAFETY_MARGIN_MS2) / 1e3;
3108
3121
  var PTClaimHandler = class {
3109
3122
  cfg;
3123
+ inFlightNonces = /* @__PURE__ */ new Map();
3110
3124
  constructor(config) {
3125
+ const lockDurationMs = config.lockDurationMs ?? DEFAULT_LOCK_MS;
3126
+ const signatureDeadlineSeconds = config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC2;
3127
+ const maxAllowedSignatureMs = lockDurationMs - M11_SAFETY_MARGIN_MS2;
3128
+ if (signatureDeadlineSeconds * 1e3 > maxAllowedSignatureMs) {
3129
+ throw new PTClaimError(
3130
+ "VALIDATION_FAILED",
3131
+ `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.`,
3132
+ {
3133
+ lockDurationMs,
3134
+ signatureDeadlineSeconds,
3135
+ maxAllowedSignatureSec: maxAllowedSignatureMs / 1e3
3136
+ }
3137
+ );
3138
+ }
3111
3139
  this.cfg = {
3112
3140
  ...config,
3113
- lockDurationMs: config.lockDurationMs ?? DEFAULT_LOCK_MS,
3114
- signatureDeadlineSeconds: config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC2,
3141
+ lockDurationMs,
3142
+ signatureDeadlineSeconds,
3115
3143
  now: config.now ?? (() => Date.now())
3116
3144
  };
3117
3145
  }
@@ -3141,77 +3169,82 @@ var PTClaimHandler = class {
3141
3169
  }
3142
3170
  const chainAddresses = (0, import_core11.getContractAddresses)(request.chainId);
3143
3171
  const { batchExecutor: batchExecutorAddress } = chainAddresses;
3172
+ let mintRequestNonce;
3173
+ try {
3174
+ mintRequestNonce = await this.cfg.provider.readContract({
3175
+ address: request.pointTokenAddress,
3176
+ abi: import_core11.POINT_TOKEN_ABI,
3177
+ functionName: "mintRequestNonces",
3178
+ args: [request.userAddress]
3179
+ });
3180
+ } catch (err) {
3181
+ throw new PTClaimError(
3182
+ "NONCE_READ_FAILED",
3183
+ `failed to read mintRequestNonces(${request.userAddress}): ${err instanceof Error ? err.message : String(err)}`
3184
+ );
3185
+ }
3186
+ const nonceKey = `${(0, import_viem10.getAddress)(request.userAddress).toLowerCase()}:${request.pointTokenAddress.toLowerCase()}`;
3187
+ let userNonces = this.inFlightNonces.get(nonceKey);
3188
+ if (!userNonces) {
3189
+ userNonces = /* @__PURE__ */ new Set();
3190
+ this.inFlightNonces.set(nonceKey, userNonces);
3191
+ }
3192
+ if (userNonces.has(mintRequestNonce)) {
3193
+ throw new PTClaimError(
3194
+ "NONCE_IN_FLIGHT",
3195
+ `concurrent claim for nonce ${mintRequestNonce} in progress; retry after the prior request completes`,
3196
+ { userAddress: request.userAddress, pointToken: request.pointTokenAddress, nonce: mintRequestNonce.toString() }
3197
+ );
3198
+ }
3199
+ userNonces.add(mintRequestNonce);
3144
3200
  const wrapperOverride = this.cfg.mintFeeWrapperAddress;
3145
3201
  const wrapperFromSdk = chainAddresses.mintFeeWrapper;
3146
3202
  const resolvedWrapper = wrapperOverride !== void 0 ? isNoWrapper2(wrapperOverride) ? void 0 : wrapperOverride : isNoWrapper2(wrapperFromSdk) ? void 0 : wrapperFromSdk;
3147
- const lockId = await this.cfg.ledger.lockForMinting(
3148
- request.userAddress,
3149
- request.amount,
3150
- this.cfg.lockDurationMs,
3151
- request.pointTokenAddress
3152
- );
3153
3203
  try {
3154
- const signatureDeadline = BigInt(
3155
- Math.floor(this.cfg.now() / 1e3) + this.cfg.signatureDeadlineSeconds
3156
- );
3157
- const previewUserOp = this.cfg.relayService.previewMintUserOp({
3158
- userAddress: request.userAddress,
3159
- aaNonce: request.aaNonce,
3160
- pointTokenAddress: request.pointTokenAddress,
3161
- amount: request.amount,
3162
- deadline: signatureDeadline,
3163
- mintFeeWrapperAddress: resolvedWrapper
3164
- });
3165
- const feeAmount = this.cfg.feeService ? await this.cfg.feeService.estimateGasFee({
3166
- scenario: resolvedWrapper ? "mint-wrapped" : "mint",
3167
- contractAddress: request.pointTokenAddress,
3168
- partialUserOp: {
3169
- sender: previewUserOp.sender,
3170
- nonce: previewUserOp.nonce,
3171
- callData: previewUserOp.callData
3172
- }
3173
- }) : 0n;
3174
- const domainName = await this.cfg.domainResolver.resolve(
3204
+ const lockCreatedAtMs = this.cfg.now();
3205
+ const lockExpiresAtMs = lockCreatedAtMs + this.cfg.lockDurationMs;
3206
+ const lockId = await this.cfg.ledger.lockForMinting(
3207
+ request.userAddress,
3208
+ request.amount,
3209
+ this.cfg.lockDurationMs,
3175
3210
  request.pointTokenAddress
3176
3211
  );
3177
- const domain = {
3178
- name: domainName,
3179
- chainId: request.chainId,
3180
- verifyingContract: request.pointTokenAddress
3181
- };
3182
- let userOp;
3183
3212
  try {
3184
- userOp = await this.cfg.relayService.prepareMint({
3213
+ const requestedDeadlineSec = Math.floor(lockCreatedAtMs / 1e3) + this.cfg.signatureDeadlineSeconds;
3214
+ const lockBoundedDeadlineSec = Math.floor(
3215
+ (lockExpiresAtMs - M11_SAFETY_MARGIN_MS2) / 1e3
3216
+ );
3217
+ const signatureDeadline = BigInt(
3218
+ Math.min(requestedDeadlineSec, lockBoundedDeadlineSec)
3219
+ );
3220
+ const previewUserOp = this.cfg.relayService.previewMintUserOp({
3185
3221
  userAddress: request.userAddress,
3186
3222
  aaNonce: request.aaNonce,
3187
- batchExecutorAddress,
3188
3223
  pointTokenAddress: request.pointTokenAddress,
3189
3224
  amount: request.amount,
3190
- issuerSignerWallet: this.cfg.issuerSignerWallet,
3191
- domain,
3192
- mintRequestNonce: request.mintRequestNonce,
3193
3225
  deadline: signatureDeadline,
3194
- mintFeeWrapperAddress: resolvedWrapper,
3195
- // Pass the bundler-estimated `feeAmount` explicitly so the
3196
- // RelayService skips its legacy `quoteOperatorFeePt` path
3197
- // (which uses the SDK's old 12_000 bps premium default).
3198
- // Without this, the response's `feeAmount` (from FeeManager,
3199
- // 100% premium on top of PAFI's 110% server-side estimate)
3200
- // would diverge from the actual PT.transfer amount in the
3201
- // UserOp batch (`quoteOperatorFeePt`'s 120%), and the user
3202
- // would see one value while the wallet transferred another.
3203
- feeAmount
3226
+ mintFeeWrapperAddress: resolvedWrapper
3204
3227
  });
3205
- } catch (err) {
3206
- throw new PTClaimError(
3207
- "BUILD_FAILED",
3208
- `prepareMint failed: ${err instanceof Error ? err.message : String(err)}`
3228
+ const feeAmount = this.cfg.feeService ? await this.cfg.feeService.estimateGasFee({
3229
+ scenario: resolvedWrapper ? "mint-wrapped" : "mint",
3230
+ contractAddress: request.pointTokenAddress,
3231
+ partialUserOp: {
3232
+ sender: previewUserOp.sender,
3233
+ nonce: previewUserOp.nonce,
3234
+ callData: previewUserOp.callData
3235
+ }
3236
+ }) : 0n;
3237
+ const domainName = await this.cfg.domainResolver.resolve(
3238
+ request.pointTokenAddress
3209
3239
  );
3210
- }
3211
- let fallback;
3212
- if (feeAmount > 0n) {
3240
+ const domain = {
3241
+ name: domainName,
3242
+ chainId: request.chainId,
3243
+ verifyingContract: request.pointTokenAddress
3244
+ };
3245
+ let userOp;
3213
3246
  try {
3214
- fallback = await this.cfg.relayService.prepareMint({
3247
+ userOp = await this.cfg.relayService.prepareMint({
3215
3248
  userAddress: request.userAddress,
3216
3249
  aaNonce: request.aaNonce,
3217
3250
  batchExecutorAddress,
@@ -3219,34 +3252,68 @@ var PTClaimHandler = class {
3219
3252
  amount: request.amount,
3220
3253
  issuerSignerWallet: this.cfg.issuerSignerWallet,
3221
3254
  domain,
3222
- mintRequestNonce: request.mintRequestNonce,
3255
+ mintRequestNonce,
3223
3256
  deadline: signatureDeadline,
3224
- feeAmount: 0n,
3225
- mintFeeWrapperAddress: resolvedWrapper
3257
+ mintFeeWrapperAddress: resolvedWrapper,
3258
+ // Pass the bundler-estimated `feeAmount` explicitly so the
3259
+ // RelayService skips its legacy `quoteOperatorFeePt` path
3260
+ // (which uses the SDK's old 12_000 bps premium default).
3261
+ // Without this, the response's `feeAmount` (from FeeManager,
3262
+ // 100% premium on top of PAFI's 110% server-side estimate)
3263
+ // would diverge from the actual PT.transfer amount in the
3264
+ // UserOp batch (`quoteOperatorFeePt`'s 120%), and the user
3265
+ // would see one value while the wallet transferred another.
3266
+ feeAmount
3226
3267
  });
3227
3268
  } catch (err) {
3228
3269
  throw new PTClaimError(
3229
3270
  "BUILD_FAILED",
3230
- `prepareMint (fallback) failed: ${err instanceof Error ? err.message : String(err)}`
3271
+ `prepareMint failed: ${err instanceof Error ? err.message : String(err)}`
3231
3272
  );
3232
3273
  }
3274
+ let fallback;
3275
+ if (feeAmount > 0n) {
3276
+ try {
3277
+ fallback = await this.cfg.relayService.prepareMint({
3278
+ userAddress: request.userAddress,
3279
+ aaNonce: request.aaNonce,
3280
+ batchExecutorAddress,
3281
+ pointTokenAddress: request.pointTokenAddress,
3282
+ amount: request.amount,
3283
+ issuerSignerWallet: this.cfg.issuerSignerWallet,
3284
+ domain,
3285
+ mintRequestNonce,
3286
+ deadline: signatureDeadline,
3287
+ feeAmount: 0n,
3288
+ mintFeeWrapperAddress: resolvedWrapper
3289
+ });
3290
+ } catch (err) {
3291
+ throw new PTClaimError(
3292
+ "BUILD_FAILED",
3293
+ `prepareMint (fallback) failed: ${err instanceof Error ? err.message : String(err)}`
3294
+ );
3295
+ }
3296
+ }
3297
+ const calls = (0, import_core11.decodeBatchExecuteCalls)(userOp.callData);
3298
+ const callsFallback = fallback ? (0, import_core11.decodeBatchExecuteCalls)(fallback.callData) : void 0;
3299
+ return {
3300
+ userOp,
3301
+ fallback,
3302
+ lockId,
3303
+ feeAmount,
3304
+ signatureDeadline,
3305
+ expiresInSeconds: Math.floor(this.cfg.lockDurationMs / 1e3),
3306
+ calls,
3307
+ callsFallback
3308
+ };
3309
+ } catch (err) {
3310
+ await this.cfg.ledger.releaseLock(lockId).catch(() => {
3311
+ });
3312
+ throw err;
3233
3313
  }
3234
- const calls = (0, import_core11.decodeBatchExecuteCalls)(userOp.callData);
3235
- const callsFallback = fallback ? (0, import_core11.decodeBatchExecuteCalls)(fallback.callData) : void 0;
3236
- return {
3237
- userOp,
3238
- fallback,
3239
- lockId,
3240
- feeAmount,
3241
- signatureDeadline,
3242
- expiresInSeconds: Math.floor(this.cfg.lockDurationMs / 1e3),
3243
- calls,
3244
- callsFallback
3245
- };
3246
- } catch (err) {
3247
- await this.cfg.ledger.releaseLock(lockId).catch(() => {
3248
- });
3249
- throw err;
3314
+ } finally {
3315
+ userNonces.delete(mintRequestNonce);
3316
+ if (userNonces.size === 0) this.inFlightNonces.delete(nonceKey);
3250
3317
  }
3251
3318
  }
3252
3319
  };
@@ -3561,7 +3628,6 @@ var IssuerApiAdapter = class {
3561
3628
  }
3562
3629
  );
3563
3630
  return {
3564
- mintRequestNonce: result.mintRequestNonce.toString(),
3565
3631
  offChainBalance: result.offChainBalance.toString(),
3566
3632
  onChainBalance: result.onChainBalance.toString(),
3567
3633
  totalBalance: result.totalBalance.toString(),
@@ -3585,8 +3651,7 @@ var IssuerApiAdapter = class {
3585
3651
  amount: input.amount,
3586
3652
  pointTokenAddress,
3587
3653
  chainId: input.chainId,
3588
- aaNonce: input.aaNonce,
3589
- mintRequestNonce: input.mintRequestNonce
3654
+ aaNonce: input.aaNonce
3590
3655
  });
3591
3656
  const sponsorAuth = await this.buildSponsorAuth(
3592
3657
  input.authenticatedAddress,
@@ -3682,8 +3747,7 @@ var IssuerApiAdapter = class {
3682
3747
  amount: input.amount,
3683
3748
  pointTokenAddress,
3684
3749
  chainId: input.chainId,
3685
- aaNonce: input.aaNonce,
3686
- mintRequestNonce: input.mintRequestNonce
3750
+ aaNonce: input.aaNonce
3687
3751
  });
3688
3752
  const prepared = await this.runMobilePrepare(
3689
3753
  input.authenticatedAddress,
@@ -5142,7 +5206,7 @@ var MemoryRedemptionHistoryStore = class {
5142
5206
  };
5143
5207
 
5144
5208
  // src/index.ts
5145
- var PAFI_ISSUER_SDK_VERSION = true ? "0.24.1" : "dev";
5209
+ var PAFI_ISSUER_SDK_VERSION = true ? "0.25.0" : "dev";
5146
5210
  // Annotate the CommonJS export names for ESM import in node:
5147
5211
  0 && (module.exports = {
5148
5212
  AdapterMisconfiguredError,