@pafi-dev/issuer 0.28.1 → 0.30.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
@@ -1884,6 +1884,27 @@ interface PTRedeemHandlerConfig {
1884
1884
  * provisioning time. Typically HSM/KMS-backed in prod.
1885
1885
  */
1886
1886
  burnerSignerWallet: WalletClient;
1887
+ /**
1888
+ * Issuer's allow-listed PointToken contracts (checksummed EIP-55).
1889
+ * Every `handle()` call validates the request's `pointTokenAddress`
1890
+ * against this set BEFORE any chain read, signer call, or pending
1891
+ * credit reservation.
1892
+ *
1893
+ * Audit PACI5-18 (redeem twin of the claim-side finding) — the read
1894
+ * surface (`IssuerApiHandlers.handleUser` / `handleRedemptionEvaluate`)
1895
+ * enforces this allowlist, but the write surface previously skipped
1896
+ * it. The asymmetry meant an issuer burner signer whitelisted on
1897
+ * multiple PointTokens could be coerced into signing a valid
1898
+ * `BurnRequest` for an off-allowlist token where no BurnIndexer is
1899
+ * running — the on-chain burn would succeed but the off-chain
1900
+ * pending credit would stay PENDING forever (or worse, resolve via
1901
+ * the bundler-receipt fallback against an unbacked credit balance).
1902
+ *
1903
+ * Pass the SAME set used to construct `IssuerApiHandlers` and
1904
+ * `PTClaimHandler` so the read, claim, and redeem paths agree on
1905
+ * what this issuer is willing to sign for.
1906
+ */
1907
+ supportedTokens: ReadonlySet<Address>;
1887
1908
  /**
1888
1909
  * Optional — when wired, used to estimate the PT gas-reimbursement
1889
1910
  * fee. The handler self-computes `feeAmount` + `feeRecipient` so the
@@ -1977,7 +1998,7 @@ interface PTRedeemResponse {
1977
1998
  /** The BurnRequest deadline (unix seconds) — FE uses this to surface a countdown. */
1978
1999
  signatureDeadline: bigint;
1979
2000
  }
1980
- type PTRedeemErrorCode = "UNAUTHORIZED" | "INVALID_AMOUNT" | "NONCE_READ_FAILED" | "NONCE_IN_FLIGHT" | "LEDGER_NOT_SUPPORTED" | "SIGNING_FAILED" | "REDEMPTION_POLICY_DENIED";
2001
+ type PTRedeemErrorCode = "UNAUTHORIZED" | "INVALID_AMOUNT" | "NONCE_READ_FAILED" | "NONCE_IN_FLIGHT" | "LEDGER_NOT_SUPPORTED" | "SIGNING_FAILED" | "REDEMPTION_POLICY_DENIED" | "UNSUPPORTED_POINT_TOKEN";
1981
2002
  declare class PTRedeemError extends PafiSdkError {
1982
2003
  readonly httpStatus: "unprocessable";
1983
2004
  readonly code: PTRedeemErrorCode;
@@ -1995,6 +2016,7 @@ declare class PTRedeemHandler {
1995
2016
  private readonly chainId;
1996
2017
  private readonly domainResolver;
1997
2018
  private readonly burnerSignerWallet;
2019
+ private readonly supportedTokens;
1998
2020
  private readonly redeemLockDurationMs;
1999
2021
  private readonly signatureDeadlineSeconds;
2000
2022
  private readonly now;
@@ -2732,7 +2754,7 @@ interface IssuerStateValidatorLike {
2732
2754
  * own composer — gg56 uses a timestamp-key 2D nonce). Caller layers
2733
2755
  * paymaster sponsorship + sponsorAuth on top of the returned UserOps.
2734
2756
  */
2735
- type PTClaimErrorCode = "INVALID_AMOUNT" | "VALIDATION_FAILED" | "BUILD_FAILED" | "NONCE_READ_FAILED" | "NONCE_IN_FLIGHT";
2757
+ type PTClaimErrorCode = "INVALID_AMOUNT" | "VALIDATION_FAILED" | "BUILD_FAILED" | "NONCE_READ_FAILED" | "NONCE_IN_FLIGHT" | "UNSUPPORTED_POINT_TOKEN";
2736
2758
  declare class PTClaimError extends PafiSdkError {
2737
2759
  readonly httpStatus: "unprocessable";
2738
2760
  readonly code: PTClaimErrorCode;
@@ -2754,6 +2776,25 @@ interface PTClaimHandlerConfig {
2754
2776
  * `chainId` + `pointTokenAddress` respectively.
2755
2777
  */
2756
2778
  domainResolver: PointTokenDomainResolver;
2779
+ /**
2780
+ * Issuer's allow-listed PointToken contracts (checksummed EIP-55).
2781
+ * Every `handle()` call validates the request's `pointTokenAddress`
2782
+ * against this set BEFORE any chain read or signer call.
2783
+ *
2784
+ * Audit PACI5-18 — the read surface (`IssuerApiHandlers.handleUser`,
2785
+ * `handleRedemptionEvaluate`) enforces this allowlist, but the
2786
+ * write surface (claim/redeem) previously skipped it. The asymmetry
2787
+ * meant an issuer signer whitelisted as minter on PointTokens
2788
+ * outside the configured indexer set could be coerced into signing
2789
+ * a valid `MintForRequest` for an off-set token — the on-chain
2790
+ * mint would succeed, but no PointIndexer was watching to debit the
2791
+ * off-chain ledger. Silent supply-invariant violation.
2792
+ *
2793
+ * Pass the SAME set used to construct `IssuerApiHandlers` so the
2794
+ * read and write paths agree on what this issuer is willing to
2795
+ * sign for.
2796
+ */
2797
+ supportedTokens: ReadonlySet<Address>;
2757
2798
  /** Optional — when wired, used to estimate the PT gas-reimbursement fee. */
2758
2799
  feeService?: FeeManager;
2759
2800
  /** Optional — pre-validates issuer status + cap before locking balance. */
package/dist/index.d.ts CHANGED
@@ -1884,6 +1884,27 @@ interface PTRedeemHandlerConfig {
1884
1884
  * provisioning time. Typically HSM/KMS-backed in prod.
1885
1885
  */
1886
1886
  burnerSignerWallet: WalletClient;
1887
+ /**
1888
+ * Issuer's allow-listed PointToken contracts (checksummed EIP-55).
1889
+ * Every `handle()` call validates the request's `pointTokenAddress`
1890
+ * against this set BEFORE any chain read, signer call, or pending
1891
+ * credit reservation.
1892
+ *
1893
+ * Audit PACI5-18 (redeem twin of the claim-side finding) — the read
1894
+ * surface (`IssuerApiHandlers.handleUser` / `handleRedemptionEvaluate`)
1895
+ * enforces this allowlist, but the write surface previously skipped
1896
+ * it. The asymmetry meant an issuer burner signer whitelisted on
1897
+ * multiple PointTokens could be coerced into signing a valid
1898
+ * `BurnRequest` for an off-allowlist token where no BurnIndexer is
1899
+ * running — the on-chain burn would succeed but the off-chain
1900
+ * pending credit would stay PENDING forever (or worse, resolve via
1901
+ * the bundler-receipt fallback against an unbacked credit balance).
1902
+ *
1903
+ * Pass the SAME set used to construct `IssuerApiHandlers` and
1904
+ * `PTClaimHandler` so the read, claim, and redeem paths agree on
1905
+ * what this issuer is willing to sign for.
1906
+ */
1907
+ supportedTokens: ReadonlySet<Address>;
1887
1908
  /**
1888
1909
  * Optional — when wired, used to estimate the PT gas-reimbursement
1889
1910
  * fee. The handler self-computes `feeAmount` + `feeRecipient` so the
@@ -1977,7 +1998,7 @@ interface PTRedeemResponse {
1977
1998
  /** The BurnRequest deadline (unix seconds) — FE uses this to surface a countdown. */
1978
1999
  signatureDeadline: bigint;
1979
2000
  }
1980
- type PTRedeemErrorCode = "UNAUTHORIZED" | "INVALID_AMOUNT" | "NONCE_READ_FAILED" | "NONCE_IN_FLIGHT" | "LEDGER_NOT_SUPPORTED" | "SIGNING_FAILED" | "REDEMPTION_POLICY_DENIED";
2001
+ type PTRedeemErrorCode = "UNAUTHORIZED" | "INVALID_AMOUNT" | "NONCE_READ_FAILED" | "NONCE_IN_FLIGHT" | "LEDGER_NOT_SUPPORTED" | "SIGNING_FAILED" | "REDEMPTION_POLICY_DENIED" | "UNSUPPORTED_POINT_TOKEN";
1981
2002
  declare class PTRedeemError extends PafiSdkError {
1982
2003
  readonly httpStatus: "unprocessable";
1983
2004
  readonly code: PTRedeemErrorCode;
@@ -1995,6 +2016,7 @@ declare class PTRedeemHandler {
1995
2016
  private readonly chainId;
1996
2017
  private readonly domainResolver;
1997
2018
  private readonly burnerSignerWallet;
2019
+ private readonly supportedTokens;
1998
2020
  private readonly redeemLockDurationMs;
1999
2021
  private readonly signatureDeadlineSeconds;
2000
2022
  private readonly now;
@@ -2732,7 +2754,7 @@ interface IssuerStateValidatorLike {
2732
2754
  * own composer — gg56 uses a timestamp-key 2D nonce). Caller layers
2733
2755
  * paymaster sponsorship + sponsorAuth on top of the returned UserOps.
2734
2756
  */
2735
- type PTClaimErrorCode = "INVALID_AMOUNT" | "VALIDATION_FAILED" | "BUILD_FAILED" | "NONCE_READ_FAILED" | "NONCE_IN_FLIGHT";
2757
+ type PTClaimErrorCode = "INVALID_AMOUNT" | "VALIDATION_FAILED" | "BUILD_FAILED" | "NONCE_READ_FAILED" | "NONCE_IN_FLIGHT" | "UNSUPPORTED_POINT_TOKEN";
2736
2758
  declare class PTClaimError extends PafiSdkError {
2737
2759
  readonly httpStatus: "unprocessable";
2738
2760
  readonly code: PTClaimErrorCode;
@@ -2754,6 +2776,25 @@ interface PTClaimHandlerConfig {
2754
2776
  * `chainId` + `pointTokenAddress` respectively.
2755
2777
  */
2756
2778
  domainResolver: PointTokenDomainResolver;
2779
+ /**
2780
+ * Issuer's allow-listed PointToken contracts (checksummed EIP-55).
2781
+ * Every `handle()` call validates the request's `pointTokenAddress`
2782
+ * against this set BEFORE any chain read or signer call.
2783
+ *
2784
+ * Audit PACI5-18 — the read surface (`IssuerApiHandlers.handleUser`,
2785
+ * `handleRedemptionEvaluate`) enforces this allowlist, but the
2786
+ * write surface (claim/redeem) previously skipped it. The asymmetry
2787
+ * meant an issuer signer whitelisted as minter on PointTokens
2788
+ * outside the configured indexer set could be coerced into signing
2789
+ * a valid `MintForRequest` for an off-set token — the on-chain
2790
+ * mint would succeed, but no PointIndexer was watching to debit the
2791
+ * off-chain ledger. Silent supply-invariant violation.
2792
+ *
2793
+ * Pass the SAME set used to construct `IssuerApiHandlers` so the
2794
+ * read and write paths agree on what this issuer is willing to
2795
+ * sign for.
2796
+ */
2797
+ supportedTokens: ReadonlySet<Address>;
2757
2798
  /** Optional — when wired, used to estimate the PT gas-reimbursement fee. */
2758
2799
  feeService?: FeeManager;
2759
2800
  /** Optional — pre-validates issuer status + cap before locking balance. */
package/dist/index.js CHANGED
@@ -2108,6 +2108,7 @@ var PTRedeemHandler = class {
2108
2108
  chainId;
2109
2109
  domainResolver;
2110
2110
  burnerSignerWallet;
2111
+ supportedTokens;
2111
2112
  redeemLockDurationMs;
2112
2113
  signatureDeadlineSeconds;
2113
2114
  now;
@@ -2147,6 +2148,13 @@ var PTRedeemHandler = class {
2147
2148
  this.chainId = config.chainId;
2148
2149
  this.domainResolver = config.domainResolver;
2149
2150
  this.burnerSignerWallet = config.burnerSignerWallet;
2151
+ if (!config.supportedTokens) {
2152
+ throw new PTRedeemError(
2153
+ "UNSUPPORTED_POINT_TOKEN",
2154
+ "PTRedeemHandler requires `supportedTokens` (issuer's allow-listed PointToken contracts). See audit PACI5-18."
2155
+ );
2156
+ }
2157
+ this.supportedTokens = config.supportedTokens;
2150
2158
  if (this.burnerSignerWallet?.account?.type === "local") {
2151
2159
  console.warn("[PAFI] PTRedeemHandler: burnerSignerWallet uses a local (private key) account. Use a KMS-backed signer in production.");
2152
2160
  }
@@ -2175,6 +2183,12 @@ var PTRedeemHandler = class {
2175
2183
  throw new PTRedeemError("INVALID_AMOUNT", "redeem amount must be positive");
2176
2184
  }
2177
2185
  const pointTokenAddress = getAddress7(request.pointTokenAddress);
2186
+ if (!this.supportedTokens.has(pointTokenAddress)) {
2187
+ throw new PTRedeemError(
2188
+ "UNSUPPORTED_POINT_TOKEN",
2189
+ `redeem: pointTokenAddress ${pointTokenAddress} is not in the issuer's supported-token allowlist. Check IssuerApiHandlers.supportedTokens and PTRedeemHandler.config.supportedTokens point at the same set.`
2190
+ );
2191
+ }
2178
2192
  if (this.redemptionService) {
2179
2193
  const decision = await this.redemptionService.evaluate(
2180
2194
  request.userAddress,
@@ -2424,13 +2438,43 @@ async function handleClaimStatus(params) {
2424
2438
  lock.userOpHash
2425
2439
  );
2426
2440
  if (receipt) {
2427
- status = receipt.success ? "MINTED" : "FAILED";
2428
- txHash = receipt.txHash;
2429
- await params.ledger.updateMintStatus(lock.lockId, status, receipt.txHash).catch((err) => {
2430
- params.onWarning?.(
2431
- `handleClaimStatus: ledger updateMintStatus failed for lock ${lock.lockId}: ${err}`
2432
- );
2433
- });
2441
+ if (receipt.success && receipt.txHash) {
2442
+ if (!lock.tokenAddress) {
2443
+ params.onWarning?.(
2444
+ `handleClaimStatus: lock ${lock.lockId} has no tokenAddress; falling back to status-only flip (PACI5-24 defence degraded). Migrate the ledger to the multi-token schema.`
2445
+ );
2446
+ await params.ledger.updateMintStatus(lock.lockId, "MINTED", receipt.txHash).catch((err) => {
2447
+ params.onWarning?.(
2448
+ `handleClaimStatus: ledger updateMintStatus failed for lock ${lock.lockId}: ${err}`
2449
+ );
2450
+ });
2451
+ status = "MINTED";
2452
+ txHash = receipt.txHash;
2453
+ } else {
2454
+ try {
2455
+ await params.ledger.deductBalance(
2456
+ lock.userAddress,
2457
+ lock.amount,
2458
+ receipt.txHash,
2459
+ lock.tokenAddress
2460
+ );
2461
+ status = "MINTED";
2462
+ txHash = receipt.txHash;
2463
+ } catch (deductErr) {
2464
+ params.onWarning?.(
2465
+ `handleClaimStatus: deductBalance failed for lock ${lock.lockId}: ${deductErr}`
2466
+ );
2467
+ }
2468
+ }
2469
+ } else {
2470
+ await params.ledger.updateMintStatus(lock.lockId, "FAILED", receipt.txHash).catch((err) => {
2471
+ params.onWarning?.(
2472
+ `handleClaimStatus: ledger updateMintStatus failed for lock ${lock.lockId}: ${err}`
2473
+ );
2474
+ });
2475
+ status = "FAILED";
2476
+ txHash = receipt.txHash;
2477
+ }
2434
2478
  }
2435
2479
  } catch (err) {
2436
2480
  params.onWarning?.(
@@ -2912,6 +2956,12 @@ var PTClaimHandler = class {
2912
2956
  cfg;
2913
2957
  inFlightNonces = /* @__PURE__ */ new Map();
2914
2958
  constructor(config) {
2959
+ if (!config.supportedTokens) {
2960
+ throw new PTClaimError(
2961
+ "UNSUPPORTED_POINT_TOKEN",
2962
+ "PTClaimHandler requires `supportedTokens` (issuer's allow-listed PointToken contracts). See audit PACI5-18."
2963
+ );
2964
+ }
2915
2965
  const lockDurationMs = config.lockDurationMs ?? DEFAULT_LOCK_MS;
2916
2966
  const signatureDeadlineSeconds = config.signatureDeadlineSeconds ?? DEFAULT_SIG_DEADLINE_SEC2;
2917
2967
  const maxAllowedSignatureMs = lockDurationMs - M11_SAFETY_MARGIN_MS2;
@@ -2943,6 +2993,14 @@ var PTClaimHandler = class {
2943
2993
  if (request.amount <= 0n) {
2944
2994
  throw new PTClaimError("INVALID_AMOUNT", "claim amount must be positive");
2945
2995
  }
2996
+ const pointTokenAddress = getAddress9(request.pointTokenAddress);
2997
+ if (!this.cfg.supportedTokens.has(pointTokenAddress)) {
2998
+ throw new PTClaimError(
2999
+ "UNSUPPORTED_POINT_TOKEN",
3000
+ `claim: pointTokenAddress ${pointTokenAddress} is not in the issuer's supported-token allowlist. Check IssuerApiHandlers.supportedTokens and PTClaimHandler.config.supportedTokens point at the same set.`,
3001
+ { requested: pointTokenAddress }
3002
+ );
3003
+ }
2946
3004
  if (this.cfg.issuerStateValidator) {
2947
3005
  try {
2948
3006
  await this.cfg.issuerStateValidator.preValidateMint(
@@ -5029,7 +5087,7 @@ var MemoryRedemptionHistoryStore = class {
5029
5087
  };
5030
5088
 
5031
5089
  // src/index.ts
5032
- var PAFI_ISSUER_SDK_VERSION = true ? "0.28.1" : "dev";
5090
+ var PAFI_ISSUER_SDK_VERSION = true ? "0.30.0" : "dev";
5033
5091
  export {
5034
5092
  AdapterMisconfiguredError,
5035
5093
  AuthError,