@pafi-dev/issuer 0.31.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.cjs CHANGED
@@ -2414,11 +2414,23 @@ var PTRedeemHandler = class {
2414
2414
  );
2415
2415
  }
2416
2416
  if (this.redemptionService) {
2417
- const decision = await this.redemptionService.evaluate(
2418
- request.userAddress,
2419
- request.amount,
2420
- pointTokenAddress
2421
- );
2417
+ let decision;
2418
+ try {
2419
+ decision = await this.redemptionService.evaluate(
2420
+ request.userAddress,
2421
+ request.amount,
2422
+ pointTokenAddress
2423
+ );
2424
+ } catch (err) {
2425
+ const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
2426
+ if (code === "POLICY_PROVIDER_UNAVAILABLE") {
2427
+ throw new PTRedeemError(
2428
+ "REDEMPTION_POLICY_UNAVAILABLE",
2429
+ "Redemption policy temporarily unavailable \u2014 please try again shortly."
2430
+ );
2431
+ }
2432
+ throw err;
2433
+ }
2422
2434
  if (!decision.allowed) {
2423
2435
  const denial = decision.denial;
2424
2436
  throw new PTRedeemError(
@@ -2634,6 +2646,45 @@ var PTRedeemHandler = class {
2634
2646
  };
2635
2647
 
2636
2648
  // src/api/statusHandlers.ts
2649
+ var DEFAULT_STATUS_CONFIRMATIONS = 3;
2650
+ async function isReceiptPastConfirmations(receipt, provider, confirmations, onWarning, handlerName) {
2651
+ if (!provider) {
2652
+ onWarning?.(
2653
+ `${handlerName}: provider missing \u2014 cannot enforce confirmation depth; deferring receipt fallback to on-chain indexer (audit PACI5-13).`
2654
+ );
2655
+ return false;
2656
+ }
2657
+ if (!receipt.blockNumber) {
2658
+ onWarning?.(
2659
+ `${handlerName}: receipt has no blockNumber \u2014 cannot enforce confirmation depth; deferring to indexer (audit PACI5-13).`
2660
+ );
2661
+ return false;
2662
+ }
2663
+ const requiredConfs = BigInt(confirmations ?? DEFAULT_STATUS_CONFIRMATIONS);
2664
+ let receiptBlock;
2665
+ try {
2666
+ receiptBlock = BigInt(receipt.blockNumber);
2667
+ } catch {
2668
+ onWarning?.(
2669
+ `${handlerName}: malformed receipt blockNumber (${receipt.blockNumber}) \u2014 deferring to indexer (audit PACI5-13).`
2670
+ );
2671
+ return false;
2672
+ }
2673
+ let head;
2674
+ try {
2675
+ head = await provider.getBlockNumber();
2676
+ } catch (err) {
2677
+ onWarning?.(
2678
+ `${handlerName}: getBlockNumber failed (${err instanceof Error ? err.message : String(err)}) \u2014 deferring to indexer (audit PACI5-13).`
2679
+ );
2680
+ return false;
2681
+ }
2682
+ const depth = head - receiptBlock;
2683
+ if (depth < requiredConfs) {
2684
+ return false;
2685
+ }
2686
+ return true;
2687
+ }
2637
2688
  var LockNotFoundError = class extends import_core.PafiSdkError {
2638
2689
  code = "LOCK_NOT_FOUND";
2639
2690
  httpStatus = "not_found";
@@ -2662,6 +2713,23 @@ async function handleClaimStatus(params) {
2662
2713
  lock.userOpHash
2663
2714
  );
2664
2715
  if (receipt) {
2716
+ const passesConfirmationDepth = await isReceiptPastConfirmations(
2717
+ receipt,
2718
+ params.provider,
2719
+ params.confirmations,
2720
+ params.onWarning,
2721
+ "handleClaimStatus"
2722
+ );
2723
+ if (!passesConfirmationDepth) {
2724
+ return {
2725
+ lockId: lock.lockId,
2726
+ status: "PENDING",
2727
+ txHash: lock.txHash ?? null,
2728
+ amount: lock.amount.toString(),
2729
+ createdAt: new Date(lock.createdAt).toISOString(),
2730
+ expiresAt: new Date(lock.expiresAt).toISOString()
2731
+ };
2732
+ }
2665
2733
  if (receipt.success && receipt.txHash) {
2666
2734
  if (!lock.tokenAddress) {
2667
2735
  params.onWarning?.(
@@ -2736,14 +2804,23 @@ async function handleRedeemStatus(params) {
2736
2804
  credit.userOpHash
2737
2805
  );
2738
2806
  if (receipt && receipt.success) {
2739
- status = "RESOLVED";
2740
- txHash = receipt.txHash;
2741
- if (params.ledger.resolveCreditByBurnTx) {
2742
- await params.ledger.resolveCreditByBurnTx(credit.lockId, receipt.txHash).catch((err) => {
2743
- params.onWarning?.(
2744
- `handleRedeemStatus: resolveCreditByBurnTx failed for lock ${credit.lockId}: ${err}`
2745
- );
2746
- });
2807
+ const passesConfirmationDepth = await isReceiptPastConfirmations(
2808
+ receipt,
2809
+ params.provider,
2810
+ params.confirmations,
2811
+ params.onWarning,
2812
+ "handleRedeemStatus"
2813
+ );
2814
+ if (passesConfirmationDepth) {
2815
+ status = "RESOLVED";
2816
+ txHash = receipt.txHash;
2817
+ if (params.ledger.resolveCreditByBurnTx) {
2818
+ await params.ledger.resolveCreditByBurnTx(credit.lockId, receipt.txHash).catch((err) => {
2819
+ params.onWarning?.(
2820
+ `handleRedeemStatus: resolveCreditByBurnTx failed for lock ${credit.lockId}: ${err}`
2821
+ );
2822
+ });
2823
+ }
2747
2824
  }
2748
2825
  }
2749
2826
  } catch (err) {
@@ -3872,10 +3949,6 @@ var IssuerApiAdapter = class {
3872
3949
  pointTokenAddress,
3873
3950
  redeemResponse.expiresInSeconds,
3874
3951
  input.eip7702Auth,
3875
- // Audit PACI5-21 — fallback path reserves a separate
3876
- // PendingCredit row for the full `amount`. Surface its lockId so
3877
- // mobile FE can poll the correct row + `handleMobileSubmit`
3878
- // routes the userOpHash bind to it on fallback submit.
3879
3952
  redeemResponse.fallback?.lockId
3880
3953
  );
3881
3954
  return {
@@ -3911,6 +3984,9 @@ var IssuerApiAdapter = class {
3911
3984
  userAddress: authenticatedAddress,
3912
3985
  ledger: this.cfg.ledger,
3913
3986
  pafiBackendClient: this.cfg.pafiBackendClient,
3987
+ // Audit PACI5-13 — pass the same provider the indexers use so
3988
+ // the receipt fallback gates on the same reorg depth.
3989
+ provider: this.cfg.provider,
3914
3990
  onWarning: this.cfg.onWarning
3915
3991
  });
3916
3992
  }
@@ -3920,6 +3996,8 @@ var IssuerApiAdapter = class {
3920
3996
  userAddress: authenticatedAddress,
3921
3997
  ledger: this.cfg.ledger,
3922
3998
  pafiBackendClient: this.cfg.pafiBackendClient,
3999
+ // Audit PACI5-13 — see claimStatus comment.
4000
+ provider: this.cfg.provider,
3923
4001
  onWarning: this.cfg.onWarning
3924
4002
  });
3925
4003
  }
@@ -4454,7 +4532,9 @@ var PafiBackendClient = class {
4454
4532
  if (!config.issuerId) throw new Error("PafiBackendClient: issuerId is required");
4455
4533
  if (!config.apiKey) throw new Error("PafiBackendClient: apiKey is required");
4456
4534
  this.config = config;
4457
- this.baseUrl = (0, import_core16.getPafiServiceUrls)(config.chainId).sponsorRelayer;
4535
+ this.baseUrl = (0, import_core16.getPafiServiceUrls)(config.chainId, {
4536
+ sponsorRelayer: config.baseUrl
4537
+ }).sponsorRelayer;
4458
4538
  }
4459
4539
  async requestSponsorship(request) {
4460
4540
  const maxAttempts = this.config.retry?.maxAttempts ?? 1;
@@ -4615,7 +4695,7 @@ var PafiBackendClient = class {
4615
4695
 
4616
4696
  // src/config.ts
4617
4697
  var import_viem15 = require("viem");
4618
- var import_core18 = require("@pafi-dev/core");
4698
+ var import_core19 = require("@pafi-dev/core");
4619
4699
 
4620
4700
  // src/redemption/evaluator.ts
4621
4701
  var SECONDS_PER_DAY = 24 * 60 * 60;
@@ -4719,6 +4799,9 @@ function nextBlackoutEndAfter(windows, nowUnixSec) {
4719
4799
  }
4720
4800
  var REDEMPTION_HISTORY_WINDOW_SEC = SECONDS_PER_DAY;
4721
4801
 
4802
+ // src/redemption/policyProvider.ts
4803
+ var import_core18 = require("@pafi-dev/core");
4804
+
4722
4805
  // src/redemption/settlementClient.ts
4723
4806
  var import_core17 = require("@pafi-dev/core");
4724
4807
  var DEFAULT_TIMEOUT_MS = 1e3;
@@ -4729,7 +4812,9 @@ var SettlementClient = class {
4729
4812
  if (!config.issuerId) throw new Error("SettlementClient: issuerId is required");
4730
4813
  if (!config.apiKey) throw new Error("SettlementClient: apiKey is required");
4731
4814
  this.config = {
4732
- baseUrl: (0, import_core17.getPafiServiceUrls)(config.chainId).issuerApi.replace(/\/+$/, ""),
4815
+ baseUrl: (0, import_core17.getPafiServiceUrls)(config.chainId, {
4816
+ issuerApi: config.baseUrl
4817
+ }).issuerApi.replace(/\/+$/, ""),
4733
4818
  issuerId: config.issuerId,
4734
4819
  apiKey: config.apiKey,
4735
4820
  fetchTimeoutMs: config.fetchTimeoutMs ?? DEFAULT_TIMEOUT_MS,
@@ -4833,10 +4918,23 @@ function defaultPolicyFor(issuerId) {
4833
4918
 
4834
4919
  // src/redemption/policyProvider.ts
4835
4920
  var DEFAULT_CACHE_TTL_MS3 = 5 * 60 * 1e3;
4921
+ var PolicyProviderUnavailableError = class extends import_core18.PafiSdkError {
4922
+ code = "POLICY_PROVIDER_UNAVAILABLE";
4923
+ httpStatus = "service_unavailable";
4924
+ details;
4925
+ constructor(issuerId, reason) {
4926
+ super(
4927
+ `Redemption policy provider unavailable for issuer ${issuerId}: ${reason}. Pre-flight redeem limit cannot be enforced \u2014 refusing to sign BurnRequest. Mobile FE: surface "try again shortly" and retry with backoff.`
4928
+ );
4929
+ this.details = { issuerId, reason };
4930
+ }
4931
+ };
4836
4932
  var PolicyProvider = class {
4837
4933
  client;
4838
4934
  issuerId;
4839
4935
  cacheTtlMs;
4936
+ onFetchFailure;
4937
+ onWarning;
4840
4938
  now;
4841
4939
  cache = null;
4842
4940
  inflight = null;
@@ -4844,6 +4942,8 @@ var PolicyProvider = class {
4844
4942
  this.client = new SettlementClient(config);
4845
4943
  this.issuerId = config.issuerId;
4846
4944
  this.cacheTtlMs = config.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS3;
4945
+ this.onFetchFailure = config.onFetchFailure ?? "fail-closed";
4946
+ this.onWarning = config.onWarning;
4847
4947
  this.now = config.now ?? (() => Date.now());
4848
4948
  }
4849
4949
  async getPolicy() {
@@ -4876,7 +4976,19 @@ var PolicyProvider = class {
4876
4976
  };
4877
4977
  return { policy: result.policy, source: "settlement" };
4878
4978
  }
4879
- return { policy: defaultPolicyFor(this.issuerId), source: "default" };
4979
+ const reason = "reason" in result && typeof result.reason === "string" ? result.reason : "unknown";
4980
+ if (this.onFetchFailure === "permissive-default") {
4981
+ this.onWarning?.(
4982
+ "PolicyProvider: settlement-api unreachable, falling back to permissive default. Pre-flight redeem limit is DEGRADED until settlement-api recovers.",
4983
+ {
4984
+ event: "policy_provider_fallback",
4985
+ issuerId: this.issuerId,
4986
+ reason
4987
+ }
4988
+ );
4989
+ return { policy: defaultPolicyFor(this.issuerId), source: "default" };
4990
+ }
4991
+ throw new PolicyProviderUnavailableError(this.issuerId, reason);
4880
4992
  }
4881
4993
  };
4882
4994
 
@@ -4966,7 +5078,7 @@ async function createIssuerService(config) {
4966
5078
  provider: config.provider
4967
5079
  });
4968
5080
  }
4969
- const sdkWrapperAddress = (0, import_core18.getContractAddresses)(config.chainId).mintFeeWrapper;
5081
+ const sdkWrapperAddress = (0, import_core19.getContractAddresses)(config.chainId).mintFeeWrapper;
4970
5082
  const wrapperOverride = config.indexer?.mintFeeWrapperAddress;
4971
5083
  const resolvedWrapperAddress = wrapperOverride !== void 0 ? wrapperOverride : sdkWrapperAddress;
4972
5084
  const baseCursorStore = config.indexer?.cursorStore;
@@ -5005,7 +5117,7 @@ async function createIssuerService(config) {
5005
5117
  }
5006
5118
  indexers.set(tokenAddress, new PointIndexer(indexerConfig));
5007
5119
  }
5008
- const chainAddresses = (0, import_core18.getContractAddresses)(config.chainId);
5120
+ const chainAddresses = (0, import_core19.getContractAddresses)(config.chainId);
5009
5121
  const resolvedContracts = {
5010
5122
  batchExecutor: chainAddresses.batchExecutor,
5011
5123
  usdt: chainAddresses.usdt,
@@ -5023,6 +5135,15 @@ async function createIssuerService(config) {
5023
5135
  issuerId: config.redemption.issuerId,
5024
5136
  apiKey: config.redemption.apiKey
5025
5137
  };
5138
+ if (config.redemption.baseUrl) {
5139
+ policyConfig.baseUrl = config.redemption.baseUrl;
5140
+ }
5141
+ if (config.redemption.onFetchFailure) {
5142
+ policyConfig.onFetchFailure = config.redemption.onFetchFailure;
5143
+ }
5144
+ if (config.redemption.onPolicyWarning) {
5145
+ policyConfig.onWarning = config.redemption.onPolicyWarning;
5146
+ }
5026
5147
  if (config.redemption.fetchImpl) policyConfig.fetchImpl = config.redemption.fetchImpl;
5027
5148
  if (config.redemption.fetchTimeoutMs !== void 0) {
5028
5149
  policyConfig.fetchTimeoutMs = config.redemption.fetchTimeoutMs;
@@ -5088,7 +5209,7 @@ async function createIssuerService(config) {
5088
5209
 
5089
5210
  // src/issuer-state/validator.ts
5090
5211
  var import_viem16 = require("viem");
5091
- var import_core19 = require("@pafi-dev/core");
5212
+ var import_core20 = require("@pafi-dev/core");
5092
5213
  var ISSUER_RECORD_TTL_MS = 3e4;
5093
5214
  var IssuerStateValidator = class _IssuerStateValidator {
5094
5215
  constructor(provider, registryAddress) {
@@ -5105,7 +5226,7 @@ var IssuerStateValidator = class _IssuerStateValidator {
5105
5226
  * `CONTRACT_ADDRESSES` map for the given chain.
5106
5227
  */
5107
5228
  static forChain(provider, chainId) {
5108
- const { issuerRegistry } = (0, import_core19.getContractAddresses)(chainId);
5229
+ const { issuerRegistry } = (0, import_core20.getContractAddresses)(chainId);
5109
5230
  return new _IssuerStateValidator(provider, issuerRegistry);
5110
5231
  }
5111
5232
  /**
@@ -5134,7 +5255,7 @@ var IssuerStateValidator = class _IssuerStateValidator {
5134
5255
  if (cached) return cached;
5135
5256
  const issuer = await this.provider.readContract({
5136
5257
  address: key,
5137
- abi: import_core19.POINT_TOKEN_ABI,
5258
+ abi: import_core20.POINT_TOKEN_ABI,
5138
5259
  functionName: "issuer"
5139
5260
  });
5140
5261
  this.pointTokenIssuerCache.set(key, (0, import_viem16.getAddress)(issuer));
@@ -5214,7 +5335,7 @@ var IssuerStateValidator = class _IssuerStateValidator {
5214
5335
  const issuerAddr = await this.getIssuerAddressForPointToken(tokenAddr);
5215
5336
  const issuerStruct = await this.provider.readContract({
5216
5337
  address: this.registryAddress,
5217
- abi: import_core19.issuerRegistryAbi,
5338
+ abi: import_core20.issuerRegistryAbi,
5218
5339
  functionName: "getIssuer",
5219
5340
  args: [issuerAddr]
5220
5341
  });
@@ -5227,7 +5348,7 @@ var IssuerStateValidator = class _IssuerStateValidator {
5227
5348
  };
5228
5349
  const equitySupply = await this.provider.readContract({
5229
5350
  address: tokenAddr,
5230
- abi: import_core19.POINT_TOKEN_ABI,
5351
+ abi: import_core20.POINT_TOKEN_ABI,
5231
5352
  functionName: "equitySupply"
5232
5353
  });
5233
5354
  const hardCap = issuer.capitalBase * BigInt(issuer.basisPoints) / 10000n;
@@ -5283,7 +5404,7 @@ var MemoryRedemptionHistoryStore = class {
5283
5404
  };
5284
5405
 
5285
5406
  // src/index.ts
5286
- var PAFI_ISSUER_SDK_VERSION = true ? "0.31.0" : "dev";
5407
+ var PAFI_ISSUER_SDK_VERSION = true ? "0.33.0" : "dev";
5287
5408
  // Annotate the CommonJS export names for ESM import in node:
5288
5409
  0 && (module.exports = {
5289
5410
  AdapterMisconfiguredError,