@pafi-dev/issuer 0.32.0 → 0.33.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.cts CHANGED
@@ -1551,17 +1551,8 @@ interface SettlementClientConfig {
1551
1551
  /**
1552
1552
  * chainId — used to derive the issuer-api base URL via
1553
1553
  * `getPafiServiceUrls(chainId).issuerApi`. SDK ships with the URL
1554
- * per chainId; bump SDK version to retarget — OR pass `baseUrl`
1555
- * to override per-deployment.
1556
1554
  */
1557
1555
  chainId: number;
1558
- /**
1559
- * Optional override for the issuer-api base URL. Audit PACI5-17 —
1560
- * production issuer backends should set this from an env var
1561
- * (e.g. `PAFI_ISSUER_API_URL`) so the deployed binary doesn't
1562
- * depend on the SDK ship-default that may target dev infrastructure.
1563
- * Undefined / empty → use the ship-default for `chainId`.
1564
- */
1565
1556
  baseUrl?: string;
1566
1557
  /** PAFI-assigned issuer id used in `X-Issuer-Id` header. */
1567
1558
  issuerId: string;
@@ -1575,33 +1566,7 @@ interface SettlementClientConfig {
1575
1566
  interface PolicyProviderConfig extends SettlementClientConfig {
1576
1567
  /** Cache TTL in milliseconds. Default 5 * 60 * 1000 (5min). */
1577
1568
  cacheTtlMs?: number;
1578
- /**
1579
- * Behavior khi settlement-api fetch fail (network blip, 5xx, timeout).
1580
- *
1581
- * Audit PACI5-17 — the pre-flight redeem limit is the SOLE gate
1582
- * before the issuer signer mints a BurnRequest. Silently degrading
1583
- * to a permissive default during settlement outages let users
1584
- * exceed their configured limit until the outage cleared.
1585
- *
1586
- * - `'fail-closed'` (DEFAULT, recommended): throw
1587
- * `PolicyProviderUnavailableError`. Caller maps to HTTP 503 so
1588
- * the FE can retry; redemptions are blocked until policy fetch
1589
- * succeeds.
1590
- *
1591
- * - `'permissive-default'`: return `defaultPolicyFor(issuerId)` and
1592
- * fire `onWarning`. Pre-fix behavior. Operators who genuinely
1593
- * prefer availability over enforcement (e.g. test issuers, low-
1594
- * risk PT) MUST opt in explicitly AND wire `onWarning` to a
1595
- * pager / Slack / Sentry alert. Silent permissive fallback is no
1596
- * longer the default.
1597
- */
1598
1569
  onFetchFailure?: "fail-closed" | "permissive-default";
1599
- /**
1600
- * Observability hook for non-fatal events: permissive-default
1601
- * fallbacks (when explicitly opted in). Wire to your logger /
1602
- * Sentry / Datadog so the `policy_provider_fallback` event reaches
1603
- * the on-call dashboard.
1604
- */
1605
1570
  onWarning?: (msg: string, ctx: Record<string, unknown>) => void;
1606
1571
  /**
1607
1572
  * Optional clock for testability. Returns unix milliseconds.
@@ -2078,17 +2043,8 @@ interface PafiBackendConfig {
2078
2043
  /**
2079
2044
  * chainId — used to derive the sponsor-relayer base URL via
2080
2045
  * `getPafiServiceUrls(chainId).sponsorRelayer`. SDK ships with the
2081
- * URL per chainId; bump SDK version to retarget — OR pass
2082
- * `baseUrl` to override per-deployment.
2083
2046
  */
2084
2047
  chainId: number;
2085
- /**
2086
- * Optional override for the sponsor-relayer base URL. Audit
2087
- * PACI5-17 — production issuer backends should set this from an env
2088
- * var (e.g. `PAFI_SPONSOR_RELAYER_URL`) so the binary doesn't depend
2089
- * on the SDK ship-default that may target dev infrastructure.
2090
- * Undefined / empty → use the ship-default for `chainId`.
2091
- */
2092
2048
  baseUrl?: string;
2093
2049
  issuerId: string;
2094
2050
  apiKey: string;
@@ -2246,12 +2202,32 @@ interface MintStatusParams {
2246
2202
  * - lock.status === "PENDING"
2247
2203
  * - lock.userOpHash is bound (set by `/claim/submit`)
2248
2204
  *
2249
- * If the bundler reports the UserOp confirmed, the handler updates
2205
+ * If the bundler reports the UserOp confirmed AND the receipt block
2206
+ * is past the configured `confirmations` depth, the handler updates
2250
2207
  * the ledger lock + returns `MINTED` immediately, bypassing
2251
2208
  * `PointIndexer`'s amount-based race (multiple PENDING locks with
2252
2209
  * the same amount can be matched to the wrong tx_hash).
2253
2210
  */
2254
2211
  pafiBackendClient?: PafiBackendClient | null;
2212
+ /**
2213
+ * Audit PACI5-13 — required for the confirmation-depth check on the
2214
+ * bundler-receipt fallback. The bundler returns success at zero
2215
+ * confs, but `PointIndexer` enforces a 3-block reorg window;
2216
+ * crediting / debiting off-chain at 0 confs while the indexer waits
2217
+ * for finality leaves an unbacked durable mutation if the tx is
2218
+ * reorged out. Pass the SAME PublicClient that the indexer uses so
2219
+ * both paths see consistent chain head. Optional only for legacy
2220
+ * callers that don't supply `pafiBackendClient`; required whenever
2221
+ * the receipt-fallback path can run.
2222
+ */
2223
+ provider?: PublicClient;
2224
+ /**
2225
+ * Audit PACI5-13 — confirmation depth required before the receipt
2226
+ * fallback applies the credit / debit. MUST match
2227
+ * `PointIndexer.confirmations` (default 3). Operators who reduce
2228
+ * the indexer depth must set this to the same value.
2229
+ */
2230
+ confirmations?: number;
2255
2231
  /** Optional logger for "ledger update failed" warnings. */
2256
2232
  onWarning?: (msg: string) => void;
2257
2233
  }
@@ -2260,6 +2236,20 @@ interface BurnStatusParams {
2260
2236
  userAddress: Address;
2261
2237
  ledger: IPointLedger;
2262
2238
  pafiBackendClient?: PafiBackendClient | null;
2239
+ /**
2240
+ * Audit PACI5-13 — see `MintStatusParams.provider` for full
2241
+ * rationale. The receipt-fallback path applies a spendable
2242
+ * off-chain credit; without a confirmation-depth gate a reorg
2243
+ * before `BurnIndexer.confirmations` leaves durable unbacked
2244
+ * credit with no reversal mechanism.
2245
+ */
2246
+ provider?: PublicClient;
2247
+ /**
2248
+ * Audit PACI5-13 — confirmation depth required before the receipt
2249
+ * fallback applies the credit. MUST match
2250
+ * `BurnIndexer.confirmations` (default 3).
2251
+ */
2252
+ confirmations?: number;
2263
2253
  onWarning?: (msg: string) => void;
2264
2254
  }
2265
2255
  declare class LockNotFoundError extends PafiSdkError {
@@ -3235,7 +3225,6 @@ interface IssuerServiceConfig {
3235
3225
  historyStore: IRedemptionHistoryStore;
3236
3226
  /**
3237
3227
  * Optional override for the PAFI issuer-api base URL. Audit
3238
- * PACI5-17 — production issuer backends should set this from an
3239
3228
  * env var (e.g. `PAFI_ISSUER_API_URL`) so policy fetch hits the
3240
3229
  * canonical environment for the deploy. Undefined → SDK
3241
3230
  * ship-default per chainId.
@@ -3243,9 +3232,6 @@ interface IssuerServiceConfig {
3243
3232
  baseUrl?: string;
3244
3233
  /**
3245
3234
  * Behavior khi settlement-api fetch fail. Default `'fail-closed'`
3246
- * (audit PACI5-17 — pre-flight redeem limit is the sole gate; do
3247
- * not silently degrade to a permissive default).
3248
- *
3249
3235
  * Opt in to `'permissive-default'` ONLY when paired with an alert
3250
3236
  * on the `policy_provider_fallback` event surfaced via
3251
3237
  * `onPolicyWarning`.
package/dist/index.d.ts CHANGED
@@ -1551,17 +1551,8 @@ interface SettlementClientConfig {
1551
1551
  /**
1552
1552
  * chainId — used to derive the issuer-api base URL via
1553
1553
  * `getPafiServiceUrls(chainId).issuerApi`. SDK ships with the URL
1554
- * per chainId; bump SDK version to retarget — OR pass `baseUrl`
1555
- * to override per-deployment.
1556
1554
  */
1557
1555
  chainId: number;
1558
- /**
1559
- * Optional override for the issuer-api base URL. Audit PACI5-17 —
1560
- * production issuer backends should set this from an env var
1561
- * (e.g. `PAFI_ISSUER_API_URL`) so the deployed binary doesn't
1562
- * depend on the SDK ship-default that may target dev infrastructure.
1563
- * Undefined / empty → use the ship-default for `chainId`.
1564
- */
1565
1556
  baseUrl?: string;
1566
1557
  /** PAFI-assigned issuer id used in `X-Issuer-Id` header. */
1567
1558
  issuerId: string;
@@ -1575,33 +1566,7 @@ interface SettlementClientConfig {
1575
1566
  interface PolicyProviderConfig extends SettlementClientConfig {
1576
1567
  /** Cache TTL in milliseconds. Default 5 * 60 * 1000 (5min). */
1577
1568
  cacheTtlMs?: number;
1578
- /**
1579
- * Behavior khi settlement-api fetch fail (network blip, 5xx, timeout).
1580
- *
1581
- * Audit PACI5-17 — the pre-flight redeem limit is the SOLE gate
1582
- * before the issuer signer mints a BurnRequest. Silently degrading
1583
- * to a permissive default during settlement outages let users
1584
- * exceed their configured limit until the outage cleared.
1585
- *
1586
- * - `'fail-closed'` (DEFAULT, recommended): throw
1587
- * `PolicyProviderUnavailableError`. Caller maps to HTTP 503 so
1588
- * the FE can retry; redemptions are blocked until policy fetch
1589
- * succeeds.
1590
- *
1591
- * - `'permissive-default'`: return `defaultPolicyFor(issuerId)` and
1592
- * fire `onWarning`. Pre-fix behavior. Operators who genuinely
1593
- * prefer availability over enforcement (e.g. test issuers, low-
1594
- * risk PT) MUST opt in explicitly AND wire `onWarning` to a
1595
- * pager / Slack / Sentry alert. Silent permissive fallback is no
1596
- * longer the default.
1597
- */
1598
1569
  onFetchFailure?: "fail-closed" | "permissive-default";
1599
- /**
1600
- * Observability hook for non-fatal events: permissive-default
1601
- * fallbacks (when explicitly opted in). Wire to your logger /
1602
- * Sentry / Datadog so the `policy_provider_fallback` event reaches
1603
- * the on-call dashboard.
1604
- */
1605
1570
  onWarning?: (msg: string, ctx: Record<string, unknown>) => void;
1606
1571
  /**
1607
1572
  * Optional clock for testability. Returns unix milliseconds.
@@ -2078,17 +2043,8 @@ interface PafiBackendConfig {
2078
2043
  /**
2079
2044
  * chainId — used to derive the sponsor-relayer base URL via
2080
2045
  * `getPafiServiceUrls(chainId).sponsorRelayer`. SDK ships with the
2081
- * URL per chainId; bump SDK version to retarget — OR pass
2082
- * `baseUrl` to override per-deployment.
2083
2046
  */
2084
2047
  chainId: number;
2085
- /**
2086
- * Optional override for the sponsor-relayer base URL. Audit
2087
- * PACI5-17 — production issuer backends should set this from an env
2088
- * var (e.g. `PAFI_SPONSOR_RELAYER_URL`) so the binary doesn't depend
2089
- * on the SDK ship-default that may target dev infrastructure.
2090
- * Undefined / empty → use the ship-default for `chainId`.
2091
- */
2092
2048
  baseUrl?: string;
2093
2049
  issuerId: string;
2094
2050
  apiKey: string;
@@ -2246,12 +2202,32 @@ interface MintStatusParams {
2246
2202
  * - lock.status === "PENDING"
2247
2203
  * - lock.userOpHash is bound (set by `/claim/submit`)
2248
2204
  *
2249
- * If the bundler reports the UserOp confirmed, the handler updates
2205
+ * If the bundler reports the UserOp confirmed AND the receipt block
2206
+ * is past the configured `confirmations` depth, the handler updates
2250
2207
  * the ledger lock + returns `MINTED` immediately, bypassing
2251
2208
  * `PointIndexer`'s amount-based race (multiple PENDING locks with
2252
2209
  * the same amount can be matched to the wrong tx_hash).
2253
2210
  */
2254
2211
  pafiBackendClient?: PafiBackendClient | null;
2212
+ /**
2213
+ * Audit PACI5-13 — required for the confirmation-depth check on the
2214
+ * bundler-receipt fallback. The bundler returns success at zero
2215
+ * confs, but `PointIndexer` enforces a 3-block reorg window;
2216
+ * crediting / debiting off-chain at 0 confs while the indexer waits
2217
+ * for finality leaves an unbacked durable mutation if the tx is
2218
+ * reorged out. Pass the SAME PublicClient that the indexer uses so
2219
+ * both paths see consistent chain head. Optional only for legacy
2220
+ * callers that don't supply `pafiBackendClient`; required whenever
2221
+ * the receipt-fallback path can run.
2222
+ */
2223
+ provider?: PublicClient;
2224
+ /**
2225
+ * Audit PACI5-13 — confirmation depth required before the receipt
2226
+ * fallback applies the credit / debit. MUST match
2227
+ * `PointIndexer.confirmations` (default 3). Operators who reduce
2228
+ * the indexer depth must set this to the same value.
2229
+ */
2230
+ confirmations?: number;
2255
2231
  /** Optional logger for "ledger update failed" warnings. */
2256
2232
  onWarning?: (msg: string) => void;
2257
2233
  }
@@ -2260,6 +2236,20 @@ interface BurnStatusParams {
2260
2236
  userAddress: Address;
2261
2237
  ledger: IPointLedger;
2262
2238
  pafiBackendClient?: PafiBackendClient | null;
2239
+ /**
2240
+ * Audit PACI5-13 — see `MintStatusParams.provider` for full
2241
+ * rationale. The receipt-fallback path applies a spendable
2242
+ * off-chain credit; without a confirmation-depth gate a reorg
2243
+ * before `BurnIndexer.confirmations` leaves durable unbacked
2244
+ * credit with no reversal mechanism.
2245
+ */
2246
+ provider?: PublicClient;
2247
+ /**
2248
+ * Audit PACI5-13 — confirmation depth required before the receipt
2249
+ * fallback applies the credit. MUST match
2250
+ * `BurnIndexer.confirmations` (default 3).
2251
+ */
2252
+ confirmations?: number;
2263
2253
  onWarning?: (msg: string) => void;
2264
2254
  }
2265
2255
  declare class LockNotFoundError extends PafiSdkError {
@@ -3235,7 +3225,6 @@ interface IssuerServiceConfig {
3235
3225
  historyStore: IRedemptionHistoryStore;
3236
3226
  /**
3237
3227
  * Optional override for the PAFI issuer-api base URL. Audit
3238
- * PACI5-17 — production issuer backends should set this from an
3239
3228
  * env var (e.g. `PAFI_ISSUER_API_URL`) so policy fetch hits the
3240
3229
  * canonical environment for the deploy. Undefined → SDK
3241
3230
  * ship-default per chainId.
@@ -3243,9 +3232,6 @@ interface IssuerServiceConfig {
3243
3232
  baseUrl?: string;
3244
3233
  /**
3245
3234
  * Behavior khi settlement-api fetch fail. Default `'fail-closed'`
3246
- * (audit PACI5-17 — pre-flight redeem limit is the sole gate; do
3247
- * not silently degrade to a permissive default).
3248
- *
3249
3235
  * Opt in to `'permissive-default'` ONLY when paired with an alert
3250
3236
  * on the `policy_provider_fallback` event surfaced via
3251
3237
  * `onPolicyWarning`.
package/dist/index.js CHANGED
@@ -2422,6 +2422,45 @@ var PTRedeemHandler = class {
2422
2422
  };
2423
2423
 
2424
2424
  // src/api/statusHandlers.ts
2425
+ var DEFAULT_STATUS_CONFIRMATIONS = 3;
2426
+ async function isReceiptPastConfirmations(receipt, provider, confirmations, onWarning, handlerName) {
2427
+ if (!provider) {
2428
+ onWarning?.(
2429
+ `${handlerName}: provider missing \u2014 cannot enforce confirmation depth; deferring receipt fallback to on-chain indexer (audit PACI5-13).`
2430
+ );
2431
+ return false;
2432
+ }
2433
+ if (!receipt.blockNumber) {
2434
+ onWarning?.(
2435
+ `${handlerName}: receipt has no blockNumber \u2014 cannot enforce confirmation depth; deferring to indexer (audit PACI5-13).`
2436
+ );
2437
+ return false;
2438
+ }
2439
+ const requiredConfs = BigInt(confirmations ?? DEFAULT_STATUS_CONFIRMATIONS);
2440
+ let receiptBlock;
2441
+ try {
2442
+ receiptBlock = BigInt(receipt.blockNumber);
2443
+ } catch {
2444
+ onWarning?.(
2445
+ `${handlerName}: malformed receipt blockNumber (${receipt.blockNumber}) \u2014 deferring to indexer (audit PACI5-13).`
2446
+ );
2447
+ return false;
2448
+ }
2449
+ let head;
2450
+ try {
2451
+ head = await provider.getBlockNumber();
2452
+ } catch (err) {
2453
+ onWarning?.(
2454
+ `${handlerName}: getBlockNumber failed (${err instanceof Error ? err.message : String(err)}) \u2014 deferring to indexer (audit PACI5-13).`
2455
+ );
2456
+ return false;
2457
+ }
2458
+ const depth = head - receiptBlock;
2459
+ if (depth < requiredConfs) {
2460
+ return false;
2461
+ }
2462
+ return true;
2463
+ }
2425
2464
  var LockNotFoundError = class extends PafiSdkError {
2426
2465
  code = "LOCK_NOT_FOUND";
2427
2466
  httpStatus = "not_found";
@@ -2450,6 +2489,23 @@ async function handleClaimStatus(params) {
2450
2489
  lock.userOpHash
2451
2490
  );
2452
2491
  if (receipt) {
2492
+ const passesConfirmationDepth = await isReceiptPastConfirmations(
2493
+ receipt,
2494
+ params.provider,
2495
+ params.confirmations,
2496
+ params.onWarning,
2497
+ "handleClaimStatus"
2498
+ );
2499
+ if (!passesConfirmationDepth) {
2500
+ return {
2501
+ lockId: lock.lockId,
2502
+ status: "PENDING",
2503
+ txHash: lock.txHash ?? null,
2504
+ amount: lock.amount.toString(),
2505
+ createdAt: new Date(lock.createdAt).toISOString(),
2506
+ expiresAt: new Date(lock.expiresAt).toISOString()
2507
+ };
2508
+ }
2453
2509
  if (receipt.success && receipt.txHash) {
2454
2510
  if (!lock.tokenAddress) {
2455
2511
  params.onWarning?.(
@@ -2524,14 +2580,23 @@ async function handleRedeemStatus(params) {
2524
2580
  credit.userOpHash
2525
2581
  );
2526
2582
  if (receipt && receipt.success) {
2527
- status = "RESOLVED";
2528
- txHash = receipt.txHash;
2529
- if (params.ledger.resolveCreditByBurnTx) {
2530
- await params.ledger.resolveCreditByBurnTx(credit.lockId, receipt.txHash).catch((err) => {
2531
- params.onWarning?.(
2532
- `handleRedeemStatus: resolveCreditByBurnTx failed for lock ${credit.lockId}: ${err}`
2533
- );
2534
- });
2583
+ const passesConfirmationDepth = await isReceiptPastConfirmations(
2584
+ receipt,
2585
+ params.provider,
2586
+ params.confirmations,
2587
+ params.onWarning,
2588
+ "handleRedeemStatus"
2589
+ );
2590
+ if (passesConfirmationDepth) {
2591
+ status = "RESOLVED";
2592
+ txHash = receipt.txHash;
2593
+ if (params.ledger.resolveCreditByBurnTx) {
2594
+ await params.ledger.resolveCreditByBurnTx(credit.lockId, receipt.txHash).catch((err) => {
2595
+ params.onWarning?.(
2596
+ `handleRedeemStatus: resolveCreditByBurnTx failed for lock ${credit.lockId}: ${err}`
2597
+ );
2598
+ });
2599
+ }
2535
2600
  }
2536
2601
  }
2537
2602
  } catch (err) {
@@ -3729,6 +3794,9 @@ var IssuerApiAdapter = class {
3729
3794
  userAddress: authenticatedAddress,
3730
3795
  ledger: this.cfg.ledger,
3731
3796
  pafiBackendClient: this.cfg.pafiBackendClient,
3797
+ // Audit PACI5-13 — pass the same provider the indexers use so
3798
+ // the receipt fallback gates on the same reorg depth.
3799
+ provider: this.cfg.provider,
3732
3800
  onWarning: this.cfg.onWarning
3733
3801
  });
3734
3802
  }
@@ -3738,6 +3806,8 @@ var IssuerApiAdapter = class {
3738
3806
  userAddress: authenticatedAddress,
3739
3807
  ledger: this.cfg.ledger,
3740
3808
  pafiBackendClient: this.cfg.pafiBackendClient,
3809
+ // Audit PACI5-13 — see claimStatus comment.
3810
+ provider: this.cfg.provider,
3741
3811
  onWarning: this.cfg.onWarning
3742
3812
  });
3743
3813
  }
@@ -4554,9 +4624,6 @@ var SettlementClient = class {
4554
4624
  if (!config.issuerId) throw new Error("SettlementClient: issuerId is required");
4555
4625
  if (!config.apiKey) throw new Error("SettlementClient: apiKey is required");
4556
4626
  this.config = {
4557
- // Audit PACI5-17 — honor optional baseUrl override wired from
4558
- // an env var (e.g. PAFI_ISSUER_API_URL). Empty / undefined →
4559
- // ship-default for chainId.
4560
4627
  baseUrl: getPafiServiceUrls2(config.chainId, {
4561
4628
  issuerApi: config.baseUrl
4562
4629
  }).issuerApi.replace(/\/+$/, ""),
@@ -5153,7 +5220,7 @@ var MemoryRedemptionHistoryStore = class {
5153
5220
  };
5154
5221
 
5155
5222
  // src/index.ts
5156
- var PAFI_ISSUER_SDK_VERSION = true ? "0.32.0" : "dev";
5223
+ var PAFI_ISSUER_SDK_VERSION = true ? "0.33.0" : "dev";
5157
5224
  export {
5158
5225
  AdapterMisconfiguredError,
5159
5226
  AuthError,