@pafi-dev/issuer 0.18.0 → 0.20.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.js CHANGED
@@ -865,21 +865,21 @@ var RelayService = class {
865
865
  // =========================================================================
866
866
  // Preview methods — produce bundler-ready partial UserOps WITHOUT signing.
867
867
  //
868
- // These exist so callers can compute an accurate gas estimate via
869
- // `bundlerClient.estimateUserOperationGas(...)` BEFORE committing to a
870
- // signed MintRequest / BurnRequest (signing is HSM-backed and expensive
871
- // in prod). The returned `callData` matches the SHAPE of the real call;
872
- // the EIP-712 signature bytes are placeholder. Bundler simulation
873
- // doesn't validate the signature, so the gas units come back accurate.
868
+ // These exist so callers can fetch an accurate gas estimate from PAFI's
869
+ // `/v1/estimate-gas-fee` BEFORE committing to a signed MintRequest /
870
+ // BurnRequest (HSM/KMS signing is expensive in production). The
871
+ // returned `callData` matches the SHAPE of the real call; the EIP-712
872
+ // signature bytes are a placeholder. Bundler simulation doesn't
873
+ // validate the signature, so the gas estimate comes back accurate.
874
874
  //
875
- // Cache-wise: same SC version + same scenario same calldata shape →
876
- // same bundler-returned gas units. The first call seeds the cache; the
877
- // rest hit it.
875
+ // Cache-wise: same SC version + same scenario produce identical
876
+ // calldata shape → identical bundler-returned gas units. The first
877
+ // call seeds the cache; subsequent ones hit it.
878
878
  // =========================================================================
879
879
  /**
880
880
  * Build a dummy `PartialUserOperation` for the mint scenario, suitable
881
881
  * for `feeManager.estimateGasFee({ partialUserOp, ... })`. NO signing —
882
- * uses a 65-byte zero signature in place of the real minter sig.
882
+ * uses a 65-byte zero signature placeholder.
883
883
  */
884
884
  previewMintUserOp(params) {
885
885
  const useWrapper = params.mintFeeWrapperAddress !== void 0;
@@ -916,7 +916,11 @@ var RelayService = class {
916
916
  nonce: params.aaNonce,
917
917
  operations: [{ target: mintTarget, value: 0n, data: mintCallData }],
918
918
  // Gas limits ignored by bundler estimate — it computes them.
919
- gasLimits: { callGasLimit: 1n, verificationGasLimit: 1n, preVerificationGas: 1n }
919
+ gasLimits: {
920
+ callGasLimit: 1n,
921
+ verificationGasLimit: 1n,
922
+ preVerificationGas: 1n
923
+ }
920
924
  });
921
925
  }
922
926
  /** Burn-side mirror of `previewMintUserOp`. */
@@ -937,7 +941,11 @@ var RelayService = class {
937
941
  operations: [
938
942
  { target: params.pointTokenAddress, value: 0n, data: burnCallData }
939
943
  ],
940
- gasLimits: { callGasLimit: 1n, verificationGasLimit: 1n, preVerificationGas: 1n }
944
+ gasLimits: {
945
+ callGasLimit: 1n,
946
+ verificationGasLimit: 1n,
947
+ preVerificationGas: 1n
948
+ }
941
949
  });
942
950
  }
943
951
  };
@@ -946,85 +954,21 @@ function errorMessage(err) {
946
954
  return err instanceof Error ? err.message : String(err);
947
955
  }
948
956
 
949
- // src/relay/gasUnitsCache.ts
950
- import { keccak256 } from "viem";
951
- var DEFAULT_TTL_MS = 5 * 6e4;
952
- var DEFAULT_CODEHASH_TTL_MS = 60 * 6e4;
953
- var DEFAULT_MAX_ENTRIES = 100;
954
- var GasUnitsCache = class {
955
- entries = /* @__PURE__ */ new Map();
956
- codehashEntries = /* @__PURE__ */ new Map();
957
- ttlMs;
958
- codehashTtlMs;
959
- maxEntries;
960
- constructor(config = {}) {
961
- this.ttlMs = config.ttlMs ?? DEFAULT_TTL_MS;
962
- this.codehashTtlMs = config.codehashTtlMs ?? DEFAULT_CODEHASH_TTL_MS;
963
- this.maxEntries = config.maxEntries ?? DEFAULT_MAX_ENTRIES;
964
- }
965
- async buildKey(params) {
966
- const codehash = await this.getCodehash(
967
- params.provider,
968
- params.contractAddress
969
- );
970
- const pm = params.paymasterAddress?.toLowerCase() ?? "0x0";
971
- return `${params.scenario}:${codehash}:${pm}`;
972
- }
973
- get(key, now = Date.now()) {
974
- const entry = this.entries.get(key);
975
- if (!entry) return null;
976
- if (entry.expiresAt <= now) {
977
- this.entries.delete(key);
978
- return null;
979
- }
980
- return entry.gasUnits;
981
- }
982
- set(key, gasUnits, now = Date.now()) {
983
- if (this.entries.size >= this.maxEntries && !this.entries.has(key)) {
984
- const eldest = this.entries.keys().next().value;
985
- if (eldest !== void 0) this.entries.delete(eldest);
986
- }
987
- this.entries.set(key, { gasUnits, expiresAt: now + this.ttlMs });
988
- }
989
- invalidate() {
990
- this.entries.clear();
991
- this.codehashEntries.clear();
992
- }
993
- size() {
994
- return this.entries.size;
995
- }
996
- async getCodehash(provider, address) {
997
- const lower = address.toLowerCase();
998
- const now = Date.now();
999
- const cached = this.codehashEntries.get(lower);
1000
- if (cached && cached.expiresAt > now) return cached.codehash;
1001
- const code = await provider.getCode({ address });
1002
- const codehash = code ? keccak256(code) : "0x0";
1003
- this.codehashEntries.set(lower, {
1004
- codehash,
1005
- expiresAt: now + this.codehashTtlMs
1006
- });
1007
- return codehash;
1008
- }
1009
- };
1010
-
1011
957
  // src/relay/feeManager.ts
1012
958
  var DEFAULT_GAS_UNITS = 500000n;
1013
959
  var DEFAULT_PREMIUM_BPS = 1e4;
1014
- var DEFAULT_PAYMASTER_OVERHEAD = 80000n;
1015
- var DUMMY_SIGNATURE = "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c";
1016
960
  var FeeManager = class _FeeManager {
1017
961
  provider;
1018
- gasUnits;
962
+ fallbackGasUnits;
1019
963
  gasPremiumBps;
1020
964
  quoteNativeToFee;
1021
965
  bundlerClient;
1022
- cache;
1023
- paymasterOverheadGas;
1024
966
  metrics;
1025
- // Short-lived in-flight fee cache (legacy behavior). Distinct from
1026
- // `cache` that one stores gasUnits per scenario; this one stores the
1027
- // FULL computed fee value, valid for 10s to absorb burst calls.
967
+ // Short-lived fee-value cache. Distinct from the estimator's cache:
968
+ // this absorbs burst calls (e.g. 5 user requests in 5s all hit the
969
+ // /gas-fee endpoint) by remembering the COMPUTED PT amount, not the
970
+ // gas units. Only used by the no-opts legacy path; estimator path
971
+ // gets its caching from the PAFI side instead.
1028
972
  cachedFee = null;
1029
973
  cacheExpiresAt = 0;
1030
974
  static FEE_CACHE_TTL_MS = 1e4;
@@ -1033,29 +977,26 @@ var FeeManager = class _FeeManager {
1033
977
  if (!config.quoteNativeToFee)
1034
978
  throw new Error("FeeManager: quoteNativeToFee required");
1035
979
  this.provider = config.provider;
1036
- this.gasUnits = config.gasUnits ?? DEFAULT_GAS_UNITS;
980
+ this.fallbackGasUnits = config.gasUnits ?? DEFAULT_GAS_UNITS;
1037
981
  this.gasPremiumBps = config.gasPremiumBps ?? DEFAULT_PREMIUM_BPS;
1038
982
  this.quoteNativeToFee = config.quoteNativeToFee;
1039
983
  this.bundlerClient = config.bundlerClient;
1040
- this.cache = new GasUnitsCache(config.cache);
1041
- this.paymasterOverheadGas = config.paymasterOverheadGas ?? DEFAULT_PAYMASTER_OVERHEAD;
1042
984
  this.metrics = config.metrics;
1043
985
  }
1044
986
  /**
1045
- * Estimate the fee (in the caller's fee currency) to charge for the
1046
- * next sponsored UserOp.
987
+ * Estimate the operator fee for the next sponsored UserOp.
1047
988
  *
1048
- * gasUnits = bundler-estimated (cached) or hardcoded fallback
1049
- * nativeCost = gasUnits × gasPrice
1050
- * withPremium = nativeCost × premiumBps / 10_000
1051
- * fee = quoteNativeToFee(withPremium)
989
+ * Without `opts` → legacy path: `gasUnits × gasPrice × premium
990
+ * quoteNativeToFee`. Cached for 10 s to absorb bursts.
1052
991
  *
1053
- * When `opts.partialUserOp` is omitted, behaves exactly like v0.16.x:
1054
- * uses the hardcoded `gasUnits` default.
992
+ * With `opts` AND `bundlerClient` estimator path. Each call may
993
+ * hit a different bundler-cached result; the SDK does NOT add its
994
+ * own value cache because the estimator's cache TTL is the source
995
+ * of truth for "how long is this estimate good for".
1055
996
  */
1056
997
  async estimateGasFee(opts = {}) {
1057
- const now = Date.now();
1058
998
  const isLegacyCall = !opts.partialUserOp && !opts.scenario && !opts.contractAddress;
999
+ const now = Date.now();
1059
1000
  if (isLegacyCall && this.cachedFee !== null && now < this.cacheExpiresAt) {
1060
1001
  return this.cachedFee;
1061
1002
  }
@@ -1080,49 +1021,32 @@ var FeeManager = class _FeeManager {
1080
1021
  }
1081
1022
  return fee;
1082
1023
  }
1083
- /**
1084
- * Manually purge the per-scenario gas-units cache. Useful after an SC
1085
- * upgrade when ops wants the next estimate to refresh immediately
1086
- * (the codehash check would catch it on the NEXT call anyway, but
1087
- * this forces it now).
1088
- */
1024
+ /** Manually purge the legacy 10s fee cache. */
1089
1025
  invalidateCache() {
1090
- this.cache.invalidate();
1091
1026
  this.cachedFee = null;
1092
1027
  this.cacheExpiresAt = 0;
1093
1028
  }
1094
1029
  async resolveGasUnits(opts) {
1095
1030
  if (!this.bundlerClient || !opts.partialUserOp || !opts.scenario || !opts.contractAddress) {
1096
- return { gasUnits: this.gasUnits, source: "fallback" };
1031
+ return { gasUnits: this.fallbackGasUnits, source: "fallback" };
1097
1032
  }
1098
1033
  try {
1099
- const cacheKey = await this.cache.buildKey({
1034
+ const result = await this.bundlerClient.getGasUnits({
1100
1035
  scenario: opts.scenario,
1101
1036
  contractAddress: opts.contractAddress,
1102
1037
  paymasterAddress: opts.paymasterAddress,
1103
- provider: this.provider
1038
+ partialUserOp: opts.partialUserOp
1104
1039
  });
1105
- const cached = this.cache.get(cacheKey);
1106
- if (cached !== null) {
1107
- return { gasUnits: cached, source: "cache" };
1108
- }
1109
- const estimate = await this.bundlerClient.estimateUserOperationGas({
1110
- sender: opts.partialUserOp.sender,
1111
- nonce: opts.partialUserOp.nonce,
1112
- callData: opts.partialUserOp.callData,
1113
- signature: opts.partialUserOp.signature ?? DUMMY_SIGNATURE
1114
- // Intentionally NO paymaster fields — avoids chicken-and-egg
1115
- // (paymasterData depends on gasLimits). Overhead added below.
1116
- });
1117
- const gasUnits = estimate.callGasLimit + estimate.verificationGasLimit + estimate.preVerificationGas + (estimate.paymasterVerificationGasLimit ?? 0n) + (estimate.paymasterPostOpGasLimit ?? 0n) + this.paymasterOverheadGas;
1118
- this.cache.set(cacheKey, gasUnits);
1119
- return { gasUnits, source: "bundler" };
1040
+ return { gasUnits: result.gasUnits, source: "estimator" };
1120
1041
  } catch (err) {
1121
1042
  const reason = err instanceof Error ? err.message : String(err);
1122
1043
  this.safeEmit(
1123
- () => this.metrics?.onBundlerError?.({ scenario: opts.scenario, reason })
1044
+ () => this.metrics?.onEstimatorError?.({
1045
+ scenario: opts.scenario,
1046
+ reason
1047
+ })
1124
1048
  );
1125
- return { gasUnits: this.gasUnits, source: "fallback" };
1049
+ return { gasUnits: this.fallbackGasUnits, source: "fallback" };
1126
1050
  }
1127
1051
  }
1128
1052
  safeEmit(fn) {
@@ -1133,6 +1057,70 @@ var FeeManager = class _FeeManager {
1133
1057
  }
1134
1058
  };
1135
1059
 
1060
+ // src/relay/bundlerEstimator.ts
1061
+ var PafiEstimatorHttpError = class extends Error {
1062
+ status;
1063
+ body;
1064
+ constructor(status, body, message) {
1065
+ super(message ?? `PAFI estimator HTTP ${status}`);
1066
+ this.status = status;
1067
+ this.body = body;
1068
+ }
1069
+ };
1070
+ function createPafiEstimatorClient(config) {
1071
+ const { baseUrl, apiKey, issuerId } = config;
1072
+ if (!baseUrl) throw new Error("createPafiEstimatorClient: baseUrl required");
1073
+ if (!apiKey) throw new Error("createPafiEstimatorClient: apiKey required");
1074
+ if (!issuerId) throw new Error("createPafiEstimatorClient: issuerId required");
1075
+ const fetchImpl = config.fetchImpl ?? globalThis.fetch;
1076
+ if (!fetchImpl) {
1077
+ throw new Error(
1078
+ "createPafiEstimatorClient: no fetch implementation available \u2014 pass `fetchImpl`"
1079
+ );
1080
+ }
1081
+ const url = `${baseUrl.replace(/\/$/, "")}/v1/estimate-gas-fee`;
1082
+ return {
1083
+ async getGasUnits(input) {
1084
+ const body = JSON.stringify({
1085
+ partialUserOp: {
1086
+ sender: input.partialUserOp.sender,
1087
+ // Hex-encode bigint for JSON safety. Sponsor-relayer parses
1088
+ // back to bigint at the DTO layer.
1089
+ nonce: `0x${input.partialUserOp.nonce.toString(16)}`,
1090
+ callData: input.partialUserOp.callData,
1091
+ signature: input.partialUserOp.signature
1092
+ },
1093
+ scenario: input.scenario,
1094
+ contractAddress: input.contractAddress,
1095
+ paymasterAddress: input.paymasterAddress
1096
+ });
1097
+ const res = await fetchImpl(url, {
1098
+ method: "POST",
1099
+ headers: {
1100
+ "content-type": "application/json",
1101
+ authorization: `Bearer ${apiKey}`,
1102
+ "x-issuer-id": issuerId
1103
+ },
1104
+ body
1105
+ });
1106
+ if (!res.ok) {
1107
+ let errBody = null;
1108
+ try {
1109
+ errBody = await res.json();
1110
+ } catch {
1111
+ }
1112
+ throw new PafiEstimatorHttpError(res.status, errBody);
1113
+ }
1114
+ const json = await res.json();
1115
+ return {
1116
+ gasUnits: BigInt(json.gasUnits),
1117
+ source: json.source,
1118
+ expiresAt: json.expiresAt
1119
+ };
1120
+ }
1121
+ };
1122
+ }
1123
+
1136
1124
  // src/indexer/types.ts
1137
1125
  var InMemoryCursorStore = class {
1138
1126
  cursor;
@@ -4768,7 +4756,7 @@ var MemoryRedemptionHistoryStore = class {
4768
4756
  };
4769
4757
 
4770
4758
  // src/index.ts
4771
- var PAFI_ISSUER_SDK_VERSION = true ? "0.18.0" : "dev";
4759
+ var PAFI_ISSUER_SDK_VERSION = true ? "0.20.0" : "dev";
4772
4760
  export {
4773
4761
  AdapterMisconfiguredError,
4774
4762
  AuthError,
@@ -4780,7 +4768,6 @@ export {
4780
4768
  DEFAULT_REDEMPTION_POLICY,
4781
4769
  DefaultPolicyEngine,
4782
4770
  FeeManager,
4783
- GasUnitsCache,
4784
4771
  InMemoryCursorStore,
4785
4772
  IssuerApiAdapter,
4786
4773
  IssuerApiHandlers,
@@ -4801,6 +4788,7 @@ export {
4801
4788
  PTRedeemHandler,
4802
4789
  PafiBackendClient,
4803
4790
  PafiBackendError,
4791
+ PafiEstimatorHttpError,
4804
4792
  PafiSdkError,
4805
4793
  PendingUserOpForbiddenError,
4806
4794
  PendingUserOpNotFoundError,
@@ -4821,6 +4809,7 @@ export {
4821
4809
  buildSdkErrorBody,
4822
4810
  createIssuerService,
4823
4811
  createNativePtQuoter,
4812
+ createPafiEstimatorClient,
4824
4813
  createSdkErrorMapper,
4825
4814
  createSubgraphNativeUsdtQuoter,
4826
4815
  createSubgraphPoolsProvider,