@pafi-dev/issuer 0.12.7 → 0.15.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.d.ts CHANGED
@@ -115,8 +115,18 @@ interface IPointLedger {
115
115
  /** List currently-pending locked mint requests for a user. */
116
116
  getLockedRequests(userAddress: Address, tokenAddress?: Address): Promise<LockedMintRequest[]>;
117
117
  /**
118
- * Transition a lock to a new lifecycle status. The on-chain tx hash is
118
+ * Transition a lock from `PENDING` to a terminal status
119
+ * (`MINTED` | `EXPIRED` | `FAILED`). The on-chain tx hash is
119
120
  * supplied when the status is `MINTED`.
121
+ *
122
+ * Terminal states are immutable: once a lock leaves `PENDING` the
123
+ * status is fixed. Calling `updateMintStatus` on a non-PENDING lock
124
+ * is a silent no-op. This protects against late-arriving writes from
125
+ * different code paths (e.g. PointIndexer.finalize and the bundler-
126
+ * receipt fallback in statusHandlers) racing to overwrite each
127
+ * other — particularly the case where a stale `EXPIRED`/`FAILED`
128
+ * write would corrupt a `MINTED` lock whose balance has already
129
+ * been deducted.
120
130
  */
121
131
  updateMintStatus(lockId: string, status: MintingStatus, txHash?: Hex): Promise<void>;
122
132
  /**
@@ -204,7 +214,7 @@ interface DefaultPolicyEngineOptions {
204
214
  provider?: PublicClient;
205
215
  mintingOracleAddress?: Address;
206
216
  /**
207
- * Override the on-chain cap check. Defaults to `@pafi/core`'s
217
+ * Override the on-chain cap check. Defaults to `@pafi-dev/core`'s
208
218
  * `verifyMintCap`, which reverts if the issuer's declared supply would
209
219
  * be exceeded. A rejected check should throw; returning normally is pass.
210
220
  */
@@ -561,7 +571,7 @@ interface RelayServiceConfig {
561
571
  chainId?: number;
562
572
  }
563
573
  /**
564
- * Builds unsigned `PartialUserOperation` payloads for the v1.4 sponsored
574
+ * Builds unsigned `PartialUserOperation` payloads for the sponsored
565
575
  * flow. The service is stateless and HTTP-client-free:
566
576
  *
567
577
  * - `prepareMint` signs a `MintRequest` EIP-712 with the caller-supplied
@@ -573,7 +583,7 @@ interface RelayServiceConfig {
573
583
  * deadline, burnerSig)`.
574
584
  *
575
585
  * There is no broadcasting, no operator wallet, no simulation — those
576
- * concerns moved to the Bundler + Paymaster in v1.4.
586
+ * concerns live in the Bundler + Paymaster.
577
587
  */
578
588
  declare class RelayService {
579
589
  private readonly provider;
@@ -607,13 +617,13 @@ declare class RelayService {
607
617
  * provides a pre-signed `BurnRequest` + sig bytes (typically from
608
618
  * `PTRedeemHandler`).
609
619
  *
610
- * Direct burn (no sig) was dropped in v1.4 — every burn now goes
611
- * through the issuer-signed `BurnRequest` path.
620
+ * Direct burn (no sig) is not used — every burn goes through the
621
+ * issuer-signed `BurnRequest` path.
612
622
  */
613
623
  prepareBurn(params: PrepareBurnParams): Promise<PartialUserOperation>;
614
624
  }
615
625
  /**
616
- * v1.4 — sig-gated `PointToken.mint(to, amount, deadline, minterSig)`.
626
+ * Sig-gated `PointToken.mint(to, amount, deadline, minterSig)`.
617
627
  *
618
628
  * The issuer backend validates off-chain (balance, policy, KYC), signs
619
629
  * a `MintRequest` EIP-712 with its minter signer, and packages the
@@ -645,16 +655,16 @@ interface PrepareMintParams {
645
655
  /** Unix timestamp after which the signature expires. */
646
656
  deadline: bigint;
647
657
  /**
648
- * Optional v1.6+ — when set, mint is routed through MintFeeWrapper:
658
+ * Optional — when set, mint is routed through MintFeeWrapper:
649
659
  * - sig.receiver = wrapper address (vs `userAddress` for direct path)
650
660
  * - calldata target = wrapper.mintWithFee(pointToken, user, gross, ...)
651
661
  * - the wrapper then mints `amount` to itself, splits per recipient list,
652
662
  * and forwards net to the user.
653
663
  *
654
- * Leave undefined to keep the v1.5 direct-mint behavior (no fee skim).
655
- * Wrapper must be registered on the PointToken via
656
- * `IssuerRegistry.addIssuer` cascade (or owner-only `wrapper.registerToken`)
657
- * before this path works on-chain.
664
+ * Leave undefined for direct-mint behavior (no fee skim). Wrapper
665
+ * must be registered on the PointToken via `IssuerRegistry.addIssuer`
666
+ * cascade (or owner-only `wrapper.registerToken`) before this path
667
+ * works on-chain.
658
668
  */
659
669
  mintFeeWrapperAddress?: Address;
660
670
  /**
@@ -669,11 +679,10 @@ interface PrepareMintParams {
669
679
  preVerificationGas?: bigint;
670
680
  }
671
681
  /**
672
- * v1.4 — sig-gated burn only. Direct (no-sig) burn was dropped because
673
- * every burn now goes through `BurnRequest` EIP-712 signed by the
674
- * issuer burner. The `mode: 'burnWithSig'` discriminant is preserved
675
- * for backwards-compat with existing callers but is no longer
676
- * branched on internally.
682
+ * Sig-gated burn only every burn goes through `BurnRequest` EIP-712
683
+ * signed by the issuer burner. The `mode: 'burnWithSig'` discriminant
684
+ * is preserved for backwards-compat with existing callers but is no
685
+ * longer branched on internally.
677
686
  */
678
687
  interface PrepareBurnParams {
679
688
  userAddress: Address;
@@ -715,8 +724,8 @@ interface FeeManagerConfig {
715
724
  gasPremiumBps?: number;
716
725
  /**
717
726
  * Quote function — given an amount of native wei, return the
718
- * equivalent amount in the fee currency (PT raw units in v1.4,
719
- * USDT 6-decimal in legacy v1.2 flows).
727
+ * equivalent amount in the fee currency (typically PT raw units;
728
+ * USDT 6-decimal for swap / perp deposit flows).
720
729
  *
721
730
  * Injected so the manager stays chain- and token-agnostic. Issuers
722
731
  * wire it to `@pafi-dev/core` V4 quoting, a subgraph query, or an
@@ -728,18 +737,14 @@ interface FeeManagerConfig {
728
737
  * Computes how much fee to collect from the user to cover the gas cost
729
738
  * of a sponsored UserOp.
730
739
  *
731
- * ## v1.4 scope change
732
- *
733
- * The fee is now expressed in the **fee currency** chosen by the caller
734
- * (PT for mint/burn, USDT for swap/perp_deposit) — not hardcoded to USDT.
740
+ * The fee is expressed in the **fee currency** chosen by the caller
741
+ * (PT for mint/burn, USDT for swap/perp_deposit) — not hardcoded.
735
742
  *
736
- * **Operator rebalancing is gone.** In v1.4 the operator no longer holds
737
- * ETH directly — gas is paid by PAFI sponsor-relayer via the paymaster-proxy
738
- * (see [SPONSORED_PATH_FLOW.md]). The fee collected here is an
743
+ * **No operator rebalancing.** The operator does not hold ETH directly;
744
+ * gas is paid by PAFI sponsor-relayer via the paymaster-proxy (see
745
+ * [SPONSORED_PATH_FLOW.md]). The fee collected here is an
739
746
  * application-level ERC-20 transfer inside the same UserOp batch, not a
740
747
  * reimbursement to a wallet that needs topping up.
741
- *
742
- * `rebalanceIfNeeded()` and `swapUsdtToNative` were removed in 0.3.0.
743
748
  */
744
749
  declare class FeeManager {
745
750
  private readonly provider;
@@ -815,8 +820,9 @@ interface PointIndexerConfig {
815
820
  pointTokenAddress: Address;
816
821
  ledger: IPointLedger;
817
822
  /**
818
- * v1.6 — when set, indexer listens to `MintFeeWrapper.MintWithFee`
819
- * (filtered by this `pointToken`) instead of `PointToken.Transfer(0x0)`.
823
+ * When set, indexer listens to `MintFeeWrapper.MintWithFee`
824
+ * (filtered by this `pointToken`) instead of
825
+ * `PointToken.Transfer(0x0)`.
820
826
  *
821
827
  * Required for wrapper-mediated mints because the on-chain Transfer
822
828
  * destination is the wrapper itself (not the end user), so a naive
@@ -874,15 +880,15 @@ interface PointIndexerConfig {
874
880
  *
875
881
  * **Two modes** — selected at construction by `mintFeeWrapperAddress`:
876
882
  *
877
- * 1. **Wrapper mode (v1.6, recommended for mainnet)**
883
+ * 1. **Wrapper mode (recommended for mainnet)**
878
884
  * Listens to `MintFeeWrapper.MintWithFee(indexed pointToken, indexed to,
879
885
  * grossAmount, netAmount, feeAmount)` filtered by `pointToken`. The
880
886
  * `to` field is the actual end-user (not the wrapper), and `grossAmount`
881
887
  * matches the off-chain lock's amount.
882
888
  *
883
- * 2. **Direct mode (legacy / chains without wrapper)**
884
- * Listens to `PointToken.Transfer(from=0x0 → to)` the original v1.5
885
- * behavior. Used when the wrapper address is undefined / zero / dead.
889
+ * 2. **Direct mode (chains without wrapper)**
890
+ * Listens to `PointToken.Transfer(from=0x0 → to)`. Used when the
891
+ * wrapper address is undefined / zero / dead.
886
892
  *
887
893
  * Finalization strategy (per event):
888
894
  * 1. Find a PENDING locked mint request for `to` with matching amount.
@@ -934,7 +940,7 @@ declare class PointIndexer {
934
940
  */
935
941
  processBlockRange(from: bigint, to: bigint): Promise<void>;
936
942
  /**
937
- * Wrapper mode (v1.6): listen for `MintWithFee` on the wrapper,
943
+ * Wrapper mode: listen for `MintWithFee` on the wrapper,
938
944
  * filtered to events for THIS pointToken only. The event's `to` field
939
945
  * is the actual end user, and `grossAmount` matches the lock amount.
940
946
  */
@@ -1071,23 +1077,21 @@ interface ApiConfigResponse {
1071
1077
  /**
1072
1078
  * EIP-7702 delegation target — the single contract every user's
1073
1079
  * EOA must delegate to before submitting sponsored UserOps. On
1074
- * Base mainnet this is Coinbase Smart Wallet v2.
1080
+ * Base mainnet this is Pimlico's `Simple7702Account` at
1081
+ * `0xe6Cae83BdE06E4c305530e199D7217f42808555B` (swapped in
1082
+ * 2026-04-27 to replace the earlier Coinbase Smart Wallet v2
1083
+ * BatchExecutor, which had a SignatureWrapper incompatibility
1084
+ * surfacing as AA23 0x3c10b94e during validateUserOp).
1075
1085
  */
1076
1086
  batchExecutor?: Address;
1077
1087
  /**
1078
- * Uniswap V4 hook that enforces the 10% fee on PT→USDT swaps
1079
- * (USDT→PT is free). FE uses this in `PoolKey.hooks` when building
1080
- * a swap; the pool is only discoverable when the hook matches.
1081
- */
1082
- pafiHook?: Address;
1083
- /**
1084
- * v1.6 — single global MintFeeWrapper that skims a fee on every
1088
+ * Single global MintFeeWrapper that skims a fee on every
1085
1089
  * sig-gated mint and distributes across the configured recipients.
1086
1090
  */
1087
1091
  mintFeeWrapper?: Address;
1088
1092
  };
1089
1093
  /**
1090
- * v1.6 — per-PointToken mint fee (in basis points, 0–10000) read from
1094
+ * Per-PointToken mint fee (in basis points, 0–10000) read from
1091
1095
  * `MintFeeWrapper.totalFeeBps(pointToken)`. Frontend uses this to show
1092
1096
  * "you'll receive X PT (Y% mint fee)" before user signs.
1093
1097
  *
@@ -1146,7 +1150,6 @@ interface ApiUserRequest {
1146
1150
  }
1147
1151
  interface ApiUserResponse {
1148
1152
  mintRequestNonce: bigint;
1149
- receiverConsentNonce: bigint;
1150
1153
  /**
1151
1154
  * Off-chain point balance from the issuer's ledger (excludes PENDING locks).
1152
1155
  * This is what the user can claim into on-chain PT via `/claim`.
@@ -1163,8 +1166,8 @@ interface ApiUserResponse {
1163
1166
  */
1164
1167
  totalBalance: bigint;
1165
1168
  /**
1166
- * @deprecated use `offChainBalance` instead. Kept for backward compatibility
1167
- * will be removed in 0.2.0.
1169
+ * @deprecated Use `offChainBalance` instead. Retained as a wire-level
1170
+ * alias for older mobile clients that still read it.
1168
1171
  */
1169
1172
  balance: bigint;
1170
1173
  isMinter: boolean;
@@ -1348,10 +1351,10 @@ interface IssuerApiHandlersConfig {
1348
1351
  */
1349
1352
  pafiWebUrl?: string;
1350
1353
  /**
1351
- * v1.6 — MintFeeWrapper address used to read per-PointToken fee
1352
- * basis points for `/config`. When omitted, the response will not
1353
- * include `mintFeeBpsByToken` and FE must assume zero fee. Wrapper
1354
- * is shared across PointTokens; per-token recipients live inside it.
1354
+ * MintFeeWrapper address used to read per-PointToken fee basis
1355
+ * points for `/config`. When omitted, the response will not include
1356
+ * `mintFeeBpsByToken` and FE must assume zero fee. Wrapper is shared
1357
+ * across PointTokens; per-token recipients live inside it.
1355
1358
  */
1356
1359
  mintFeeWrapperAddress?: Address;
1357
1360
  /** Required by `handleGasFee`; omit to disable the endpoint. */
@@ -1380,7 +1383,7 @@ interface IssuerApiHandlersConfig {
1380
1383
  *
1381
1384
  * Every protected handler takes a pre-verified `userAddress` as its first
1382
1385
  * argument. The issuer's middleware wraps `authenticateRequest()` from
1383
- * `@pafi/issuer` to extract that address from the Bearer token before
1386
+ * `@pafi-dev/issuer` to extract that address from the Bearer token before
1384
1387
  * routing.
1385
1388
  */
1386
1389
  declare class IssuerApiHandlers {
@@ -1454,9 +1457,8 @@ declare class IssuerApiHandlers {
1454
1457
  /**
1455
1458
  * `GET /user?chainId=<id>&user=<addr>&pointToken=<addr>`
1456
1459
  *
1457
- * Returns per-user state the frontend needs to build a fresh
1458
- * `ReceiverConsent`: on-chain nonces + minter status + off-chain
1459
- * balance.
1460
+ * Returns per-user state the frontend needs to build a fresh mint:
1461
+ * on-chain nonces + minter status + off-chain balance.
1460
1462
  */
1461
1463
  handleUser(userAddress: Address, request: ApiUserRequest): Promise<ApiUserResponse>;
1462
1464
  /**
@@ -1482,7 +1484,7 @@ declare class IssuerApiHandlers {
1482
1484
  }
1483
1485
 
1484
1486
  /**
1485
- * v1.4 reverse flow — user-initiated PT redeem.
1487
+ * Reverse flow — user-initiated PT redeem.
1486
1488
  *
1487
1489
  * User has on-chain PT, wants to convert back to off-chain points. The
1488
1490
  * issuer backend signs a `BurnRequest` with its burner signer, reserves
@@ -2037,7 +2039,13 @@ declare function serializeUserOpTypedData(td: UserOpTypedData): SerializedUserOp
2037
2039
  * (without re-estimated gas) don't accidentally zero out the original
2038
2040
  * estimates.
2039
2041
  */
2040
- declare function mergePaymasterFields<T extends object>(userOp: T, paymasterFields: {
2042
+ /**
2043
+ * Subset of `SponsorshipResponse` consumed by the merge/hash helpers.
2044
+ * Inlined as a structural type (rather than importing
2045
+ * `SponsorshipResponse` from `pafi-backend/client`) to keep
2046
+ * `userop-store` independent of the HTTP-client module.
2047
+ */
2048
+ interface PaymasterGasEstimates {
2041
2049
  paymaster: Address;
2042
2050
  paymasterData: Hex;
2043
2051
  paymasterVerificationGasLimit: bigint;
@@ -2047,7 +2055,8 @@ declare function mergePaymasterFields<T extends object>(userOp: T, paymasterFiel
2047
2055
  preVerificationGas?: bigint;
2048
2056
  maxFeePerGas?: bigint;
2049
2057
  maxPriorityFeePerGas?: bigint;
2050
- } | undefined): T;
2058
+ }
2059
+ declare function mergePaymasterFields<T extends object>(userOp: T, paymasterFields: PaymasterGasEstimates | undefined): T;
2051
2060
  interface PreparedUserOp {
2052
2061
  /** The bundler-ready UserOp (with paymaster + Pimlico-quoted gas). */
2053
2062
  userOp: PartialUserOperation & {
@@ -2063,6 +2072,51 @@ interface PreparedUserOp {
2063
2072
  /** Typed-data payload — pass directly to `eth_signTypedData_v4`. */
2064
2073
  typedData: SerializedUserOpTypedData;
2065
2074
  }
2075
+ /**
2076
+ * Atomic "merge paymaster fields + compute hash + build typed data" —
2077
+ * the only place the SDK should be doing all three operations.
2078
+ *
2079
+ * Why bundled into one call:
2080
+ *
2081
+ * Pimlico's `pm_sponsorUserOperation` re-estimates
2082
+ * `callGasLimit` / `verificationGasLimit` / `preVerificationGas` and
2083
+ * the bundler fee fields (`maxFeePerGas` / `maxPriorityFeePerGas`),
2084
+ * then signs `paymasterData` over the new values. The EntryPoint
2085
+ * hashes the actual on-chain field values during validation, so
2086
+ * callers MUST overwrite the matching userOp fields BEFORE
2087
+ * computing the userOpHash. Forgetting any field triggers AA34
2088
+ * (paymaster signature mismatch) and/or AA24 (sender signature
2089
+ * mismatch) — opaque failures that take hours to debug.
2090
+ *
2091
+ * Splitting "merge" and "hash" into two free functions invited
2092
+ * exactly this bug — each call site reinvented the merge logic,
2093
+ * and one was already drifting (see audit M-02). Returning all
2094
+ * three pieces (`userOp`, `userOpHash`, `typedData`) from a single
2095
+ * call makes "hash before merge" structurally impossible: there is
2096
+ * no intermediate userOp to compute a stale hash over.
2097
+ *
2098
+ * Behaviour:
2099
+ * - When `paymasterFields` is undefined (paymaster declined / Path
2100
+ * C self-pay), the result is just the hash + typed-data of the
2101
+ * caller's `partialUserOp` — no fields are touched.
2102
+ * - When provided, only defined keys in `paymasterFields` overwrite
2103
+ * the matching fields. Original gas/fee estimates survive when
2104
+ * the paymaster did not re-estimate.
2105
+ *
2106
+ * Replaces:
2107
+ * - manual spread merges (e.g. previous `delegateHandler.ts`)
2108
+ * - `mergePaymasterFields` + separate `computeUserOpHash` +
2109
+ * `buildUserOpTypedData` triplets in any new call site.
2110
+ *
2111
+ * Note: the original `mergePaymasterFields` and the raw
2112
+ * `computeUserOpHash` / `buildUserOpTypedData` exports remain
2113
+ * available — use this helper unless you have a specific reason to
2114
+ * stage the operations.
2115
+ */
2116
+ declare function applyPaymasterGasEstimates(partialUserOp: PartialUserOperation & {
2117
+ maxFeePerGas: bigint;
2118
+ maxPriorityFeePerGas: bigint;
2119
+ }, paymasterFields: PaymasterGasEstimates | undefined, chainId: number): PreparedUserOp;
2066
2120
  interface PrepareMobileUserOpParams {
2067
2121
  /** Lock id (issuer-generated) keying both store entry + ledger row. */
2068
2122
  lockId: string;
@@ -2276,7 +2330,7 @@ interface IssuerStateValidatorLike {
2276
2330
  preValidateMint(pointToken: Address, amount: bigint): Promise<unknown>;
2277
2331
  }
2278
2332
  /**
2279
- * v1.4 sig-gated mint handler — mirrors `PTRedeemHandler` on the mint side.
2333
+ * Sig-gated mint handler — mirrors `PTRedeemHandler` on the mint side.
2280
2334
  *
2281
2335
  * Pre-validates against IssuerRegistry + on-chain totalSupply, locks the
2282
2336
  * off-chain balance, builds the sponsored UserOp (mint + PT fee
@@ -2316,7 +2370,7 @@ interface PTClaimHandlerConfig {
2316
2370
  signatureDeadlineSeconds?: number;
2317
2371
  now?: () => number;
2318
2372
  /**
2319
- * Optional override for the v1.6+ MintFeeWrapper address. By default the
2373
+ * Optional override for the MintFeeWrapper address. By default the
2320
2374
  * handler auto-resolves the wrapper from the request's `chainId` via
2321
2375
  * `getContractAddresses(chainId).mintFeeWrapper`. Set this only when:
2322
2376
  * - testing against a non-canonical wrapper deploy, or
@@ -2557,12 +2611,12 @@ declare function createSdkErrorMapper(factories: SdkErrorMapperFactories): (err:
2557
2611
  /**
2558
2612
  * Top-level configuration for `createIssuerService`.
2559
2613
  *
2560
- * In v1.4 the SDK is HTTP-client-free: it signs EIP-712 messages, reads
2614
+ * The SDK is HTTP-client-free: it signs EIP-712 messages, reads
2561
2615
  * on-chain state, builds unsigned UserOperations, and maintains the
2562
2616
  * off-chain ledger. It never broadcasts transactions — that's the
2563
2617
  * frontend's responsibility via Bundler + Paymaster.
2564
2618
  *
2565
- * **Multi-token (0.2.0+):** Pass `pointTokenAddresses: Address[]` to
2619
+ * **Multi-token:** Pass `pointTokenAddresses: Address[]` to
2566
2620
  * support multiple PointTokens under a single issuer backend. Legacy
2567
2621
  * `pointTokenAddress: Address` still works for single-token deployments.
2568
2622
  * When both are provided, `pointTokenAddresses` takes precedence.
@@ -2576,8 +2630,8 @@ interface IssuerServiceConfig {
2576
2630
  /**
2577
2631
  * Issuer-specific contract addresses merged into the `/config` response.
2578
2632
  * PAFI-owned addresses (batchExecutor, usdt, issuerRegistry, mintingOracle,
2579
- * pafiHook) are auto-resolved from `getContractAddresses(chainId)` and
2580
- * can be omitted. Only `pointToken` / `pointTokens` must be provided.
2633
+ * mintFeeWrapper, …) are auto-resolved from `getContractAddresses(chainId)`
2634
+ * and can be omitted. Only `pointToken` / `pointTokens` must be provided.
2581
2635
  */
2582
2636
  contracts?: Pick<ApiConfigResponse["contracts"], "pointToken" | "pointTokens" | "relay">;
2583
2637
  /**
@@ -2629,11 +2683,11 @@ interface IssuerServiceConfig {
2629
2683
  */
2630
2684
  autoStart?: boolean;
2631
2685
  /**
2632
- * v1.6 — override the MintFeeWrapper address used by the indexer.
2633
- * When omitted, the factory auto-resolves from
2686
+ * Override the MintFeeWrapper address used by the indexer. When
2687
+ * omitted, the factory auto-resolves from
2634
2688
  * `getContractAddresses(chainId).mintFeeWrapper`. Pass the
2635
2689
  * dead-zero address (`0x...dEaD`) to force direct-Transfer mode
2636
- * (legacy v1.5, useful for local fork tests).
2690
+ * (useful for local fork tests).
2637
2691
  */
2638
2692
  mintFeeWrapperAddress?: Address;
2639
2693
  };
@@ -2673,11 +2727,6 @@ interface IssuerService {
2673
2727
  fee: FeeManager | undefined;
2674
2728
  /** All indexers keyed by PointToken address. */
2675
2729
  indexers: Map<Address, PointIndexer>;
2676
- /**
2677
- * First indexer. Kept for backward compat with 0.1.x callers.
2678
- * @deprecated use `indexers.get(tokenAddress)` for multi-token.
2679
- */
2680
- indexer: PointIndexer;
2681
2730
  /** Framework-agnostic HTTP handlers — wire into Express / Fastify / Hono. */
2682
2731
  api: IssuerApiHandlers;
2683
2732
  /**
@@ -2756,7 +2805,6 @@ interface PoolsDto {
2756
2805
  }
2757
2806
  interface UserDto {
2758
2807
  mintRequestNonce: string;
2759
- receiverConsentNonce: string;
2760
2808
  offChainBalance: string;
2761
2809
  onChainBalance: string;
2762
2810
  totalBalance: string;
@@ -2986,12 +3034,22 @@ interface SubgraphPoolsProviderConfig {
2986
3034
  * Optional clock override for tests.
2987
3035
  */
2988
3036
  now?: () => number;
3037
+ /**
3038
+ * Optional error callback. Invoked on every recoverable error (subgraph
3039
+ * unreachable, non-200 response, GraphQL errors, invalid pool payload).
3040
+ * The provider continues to return `{ pools: [] }` on error and also
3041
+ * logs via `console.warn`/`console.error`; this hook lets host apps
3042
+ * forward errors to their own observability stack without parsing logs.
3043
+ * Throwing inside this callback is swallowed so the provider remains
3044
+ * total. (Addresses CODE_REVIEW issuer MEDIUM-5.)
3045
+ */
3046
+ onError?: (error: Error) => void;
2989
3047
  }
2990
3048
  /**
2991
3049
  * Create a `PoolsProvider` backed by the PAFI subgraph.
2992
3050
  *
2993
3051
  * Queries the `pafiTokens` entity for the given `pointTokenAddress`,
2994
- * reads its linked `Pool`, and returns a single-element `PoolKey[]`.
3052
+ * reads its linked V3 `Pool`, and returns a single-element `PoolKey[]`.
2995
3053
  * Multiple pools per token would require a subgraph schema change.
2996
3054
  *
2997
3055
  * The result is cached in-process with a short TTL (default 30s). Pool
@@ -3002,19 +3060,22 @@ interface SubgraphPoolsProviderConfig {
3002
3060
  * - the token is not registered on PAFI yet (no PafiToken entity)
3003
3061
  * - the token is registered but its pool has not been initialised
3004
3062
  * - the subgraph is unreachable or returns an error (logs to console,
3005
- * does not throw — callers should handle empty pool list gracefully)
3063
+ * invokes `onError` if provided; does not throw — callers should
3064
+ * handle empty pool list gracefully)
3006
3065
  *
3007
- * Assumes the PAFI subgraph schema. Issuers with a custom subgraph must
3066
+ * Assumes the PAFI subgraph schema (`pafiToken { pool { id feeTier
3067
+ * token0 { id } token1 { id } } }`). Issuers with a custom subgraph must
3008
3068
  * implement `PoolsProvider` themselves instead of using this helper.
3009
3069
  *
3010
3070
  * @example
3011
3071
  * ```ts
3012
- * import { createSubgraphPoolsProvider, createIssuerService } from "@pafi/issuer";
3072
+ * import { createSubgraphPoolsProvider, createIssuerService } from "@pafi-dev/issuer";
3013
3073
  *
3014
3074
  * const service = createIssuerService({
3015
3075
  * // ...other config
3016
3076
  * poolsProvider: createSubgraphPoolsProvider({
3017
3077
  * subgraphUrl: "https://graph.pacificfinance.org/subgraphs/name/pafi",
3078
+ * onError: (err) => metrics.increment("issuer.pools.error", { reason: err.message }),
3018
3079
  * }),
3019
3080
  * });
3020
3081
  * ```
@@ -3065,8 +3126,8 @@ interface SubgraphNativeUsdtQuoterConfig {
3065
3126
  * Create a native→USDT quoter backed by the PAFI subgraph's
3066
3127
  * `Bundle.ethPriceUSD`. The returned function has the shape
3067
3128
  * `(amountNative: bigint) => Promise<bigint>` and can be passed as
3068
- * `quoteNativeToFee` to `FeeManager` — in v1.4 the fee currency
3069
- * is configured at the integration layer, not hardcoded here.
3129
+ * `quoteNativeToFee` to `FeeManager` — the fee currency is configured
3130
+ * at the integration layer, not hardcoded here.
3070
3131
  *
3071
3132
  * Used by `FeeManager.estimateGasFee()` to convert the gas cost into
3072
3133
  * an ERC-20 amount charged as part of the sponsored UserOp batch.
@@ -3135,50 +3196,6 @@ interface NativePtQuoterConfig {
3135
3196
  */
3136
3197
  declare function createNativePtQuoter(config: NativePtQuoterConfig): (amountNative: bigint) => Promise<bigint>;
3137
3198
 
3138
- /**
3139
- * Combined off-chain + on-chain balance for a single user / token pair.
3140
- *
3141
- * - `offChain` — the issuer's ledger balance (available, excluding locks)
3142
- * - `onChain` — the user's ERC-20 balance from `PointToken.balanceOf`
3143
- * - `total` — `offChain + onChain` (what the Issuer App displays)
3144
- */
3145
- interface CombinedBalance {
3146
- offChain: bigint;
3147
- onChain: bigint;
3148
- total: bigint;
3149
- }
3150
- interface BalanceAggregatorConfig {
3151
- provider: PublicClient;
3152
- ledger: IPointLedger;
3153
- }
3154
- /**
3155
- * v1.4 utility — aggregates off-chain + on-chain point balances into a
3156
- * single view for the "combined balance" UI in the Issuer App.
3157
- *
3158
- * The `/user` API handler uses this internally; the helper is exposed
3159
- * publicly so Issuer Apps can call it directly without going through
3160
- * the HTTP layer (e.g., for server-rendered pages or admin dashboards).
3161
- *
3162
- * See [REQUIREMENTS_V2.md] §1 — "The Issuer App displays a combined
3163
- * balance (off-chain points + on-chain PT) and does not surface USDT."
3164
- */
3165
- declare class BalanceAggregator {
3166
- private readonly provider;
3167
- private readonly ledger;
3168
- constructor(config: BalanceAggregatorConfig);
3169
- /**
3170
- * Combined balance for a single (user, token) pair. Fetches off-chain
3171
- * + on-chain in parallel.
3172
- */
3173
- getCombinedBalance(user: Address, pointToken: Address): Promise<CombinedBalance>;
3174
- /**
3175
- * Combined balance for multiple tokens owned by the same user. Runs
3176
- * all lookups in parallel. Returns a Map keyed by the token address
3177
- * (same casing as supplied — caller should normalize if needed).
3178
- */
3179
- getCombinedBalanceMulti(user: Address, pointTokens: Address[]): Promise<Map<Address, CombinedBalance>>;
3180
- }
3181
-
3182
3199
  /**
3183
3200
  * Typed errors thrown by the helpers below — issuer controllers map
3184
3201
  * these to the appropriate HTTP status. We don't depend on @nestjs/common
@@ -3299,9 +3316,9 @@ interface IssuerRegistryRecord {
3299
3316
  mintingOracle: Address;
3300
3317
  }
3301
3318
  /**
3302
- * v1.6 — per-token cap config read from `MintingOracle.tokenCaps()`.
3303
- * Caps moved off the issuer record so a single issuer can run many
3304
- * PointTokens with different supply policies.
3319
+ * Per-token cap config read from `MintingOracle.tokenCaps()`. Caps
3320
+ * live per PointToken (not per issuer), so a single issuer can run
3321
+ * many PointTokens with different supply policies.
3305
3322
  */
3306
3323
  interface TokenCapRecord {
3307
3324
  declaredTotalSupply: bigint;
@@ -3470,4 +3487,4 @@ declare class MemoryRedemptionHistoryStore implements IRedemptionHistoryStore {
3470
3487
 
3471
3488
  declare const PAFI_ISSUER_SDK_VERSION: string;
3472
3489
 
3473
- export { AdapterMisconfiguredError, type ApiConfigResponse, type ApiGasFeeResponse, type ApiLoginRequest, type ApiLoginResponse, type ApiNonceResponse, type ApiPoolsRequest, type ApiPoolsResponse, type ApiRedemptionEvaluateRequest, type ApiRedemptionEvaluateResponse, type ApiRedemptionPreviewRequest, type ApiRedemptionPreviewResponse, type ApiUserRequest, type ApiUserResponse, type AuthContext, AuthError, type AuthErrorCode, AuthService, type AuthServiceConfig, BalanceAggregator, type BalanceAggregatorConfig, BundlerNotConfiguredError, BundlerRejectedError, type BurnEvent, BurnIndexer, type BurnIndexerConfig, type BurnStatusParams, type BurnStatusResponse, type ClaimDto, type CombinedBalance, type ConfigDto, ConfigurationError, DEFAULT_REDEMPTION_POLICY, type DecodedCallDto, DefaultPolicyEngine, type DefaultPolicyEngineOptions, type DelegatePrepareDto, type DelegateStatusDto, type EvaluateInput, FeeManager, type FeeManagerConfig, type FetchFailureReason, type FetchResult, type GasFeeDto, type HandleDelegateSubmitParams, type HandleDelegateSubmitResult, type HandleMobilePrepareParams, type HandleMobilePrepareResult, type HandleMobileSubmitParams, type IIndexerCursorStore, type IPendingUserOpStore, type IPointLedger, type IPolicyEngine, type IRateLimiter, type IRedemptionHistoryStore, type ISessionStore, InMemoryCursorStore, IssuerApiAdapter, type IssuerApiAdapterConfig, IssuerApiHandlers, type IssuerApiHandlersConfig, type IssuerRegistryRecord, type IssuerService, type IssuerServiceConfig, IssuerStateError, IssuerStateValidator, LockNotFoundError, type LockedMintRequest, type LoginResult, MemoryPendingUserOpStore, MemoryRateLimiter, MemoryRedemptionHistoryStore, MemorySessionStore, type MemorySessionStoreOptions, type MintEvent, type MintStatusParams, type MintStatusResponse, type MintingStatus, type MobilePrepareDto, type MobileSubmitDto, type NativePtQuoterConfig, NonceManager, NoopRateLimiter, PAFI_ISSUER_SDK_VERSION, PTClaimError, PTClaimHandler, type PTClaimHandlerConfig, type PTClaimRequest, type PTClaimResponse, PTRedeemError, PTRedeemHandler, type PTRedeemHandlerConfig, type PTRedeemRequest, type PTRedeemResponse, PafiBackendClient, type PafiBackendConfig, PafiBackendError, type PafiBackendErrorCode, type PendingCredit, type PendingUserOpEntry, PendingUserOpForbiddenError, PendingUserOpNotFoundError, type PerpDepositDto, PerpDepositError, PerpDepositHandler, type PerpDepositHandlerConfig, type PerpDepositRequest, type PerpDepositResponse, PointIndexer, type PointIndexerConfig, type PolicyDecision, type PolicyEvalRequest, PolicyProvider, type PolicyProviderConfig, type PoolsDto, type PoolsProvider, type PreValidateMintResult, type PrepareBurnParams, type PrepareMintParams, type PrepareMobileUserOpParams, type PrepareMobileUserOpResult, type PreparedUserOp, REDEMPTION_HISTORY_WINDOW_SEC, type RateLimitAction, type RateLimiterConfig, type RedeemDto, type RedeemPrepareDto, RedemptionService, type RedemptionServiceConfig, RelayError, type RelayErrorCode, RelayService, type RelayUserOpParams, type RelayUserOpRequest, type RelayUserOpResponse, type RequestPaymasterParams, type ResolvedPolicy, type RetryConfig, type SdkErrorBody, type SdkErrorMapperFactories, type SdkErrorStatus, type SerializedUserOpTypedData, type Session, SettlementClient, type SettlementClientConfig, type SponsorshipRequest, type SponsorshipResponse, type SponsorshipTarget, type SponsorshipUserOp, type SubgraphNativeUsdtQuoterConfig, type SubgraphPoolsProviderConfig, type UserDto, type UserHistory, authenticateRequest, buildSdkErrorBody, createIssuerService, createNativePtQuoter, createSdkErrorMapper, createSubgraphNativeUsdtQuoter, createSubgraphPoolsProvider, defaultPolicyFor, evaluateRedemption, handleClaimStatus, handleDelegateSubmit, handleMobilePrepare, handleMobileSubmit, handleRedeemStatus, mergePaymasterFields, prepareMobileUserOp, relayUserOp, requestPaymaster, serializeEntryToJsonRpc, serializeUserOpTypedData };
3490
+ export { AdapterMisconfiguredError, type ApiConfigResponse, type ApiGasFeeResponse, type ApiLoginRequest, type ApiLoginResponse, type ApiNonceResponse, type ApiPoolsRequest, type ApiPoolsResponse, type ApiRedemptionEvaluateRequest, type ApiRedemptionEvaluateResponse, type ApiRedemptionPreviewRequest, type ApiRedemptionPreviewResponse, type ApiUserRequest, type ApiUserResponse, type AuthContext, AuthError, type AuthErrorCode, AuthService, type AuthServiceConfig, BundlerNotConfiguredError, BundlerRejectedError, type BurnEvent, BurnIndexer, type BurnIndexerConfig, type BurnStatusParams, type BurnStatusResponse, type ClaimDto, type ConfigDto, ConfigurationError, DEFAULT_REDEMPTION_POLICY, type DecodedCallDto, DefaultPolicyEngine, type DefaultPolicyEngineOptions, type DelegatePrepareDto, type DelegateStatusDto, type EvaluateInput, FeeManager, type FeeManagerConfig, type FetchFailureReason, type FetchResult, type GasFeeDto, type HandleDelegateSubmitParams, type HandleDelegateSubmitResult, type HandleMobilePrepareParams, type HandleMobilePrepareResult, type HandleMobileSubmitParams, type IIndexerCursorStore, type IPendingUserOpStore, type IPointLedger, type IPolicyEngine, type IRateLimiter, type IRedemptionHistoryStore, type ISessionStore, InMemoryCursorStore, IssuerApiAdapter, type IssuerApiAdapterConfig, IssuerApiHandlers, type IssuerApiHandlersConfig, type IssuerRegistryRecord, type IssuerService, type IssuerServiceConfig, IssuerStateError, IssuerStateValidator, LockNotFoundError, type LockedMintRequest, type LoginResult, MemoryPendingUserOpStore, MemoryRateLimiter, MemoryRedemptionHistoryStore, MemorySessionStore, type MemorySessionStoreOptions, type MintEvent, type MintStatusParams, type MintStatusResponse, type MintingStatus, type MobilePrepareDto, type MobileSubmitDto, type NativePtQuoterConfig, NonceManager, NoopRateLimiter, PAFI_ISSUER_SDK_VERSION, PTClaimError, PTClaimHandler, type PTClaimHandlerConfig, type PTClaimRequest, type PTClaimResponse, PTRedeemError, PTRedeemHandler, type PTRedeemHandlerConfig, type PTRedeemRequest, type PTRedeemResponse, PafiBackendClient, type PafiBackendConfig, PafiBackendError, type PafiBackendErrorCode, type PaymasterGasEstimates, type PendingCredit, type PendingUserOpEntry, PendingUserOpForbiddenError, PendingUserOpNotFoundError, type PerpDepositDto, PerpDepositError, PerpDepositHandler, type PerpDepositHandlerConfig, type PerpDepositRequest, type PerpDepositResponse, PointIndexer, type PointIndexerConfig, type PolicyDecision, type PolicyEvalRequest, PolicyProvider, type PolicyProviderConfig, type PoolsDto, type PoolsProvider, type PreValidateMintResult, type PrepareBurnParams, type PrepareMintParams, type PrepareMobileUserOpParams, type PrepareMobileUserOpResult, type PreparedUserOp, REDEMPTION_HISTORY_WINDOW_SEC, type RateLimitAction, type RateLimiterConfig, type RedeemDto, type RedeemPrepareDto, RedemptionService, type RedemptionServiceConfig, RelayError, type RelayErrorCode, RelayService, type RelayUserOpParams, type RelayUserOpRequest, type RelayUserOpResponse, type RequestPaymasterParams, type ResolvedPolicy, type RetryConfig, type SdkErrorBody, type SdkErrorMapperFactories, type SdkErrorStatus, type SerializedUserOpTypedData, type Session, SettlementClient, type SettlementClientConfig, type SponsorshipRequest, type SponsorshipResponse, type SponsorshipTarget, type SponsorshipUserOp, type SubgraphNativeUsdtQuoterConfig, type SubgraphPoolsProviderConfig, type UserDto, type UserHistory, applyPaymasterGasEstimates, authenticateRequest, buildSdkErrorBody, createIssuerService, createNativePtQuoter, createSdkErrorMapper, createSubgraphNativeUsdtQuoter, createSubgraphPoolsProvider, defaultPolicyFor, evaluateRedemption, handleClaimStatus, handleDelegateSubmit, handleMobilePrepare, handleMobileSubmit, handleRedeemStatus, mergePaymasterFields, prepareMobileUserOp, relayUserOp, requestPaymaster, serializeEntryToJsonRpc, serializeUserOpTypedData };