@pafi-dev/issuer 0.13.0 → 0.15.1

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/README.md CHANGED
@@ -14,40 +14,20 @@ interfaces. Don't bundle into a browser app — use
14
14
 
15
15
  ---
16
16
 
17
- ## What's new in 0.13.0 (breaking)
18
-
19
- Tracks `@pafi-dev/core@0.10.0`. The `ReceiverConsent` mint path was
20
- removed from the entire SDK chain because the deployed `PointToken`
21
- contract never had it — what was conceptually a "sponsored mint" is
22
- actually the path-2 `MintForRequest` sig-gated mint with sponsor-relayer
23
- paying gas.
24
-
25
- | Change | Detail |
26
- |---|---|
27
- | `ApiUserResponse.receiverConsentNonce` removed | The `/user` endpoint no longer returns this field. Callers using it for signing should call the path-2 mint flow with `mintRequestNonce` instead. |
28
- | `UserDto.receiverConsentNonce` removed | Wire DTO drops the field — `IssuerApiAdapter.user()` response is one field smaller. |
29
- | Action required for issuer backends | Bump `@pafi-dev/core` to `0.10.0`, remove any `receiverConsentNonce` field from your own DTOs / mobile API surface, drop FE code that signed `ReceiverConsent` typed data. |
30
-
31
- ### What's new in v1.6 / 0.12.x
32
-
33
- | Change | Detail |
34
- |---|---|
35
- | `RelayService.prepareMint` branches direct vs wrapper | Auto-detects `MintFeeWrapper` from chainId. Direct: `pointToken.mint(...)`. Wrapper: `mintFeeWrapper.mintWithFee(...)` |
36
- | `PTClaimHandler` auto-resolves wrapper | No env config needed — `getContractAddresses(chainId).mintFeeWrapper`. Override via `mintFeeWrapperAddress` for fork tests |
37
- | `PointIndexer` wrapper mode | Listens to `MintFeeWrapper.MintWithFee(pointToken, to, gross, net, fee)` instead of `Transfer(0x0→user)` when wrapper is configured |
38
- | `IssuerStateValidator` v1.6 struct | Reads 7-field Issuer struct + queries `MintingOracle.tokenCaps()` for caps |
39
- | `/config` exposes `mintFeeBpsByToken` | FE can preview "you'll receive net = gross × (1-bps/10000)" before user signs |
40
- | Operator fee fallback enabled by default | Mint not blocked when subgraph has no pool yet |
41
- | `PafiBackendClient` error envelope fix | Reads nested `error.message` — exposes Pimlico AA codes instead of generic "HTTP 500" |
17
+ ## v0.15.0 — Uniswap V3 migration (breaking)
42
18
 
43
- ---
19
+ - `createSubgraphPoolsProvider` now returns the V3 `PoolKey` shape (`{ token0, token1, fee }`) instead of the V4 shape (`{ currency0, currency1, fee, tickSpacing, hooks }`). Pulls through from `@pafi-dev/core`.
20
+ - New `onError?: (err: Error) => void` option — forward recoverable errors (network, GraphQL, parse) to your observability stack. Throws inside the callback are swallowed so the provider stays total.
21
+ - `feeTier` is range-checked (uint24, < 1_000_000) before building the `PoolKey`; invalid values are skipped with a console.error + `onError` invocation.
22
+ - Subgraph endpoint default → `…/pafi-subgraph-v4` (PAFI V3-fork DEX + extensions); see `@pafi-dev/core` for the constant.
23
+ - Bumps peer-deps on `@pafi-dev/core` to `^0.13.0` (V3 types + ABIs).
44
24
 
45
25
  ## Requirements
46
26
 
47
27
  - Node.js ≥ 18
48
28
  - TypeScript ≥ 5.0
49
29
  - `viem` ^2.0.0 (peer)
50
- - `@pafi-dev/core` (transitive — re-exported)
30
+ - `@pafi-dev/core` ^0.13.0 (transitive — re-exported)
51
31
 
52
32
  ## Installation
53
33
 
@@ -78,12 +58,11 @@ pnpm add @pafi-dev/issuer-postgres typeorm pg
78
58
  ├── userop-store — IPendingUserOpStore + MemoryPendingUserOpStore
79
59
  ├── pafi-backend — sponsor-relayer client + relay/paymaster helpers
80
60
  ├── auth — ISessionStore, AuthService (SIWE), MemorySessionStore
81
- ├── relay — RelayService (v1.6 wrapper-aware), FeeManager
61
+ ├── relay — RelayService (wrapper-aware), FeeManager
82
62
  ├── pools — createSubgraphPoolsProvider, NativePtQuoter
83
63
  ├── policy — IPolicyEngine + DefaultPolicyEngine
84
- ├── balance — BalanceAggregator (off-chain + on-chain)
85
64
  ├── indexer — PointIndexer (wrapper + direct mode), BurnIndexer
86
- ├── issuer-state — IssuerStateValidator (v1.6 struct + oracle.tokenCaps)
65
+ ├── issuer-state — IssuerStateValidator (7-field struct + oracle.tokenCaps)
87
66
  ├── redemption — RedemptionService (per-issuer policy enforcement)
88
67
  └── errors — PafiSdkError base class for typed errors
89
68
  ```
@@ -102,7 +81,7 @@ Controller ← thin: routing + DTO + auth context
102
81
  IssuerApiAdapter ← orchestrates flows
103
82
 
104
83
  PTClaimHandler ← signs MintForRequest, locks balance, builds UserOp
105
- (v1.6: routes to wrapper.mintWithFee)
84
+ (routes to wrapper.mintWithFee when wrapper configured)
106
85
  PTRedeemHandler ← signs BurnRequest, reserves credit, builds UserOp
107
86
  PerpDepositHandler ← Orderly Vault via PAFI Relay
108
87
 
@@ -113,7 +92,7 @@ PafiBackendClient ← sponsor-relayer proxy
113
92
 
114
93
  ---
115
94
 
116
- ## v1.6 wrapper-mediated mint flow
95
+ ## Wrapper-mediated mint flow
117
96
 
118
97
  ```
119
98
  [FE / mobile] → POST /claim/prepare
@@ -277,11 +256,11 @@ async function wrap<T>(fn: () => Promise<T>): Promise<T> {
277
256
  | --- | --- | --- |
278
257
  | `pools(authedAddr, chainId, pointToken)` | `GET /pools` | |
279
258
  | `user(authedAddr, chainId, userAddr, pointToken)` | `GET /user` | |
280
- | `config(chainId)` | `GET /config` | v1.6: includes `mintFeeBpsByToken` + `contracts.mintFeeWrapper` |
259
+ | `config(chainId)` | `GET /config` | Includes `mintFeeBpsByToken` + `contracts.mintFeeWrapper` |
281
260
  | `claim({ ...nonces })` | `POST /claim` (web — sync `calls[]`) | |
282
261
  | `redeem({ amount, aaNonce, ... })` | `POST /redeem` | |
283
262
  | `perpDeposit({ amount, brokerId, aaNonce, ... })` | `POST /perp-deposit` | |
284
- | `claimPrepare(...)` / `claimSubmit(...)` | Mobile claim flow | v1.6 wrapper-aware |
263
+ | `claimPrepare(...)` / `claimSubmit(...)` | Mobile claim flow | wrapper-aware |
285
264
  | `redeemPrepare(...)` / `redeemSubmit(...)` | Mobile redeem flow | |
286
265
  | `claimStatus(authedAddr, lockId)` / `redeemStatus(...)` | Status polling | |
287
266
  | `delegateStatus(authedAddr, chainId)` | EIP-7702 — check delegation | |
@@ -358,7 +337,7 @@ const service = createIssuerService({
358
337
 
359
338
  // Indexer listens to:
360
339
  // - mode wrapper: MintFeeWrapper.MintWithFee filtered by pointToken
361
- // (when wrapper is configured at chainId — v1.6 default)
340
+ // (when wrapper is configured at chainId)
362
341
  // - mode direct: PointToken.Transfer(0x0 → user) (legacy / no wrapper)
363
342
  ```
364
343
 
@@ -387,29 +366,10 @@ All SDK errors inherit `PafiSdkError`. Subclasses + HTTP mapping:
387
366
 
388
367
  ---
389
368
 
390
- ## v1.5 → v1.6 migration checklist
391
-
392
- 1. `pnpm add @pafi-dev/issuer@latest @pafi-dev/core@latest` (≥ 0.12.7 + ≥ 0.9.6)
393
- 2. Update env: drop `MINT_FEE_WRAPPER_ADDRESS` if previously set
394
- (SDK auto-resolves from chainId)
395
- 3. Re-deploy your issuer backend — `RelayService` now branches based on
396
- wrapper presence; calldata changes from `mint(...)` to `mintWithFee(...)`
397
- 4. Verify on-chain prerequisites (PAFI ops responsibility):
398
- - `IssuerRegistry.addIssuer(...)` cascade configures wrapper recipients
399
- - `MintingOracle.registerToken(pointToken, issuer)` sets cap
400
- - `PointToken.addMinter(signerAddress)` whitelists your signer
401
- 5. Indexer will pick up `MintWithFee` events automatically. If you wrote
402
- custom Transfer-event consumers, switch to `MintWithFee`.
403
- 6. `/config` response now includes `mintFeeBpsByToken` — FE can preview
404
- fee without round-tripping `getMintFeeBps()` separately.
405
-
406
- ---
407
-
408
369
  ## References
409
370
 
410
371
  - Architecture: [`ARCHITECTURE.md`](../../ARCHITECTURE.md) at SDK root
411
372
  - Fee flow & math: [`docs/FEE_FLOW.md`](../../../docs/FEE_FLOW.md)
412
- - v1.6 SC commits: `cc26f62` (struct simplification), wrapper added
413
373
 
414
374
  ## License
415
375
 
package/dist/index.cjs CHANGED
@@ -23,7 +23,6 @@ __export(index_exports, {
23
23
  AdapterMisconfiguredError: () => AdapterMisconfiguredError,
24
24
  AuthError: () => AuthError,
25
25
  AuthService: () => AuthService,
26
- BalanceAggregator: () => BalanceAggregator,
27
26
  BundlerNotConfiguredError: () => BundlerNotConfiguredError,
28
27
  BundlerRejectedError: () => BundlerRejectedError,
29
28
  BurnIndexer: () => BurnIndexer,
@@ -65,6 +64,7 @@ __export(index_exports, {
65
64
  SDK_ERROR_HTTP_STATUS_CODE: () => import_core.SDK_ERROR_HTTP_STATUS_CODE,
66
65
  SettlementClient: () => SettlementClient,
67
66
  ValidationError: () => import_core3.ValidationError,
67
+ applyPaymasterGasEstimates: () => applyPaymasterGasEstimates,
68
68
  authenticateRequest: () => authenticateRequest,
69
69
  buildErrorEnvelope: () => buildErrorEnvelope,
70
70
  buildSdkErrorBody: () => buildSdkErrorBody,
@@ -945,7 +945,7 @@ var RelayService = class {
945
945
  mintTarget = params.mintFeeWrapperAddress;
946
946
  } else {
947
947
  mintCallData = (0, import_viem3.encodeFunctionData)({
948
- abi: import_core5.POINT_TOKEN_V2_ABI,
948
+ abi: import_core5.POINT_TOKEN_ABI,
949
949
  functionName: "mint",
950
950
  args: [params.userAddress, params.amount, params.deadline, minterSig]
951
951
  });
@@ -1010,8 +1010,8 @@ var RelayService = class {
1010
1010
  * provides a pre-signed `BurnRequest` + sig bytes (typically from
1011
1011
  * `PTRedeemHandler`).
1012
1012
  *
1013
- * Direct burn (no sig) was dropped in v1.4 — every burn now goes
1014
- * through the issuer-signed `BurnRequest` path.
1013
+ * Direct burn (no sig) is not used — every burn goes through the
1014
+ * issuer-signed `BurnRequest` path.
1015
1015
  */
1016
1016
  async prepareBurn(params) {
1017
1017
  if (!params.pointTokenAddress) {
@@ -1032,7 +1032,7 @@ var RelayService = class {
1032
1032
  let burnCallData;
1033
1033
  try {
1034
1034
  burnCallData = (0, import_viem3.encodeFunctionData)({
1035
- abi: import_core5.POINT_TOKEN_V2_ABI,
1035
+ abi: import_core5.POINT_TOKEN_ABI,
1036
1036
  functionName: "burn",
1037
1037
  args: [
1038
1038
  params.burnRequest.from,
@@ -1295,7 +1295,7 @@ var PointIndexer = class {
1295
1295
  // Event fetching — two modes (wrapper vs direct)
1296
1296
  // -------------------------------------------------------------------------
1297
1297
  /**
1298
- * Wrapper mode (v1.6): listen for `MintWithFee` on the wrapper,
1298
+ * Wrapper mode: listen for `MintWithFee` on the wrapper,
1299
1299
  * filtered to events for THIS pointToken only. The event's `to` field
1300
1300
  * is the actual end user, and `grossAmount` matches the lock amount.
1301
1301
  */
@@ -1817,9 +1817,8 @@ var IssuerApiHandlers = class _IssuerApiHandlers {
1817
1817
  /**
1818
1818
  * `GET /user?chainId=<id>&user=<addr>&pointToken=<addr>`
1819
1819
  *
1820
- * Returns per-user state the frontend needs to build a fresh
1821
- * `ReceiverConsent`: on-chain nonces + minter status + off-chain
1822
- * balance.
1820
+ * Returns per-user state the frontend needs to build a fresh mint:
1821
+ * on-chain nonces + minter status + off-chain balance.
1823
1822
  */
1824
1823
  async handleUser(userAddress, request) {
1825
1824
  if (request.chainId !== this.chainId) {
@@ -2041,7 +2040,7 @@ var PTRedeemHandler = class {
2041
2040
  try {
2042
2041
  burnNonce = await this.provider.readContract({
2043
2042
  address: this.pointTokenAddress,
2044
- abi: import_core7.POINT_TOKEN_V2_ABI,
2043
+ abi: import_core7.POINT_TOKEN_ABI,
2045
2044
  functionName: "burnRequestNonces",
2046
2045
  args: [request.userAddress]
2047
2046
  });
@@ -2421,38 +2420,42 @@ function mergePaymasterFields(userOp, paymasterFields) {
2421
2420
  }
2422
2421
  return merged;
2423
2422
  }
2424
- async function prepareMobileUserOp(params) {
2423
+ function applyPaymasterGasEstimates(partialUserOp, paymasterFields, chainId) {
2425
2424
  const userOp = mergePaymasterFields(
2426
- params.partialUserOp,
2427
- params.paymasterFields
2425
+ partialUserOp,
2426
+ paymasterFields
2428
2427
  );
2429
- const userOpHash = (0, import_core9.computeUserOpHash)(userOp, params.chainId);
2430
- const typedData = serializeUserOpTypedData(
2431
- (0, import_core9.buildUserOpTypedData)(userOp, params.chainId)
2428
+ return {
2429
+ userOp,
2430
+ userOpHash: (0, import_core9.computeUserOpHash)(userOp, chainId),
2431
+ typedData: serializeUserOpTypedData((0, import_core9.buildUserOpTypedData)(userOp, chainId))
2432
+ };
2433
+ }
2434
+ async function prepareMobileUserOp(params) {
2435
+ const sponsored = applyPaymasterGasEstimates(
2436
+ params.partialUserOp,
2437
+ params.paymasterFields,
2438
+ params.chainId
2432
2439
  );
2440
+ const { userOp, userOpHash } = sponsored;
2433
2441
  let fallback;
2434
2442
  let fallbackEntry;
2435
2443
  if (params.partialUserOpFallback) {
2436
- const fallbackUserOp = {
2437
- ...params.partialUserOpFallback,
2438
- maxFeePerGas: userOp.maxFeePerGas,
2439
- maxPriorityFeePerGas: userOp.maxPriorityFeePerGas
2440
- };
2441
- const fallbackHash = (0, import_core9.computeUserOpHash)(fallbackUserOp, params.chainId);
2442
- const fallbackTypedData = serializeUserOpTypedData(
2443
- (0, import_core9.buildUserOpTypedData)(fallbackUserOp, params.chainId)
2444
+ fallback = applyPaymasterGasEstimates(
2445
+ {
2446
+ ...params.partialUserOpFallback,
2447
+ maxFeePerGas: userOp.maxFeePerGas,
2448
+ maxPriorityFeePerGas: userOp.maxPriorityFeePerGas
2449
+ },
2450
+ void 0,
2451
+ params.chainId
2444
2452
  );
2445
- fallback = {
2446
- userOp: fallbackUserOp,
2447
- userOpHash: fallbackHash,
2448
- typedData: fallbackTypedData
2449
- };
2450
2453
  fallbackEntry = {
2451
- callData: fallbackUserOp.callData,
2452
- callGasLimit: fallbackUserOp.callGasLimit.toString(),
2453
- verificationGasLimit: fallbackUserOp.verificationGasLimit.toString(),
2454
- preVerificationGas: fallbackUserOp.preVerificationGas.toString(),
2455
- userOpHash: fallbackHash
2454
+ callData: fallback.userOp.callData,
2455
+ callGasLimit: fallback.userOp.callGasLimit.toString(),
2456
+ verificationGasLimit: fallback.userOp.verificationGasLimit.toString(),
2457
+ preVerificationGas: fallback.userOp.preVerificationGas.toString(),
2458
+ userOpHash: fallback.userOpHash
2456
2459
  };
2457
2460
  }
2458
2461
  const entry = {
@@ -2474,7 +2477,7 @@ async function prepareMobileUserOp(params) {
2474
2477
  };
2475
2478
  await params.store.save(params.lockId, entry, params.ttlSeconds);
2476
2479
  return {
2477
- sponsored: { userOp, userOpHash, typedData },
2480
+ sponsored,
2478
2481
  fallback,
2479
2482
  entry
2480
2483
  };
@@ -3001,22 +3004,13 @@ async function handleDelegatePrepare(params) {
3001
3004
  eip7702Auth: authorization,
3002
3005
  onWarning: params.onWarning
3003
3006
  });
3004
- const merged = {
3005
- sender: userOp.sender,
3006
- nonce: userOp.nonce,
3007
- callData: userOp.callData,
3008
- callGasLimit: paymasterFields?.callGasLimit ?? userOp.callGasLimit,
3009
- verificationGasLimit: paymasterFields?.verificationGasLimit ?? userOp.verificationGasLimit,
3010
- preVerificationGas: paymasterFields?.preVerificationGas ?? userOp.preVerificationGas,
3011
- maxFeePerGas: paymasterFields?.maxFeePerGas ?? userOp.maxFeePerGas,
3012
- maxPriorityFeePerGas: paymasterFields?.maxPriorityFeePerGas ?? userOp.maxPriorityFeePerGas,
3013
- paymaster: paymasterFields?.paymaster,
3014
- paymasterVerificationGasLimit: paymasterFields?.paymasterVerificationGasLimit,
3015
- paymasterPostOpGasLimit: paymasterFields?.paymasterPostOpGasLimit,
3016
- paymasterData: paymasterFields?.paymasterData
3017
- };
3018
- const userOpHash = (0, import_core13.computeUserOpHash)(merged, params.chainId);
3019
- const typed = (0, import_core13.buildUserOpTypedData)(merged, params.chainId);
3007
+ const prepared = applyPaymasterGasEstimates(
3008
+ userOp,
3009
+ paymasterFields,
3010
+ params.chainId
3011
+ );
3012
+ const merged = prepared.userOp;
3013
+ const userOpHash = prepared.userOpHash;
3020
3014
  await params.store.save(
3021
3015
  params.lockId,
3022
3016
  {
@@ -3045,7 +3039,7 @@ async function handleDelegatePrepare(params) {
3045
3039
  return {
3046
3040
  lockId: params.lockId,
3047
3041
  userOpHash,
3048
- typedData: serializeUserOpTypedData(typed),
3042
+ typedData: prepared.typedData,
3049
3043
  expiresInSeconds: params.ttlSeconds,
3050
3044
  isSponsored: !!paymasterFields
3051
3045
  };
@@ -3499,6 +3493,7 @@ var IssuerApiAdapter = class {
3499
3493
  var import_viem12 = require("viem");
3500
3494
  var import_core15 = require("@pafi-dev/core");
3501
3495
  var DEFAULT_CACHE_TTL_MS = 3e4;
3496
+ var MAX_REASONABLE_FEE_TIER = 1e6;
3502
3497
  var POOL_QUERY = `
3503
3498
  query GetPoolForPointToken($id: ID!) {
3504
3499
  pafiToken(id: $id) {
@@ -3506,8 +3501,6 @@ var POOL_QUERY = `
3506
3501
  pool {
3507
3502
  id
3508
3503
  feeTier
3509
- tickSpacing
3510
- hooks
3511
3504
  token0 { id }
3512
3505
  token1 { id }
3513
3506
  }
@@ -3532,12 +3525,20 @@ function createSubgraphPoolsProvider(config = {}) {
3532
3525
  const cacheTtl = config.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
3533
3526
  const fetchImpl = config.fetchImpl ?? globalThis.fetch;
3534
3527
  const now = config.now ?? (() => Date.now());
3528
+ const onError = config.onError;
3535
3529
  const cache = /* @__PURE__ */ new Map();
3536
3530
  if (!fetchImpl) {
3537
3531
  throw new Error(
3538
3532
  "createSubgraphPoolsProvider: no fetch implementation available \u2014 pass `fetchImpl` or run on Node 18+"
3539
3533
  );
3540
3534
  }
3535
+ const reportError = (err) => {
3536
+ if (!onError) return;
3537
+ try {
3538
+ onError(err);
3539
+ } catch {
3540
+ }
3541
+ };
3541
3542
  return async (request) => {
3542
3543
  const cacheKey = `${request.chainId}:${request.pointTokenAddress.toLowerCase()}`;
3543
3544
  if (cacheTtl > 0) {
@@ -3549,7 +3550,8 @@ function createSubgraphPoolsProvider(config = {}) {
3549
3550
  const pools = await fetchPoolsFromSubgraph(
3550
3551
  fetchImpl,
3551
3552
  subgraphUrl,
3552
- request.pointTokenAddress
3553
+ request.pointTokenAddress,
3554
+ reportError
3553
3555
  );
3554
3556
  if (cacheTtl > 0) {
3555
3557
  cache.set(cacheKey, {
@@ -3560,7 +3562,7 @@ function createSubgraphPoolsProvider(config = {}) {
3560
3562
  return { pools };
3561
3563
  };
3562
3564
  }
3563
- async function fetchPoolsFromSubgraph(fetchImpl, subgraphUrl, pointTokenAddress) {
3565
+ async function fetchPoolsFromSubgraph(fetchImpl, subgraphUrl, pointTokenAddress, reportError) {
3564
3566
  let response;
3565
3567
  try {
3566
3568
  response = await fetchImpl(subgraphUrl, {
@@ -3572,24 +3574,38 @@ async function fetchPoolsFromSubgraph(fetchImpl, subgraphUrl, pointTokenAddress)
3572
3574
  })
3573
3575
  });
3574
3576
  } catch (err) {
3577
+ const error = err instanceof Error ? err : new Error(String(err));
3575
3578
  console.warn(
3576
3579
  "[subgraphPoolsProvider] subgraph unreachable:",
3577
- err.message
3580
+ error.message
3578
3581
  );
3582
+ reportError(error);
3579
3583
  return [];
3580
3584
  }
3581
3585
  if (!response.ok) {
3582
- console.warn(
3583
- `[subgraphPoolsProvider] subgraph returned ${response.status}`
3586
+ const error = new Error(
3587
+ `subgraph returned HTTP ${response.status}`
3584
3588
  );
3589
+ console.warn(`[subgraphPoolsProvider] ${error.message}`);
3590
+ reportError(error);
3585
3591
  return [];
3586
3592
  }
3587
- const json = await response.json();
3588
- if (json.errors && json.errors.length > 0) {
3593
+ let json;
3594
+ try {
3595
+ json = await response.json();
3596
+ } catch (err) {
3597
+ const error = err instanceof Error ? err : new Error(String(err));
3589
3598
  console.warn(
3590
- "[subgraphPoolsProvider] subgraph errors:",
3591
- json.errors.map((e) => e.message).join("; ")
3599
+ "[subgraphPoolsProvider] subgraph returned non-JSON:",
3600
+ error.message
3592
3601
  );
3602
+ reportError(error);
3603
+ return [];
3604
+ }
3605
+ if (json.errors && json.errors.length > 0) {
3606
+ const msg = json.errors.map((e) => e.message).join("; ");
3607
+ console.warn("[subgraphPoolsProvider] subgraph errors:", msg);
3608
+ reportError(new Error(`subgraph GraphQL errors: ${msg}`));
3593
3609
  return [];
3594
3610
  }
3595
3611
  const token = json.data?.pafiToken;
@@ -3597,40 +3613,35 @@ async function fetchPoolsFromSubgraph(fetchImpl, subgraphUrl, pointTokenAddress)
3597
3613
  return [];
3598
3614
  }
3599
3615
  const { pool } = token;
3600
- if (!(0, import_viem12.isAddress)(pool.hooks)) {
3601
- console.error(
3602
- "[PAFI] SubgraphPoolsProvider: invalid hooks address in response:",
3603
- pool.hooks,
3604
- "\u2014 skipping pool"
3605
- );
3606
- return [];
3607
- }
3608
3616
  if (!(0, import_viem12.isAddress)(pool.token0.id) || !(0, import_viem12.isAddress)(pool.token1.id)) {
3609
- console.error(
3610
- "[PAFI] SubgraphPoolsProvider: invalid token address in response \u2014 skipping pool"
3617
+ const error = new Error(
3618
+ "[PAFI] SubgraphPoolsProvider: invalid token address in response"
3611
3619
  );
3620
+ console.error(error.message, "\u2014 skipping pool");
3621
+ reportError(error);
3612
3622
  return [];
3613
3623
  }
3614
- if (!Number.isFinite(Number(pool.feeTier)) || !Number.isFinite(Number(pool.tickSpacing))) {
3615
- console.error(
3616
- "[PAFI] SubgraphPoolsProvider: invalid feeTier/tickSpacing \u2014 skipping pool"
3624
+ const feeNum = Number(pool.feeTier);
3625
+ if (!Number.isInteger(feeNum) || feeNum < 0 || feeNum >= MAX_REASONABLE_FEE_TIER) {
3626
+ const error = new Error(
3627
+ `[PAFI] SubgraphPoolsProvider: invalid feeTier value: ${pool.feeTier}`
3617
3628
  );
3629
+ console.error(error.message, "\u2014 skipping pool");
3630
+ reportError(error);
3618
3631
  return [];
3619
3632
  }
3620
- const [currency0, currency1] = sortCurrencies(
3633
+ const [token0, token1] = sortTokens(
3621
3634
  pool.token0.id,
3622
3635
  pool.token1.id
3623
3636
  );
3624
3637
  const poolKey = {
3625
- currency0,
3626
- currency1,
3627
- fee: Number(pool.feeTier),
3628
- tickSpacing: Number(pool.tickSpacing),
3629
- hooks: pool.hooks
3638
+ token0,
3639
+ token1,
3640
+ fee: feeNum
3630
3641
  };
3631
3642
  return [poolKey];
3632
3643
  }
3633
- function sortCurrencies(a, b) {
3644
+ function sortTokens(a, b) {
3634
3645
  return a.toLowerCase() < b.toLowerCase() ? [a, b] : [b, a];
3635
3646
  }
3636
3647
 
@@ -3858,54 +3869,8 @@ function parseBigDecimalTo18(s) {
3858
3869
  return BigInt(whole + padded);
3859
3870
  }
3860
3871
 
3861
- // src/balance/balanceAggregator.ts
3862
- var import_core16 = require("@pafi-dev/core");
3863
- var BalanceAggregator = class {
3864
- provider;
3865
- ledger;
3866
- constructor(config) {
3867
- if (!config.provider) {
3868
- throw new Error("BalanceAggregator: provider is required");
3869
- }
3870
- if (!config.ledger) {
3871
- throw new Error("BalanceAggregator: ledger is required");
3872
- }
3873
- this.provider = config.provider;
3874
- this.ledger = config.ledger;
3875
- }
3876
- /**
3877
- * Combined balance for a single (user, token) pair. Fetches off-chain
3878
- * + on-chain in parallel.
3879
- */
3880
- async getCombinedBalance(user, pointToken) {
3881
- const [offChain, onChain] = await Promise.all([
3882
- this.ledger.getBalance(user, pointToken),
3883
- (0, import_core16.getPointTokenBalance)(this.provider, pointToken, user)
3884
- ]);
3885
- return {
3886
- offChain,
3887
- onChain,
3888
- total: offChain + onChain
3889
- };
3890
- }
3891
- /**
3892
- * Combined balance for multiple tokens owned by the same user. Runs
3893
- * all lookups in parallel. Returns a Map keyed by the token address
3894
- * (same casing as supplied — caller should normalize if needed).
3895
- */
3896
- async getCombinedBalanceMulti(user, pointTokens) {
3897
- const entries = await Promise.all(
3898
- pointTokens.map(async (token) => {
3899
- const balance = await this.getCombinedBalance(user, token);
3900
- return [token, balance];
3901
- })
3902
- );
3903
- return new Map(entries);
3904
- }
3905
- };
3906
-
3907
3872
  // src/pafi-backend/client.ts
3908
- var import_core17 = require("@pafi-dev/core");
3873
+ var import_core16 = require("@pafi-dev/core");
3909
3874
  function extractPafiErrorFields(json, status) {
3910
3875
  const inner = typeof json.error === "object" && json.error !== null ? json.error : null;
3911
3876
  const code = inner?.code ?? json.code ?? "INTERNAL_ERROR";
@@ -3926,7 +3891,7 @@ var PafiBackendClient = class {
3926
3891
  if (!config.issuerId) throw new Error("PafiBackendClient: issuerId is required");
3927
3892
  if (!config.apiKey) throw new Error("PafiBackendClient: apiKey is required");
3928
3893
  this.config = config;
3929
- this.baseUrl = (0, import_core17.getPafiServiceUrls)(config.chainId).sponsorRelayer;
3894
+ this.baseUrl = (0, import_core16.getPafiServiceUrls)(config.chainId).sponsorRelayer;
3930
3895
  }
3931
3896
  async requestSponsorship(request) {
3932
3897
  const maxAttempts = this.config.retry?.maxAttempts ?? 1;
@@ -4087,7 +4052,7 @@ var PafiBackendClient = class {
4087
4052
 
4088
4053
  // src/config.ts
4089
4054
  var import_viem14 = require("viem");
4090
- var import_core19 = require("@pafi-dev/core");
4055
+ var import_core18 = require("@pafi-dev/core");
4091
4056
 
4092
4057
  // src/redemption/evaluator.ts
4093
4058
  var SECONDS_PER_DAY = 24 * 60 * 60;
@@ -4192,7 +4157,7 @@ function nextBlackoutEndAfter(windows, nowUnixSec) {
4192
4157
  var REDEMPTION_HISTORY_WINDOW_SEC = SECONDS_PER_DAY;
4193
4158
 
4194
4159
  // src/redemption/settlementClient.ts
4195
- var import_core18 = require("@pafi-dev/core");
4160
+ var import_core17 = require("@pafi-dev/core");
4196
4161
  var DEFAULT_TIMEOUT_MS = 1e3;
4197
4162
  var SettlementClient = class {
4198
4163
  config;
@@ -4201,7 +4166,7 @@ var SettlementClient = class {
4201
4166
  if (!config.issuerId) throw new Error("SettlementClient: issuerId is required");
4202
4167
  if (!config.apiKey) throw new Error("SettlementClient: apiKey is required");
4203
4168
  this.config = {
4204
- baseUrl: (0, import_core18.getPafiServiceUrls)(config.chainId).issuerApi.replace(/\/+$/, ""),
4169
+ baseUrl: (0, import_core17.getPafiServiceUrls)(config.chainId).issuerApi.replace(/\/+$/, ""),
4205
4170
  issuerId: config.issuerId,
4206
4171
  apiKey: config.apiKey,
4207
4172
  fetchTimeoutMs: config.fetchTimeoutMs ?? DEFAULT_TIMEOUT_MS,
@@ -4438,7 +4403,7 @@ function createIssuerService(config) {
4438
4403
  provider: config.provider
4439
4404
  });
4440
4405
  }
4441
- const sdkWrapperAddress = (0, import_core19.getContractAddresses)(config.chainId).mintFeeWrapper;
4406
+ const sdkWrapperAddress = (0, import_core18.getContractAddresses)(config.chainId).mintFeeWrapper;
4442
4407
  const wrapperOverride = config.indexer?.mintFeeWrapperAddress;
4443
4408
  const resolvedWrapperAddress = wrapperOverride !== void 0 ? wrapperOverride : sdkWrapperAddress;
4444
4409
  const indexers = /* @__PURE__ */ new Map();
@@ -4468,14 +4433,12 @@ function createIssuerService(config) {
4468
4433
  }
4469
4434
  indexers.set(tokenAddress, new PointIndexer(indexerConfig));
4470
4435
  }
4471
- const firstIndexer = indexers.get(tokenAddresses[0]);
4472
- const chainAddresses = (0, import_core19.getContractAddresses)(config.chainId);
4436
+ const chainAddresses = (0, import_core18.getContractAddresses)(config.chainId);
4473
4437
  const resolvedContracts = {
4474
4438
  batchExecutor: chainAddresses.batchExecutor,
4475
4439
  usdt: chainAddresses.usdt,
4476
4440
  issuerRegistry: chainAddresses.issuerRegistry,
4477
4441
  mintingOracle: chainAddresses.mintingOracle,
4478
- pafiHook: chainAddresses.pafiHook,
4479
4442
  ...config.contracts
4480
4443
  };
4481
4444
  if (resolvedWrapperAddress !== void 0) {
@@ -4528,7 +4491,6 @@ function createIssuerService(config) {
4528
4491
  relay: relayService,
4529
4492
  fee: feeManager,
4530
4493
  indexers,
4531
- indexer: firstIndexer,
4532
4494
  api: handlers,
4533
4495
  redemption
4534
4496
  };
@@ -4536,7 +4498,7 @@ function createIssuerService(config) {
4536
4498
 
4537
4499
  // src/issuer-state/validator.ts
4538
4500
  var import_viem15 = require("viem");
4539
- var import_core20 = require("@pafi-dev/core");
4501
+ var import_core19 = require("@pafi-dev/core");
4540
4502
  var ISSUER_RECORD_TTL_MS = 3e4;
4541
4503
  var IssuerStateValidator = class _IssuerStateValidator {
4542
4504
  constructor(provider, registryAddress) {
@@ -4553,7 +4515,7 @@ var IssuerStateValidator = class _IssuerStateValidator {
4553
4515
  * `CONTRACT_ADDRESSES` map for the given chain.
4554
4516
  */
4555
4517
  static forChain(provider, chainId) {
4556
- const { issuerRegistry } = (0, import_core20.getContractAddresses)(chainId);
4518
+ const { issuerRegistry } = (0, import_core19.getContractAddresses)(chainId);
4557
4519
  return new _IssuerStateValidator(provider, issuerRegistry);
4558
4520
  }
4559
4521
  /**
@@ -4582,7 +4544,7 @@ var IssuerStateValidator = class _IssuerStateValidator {
4582
4544
  if (cached) return cached;
4583
4545
  const issuer = await this.provider.readContract({
4584
4546
  address: key,
4585
- abi: import_core20.POINT_TOKEN_V2_ABI,
4547
+ abi: import_core19.POINT_TOKEN_ABI,
4586
4548
  functionName: "issuer"
4587
4549
  });
4588
4550
  this.pointTokenIssuerCache.set(key, (0, import_viem15.getAddress)(issuer));
@@ -4662,7 +4624,7 @@ var IssuerStateValidator = class _IssuerStateValidator {
4662
4624
  const issuerAddr = await this.getIssuerAddressForPointToken(tokenAddr);
4663
4625
  const issuerStruct = await this.provider.readContract({
4664
4626
  address: this.registryAddress,
4665
- abi: import_core20.issuerRegistryAbi,
4627
+ abi: import_core19.issuerRegistryAbi,
4666
4628
  functionName: "getIssuer",
4667
4629
  args: [issuerAddr]
4668
4630
  });
@@ -4676,10 +4638,10 @@ var IssuerStateValidator = class _IssuerStateValidator {
4676
4638
  mintingOracle: issuerStruct.mintingOracle
4677
4639
  };
4678
4640
  const [tokenCap, totalSupply] = await Promise.all([
4679
- (0, import_core20.getTokenCap)(this.provider, issuer.mintingOracle, tokenAddr),
4641
+ (0, import_core19.getTokenCap)(this.provider, issuer.mintingOracle, tokenAddr),
4680
4642
  this.provider.readContract({
4681
4643
  address: tokenAddr,
4682
- abi: import_core20.POINT_TOKEN_V2_ABI,
4644
+ abi: import_core19.POINT_TOKEN_ABI,
4683
4645
  functionName: "totalSupply"
4684
4646
  })
4685
4647
  ]);
@@ -4736,13 +4698,12 @@ var MemoryRedemptionHistoryStore = class {
4736
4698
  };
4737
4699
 
4738
4700
  // src/index.ts
4739
- var PAFI_ISSUER_SDK_VERSION = true ? "0.13.0" : "dev";
4701
+ var PAFI_ISSUER_SDK_VERSION = true ? "0.15.1" : "dev";
4740
4702
  // Annotate the CommonJS export names for ESM import in node:
4741
4703
  0 && (module.exports = {
4742
4704
  AdapterMisconfiguredError,
4743
4705
  AuthError,
4744
4706
  AuthService,
4745
- BalanceAggregator,
4746
4707
  BundlerNotConfiguredError,
4747
4708
  BundlerRejectedError,
4748
4709
  BurnIndexer,
@@ -4784,6 +4745,7 @@ var PAFI_ISSUER_SDK_VERSION = true ? "0.13.0" : "dev";
4784
4745
  SDK_ERROR_HTTP_STATUS_CODE,
4785
4746
  SettlementClient,
4786
4747
  ValidationError,
4748
+ applyPaymasterGasEstimates,
4787
4749
  authenticateRequest,
4788
4750
  buildErrorEnvelope,
4789
4751
  buildSdkErrorBody,