@pafi-dev/issuer 0.19.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 +266 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +202 -33
- package/dist/index.d.ts +202 -33
- package/dist/index.js +264 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -862,58 +862,265 @@ 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 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
|
+
//
|
|
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
|
+
// =========================================================================
|
|
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 placeholder.
|
|
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: {
|
|
920
|
+
callGasLimit: 1n,
|
|
921
|
+
verificationGasLimit: 1n,
|
|
922
|
+
preVerificationGas: 1n
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
/** Burn-side mirror of `previewMintUserOp`. */
|
|
927
|
+
previewBurnUserOp(params) {
|
|
928
|
+
const burnCallData = encodeFunctionData({
|
|
929
|
+
abi: POINT_TOKEN_ABI,
|
|
930
|
+
functionName: "burn",
|
|
931
|
+
args: [
|
|
932
|
+
params.userAddress,
|
|
933
|
+
params.amount,
|
|
934
|
+
params.deadline,
|
|
935
|
+
PLACEHOLDER_SIG_65
|
|
936
|
+
]
|
|
937
|
+
});
|
|
938
|
+
return buildPartialUserOperation({
|
|
939
|
+
sender: params.userAddress,
|
|
940
|
+
nonce: params.aaNonce,
|
|
941
|
+
operations: [
|
|
942
|
+
{ target: params.pointTokenAddress, value: 0n, data: burnCallData }
|
|
943
|
+
],
|
|
944
|
+
gasLimits: {
|
|
945
|
+
callGasLimit: 1n,
|
|
946
|
+
verificationGasLimit: 1n,
|
|
947
|
+
preVerificationGas: 1n
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
}
|
|
865
951
|
};
|
|
952
|
+
var PLACEHOLDER_SIG_65 = `0x${"00".repeat(65)}`;
|
|
866
953
|
function errorMessage(err) {
|
|
867
954
|
return err instanceof Error ? err.message : String(err);
|
|
868
955
|
}
|
|
869
956
|
|
|
870
957
|
// src/relay/feeManager.ts
|
|
871
958
|
var DEFAULT_GAS_UNITS = 500000n;
|
|
872
|
-
var DEFAULT_PREMIUM_BPS =
|
|
959
|
+
var DEFAULT_PREMIUM_BPS = 1e4;
|
|
873
960
|
var FeeManager = class _FeeManager {
|
|
874
961
|
provider;
|
|
875
|
-
|
|
962
|
+
fallbackGasUnits;
|
|
876
963
|
gasPremiumBps;
|
|
877
964
|
quoteNativeToFee;
|
|
965
|
+
bundlerClient;
|
|
966
|
+
metrics;
|
|
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.
|
|
878
972
|
cachedFee = null;
|
|
879
973
|
cacheExpiresAt = 0;
|
|
880
|
-
static
|
|
974
|
+
static FEE_CACHE_TTL_MS = 1e4;
|
|
881
975
|
constructor(config) {
|
|
882
976
|
if (!config.provider) throw new Error("FeeManager: provider required");
|
|
883
977
|
if (!config.quoteNativeToFee)
|
|
884
978
|
throw new Error("FeeManager: quoteNativeToFee required");
|
|
885
979
|
this.provider = config.provider;
|
|
886
|
-
this.
|
|
980
|
+
this.fallbackGasUnits = config.gasUnits ?? DEFAULT_GAS_UNITS;
|
|
887
981
|
this.gasPremiumBps = config.gasPremiumBps ?? DEFAULT_PREMIUM_BPS;
|
|
888
982
|
this.quoteNativeToFee = config.quoteNativeToFee;
|
|
983
|
+
this.bundlerClient = config.bundlerClient;
|
|
984
|
+
this.metrics = config.metrics;
|
|
889
985
|
}
|
|
890
986
|
/**
|
|
891
|
-
* Estimate the fee
|
|
892
|
-
* next sponsored UserOp:
|
|
987
|
+
* Estimate the operator fee for the next sponsored UserOp.
|
|
893
988
|
*
|
|
894
|
-
*
|
|
895
|
-
*
|
|
896
|
-
* fee = quoteNativeToFee(withPremium)
|
|
989
|
+
* Without `opts` → legacy path: `gasUnits × gasPrice × premium →
|
|
990
|
+
* quoteNativeToFee`. Cached for 10 s to absorb bursts.
|
|
897
991
|
*
|
|
898
|
-
*
|
|
899
|
-
*
|
|
900
|
-
*
|
|
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".
|
|
901
996
|
*/
|
|
902
|
-
async estimateGasFee() {
|
|
997
|
+
async estimateGasFee(opts = {}) {
|
|
998
|
+
const isLegacyCall = !opts.partialUserOp && !opts.scenario && !opts.contractAddress;
|
|
903
999
|
const now = Date.now();
|
|
904
|
-
if (this.cachedFee !== null && now < this.cacheExpiresAt) {
|
|
1000
|
+
if (isLegacyCall && this.cachedFee !== null && now < this.cacheExpiresAt) {
|
|
905
1001
|
return this.cachedFee;
|
|
906
1002
|
}
|
|
1003
|
+
const t0 = Date.now();
|
|
1004
|
+
const { gasUnits, source } = await this.resolveGasUnits(opts);
|
|
1005
|
+
const latencyMs = Date.now() - t0;
|
|
1006
|
+
this.safeEmit(
|
|
1007
|
+
() => this.metrics?.onEstimate?.({
|
|
1008
|
+
source,
|
|
1009
|
+
scenario: opts.scenario,
|
|
1010
|
+
gasUnits,
|
|
1011
|
+
latencyMs
|
|
1012
|
+
})
|
|
1013
|
+
);
|
|
907
1014
|
const gasPrice = await this.provider.getGasPrice();
|
|
908
|
-
const nativeCost = gasPrice *
|
|
1015
|
+
const nativeCost = gasPrice * gasUnits;
|
|
909
1016
|
const withPremium = nativeCost * BigInt(this.gasPremiumBps) / 10000n;
|
|
910
1017
|
const fee = await this.quoteNativeToFee(withPremium);
|
|
911
|
-
|
|
912
|
-
|
|
1018
|
+
if (isLegacyCall) {
|
|
1019
|
+
this.cachedFee = fee;
|
|
1020
|
+
this.cacheExpiresAt = now + _FeeManager.FEE_CACHE_TTL_MS;
|
|
1021
|
+
}
|
|
913
1022
|
return fee;
|
|
914
1023
|
}
|
|
1024
|
+
/** Manually purge the legacy 10s fee cache. */
|
|
1025
|
+
invalidateCache() {
|
|
1026
|
+
this.cachedFee = null;
|
|
1027
|
+
this.cacheExpiresAt = 0;
|
|
1028
|
+
}
|
|
1029
|
+
async resolveGasUnits(opts) {
|
|
1030
|
+
if (!this.bundlerClient || !opts.partialUserOp || !opts.scenario || !opts.contractAddress) {
|
|
1031
|
+
return { gasUnits: this.fallbackGasUnits, source: "fallback" };
|
|
1032
|
+
}
|
|
1033
|
+
try {
|
|
1034
|
+
const result = await this.bundlerClient.getGasUnits({
|
|
1035
|
+
scenario: opts.scenario,
|
|
1036
|
+
contractAddress: opts.contractAddress,
|
|
1037
|
+
paymasterAddress: opts.paymasterAddress,
|
|
1038
|
+
partialUserOp: opts.partialUserOp
|
|
1039
|
+
});
|
|
1040
|
+
return { gasUnits: result.gasUnits, source: "estimator" };
|
|
1041
|
+
} catch (err) {
|
|
1042
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1043
|
+
this.safeEmit(
|
|
1044
|
+
() => this.metrics?.onEstimatorError?.({
|
|
1045
|
+
scenario: opts.scenario,
|
|
1046
|
+
reason
|
|
1047
|
+
})
|
|
1048
|
+
);
|
|
1049
|
+
return { gasUnits: this.fallbackGasUnits, source: "fallback" };
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
safeEmit(fn) {
|
|
1053
|
+
try {
|
|
1054
|
+
fn();
|
|
1055
|
+
} catch {
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
915
1058
|
};
|
|
916
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
|
+
|
|
917
1124
|
// src/indexer/types.ts
|
|
918
1125
|
var InMemoryCursorStore = class {
|
|
919
1126
|
cursor;
|
|
@@ -1850,11 +2057,29 @@ var PTRedeemHandler = class {
|
|
|
1850
2057
|
}
|
|
1851
2058
|
}
|
|
1852
2059
|
async _handleAfterNonceLock(request, burnNonce) {
|
|
2060
|
+
const previewDeadline = BigInt(
|
|
2061
|
+
Math.floor(this.now() / 1e3) + this.signatureDeadlineSeconds
|
|
2062
|
+
);
|
|
1853
2063
|
let fee;
|
|
1854
2064
|
if (request.feeAmount !== void 0) {
|
|
1855
2065
|
fee = request.feeAmount > 0n ? request.feeAmount : 0n;
|
|
1856
2066
|
} else if (this.feeService) {
|
|
1857
|
-
|
|
2067
|
+
const previewUserOp = this.relayService.previewBurnUserOp({
|
|
2068
|
+
userAddress: request.userAddress,
|
|
2069
|
+
aaNonce: burnNonce,
|
|
2070
|
+
pointTokenAddress: this.pointTokenAddress,
|
|
2071
|
+
amount: request.amount,
|
|
2072
|
+
deadline: previewDeadline
|
|
2073
|
+
});
|
|
2074
|
+
fee = await this.feeService.estimateGasFee({
|
|
2075
|
+
scenario: "burn",
|
|
2076
|
+
contractAddress: this.pointTokenAddress,
|
|
2077
|
+
partialUserOp: {
|
|
2078
|
+
sender: previewUserOp.sender,
|
|
2079
|
+
nonce: previewUserOp.nonce,
|
|
2080
|
+
callData: previewUserOp.callData
|
|
2081
|
+
}
|
|
2082
|
+
});
|
|
1858
2083
|
} else {
|
|
1859
2084
|
fee = 0n;
|
|
1860
2085
|
}
|
|
@@ -1876,9 +2101,7 @@ var PTRedeemHandler = class {
|
|
|
1876
2101
|
`insufficient on-chain PT balance: have ${onChainBalance}, need ${request.amount}`
|
|
1877
2102
|
);
|
|
1878
2103
|
}
|
|
1879
|
-
const deadline =
|
|
1880
|
-
Math.floor(this.now() / 1e3) + this.signatureDeadlineSeconds
|
|
1881
|
-
);
|
|
2104
|
+
const deadline = previewDeadline;
|
|
1882
2105
|
const domain = {
|
|
1883
2106
|
name: this.domain.name,
|
|
1884
2107
|
chainId: this.chainId,
|
|
@@ -2550,7 +2773,23 @@ var PTClaimHandler = class {
|
|
|
2550
2773
|
const signatureDeadline = BigInt(
|
|
2551
2774
|
Math.floor(this.cfg.now() / 1e3) + this.cfg.signatureDeadlineSeconds
|
|
2552
2775
|
);
|
|
2553
|
-
const
|
|
2776
|
+
const previewUserOp = this.cfg.relayService.previewMintUserOp({
|
|
2777
|
+
userAddress: request.userAddress,
|
|
2778
|
+
aaNonce: request.aaNonce,
|
|
2779
|
+
pointTokenAddress: request.pointTokenAddress,
|
|
2780
|
+
amount: request.amount,
|
|
2781
|
+
deadline: signatureDeadline,
|
|
2782
|
+
mintFeeWrapperAddress: resolvedWrapper
|
|
2783
|
+
});
|
|
2784
|
+
const feeAmount = this.cfg.feeService ? await this.cfg.feeService.estimateGasFee({
|
|
2785
|
+
scenario: resolvedWrapper ? "mint-wrapped" : "mint",
|
|
2786
|
+
contractAddress: request.pointTokenAddress,
|
|
2787
|
+
partialUserOp: {
|
|
2788
|
+
sender: previewUserOp.sender,
|
|
2789
|
+
nonce: previewUserOp.nonce,
|
|
2790
|
+
callData: previewUserOp.callData
|
|
2791
|
+
}
|
|
2792
|
+
}) : 0n;
|
|
2554
2793
|
const domain = {
|
|
2555
2794
|
name: this.cfg.pointTokenDomainName,
|
|
2556
2795
|
chainId: request.chainId,
|
|
@@ -4517,7 +4756,7 @@ var MemoryRedemptionHistoryStore = class {
|
|
|
4517
4756
|
};
|
|
4518
4757
|
|
|
4519
4758
|
// src/index.ts
|
|
4520
|
-
var PAFI_ISSUER_SDK_VERSION = true ? "0.
|
|
4759
|
+
var PAFI_ISSUER_SDK_VERSION = true ? "0.20.0" : "dev";
|
|
4521
4760
|
export {
|
|
4522
4761
|
AdapterMisconfiguredError,
|
|
4523
4762
|
AuthError,
|
|
@@ -4549,6 +4788,7 @@ export {
|
|
|
4549
4788
|
PTRedeemHandler,
|
|
4550
4789
|
PafiBackendClient,
|
|
4551
4790
|
PafiBackendError,
|
|
4791
|
+
PafiEstimatorHttpError,
|
|
4552
4792
|
PafiSdkError,
|
|
4553
4793
|
PendingUserOpForbiddenError,
|
|
4554
4794
|
PendingUserOpNotFoundError,
|
|
@@ -4569,6 +4809,7 @@ export {
|
|
|
4569
4809
|
buildSdkErrorBody,
|
|
4570
4810
|
createIssuerService,
|
|
4571
4811
|
createNativePtQuoter,
|
|
4812
|
+
createPafiEstimatorClient,
|
|
4572
4813
|
createSdkErrorMapper,
|
|
4573
4814
|
createSubgraphNativeUsdtQuoter,
|
|
4574
4815
|
createSubgraphPoolsProvider,
|