@pafi-dev/issuer 0.20.0 → 0.22.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
@@ -57,6 +57,7 @@ __export(index_exports, {
57
57
  PerpDepositError: () => PerpDepositError,
58
58
  PerpDepositHandler: () => PerpDepositHandler,
59
59
  PointIndexer: () => PointIndexer,
60
+ PointTokenDomainResolver: () => PointTokenDomainResolver,
60
61
  PolicyProvider: () => PolicyProvider,
61
62
  REDEMPTION_HISTORY_WINDOW_SEC: () => REDEMPTION_HISTORY_WINDOW_SEC,
62
63
  RedemptionService: () => RedemptionService,
@@ -83,6 +84,7 @@ __export(index_exports, {
83
84
  handleMobilePrepare: () => handleMobilePrepare,
84
85
  handleMobileSubmit: () => handleMobileSubmit,
85
86
  handleRedeemStatus: () => handleRedeemStatus,
87
+ makePostgresSingletonLock: () => makePostgresSingletonLock,
86
88
  mergePaymasterFields: () => mergePaymasterFields,
87
89
  payloadFromGenericError: () => payloadFromGenericError,
88
90
  payloadFromHttpException: () => payloadFromHttpException,
@@ -1624,6 +1626,10 @@ var DEFAULT_BATCH_SIZE2 = 2000n;
1624
1626
  var DEFAULT_POLL_INTERVAL_MS2 = 5e3;
1625
1627
  var BurnIndexer = class {
1626
1628
  provider;
1629
+ /**
1630
+ * The PointToken this indexer watches. Exposed so callers can key
1631
+ * leader-election locks / cursor stores by token (audit H-04 fix).
1632
+ */
1627
1633
  pointTokenAddress;
1628
1634
  ledger;
1629
1635
  cursorStore;
@@ -1783,6 +1789,45 @@ var BurnIndexer = class {
1783
1789
  }
1784
1790
  };
1785
1791
 
1792
+ // src/indexer/postgresSingletonLock.ts
1793
+ function makePostgresSingletonLock(runner) {
1794
+ return {
1795
+ async acquire(key) {
1796
+ const lockId = hashKeyToInt64(key);
1797
+ const rows = await runner.query(
1798
+ "SELECT pg_try_advisory_lock($1::bigint) AS got",
1799
+ [lockId]
1800
+ );
1801
+ const got = rows[0]?.got === true;
1802
+ if (!got) return null;
1803
+ return {
1804
+ async release() {
1805
+ try {
1806
+ await runner.query("SELECT pg_advisory_unlock($1::bigint)", [
1807
+ lockId
1808
+ ]);
1809
+ } catch {
1810
+ }
1811
+ }
1812
+ };
1813
+ }
1814
+ };
1815
+ }
1816
+ function hashKeyToInt64(key) {
1817
+ const FNV_OFFSET = 0xcbf29ce484222325n;
1818
+ const FNV_PRIME = 0x100000001b3n;
1819
+ const MASK_64 = (1n << 64n) - 1n;
1820
+ let hash = FNV_OFFSET;
1821
+ for (let i = 0; i < key.length; i++) {
1822
+ hash ^= BigInt(key.charCodeAt(i));
1823
+ hash = hash * FNV_PRIME & MASK_64;
1824
+ }
1825
+ const SIGNED_MAX = (1n << 63n) - 1n;
1826
+ const TWO64 = 1n << 64n;
1827
+ const signed = hash > SIGNED_MAX ? hash - TWO64 : hash;
1828
+ return signed.toString();
1829
+ }
1830
+
1786
1831
  // src/api/handlers.ts
1787
1832
  var import_viem6 = require("viem");
1788
1833
  var import_core6 = require("@pafi-dev/core");
@@ -2143,8 +2188,59 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
2143
2188
  }
2144
2189
  };
2145
2190
 
2146
- // src/api/handlers/ptRedeemHandler.ts
2191
+ // src/api/pointTokenDomainResolver.ts
2147
2192
  var import_viem7 = require("viem");
2193
+ var NAME_ABI = [
2194
+ {
2195
+ type: "function",
2196
+ name: "name",
2197
+ stateMutability: "view",
2198
+ inputs: [],
2199
+ outputs: [{ type: "string" }]
2200
+ }
2201
+ ];
2202
+ var PointTokenDomainResolver = class {
2203
+ provider;
2204
+ overrides;
2205
+ cache = /* @__PURE__ */ new Map();
2206
+ constructor(config) {
2207
+ this.provider = config.provider;
2208
+ this.overrides = /* @__PURE__ */ new Map();
2209
+ if (config.overrides) {
2210
+ for (const [addr, name] of Object.entries(config.overrides)) {
2211
+ this.overrides.set((0, import_viem7.getAddress)(addr).toLowerCase(), name);
2212
+ }
2213
+ }
2214
+ }
2215
+ async resolve(pointTokenAddress) {
2216
+ const key = (0, import_viem7.getAddress)(pointTokenAddress).toLowerCase();
2217
+ const cached = this.cache.get(key);
2218
+ if (cached !== void 0) return cached;
2219
+ const override = this.overrides.get(key);
2220
+ if (override !== void 0) {
2221
+ this.cache.set(key, override);
2222
+ return override;
2223
+ }
2224
+ const name = await this.provider.readContract({
2225
+ address: pointTokenAddress,
2226
+ abi: NAME_ABI,
2227
+ functionName: "name"
2228
+ });
2229
+ this.cache.set(key, name);
2230
+ return name;
2231
+ }
2232
+ /** Invalidate one address (after deploy / proxy upgrade) or all. */
2233
+ invalidate(pointTokenAddress) {
2234
+ if (pointTokenAddress) {
2235
+ this.cache.delete((0, import_viem7.getAddress)(pointTokenAddress).toLowerCase());
2236
+ } else {
2237
+ this.cache.clear();
2238
+ }
2239
+ }
2240
+ };
2241
+
2242
+ // src/api/handlers/ptRedeemHandler.ts
2243
+ var import_viem8 = require("viem");
2148
2244
  var import_core7 = require("@pafi-dev/core");
2149
2245
  var DEFAULT_REDEEM_LOCK_MS = 15 * 60 * 1e3;
2150
2246
  var DEFAULT_SIG_DEADLINE_SEC = 15 * 60;
@@ -2165,10 +2261,9 @@ var PTRedeemHandler = class {
2165
2261
  relayService;
2166
2262
  provider;
2167
2263
  feeService;
2168
- pointTokenAddress;
2169
2264
  batchExecutorAddress;
2170
2265
  chainId;
2171
- domain;
2266
+ domainResolver;
2172
2267
  burnerSignerWallet;
2173
2268
  redeemLockDurationMs;
2174
2269
  signatureDeadlineSeconds;
@@ -2205,10 +2300,9 @@ var PTRedeemHandler = class {
2205
2300
  this.relayService = config.relayService;
2206
2301
  this.provider = config.provider;
2207
2302
  this.feeService = config.feeService;
2208
- this.pointTokenAddress = (0, import_viem7.getAddress)(config.pointTokenAddress);
2209
- this.batchExecutorAddress = (0, import_viem7.getAddress)(config.batchExecutorAddress);
2303
+ this.batchExecutorAddress = (0, import_viem8.getAddress)(config.batchExecutorAddress);
2210
2304
  this.chainId = config.chainId;
2211
- this.domain = config.domain;
2305
+ this.domainResolver = config.domainResolver;
2212
2306
  this.burnerSignerWallet = config.burnerSignerWallet;
2213
2307
  if (this.burnerSignerWallet?.account?.type === "local") {
2214
2308
  console.warn("[PAFI] PTRedeemHandler: burnerSignerWallet uses a local (private key) account. Use a KMS-backed signer in production.");
@@ -2221,7 +2315,7 @@ var PTRedeemHandler = class {
2221
2315
  }
2222
2316
  }
2223
2317
  async handle(request) {
2224
- if ((0, import_viem7.getAddress)(request.authenticatedAddress) !== (0, import_viem7.getAddress)(request.userAddress)) {
2318
+ if ((0, import_viem8.getAddress)(request.authenticatedAddress) !== (0, import_viem8.getAddress)(request.userAddress)) {
2225
2319
  throw new PTRedeemError(
2226
2320
  "UNAUTHORIZED",
2227
2321
  `userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`
@@ -2230,11 +2324,12 @@ var PTRedeemHandler = class {
2230
2324
  if (request.amount <= 0n) {
2231
2325
  throw new PTRedeemError("INVALID_AMOUNT", "redeem amount must be positive");
2232
2326
  }
2327
+ const pointTokenAddress = (0, import_viem8.getAddress)(request.pointTokenAddress);
2233
2328
  if (this.redemptionService) {
2234
2329
  const decision = await this.redemptionService.evaluate(
2235
2330
  request.userAddress,
2236
2331
  request.amount,
2237
- this.pointTokenAddress
2332
+ pointTokenAddress
2238
2333
  );
2239
2334
  if (!decision.allowed) {
2240
2335
  const denial = decision.denial;
@@ -2248,7 +2343,7 @@ var PTRedeemHandler = class {
2248
2343
  let burnNonce;
2249
2344
  try {
2250
2345
  burnNonce = await this.provider.readContract({
2251
- address: this.pointTokenAddress,
2346
+ address: pointTokenAddress,
2252
2347
  abi: import_core7.POINT_TOKEN_ABI,
2253
2348
  functionName: "burnRequestNonces",
2254
2349
  args: [request.userAddress]
@@ -2259,27 +2354,27 @@ var PTRedeemHandler = class {
2259
2354
  `failed to read burnRequestNonces(${request.userAddress}): ${err instanceof Error ? err.message : String(err)}`
2260
2355
  );
2261
2356
  }
2262
- const userKey = (0, import_viem7.getAddress)(request.userAddress).toLowerCase();
2263
- let userNonces = this.inFlightNonces.get(userKey);
2357
+ const nonceKey = `${(0, import_viem8.getAddress)(request.userAddress).toLowerCase()}:${pointTokenAddress.toLowerCase()}`;
2358
+ let userNonces = this.inFlightNonces.get(nonceKey);
2264
2359
  if (!userNonces) {
2265
2360
  userNonces = /* @__PURE__ */ new Set();
2266
- this.inFlightNonces.set(userKey, userNonces);
2361
+ this.inFlightNonces.set(nonceKey, userNonces);
2267
2362
  }
2268
2363
  if (userNonces.has(burnNonce)) {
2269
2364
  throw new PTRedeemError(
2270
2365
  "NONCE_IN_FLIGHT",
2271
- `A burn request for nonce ${burnNonce} is already in progress for ${request.userAddress}. Retry after the current request completes.`
2366
+ `A burn request for nonce ${burnNonce} is already in progress for ${request.userAddress} on ${pointTokenAddress}. Retry after the current request completes.`
2272
2367
  );
2273
2368
  }
2274
2369
  userNonces.add(burnNonce);
2275
2370
  try {
2276
- return await this._handleAfterNonceLock(request, burnNonce);
2371
+ return await this._handleAfterNonceLock(request, burnNonce, pointTokenAddress);
2277
2372
  } finally {
2278
2373
  userNonces.delete(burnNonce);
2279
- if (userNonces.size === 0) this.inFlightNonces.delete(userKey);
2374
+ if (userNonces.size === 0) this.inFlightNonces.delete(nonceKey);
2280
2375
  }
2281
2376
  }
2282
- async _handleAfterNonceLock(request, burnNonce) {
2377
+ async _handleAfterNonceLock(request, burnNonce, pointTokenAddress) {
2283
2378
  const previewDeadline = BigInt(
2284
2379
  Math.floor(this.now() / 1e3) + this.signatureDeadlineSeconds
2285
2380
  );
@@ -2290,13 +2385,13 @@ var PTRedeemHandler = class {
2290
2385
  const previewUserOp = this.relayService.previewBurnUserOp({
2291
2386
  userAddress: request.userAddress,
2292
2387
  aaNonce: burnNonce,
2293
- pointTokenAddress: this.pointTokenAddress,
2388
+ pointTokenAddress,
2294
2389
  amount: request.amount,
2295
2390
  deadline: previewDeadline
2296
2391
  });
2297
2392
  fee = await this.feeService.estimateGasFee({
2298
2393
  scenario: "burn",
2299
- contractAddress: this.pointTokenAddress,
2394
+ contractAddress: pointTokenAddress,
2300
2395
  partialUserOp: {
2301
2396
  sender: previewUserOp.sender,
2302
2397
  nonce: previewUserOp.nonce,
@@ -2315,7 +2410,7 @@ var PTRedeemHandler = class {
2315
2410
  }
2316
2411
  const onChainBalance = await (0, import_core7.getPointTokenBalance)(
2317
2412
  this.provider,
2318
- this.pointTokenAddress,
2413
+ pointTokenAddress,
2319
2414
  request.userAddress
2320
2415
  );
2321
2416
  if (onChainBalance < request.amount) {
@@ -2325,10 +2420,11 @@ var PTRedeemHandler = class {
2325
2420
  );
2326
2421
  }
2327
2422
  const deadline = previewDeadline;
2423
+ const domainName = await this.domainResolver.resolve(pointTokenAddress);
2328
2424
  const domain = {
2329
- name: this.domain.name,
2425
+ name: domainName,
2330
2426
  chainId: this.chainId,
2331
- verifyingContract: this.domain.verifyingContract ?? this.pointTokenAddress
2427
+ verifyingContract: pointTokenAddress
2332
2428
  };
2333
2429
  const sponsoredBurnAmount = request.amount - fee;
2334
2430
  const sponsoredBurnRequest = {
@@ -2350,14 +2446,14 @@ var PTRedeemHandler = class {
2350
2446
  request.userAddress,
2351
2447
  sponsoredBurnAmount,
2352
2448
  this.redeemLockDurationMs,
2353
- this.pointTokenAddress
2449
+ pointTokenAddress
2354
2450
  );
2355
2451
  try {
2356
2452
  const sponsoredUserOp = await this.relayService.prepareBurn({
2357
2453
  mode: "burnWithSig",
2358
2454
  userAddress: request.userAddress,
2359
2455
  aaNonce: request.aaNonce,
2360
- pointTokenAddress: this.pointTokenAddress,
2456
+ pointTokenAddress,
2361
2457
  batchExecutorAddress: this.batchExecutorAddress,
2362
2458
  burnRequest: sponsoredBurnRequest,
2363
2459
  burnerSignature: sponsoredSig,
@@ -2385,7 +2481,7 @@ var PTRedeemHandler = class {
2385
2481
  request.userAddress,
2386
2482
  request.amount,
2387
2483
  this.redeemLockDurationMs,
2388
- this.pointTokenAddress
2484
+ pointTokenAddress
2389
2485
  );
2390
2486
  let fallbackUserOp;
2391
2487
  try {
@@ -2393,7 +2489,7 @@ var PTRedeemHandler = class {
2393
2489
  mode: "burnWithSig",
2394
2490
  userAddress: request.userAddress,
2395
2491
  aaNonce: request.aaNonce,
2396
- pointTokenAddress: this.pointTokenAddress,
2492
+ pointTokenAddress,
2397
2493
  batchExecutorAddress: this.batchExecutorAddress,
2398
2494
  burnRequest: fallbackBurnRequest,
2399
2495
  burnerSignature: fallbackSig,
@@ -2418,7 +2514,7 @@ var PTRedeemHandler = class {
2418
2514
  await this.redemptionService.recordSuccessfulInitiate({
2419
2515
  user: request.userAddress,
2420
2516
  amountPt: request.amount,
2421
- pointTokenAddress: this.pointTokenAddress,
2517
+ pointTokenAddress,
2422
2518
  reservationId: sponsoredLockId
2423
2519
  }).catch(() => {
2424
2520
  });
@@ -2541,7 +2637,7 @@ async function handleRedeemStatus(params) {
2541
2637
  }
2542
2638
 
2543
2639
  // src/api/mobileHandlers.ts
2544
- var import_viem8 = require("viem");
2640
+ var import_viem9 = require("viem");
2545
2641
  var import_core10 = require("@pafi-dev/core");
2546
2642
 
2547
2643
  // src/userop-store/serialize.ts
@@ -2887,7 +2983,7 @@ async function handleMobileSubmit(params) {
2887
2983
  if (!entry) {
2888
2984
  throw new PendingUserOpNotFoundError(params.lockId);
2889
2985
  }
2890
- if ((0, import_viem8.getAddress)(entry.sender) !== (0, import_viem8.getAddress)(params.authenticatedAddress)) {
2986
+ if ((0, import_viem9.getAddress)(entry.sender) !== (0, import_viem9.getAddress)(params.authenticatedAddress)) {
2891
2987
  throw new PendingUserOpForbiddenError(params.lockId);
2892
2988
  }
2893
2989
  const variant = params.variant ?? "sponsored";
@@ -2903,7 +2999,7 @@ async function handleMobileSubmit(params) {
2903
2999
  }
2904
3000
 
2905
3001
  // src/api/handlers/ptClaimHandler.ts
2906
- var import_viem9 = require("viem");
3002
+ var import_viem10 = require("viem");
2907
3003
  var import_core11 = require("@pafi-dev/core");
2908
3004
 
2909
3005
  // src/issuer-state/types.ts
@@ -2949,7 +3045,7 @@ var PTClaimHandler = class {
2949
3045
  };
2950
3046
  }
2951
3047
  async handle(request) {
2952
- if ((0, import_viem9.getAddress)(request.authenticatedAddress) !== (0, import_viem9.getAddress)(request.userAddress)) {
3048
+ if ((0, import_viem10.getAddress)(request.authenticatedAddress) !== (0, import_viem10.getAddress)(request.userAddress)) {
2953
3049
  throw new PTClaimError(
2954
3050
  "VALIDATION_FAILED",
2955
3051
  `userAddress (${request.userAddress}) does not match authenticated session (${request.authenticatedAddress})`
@@ -3004,8 +3100,11 @@ var PTClaimHandler = class {
3004
3100
  callData: previewUserOp.callData
3005
3101
  }
3006
3102
  }) : 0n;
3103
+ const domainName = await this.cfg.domainResolver.resolve(
3104
+ request.pointTokenAddress
3105
+ );
3007
3106
  const domain = {
3008
- name: this.cfg.pointTokenDomainName,
3107
+ name: domainName,
3009
3108
  chainId: request.chainId,
3010
3109
  verifyingContract: request.pointTokenAddress
3011
3110
  };
@@ -3021,8 +3120,16 @@ var PTClaimHandler = class {
3021
3120
  domain,
3022
3121
  mintRequestNonce: request.mintRequestNonce,
3023
3122
  deadline: signatureDeadline,
3024
- mintFeeWrapperAddress: resolvedWrapper
3025
- // No feeAmount/feeRecipient RelayService auto-resolves.
3123
+ mintFeeWrapperAddress: resolvedWrapper,
3124
+ // Pass the bundler-estimated `feeAmount` explicitly so the
3125
+ // RelayService skips its legacy `quoteOperatorFeePt` path
3126
+ // (which uses the SDK's old 12_000 bps premium default).
3127
+ // Without this, the response's `feeAmount` (from FeeManager,
3128
+ // 100% premium on top of PAFI's 110% server-side estimate)
3129
+ // would diverge from the actual PT.transfer amount in the
3130
+ // UserOp batch (`quoteOperatorFeePt`'s 120%), and the user
3131
+ // would see one value while the wallet transferred another.
3132
+ feeAmount
3026
3133
  });
3027
3134
  } catch (err) {
3028
3135
  throw new PTClaimError(
@@ -3203,7 +3310,7 @@ var PerpDepositHandler = class {
3203
3310
 
3204
3311
  // src/api/delegateHandler.ts
3205
3312
  var import_core13 = require("@pafi-dev/core");
3206
- var import_viem10 = require("viem");
3313
+ var import_viem11 = require("viem");
3207
3314
  var DEFAULT_DELEGATE_GAS = {
3208
3315
  callGasLimit: 100000n,
3209
3316
  verificationGasLimit: 150000n,
@@ -3290,7 +3397,7 @@ async function handleDelegateSubmit(params) {
3290
3397
  if (!entry) {
3291
3398
  throw new PendingUserOpNotFoundError(params.lockId);
3292
3399
  }
3293
- if ((0, import_viem10.getAddress)(entry.sender) !== (0, import_viem10.getAddress)(params.authenticatedAddress)) {
3400
+ if ((0, import_viem11.getAddress)(entry.sender) !== (0, import_viem11.getAddress)(params.authenticatedAddress)) {
3294
3401
  throw new PendingUserOpForbiddenError(params.lockId);
3295
3402
  }
3296
3403
  if (!entry.eip7702Auth) {
@@ -3311,7 +3418,7 @@ async function handleDelegateSubmit(params) {
3311
3418
 
3312
3419
  // src/api/issuerApiAdapter.ts
3313
3420
  var import_node_crypto3 = require("crypto");
3314
- var import_viem11 = require("viem");
3421
+ var import_viem12 = require("viem");
3315
3422
  var import_core14 = require("@pafi-dev/core");
3316
3423
  var AdapterMisconfiguredError = class extends Error {
3317
3424
  code = "ADAPTER_MISCONFIGURED";
@@ -3369,7 +3476,7 @@ var IssuerApiAdapter = class {
3369
3476
  async pools(authenticatedAddress, chainId, pointTokenAddress) {
3370
3477
  const result = await this.cfg.issuerService.api.handlePools(
3371
3478
  authenticatedAddress,
3372
- { chainId, pointTokenAddress: (0, import_viem11.getAddress)(pointTokenAddress) }
3479
+ { chainId, pointTokenAddress: (0, import_viem12.getAddress)(pointTokenAddress) }
3373
3480
  );
3374
3481
  return { pools: result.pools };
3375
3482
  }
@@ -3378,8 +3485,8 @@ var IssuerApiAdapter = class {
3378
3485
  authenticatedAddress,
3379
3486
  {
3380
3487
  chainId,
3381
- userAddress: (0, import_viem11.getAddress)(userAddress),
3382
- pointTokenAddress: (0, import_viem11.getAddress)(pointTokenAddress)
3488
+ userAddress: (0, import_viem12.getAddress)(userAddress),
3489
+ pointTokenAddress: (0, import_viem12.getAddress)(pointTokenAddress)
3383
3490
  }
3384
3491
  );
3385
3492
  return {
@@ -3400,7 +3507,7 @@ var IssuerApiAdapter = class {
3400
3507
  "ptClaimHandler",
3401
3508
  "claim"
3402
3509
  );
3403
- const pointTokenAddress = (0, import_viem11.getAddress)(input.pointTokenAddress);
3510
+ const pointTokenAddress = (0, import_viem12.getAddress)(input.pointTokenAddress);
3404
3511
  const result = await ptClaimHandler.handle({
3405
3512
  authenticatedAddress: input.authenticatedAddress,
3406
3513
  userAddress: input.authenticatedAddress,
@@ -3427,9 +3534,11 @@ var IssuerApiAdapter = class {
3427
3534
  }
3428
3535
  async redeem(input) {
3429
3536
  this.assertRedeemHandler();
3537
+ const pointTokenAddress = (0, import_viem12.getAddress)(input.pointTokenAddress);
3430
3538
  const response = await this.cfg.ptRedeemHandler.handle({
3431
3539
  userAddress: input.authenticatedAddress,
3432
3540
  authenticatedAddress: input.authenticatedAddress,
3541
+ pointTokenAddress,
3433
3542
  amount: input.amount,
3434
3543
  aaNonce: input.aaNonce,
3435
3544
  chainId: input.chainId
@@ -3495,7 +3604,7 @@ var IssuerApiAdapter = class {
3495
3604
  "ptClaimHandler",
3496
3605
  "claimPrepare"
3497
3606
  );
3498
- const pointTokenAddress = (0, import_viem11.getAddress)(input.pointTokenAddress);
3607
+ const pointTokenAddress = (0, import_viem12.getAddress)(input.pointTokenAddress);
3499
3608
  const claimResult = await ptClaimHandler.handle({
3500
3609
  authenticatedAddress: input.authenticatedAddress,
3501
3610
  userAddress: input.authenticatedAddress,
@@ -3541,10 +3650,11 @@ var IssuerApiAdapter = class {
3541
3650
  }
3542
3651
  async redeemPrepare(input) {
3543
3652
  this.assertRedeemHandler();
3544
- const pointTokenAddress = (0, import_viem11.getAddress)(input.pointTokenAddress);
3653
+ const pointTokenAddress = (0, import_viem12.getAddress)(input.pointTokenAddress);
3545
3654
  const redeemResponse = await this.cfg.ptRedeemHandler.handle({
3546
3655
  userAddress: input.authenticatedAddress,
3547
3656
  authenticatedAddress: input.authenticatedAddress,
3657
+ pointTokenAddress,
3548
3658
  amount: input.amount,
3549
3659
  aaNonce: input.aaNonce,
3550
3660
  chainId: input.chainId
@@ -3731,7 +3841,7 @@ var IssuerApiAdapter = class {
3731
3841
  };
3732
3842
 
3733
3843
  // src/pools/subgraphPoolsProvider.ts
3734
- var import_viem12 = require("viem");
3844
+ var import_viem13 = require("viem");
3735
3845
  var import_core15 = require("@pafi-dev/core");
3736
3846
  var DEFAULT_CACHE_TTL_MS = 3e4;
3737
3847
  var MAX_REASONABLE_FEE_TIER = 1e6;
@@ -3854,7 +3964,7 @@ async function fetchPoolsFromSubgraph(fetchImpl, subgraphUrl, pointTokenAddress,
3854
3964
  return [];
3855
3965
  }
3856
3966
  const { pool } = token;
3857
- if (!(0, import_viem12.isAddress)(pool.token0.id) || !(0, import_viem12.isAddress)(pool.token1.id)) {
3967
+ if (!(0, import_viem13.isAddress)(pool.token0.id) || !(0, import_viem13.isAddress)(pool.token1.id)) {
3858
3968
  const error = new Error(
3859
3969
  "[PAFI] SubgraphPoolsProvider: invalid token address in response"
3860
3970
  );
@@ -4007,8 +4117,8 @@ function toUsdtPerNative(priceFloat, usdtDecimals) {
4007
4117
  }
4008
4118
 
4009
4119
  // src/pools/nativePtQuoter.ts
4010
- var import_viem13 = require("viem");
4011
- var CHAINLINK_ABI = (0, import_viem13.parseAbi)([
4120
+ var import_viem14 = require("viem");
4121
+ var CHAINLINK_ABI = (0, import_viem14.parseAbi)([
4012
4122
  "function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)"
4013
4123
  ]);
4014
4124
  var CHAINLINK_MAX_AGE_S = 3600n;
@@ -4292,7 +4402,7 @@ var PafiBackendClient = class {
4292
4402
  };
4293
4403
 
4294
4404
  // src/config.ts
4295
- var import_viem14 = require("viem");
4405
+ var import_viem15 = require("viem");
4296
4406
  var import_core18 = require("@pafi-dev/core");
4297
4407
 
4298
4408
  // src/redemption/evaluator.ts
@@ -4600,7 +4710,7 @@ var RedemptionService = class {
4600
4710
  };
4601
4711
 
4602
4712
  // src/config.ts
4603
- function createIssuerService(config) {
4713
+ async function createIssuerService(config) {
4604
4714
  if (!config.provider) {
4605
4715
  throw new Error("createIssuerService: provider is required");
4606
4716
  }
@@ -4619,7 +4729,7 @@ function createIssuerService(config) {
4619
4729
  "createIssuerService: at least one of pointTokenAddress / pointTokenAddresses is required"
4620
4730
  );
4621
4731
  }
4622
- const tokenAddresses = rawAddresses.map((a) => (0, import_viem14.getAddress)(a));
4732
+ const tokenAddresses = rawAddresses.map((a) => (0, import_viem15.getAddress)(a));
4623
4733
  const ledger = config.ledger;
4624
4734
  const sessionStore = config.sessionStore ?? new MemorySessionStore();
4625
4735
  const policy = config.policy ?? new DefaultPolicyEngine({ ledger });
@@ -4719,9 +4829,26 @@ function createIssuerService(config) {
4719
4829
  handlersConfig.mintFeeWrapperAddress = resolvedWrapperAddress;
4720
4830
  }
4721
4831
  const handlers = new IssuerApiHandlers(handlersConfig);
4832
+ const indexerLeaderLocks = [];
4722
4833
  if (config.indexer?.autoStart) {
4723
- for (const idx of indexers.values()) {
4724
- idx.start();
4834
+ const lock = config.indexer.singletonLock;
4835
+ if (!lock) {
4836
+ console.warn(
4837
+ "[@pafi-dev/issuer] indexer.autoStart=true without singletonLock \u2014 this is UNSAFE in multi-replica deployments (audit finding H-04). Either set replicas=1 + INDEXER_AUTOSTART=false on non-leader pods, or pass `singletonLock: makePostgresSingletonLock(dataSource)`. This permissive path will be removed in a future major release."
4838
+ );
4839
+ for (const idx of indexers.values()) {
4840
+ idx.start();
4841
+ }
4842
+ } else {
4843
+ for (const [tokenAddr, idx] of indexers.entries()) {
4844
+ const key = `pafi-issuer:point-indexer:${tokenAddr.toLowerCase()}`;
4845
+ const handle = await lock.acquire(key);
4846
+ if (!handle) {
4847
+ continue;
4848
+ }
4849
+ idx.start();
4850
+ indexerLeaderLocks.push(handle);
4851
+ }
4725
4852
  }
4726
4853
  }
4727
4854
  return {
@@ -4732,13 +4859,14 @@ function createIssuerService(config) {
4732
4859
  relay: relayService,
4733
4860
  fee: feeManager,
4734
4861
  indexers,
4862
+ indexerLeaderLocks,
4735
4863
  api: handlers,
4736
4864
  redemption
4737
4865
  };
4738
4866
  }
4739
4867
 
4740
4868
  // src/issuer-state/validator.ts
4741
- var import_viem15 = require("viem");
4869
+ var import_viem16 = require("viem");
4742
4870
  var import_core19 = require("@pafi-dev/core");
4743
4871
  var ISSUER_RECORD_TTL_MS = 3e4;
4744
4872
  var IssuerStateValidator = class _IssuerStateValidator {
@@ -4765,7 +4893,7 @@ var IssuerStateValidator = class _IssuerStateValidator {
4765
4893
  */
4766
4894
  invalidate(pointToken) {
4767
4895
  if (pointToken) {
4768
- const key = (0, import_viem15.getAddress)(pointToken);
4896
+ const key = (0, import_viem16.getAddress)(pointToken);
4769
4897
  this.pointTokenIssuerCache.delete(key);
4770
4898
  this.stateCache.delete(key);
4771
4899
  this.inflight.delete(key);
@@ -4780,7 +4908,7 @@ var IssuerStateValidator = class _IssuerStateValidator {
4780
4908
  * The issuer field is set at `initialize()` and never changes.
4781
4909
  */
4782
4910
  async getIssuerAddressForPointToken(pointToken) {
4783
- const key = (0, import_viem15.getAddress)(pointToken);
4911
+ const key = (0, import_viem16.getAddress)(pointToken);
4784
4912
  const cached = this.pointTokenIssuerCache.get(key);
4785
4913
  if (cached) return cached;
4786
4914
  const issuer = await this.provider.readContract({
@@ -4788,15 +4916,15 @@ var IssuerStateValidator = class _IssuerStateValidator {
4788
4916
  abi: import_core19.POINT_TOKEN_ABI,
4789
4917
  functionName: "issuer"
4790
4918
  });
4791
- this.pointTokenIssuerCache.set(key, (0, import_viem15.getAddress)(issuer));
4792
- return (0, import_viem15.getAddress)(issuer);
4919
+ this.pointTokenIssuerCache.set(key, (0, import_viem16.getAddress)(issuer));
4920
+ return (0, import_viem16.getAddress)(issuer);
4793
4921
  }
4794
4922
  /**
4795
4923
  * Read registry record + totalSupply, with 30s cache and in-flight
4796
4924
  * deduplication. Does NOT throw on inactive/missing — returns raw state.
4797
4925
  */
4798
4926
  async getIssuerState(pointToken) {
4799
- const tokenAddr = (0, import_viem15.getAddress)(pointToken);
4927
+ const tokenAddr = (0, import_viem16.getAddress)(pointToken);
4800
4928
  const now = Date.now();
4801
4929
  const cached = this.stateCache.get(tokenAddr);
4802
4930
  if (cached && cached.expiresAt > now) return cached.value;
@@ -4939,7 +5067,7 @@ var MemoryRedemptionHistoryStore = class {
4939
5067
  };
4940
5068
 
4941
5069
  // src/index.ts
4942
- var PAFI_ISSUER_SDK_VERSION = true ? "0.20.0" : "dev";
5070
+ var PAFI_ISSUER_SDK_VERSION = true ? "0.22.0" : "dev";
4943
5071
  // Annotate the CommonJS export names for ESM import in node:
4944
5072
  0 && (module.exports = {
4945
5073
  AdapterMisconfiguredError,
@@ -4979,6 +5107,7 @@ var PAFI_ISSUER_SDK_VERSION = true ? "0.20.0" : "dev";
4979
5107
  PerpDepositError,
4980
5108
  PerpDepositHandler,
4981
5109
  PointIndexer,
5110
+ PointTokenDomainResolver,
4982
5111
  PolicyProvider,
4983
5112
  REDEMPTION_HISTORY_WINDOW_SEC,
4984
5113
  RedemptionService,
@@ -5005,6 +5134,7 @@ var PAFI_ISSUER_SDK_VERSION = true ? "0.20.0" : "dev";
5005
5134
  handleMobilePrepare,
5006
5135
  handleMobileSubmit,
5007
5136
  handleRedeemStatus,
5137
+ makePostgresSingletonLock,
5008
5138
  mergePaymasterFields,
5009
5139
  payloadFromGenericError,
5010
5140
  payloadFromHttpException,