@pafi-dev/issuer 0.16.0 → 0.18.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 +324 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +212 -17
- package/dist/index.d.ts +212 -17
- package/dist/index.js +269 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -862,22 +862,172 @@ var RelayService = class {
|
|
|
862
862
|
}
|
|
863
863
|
});
|
|
864
864
|
}
|
|
865
|
+
// =========================================================================
|
|
866
|
+
// Preview methods — produce bundler-ready partial UserOps WITHOUT signing.
|
|
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.
|
|
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.
|
|
878
|
+
// =========================================================================
|
|
879
|
+
/**
|
|
880
|
+
* Build a dummy `PartialUserOperation` for the mint scenario, suitable
|
|
881
|
+
* for `feeManager.estimateGasFee({ partialUserOp, ... })`. NO signing —
|
|
882
|
+
* uses a 65-byte zero signature in place of the real minter sig.
|
|
883
|
+
*/
|
|
884
|
+
previewMintUserOp(params) {
|
|
885
|
+
const useWrapper = params.mintFeeWrapperAddress !== void 0;
|
|
886
|
+
let mintCallData;
|
|
887
|
+
let mintTarget;
|
|
888
|
+
if (useWrapper) {
|
|
889
|
+
mintCallData = encodeFunctionData({
|
|
890
|
+
abi: mintFeeWrapperAbi,
|
|
891
|
+
functionName: "mintWithFee",
|
|
892
|
+
args: [
|
|
893
|
+
params.pointTokenAddress,
|
|
894
|
+
params.userAddress,
|
|
895
|
+
params.amount,
|
|
896
|
+
params.deadline,
|
|
897
|
+
PLACEHOLDER_SIG_65
|
|
898
|
+
]
|
|
899
|
+
});
|
|
900
|
+
mintTarget = params.mintFeeWrapperAddress;
|
|
901
|
+
} else {
|
|
902
|
+
mintCallData = encodeFunctionData({
|
|
903
|
+
abi: POINT_TOKEN_ABI,
|
|
904
|
+
functionName: "mint",
|
|
905
|
+
args: [
|
|
906
|
+
params.userAddress,
|
|
907
|
+
params.amount,
|
|
908
|
+
params.deadline,
|
|
909
|
+
PLACEHOLDER_SIG_65
|
|
910
|
+
]
|
|
911
|
+
});
|
|
912
|
+
mintTarget = params.pointTokenAddress;
|
|
913
|
+
}
|
|
914
|
+
return buildPartialUserOperation({
|
|
915
|
+
sender: params.userAddress,
|
|
916
|
+
nonce: params.aaNonce,
|
|
917
|
+
operations: [{ target: mintTarget, value: 0n, data: mintCallData }],
|
|
918
|
+
// Gas limits ignored by bundler estimate — it computes them.
|
|
919
|
+
gasLimits: { callGasLimit: 1n, verificationGasLimit: 1n, preVerificationGas: 1n }
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
/** Burn-side mirror of `previewMintUserOp`. */
|
|
923
|
+
previewBurnUserOp(params) {
|
|
924
|
+
const burnCallData = encodeFunctionData({
|
|
925
|
+
abi: POINT_TOKEN_ABI,
|
|
926
|
+
functionName: "burn",
|
|
927
|
+
args: [
|
|
928
|
+
params.userAddress,
|
|
929
|
+
params.amount,
|
|
930
|
+
params.deadline,
|
|
931
|
+
PLACEHOLDER_SIG_65
|
|
932
|
+
]
|
|
933
|
+
});
|
|
934
|
+
return buildPartialUserOperation({
|
|
935
|
+
sender: params.userAddress,
|
|
936
|
+
nonce: params.aaNonce,
|
|
937
|
+
operations: [
|
|
938
|
+
{ target: params.pointTokenAddress, value: 0n, data: burnCallData }
|
|
939
|
+
],
|
|
940
|
+
gasLimits: { callGasLimit: 1n, verificationGasLimit: 1n, preVerificationGas: 1n }
|
|
941
|
+
});
|
|
942
|
+
}
|
|
865
943
|
};
|
|
944
|
+
var PLACEHOLDER_SIG_65 = `0x${"00".repeat(65)}`;
|
|
866
945
|
function errorMessage(err) {
|
|
867
946
|
return err instanceof Error ? err.message : String(err);
|
|
868
947
|
}
|
|
869
948
|
|
|
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
|
+
|
|
870
1011
|
// src/relay/feeManager.ts
|
|
871
1012
|
var DEFAULT_GAS_UNITS = 500000n;
|
|
872
|
-
var DEFAULT_PREMIUM_BPS =
|
|
1013
|
+
var DEFAULT_PREMIUM_BPS = 1e4;
|
|
1014
|
+
var DEFAULT_PAYMASTER_OVERHEAD = 80000n;
|
|
1015
|
+
var DUMMY_SIGNATURE = "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c";
|
|
873
1016
|
var FeeManager = class _FeeManager {
|
|
874
1017
|
provider;
|
|
875
1018
|
gasUnits;
|
|
876
1019
|
gasPremiumBps;
|
|
877
1020
|
quoteNativeToFee;
|
|
1021
|
+
bundlerClient;
|
|
1022
|
+
cache;
|
|
1023
|
+
paymasterOverheadGas;
|
|
1024
|
+
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.
|
|
878
1028
|
cachedFee = null;
|
|
879
1029
|
cacheExpiresAt = 0;
|
|
880
|
-
static
|
|
1030
|
+
static FEE_CACHE_TTL_MS = 1e4;
|
|
881
1031
|
constructor(config) {
|
|
882
1032
|
if (!config.provider) throw new Error("FeeManager: provider required");
|
|
883
1033
|
if (!config.quoteNativeToFee)
|
|
@@ -886,32 +1036,101 @@ var FeeManager = class _FeeManager {
|
|
|
886
1036
|
this.gasUnits = config.gasUnits ?? DEFAULT_GAS_UNITS;
|
|
887
1037
|
this.gasPremiumBps = config.gasPremiumBps ?? DEFAULT_PREMIUM_BPS;
|
|
888
1038
|
this.quoteNativeToFee = config.quoteNativeToFee;
|
|
1039
|
+
this.bundlerClient = config.bundlerClient;
|
|
1040
|
+
this.cache = new GasUnitsCache(config.cache);
|
|
1041
|
+
this.paymasterOverheadGas = config.paymasterOverheadGas ?? DEFAULT_PAYMASTER_OVERHEAD;
|
|
1042
|
+
this.metrics = config.metrics;
|
|
889
1043
|
}
|
|
890
1044
|
/**
|
|
891
1045
|
* Estimate the fee (in the caller's fee currency) to charge for the
|
|
892
|
-
* next sponsored UserOp
|
|
1046
|
+
* next sponsored UserOp.
|
|
893
1047
|
*
|
|
1048
|
+
* gasUnits = bundler-estimated (cached) or hardcoded fallback
|
|
894
1049
|
* nativeCost = gasUnits × gasPrice
|
|
895
1050
|
* withPremium = nativeCost × premiumBps / 10_000
|
|
896
1051
|
* fee = quoteNativeToFee(withPremium)
|
|
897
1052
|
*
|
|
898
|
-
*
|
|
899
|
-
*
|
|
900
|
-
* currency depends on how the caller wired `quoteNativeToFee`.
|
|
1053
|
+
* When `opts.partialUserOp` is omitted, behaves exactly like v0.16.x:
|
|
1054
|
+
* uses the hardcoded `gasUnits` default.
|
|
901
1055
|
*/
|
|
902
|
-
async estimateGasFee() {
|
|
1056
|
+
async estimateGasFee(opts = {}) {
|
|
903
1057
|
const now = Date.now();
|
|
904
|
-
|
|
1058
|
+
const isLegacyCall = !opts.partialUserOp && !opts.scenario && !opts.contractAddress;
|
|
1059
|
+
if (isLegacyCall && this.cachedFee !== null && now < this.cacheExpiresAt) {
|
|
905
1060
|
return this.cachedFee;
|
|
906
1061
|
}
|
|
1062
|
+
const t0 = Date.now();
|
|
1063
|
+
const { gasUnits, source } = await this.resolveGasUnits(opts);
|
|
1064
|
+
const latencyMs = Date.now() - t0;
|
|
1065
|
+
this.safeEmit(
|
|
1066
|
+
() => this.metrics?.onEstimate?.({
|
|
1067
|
+
source,
|
|
1068
|
+
scenario: opts.scenario,
|
|
1069
|
+
gasUnits,
|
|
1070
|
+
latencyMs
|
|
1071
|
+
})
|
|
1072
|
+
);
|
|
907
1073
|
const gasPrice = await this.provider.getGasPrice();
|
|
908
|
-
const nativeCost = gasPrice *
|
|
1074
|
+
const nativeCost = gasPrice * gasUnits;
|
|
909
1075
|
const withPremium = nativeCost * BigInt(this.gasPremiumBps) / 10000n;
|
|
910
1076
|
const fee = await this.quoteNativeToFee(withPremium);
|
|
911
|
-
|
|
912
|
-
|
|
1077
|
+
if (isLegacyCall) {
|
|
1078
|
+
this.cachedFee = fee;
|
|
1079
|
+
this.cacheExpiresAt = now + _FeeManager.FEE_CACHE_TTL_MS;
|
|
1080
|
+
}
|
|
913
1081
|
return fee;
|
|
914
1082
|
}
|
|
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
|
+
*/
|
|
1089
|
+
invalidateCache() {
|
|
1090
|
+
this.cache.invalidate();
|
|
1091
|
+
this.cachedFee = null;
|
|
1092
|
+
this.cacheExpiresAt = 0;
|
|
1093
|
+
}
|
|
1094
|
+
async resolveGasUnits(opts) {
|
|
1095
|
+
if (!this.bundlerClient || !opts.partialUserOp || !opts.scenario || !opts.contractAddress) {
|
|
1096
|
+
return { gasUnits: this.gasUnits, source: "fallback" };
|
|
1097
|
+
}
|
|
1098
|
+
try {
|
|
1099
|
+
const cacheKey = await this.cache.buildKey({
|
|
1100
|
+
scenario: opts.scenario,
|
|
1101
|
+
contractAddress: opts.contractAddress,
|
|
1102
|
+
paymasterAddress: opts.paymasterAddress,
|
|
1103
|
+
provider: this.provider
|
|
1104
|
+
});
|
|
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" };
|
|
1120
|
+
} catch (err) {
|
|
1121
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1122
|
+
this.safeEmit(
|
|
1123
|
+
() => this.metrics?.onBundlerError?.({ scenario: opts.scenario, reason })
|
|
1124
|
+
);
|
|
1125
|
+
return { gasUnits: this.gasUnits, source: "fallback" };
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
safeEmit(fn) {
|
|
1129
|
+
try {
|
|
1130
|
+
fn();
|
|
1131
|
+
} catch {
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
915
1134
|
};
|
|
916
1135
|
|
|
917
1136
|
// src/indexer/types.ts
|
|
@@ -1850,11 +2069,29 @@ var PTRedeemHandler = class {
|
|
|
1850
2069
|
}
|
|
1851
2070
|
}
|
|
1852
2071
|
async _handleAfterNonceLock(request, burnNonce) {
|
|
2072
|
+
const previewDeadline = BigInt(
|
|
2073
|
+
Math.floor(this.now() / 1e3) + this.signatureDeadlineSeconds
|
|
2074
|
+
);
|
|
1853
2075
|
let fee;
|
|
1854
2076
|
if (request.feeAmount !== void 0) {
|
|
1855
2077
|
fee = request.feeAmount > 0n ? request.feeAmount : 0n;
|
|
1856
2078
|
} else if (this.feeService) {
|
|
1857
|
-
|
|
2079
|
+
const previewUserOp = this.relayService.previewBurnUserOp({
|
|
2080
|
+
userAddress: request.userAddress,
|
|
2081
|
+
aaNonce: burnNonce,
|
|
2082
|
+
pointTokenAddress: this.pointTokenAddress,
|
|
2083
|
+
amount: request.amount,
|
|
2084
|
+
deadline: previewDeadline
|
|
2085
|
+
});
|
|
2086
|
+
fee = await this.feeService.estimateGasFee({
|
|
2087
|
+
scenario: "burn",
|
|
2088
|
+
contractAddress: this.pointTokenAddress,
|
|
2089
|
+
partialUserOp: {
|
|
2090
|
+
sender: previewUserOp.sender,
|
|
2091
|
+
nonce: previewUserOp.nonce,
|
|
2092
|
+
callData: previewUserOp.callData
|
|
2093
|
+
}
|
|
2094
|
+
});
|
|
1858
2095
|
} else {
|
|
1859
2096
|
fee = 0n;
|
|
1860
2097
|
}
|
|
@@ -1876,9 +2113,7 @@ var PTRedeemHandler = class {
|
|
|
1876
2113
|
`insufficient on-chain PT balance: have ${onChainBalance}, need ${request.amount}`
|
|
1877
2114
|
);
|
|
1878
2115
|
}
|
|
1879
|
-
const deadline =
|
|
1880
|
-
Math.floor(this.now() / 1e3) + this.signatureDeadlineSeconds
|
|
1881
|
-
);
|
|
2116
|
+
const deadline = previewDeadline;
|
|
1882
2117
|
const domain = {
|
|
1883
2118
|
name: this.domain.name,
|
|
1884
2119
|
chainId: this.chainId,
|
|
@@ -2550,7 +2785,23 @@ var PTClaimHandler = class {
|
|
|
2550
2785
|
const signatureDeadline = BigInt(
|
|
2551
2786
|
Math.floor(this.cfg.now() / 1e3) + this.cfg.signatureDeadlineSeconds
|
|
2552
2787
|
);
|
|
2553
|
-
const
|
|
2788
|
+
const previewUserOp = this.cfg.relayService.previewMintUserOp({
|
|
2789
|
+
userAddress: request.userAddress,
|
|
2790
|
+
aaNonce: request.aaNonce,
|
|
2791
|
+
pointTokenAddress: request.pointTokenAddress,
|
|
2792
|
+
amount: request.amount,
|
|
2793
|
+
deadline: signatureDeadline,
|
|
2794
|
+
mintFeeWrapperAddress: resolvedWrapper
|
|
2795
|
+
});
|
|
2796
|
+
const feeAmount = this.cfg.feeService ? await this.cfg.feeService.estimateGasFee({
|
|
2797
|
+
scenario: resolvedWrapper ? "mint-wrapped" : "mint",
|
|
2798
|
+
contractAddress: request.pointTokenAddress,
|
|
2799
|
+
partialUserOp: {
|
|
2800
|
+
sender: previewUserOp.sender,
|
|
2801
|
+
nonce: previewUserOp.nonce,
|
|
2802
|
+
callData: previewUserOp.callData
|
|
2803
|
+
}
|
|
2804
|
+
}) : 0n;
|
|
2554
2805
|
const domain = {
|
|
2555
2806
|
name: this.cfg.pointTokenDomainName,
|
|
2556
2807
|
chainId: request.chainId,
|
|
@@ -4517,7 +4768,7 @@ var MemoryRedemptionHistoryStore = class {
|
|
|
4517
4768
|
};
|
|
4518
4769
|
|
|
4519
4770
|
// src/index.ts
|
|
4520
|
-
var PAFI_ISSUER_SDK_VERSION = true ? "0.
|
|
4771
|
+
var PAFI_ISSUER_SDK_VERSION = true ? "0.18.0" : "dev";
|
|
4521
4772
|
export {
|
|
4522
4773
|
AdapterMisconfiguredError,
|
|
4523
4774
|
AuthError,
|
|
@@ -4529,6 +4780,7 @@ export {
|
|
|
4529
4780
|
DEFAULT_REDEMPTION_POLICY,
|
|
4530
4781
|
DefaultPolicyEngine,
|
|
4531
4782
|
FeeManager,
|
|
4783
|
+
GasUnitsCache,
|
|
4532
4784
|
InMemoryCursorStore,
|
|
4533
4785
|
IssuerApiAdapter,
|
|
4534
4786
|
IssuerApiHandlers,
|