@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.cjs +168 -178
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +149 -175
- package/dist/index.d.ts +149 -175
- package/dist/index.js +112 -123
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
869
|
-
// `
|
|
870
|
-
//
|
|
871
|
-
//
|
|
872
|
-
//
|
|
873
|
-
//
|
|
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
|
|
876
|
-
//
|
|
877
|
-
//
|
|
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
|
|
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: {
|
|
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: {
|
|
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
|
-
|
|
962
|
+
fallbackGasUnits;
|
|
1019
963
|
gasPremiumBps;
|
|
1020
964
|
quoteNativeToFee;
|
|
1021
965
|
bundlerClient;
|
|
1022
|
-
cache;
|
|
1023
|
-
paymasterOverheadGas;
|
|
1024
966
|
metrics;
|
|
1025
|
-
// Short-lived
|
|
1026
|
-
//
|
|
1027
|
-
//
|
|
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.
|
|
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
|
|
1046
|
-
* next sponsored UserOp.
|
|
987
|
+
* Estimate the operator fee for the next sponsored UserOp.
|
|
1047
988
|
*
|
|
1048
|
-
*
|
|
1049
|
-
*
|
|
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
|
-
*
|
|
1054
|
-
*
|
|
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.
|
|
1031
|
+
return { gasUnits: this.fallbackGasUnits, source: "fallback" };
|
|
1097
1032
|
}
|
|
1098
1033
|
try {
|
|
1099
|
-
const
|
|
1034
|
+
const result = await this.bundlerClient.getGasUnits({
|
|
1100
1035
|
scenario: opts.scenario,
|
|
1101
1036
|
contractAddress: opts.contractAddress,
|
|
1102
1037
|
paymasterAddress: opts.paymasterAddress,
|
|
1103
|
-
|
|
1038
|
+
partialUserOp: opts.partialUserOp
|
|
1104
1039
|
});
|
|
1105
|
-
|
|
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?.
|
|
1044
|
+
() => this.metrics?.onEstimatorError?.({
|
|
1045
|
+
scenario: opts.scenario,
|
|
1046
|
+
reason
|
|
1047
|
+
})
|
|
1124
1048
|
);
|
|
1125
|
-
return { gasUnits: this.
|
|
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.
|
|
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,
|