@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.cjs
CHANGED
|
@@ -50,6 +50,7 @@ __export(index_exports, {
|
|
|
50
50
|
PTRedeemHandler: () => PTRedeemHandler,
|
|
51
51
|
PafiBackendClient: () => PafiBackendClient,
|
|
52
52
|
PafiBackendError: () => PafiBackendError,
|
|
53
|
+
PafiEstimatorHttpError: () => PafiEstimatorHttpError,
|
|
53
54
|
PafiSdkError: () => import_core.PafiSdkError,
|
|
54
55
|
PendingUserOpForbiddenError: () => PendingUserOpForbiddenError,
|
|
55
56
|
PendingUserOpNotFoundError: () => PendingUserOpNotFoundError,
|
|
@@ -70,6 +71,7 @@ __export(index_exports, {
|
|
|
70
71
|
buildSdkErrorBody: () => buildSdkErrorBody,
|
|
71
72
|
createIssuerService: () => createIssuerService,
|
|
72
73
|
createNativePtQuoter: () => createNativePtQuoter,
|
|
74
|
+
createPafiEstimatorClient: () => createPafiEstimatorClient,
|
|
73
75
|
createSdkErrorMapper: () => createSdkErrorMapper,
|
|
74
76
|
createSubgraphNativeUsdtQuoter: () => createSubgraphNativeUsdtQuoter,
|
|
75
77
|
createSubgraphPoolsProvider: () => createSubgraphPoolsProvider,
|
|
@@ -1093,58 +1095,265 @@ var RelayService = class {
|
|
|
1093
1095
|
}
|
|
1094
1096
|
});
|
|
1095
1097
|
}
|
|
1098
|
+
// =========================================================================
|
|
1099
|
+
// Preview methods — produce bundler-ready partial UserOps WITHOUT signing.
|
|
1100
|
+
//
|
|
1101
|
+
// These exist so callers can fetch an accurate gas estimate from PAFI's
|
|
1102
|
+
// `/v1/estimate-gas-fee` BEFORE committing to a signed MintRequest /
|
|
1103
|
+
// BurnRequest (HSM/KMS signing is expensive in production). The
|
|
1104
|
+
// returned `callData` matches the SHAPE of the real call; the EIP-712
|
|
1105
|
+
// signature bytes are a placeholder. Bundler simulation doesn't
|
|
1106
|
+
// validate the signature, so the gas estimate comes back accurate.
|
|
1107
|
+
//
|
|
1108
|
+
// Cache-wise: same SC version + same scenario produce identical
|
|
1109
|
+
// calldata shape → identical bundler-returned gas units. The first
|
|
1110
|
+
// call seeds the cache; subsequent ones hit it.
|
|
1111
|
+
// =========================================================================
|
|
1112
|
+
/**
|
|
1113
|
+
* Build a dummy `PartialUserOperation` for the mint scenario, suitable
|
|
1114
|
+
* for `feeManager.estimateGasFee({ partialUserOp, ... })`. NO signing —
|
|
1115
|
+
* uses a 65-byte zero signature placeholder.
|
|
1116
|
+
*/
|
|
1117
|
+
previewMintUserOp(params) {
|
|
1118
|
+
const useWrapper = params.mintFeeWrapperAddress !== void 0;
|
|
1119
|
+
let mintCallData;
|
|
1120
|
+
let mintTarget;
|
|
1121
|
+
if (useWrapper) {
|
|
1122
|
+
mintCallData = (0, import_viem3.encodeFunctionData)({
|
|
1123
|
+
abi: import_core5.mintFeeWrapperAbi,
|
|
1124
|
+
functionName: "mintWithFee",
|
|
1125
|
+
args: [
|
|
1126
|
+
params.pointTokenAddress,
|
|
1127
|
+
params.userAddress,
|
|
1128
|
+
params.amount,
|
|
1129
|
+
params.deadline,
|
|
1130
|
+
PLACEHOLDER_SIG_65
|
|
1131
|
+
]
|
|
1132
|
+
});
|
|
1133
|
+
mintTarget = params.mintFeeWrapperAddress;
|
|
1134
|
+
} else {
|
|
1135
|
+
mintCallData = (0, import_viem3.encodeFunctionData)({
|
|
1136
|
+
abi: import_core5.POINT_TOKEN_ABI,
|
|
1137
|
+
functionName: "mint",
|
|
1138
|
+
args: [
|
|
1139
|
+
params.userAddress,
|
|
1140
|
+
params.amount,
|
|
1141
|
+
params.deadline,
|
|
1142
|
+
PLACEHOLDER_SIG_65
|
|
1143
|
+
]
|
|
1144
|
+
});
|
|
1145
|
+
mintTarget = params.pointTokenAddress;
|
|
1146
|
+
}
|
|
1147
|
+
return (0, import_core5.buildPartialUserOperation)({
|
|
1148
|
+
sender: params.userAddress,
|
|
1149
|
+
nonce: params.aaNonce,
|
|
1150
|
+
operations: [{ target: mintTarget, value: 0n, data: mintCallData }],
|
|
1151
|
+
// Gas limits ignored by bundler estimate — it computes them.
|
|
1152
|
+
gasLimits: {
|
|
1153
|
+
callGasLimit: 1n,
|
|
1154
|
+
verificationGasLimit: 1n,
|
|
1155
|
+
preVerificationGas: 1n
|
|
1156
|
+
}
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
/** Burn-side mirror of `previewMintUserOp`. */
|
|
1160
|
+
previewBurnUserOp(params) {
|
|
1161
|
+
const burnCallData = (0, import_viem3.encodeFunctionData)({
|
|
1162
|
+
abi: import_core5.POINT_TOKEN_ABI,
|
|
1163
|
+
functionName: "burn",
|
|
1164
|
+
args: [
|
|
1165
|
+
params.userAddress,
|
|
1166
|
+
params.amount,
|
|
1167
|
+
params.deadline,
|
|
1168
|
+
PLACEHOLDER_SIG_65
|
|
1169
|
+
]
|
|
1170
|
+
});
|
|
1171
|
+
return (0, import_core5.buildPartialUserOperation)({
|
|
1172
|
+
sender: params.userAddress,
|
|
1173
|
+
nonce: params.aaNonce,
|
|
1174
|
+
operations: [
|
|
1175
|
+
{ target: params.pointTokenAddress, value: 0n, data: burnCallData }
|
|
1176
|
+
],
|
|
1177
|
+
gasLimits: {
|
|
1178
|
+
callGasLimit: 1n,
|
|
1179
|
+
verificationGasLimit: 1n,
|
|
1180
|
+
preVerificationGas: 1n
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1096
1184
|
};
|
|
1185
|
+
var PLACEHOLDER_SIG_65 = `0x${"00".repeat(65)}`;
|
|
1097
1186
|
function errorMessage(err) {
|
|
1098
1187
|
return err instanceof Error ? err.message : String(err);
|
|
1099
1188
|
}
|
|
1100
1189
|
|
|
1101
1190
|
// src/relay/feeManager.ts
|
|
1102
1191
|
var DEFAULT_GAS_UNITS = 500000n;
|
|
1103
|
-
var DEFAULT_PREMIUM_BPS =
|
|
1192
|
+
var DEFAULT_PREMIUM_BPS = 1e4;
|
|
1104
1193
|
var FeeManager = class _FeeManager {
|
|
1105
1194
|
provider;
|
|
1106
|
-
|
|
1195
|
+
fallbackGasUnits;
|
|
1107
1196
|
gasPremiumBps;
|
|
1108
1197
|
quoteNativeToFee;
|
|
1198
|
+
bundlerClient;
|
|
1199
|
+
metrics;
|
|
1200
|
+
// Short-lived fee-value cache. Distinct from the estimator's cache:
|
|
1201
|
+
// this absorbs burst calls (e.g. 5 user requests in 5s all hit the
|
|
1202
|
+
// /gas-fee endpoint) by remembering the COMPUTED PT amount, not the
|
|
1203
|
+
// gas units. Only used by the no-opts legacy path; estimator path
|
|
1204
|
+
// gets its caching from the PAFI side instead.
|
|
1109
1205
|
cachedFee = null;
|
|
1110
1206
|
cacheExpiresAt = 0;
|
|
1111
|
-
static
|
|
1207
|
+
static FEE_CACHE_TTL_MS = 1e4;
|
|
1112
1208
|
constructor(config) {
|
|
1113
1209
|
if (!config.provider) throw new Error("FeeManager: provider required");
|
|
1114
1210
|
if (!config.quoteNativeToFee)
|
|
1115
1211
|
throw new Error("FeeManager: quoteNativeToFee required");
|
|
1116
1212
|
this.provider = config.provider;
|
|
1117
|
-
this.
|
|
1213
|
+
this.fallbackGasUnits = config.gasUnits ?? DEFAULT_GAS_UNITS;
|
|
1118
1214
|
this.gasPremiumBps = config.gasPremiumBps ?? DEFAULT_PREMIUM_BPS;
|
|
1119
1215
|
this.quoteNativeToFee = config.quoteNativeToFee;
|
|
1216
|
+
this.bundlerClient = config.bundlerClient;
|
|
1217
|
+
this.metrics = config.metrics;
|
|
1120
1218
|
}
|
|
1121
1219
|
/**
|
|
1122
|
-
* Estimate the fee
|
|
1123
|
-
* next sponsored UserOp:
|
|
1220
|
+
* Estimate the operator fee for the next sponsored UserOp.
|
|
1124
1221
|
*
|
|
1125
|
-
*
|
|
1126
|
-
*
|
|
1127
|
-
* fee = quoteNativeToFee(withPremium)
|
|
1222
|
+
* Without `opts` → legacy path: `gasUnits × gasPrice × premium →
|
|
1223
|
+
* quoteNativeToFee`. Cached for 10 s to absorb bursts.
|
|
1128
1224
|
*
|
|
1129
|
-
*
|
|
1130
|
-
*
|
|
1131
|
-
*
|
|
1225
|
+
* With `opts` AND `bundlerClient` → estimator path. Each call may
|
|
1226
|
+
* hit a different bundler-cached result; the SDK does NOT add its
|
|
1227
|
+
* own value cache because the estimator's cache TTL is the source
|
|
1228
|
+
* of truth for "how long is this estimate good for".
|
|
1132
1229
|
*/
|
|
1133
|
-
async estimateGasFee() {
|
|
1230
|
+
async estimateGasFee(opts = {}) {
|
|
1231
|
+
const isLegacyCall = !opts.partialUserOp && !opts.scenario && !opts.contractAddress;
|
|
1134
1232
|
const now = Date.now();
|
|
1135
|
-
if (this.cachedFee !== null && now < this.cacheExpiresAt) {
|
|
1233
|
+
if (isLegacyCall && this.cachedFee !== null && now < this.cacheExpiresAt) {
|
|
1136
1234
|
return this.cachedFee;
|
|
1137
1235
|
}
|
|
1236
|
+
const t0 = Date.now();
|
|
1237
|
+
const { gasUnits, source } = await this.resolveGasUnits(opts);
|
|
1238
|
+
const latencyMs = Date.now() - t0;
|
|
1239
|
+
this.safeEmit(
|
|
1240
|
+
() => this.metrics?.onEstimate?.({
|
|
1241
|
+
source,
|
|
1242
|
+
scenario: opts.scenario,
|
|
1243
|
+
gasUnits,
|
|
1244
|
+
latencyMs
|
|
1245
|
+
})
|
|
1246
|
+
);
|
|
1138
1247
|
const gasPrice = await this.provider.getGasPrice();
|
|
1139
|
-
const nativeCost = gasPrice *
|
|
1248
|
+
const nativeCost = gasPrice * gasUnits;
|
|
1140
1249
|
const withPremium = nativeCost * BigInt(this.gasPremiumBps) / 10000n;
|
|
1141
1250
|
const fee = await this.quoteNativeToFee(withPremium);
|
|
1142
|
-
|
|
1143
|
-
|
|
1251
|
+
if (isLegacyCall) {
|
|
1252
|
+
this.cachedFee = fee;
|
|
1253
|
+
this.cacheExpiresAt = now + _FeeManager.FEE_CACHE_TTL_MS;
|
|
1254
|
+
}
|
|
1144
1255
|
return fee;
|
|
1145
1256
|
}
|
|
1257
|
+
/** Manually purge the legacy 10s fee cache. */
|
|
1258
|
+
invalidateCache() {
|
|
1259
|
+
this.cachedFee = null;
|
|
1260
|
+
this.cacheExpiresAt = 0;
|
|
1261
|
+
}
|
|
1262
|
+
async resolveGasUnits(opts) {
|
|
1263
|
+
if (!this.bundlerClient || !opts.partialUserOp || !opts.scenario || !opts.contractAddress) {
|
|
1264
|
+
return { gasUnits: this.fallbackGasUnits, source: "fallback" };
|
|
1265
|
+
}
|
|
1266
|
+
try {
|
|
1267
|
+
const result = await this.bundlerClient.getGasUnits({
|
|
1268
|
+
scenario: opts.scenario,
|
|
1269
|
+
contractAddress: opts.contractAddress,
|
|
1270
|
+
paymasterAddress: opts.paymasterAddress,
|
|
1271
|
+
partialUserOp: opts.partialUserOp
|
|
1272
|
+
});
|
|
1273
|
+
return { gasUnits: result.gasUnits, source: "estimator" };
|
|
1274
|
+
} catch (err) {
|
|
1275
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1276
|
+
this.safeEmit(
|
|
1277
|
+
() => this.metrics?.onEstimatorError?.({
|
|
1278
|
+
scenario: opts.scenario,
|
|
1279
|
+
reason
|
|
1280
|
+
})
|
|
1281
|
+
);
|
|
1282
|
+
return { gasUnits: this.fallbackGasUnits, source: "fallback" };
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
safeEmit(fn) {
|
|
1286
|
+
try {
|
|
1287
|
+
fn();
|
|
1288
|
+
} catch {
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1146
1291
|
};
|
|
1147
1292
|
|
|
1293
|
+
// src/relay/bundlerEstimator.ts
|
|
1294
|
+
var PafiEstimatorHttpError = class extends Error {
|
|
1295
|
+
status;
|
|
1296
|
+
body;
|
|
1297
|
+
constructor(status, body, message) {
|
|
1298
|
+
super(message ?? `PAFI estimator HTTP ${status}`);
|
|
1299
|
+
this.status = status;
|
|
1300
|
+
this.body = body;
|
|
1301
|
+
}
|
|
1302
|
+
};
|
|
1303
|
+
function createPafiEstimatorClient(config) {
|
|
1304
|
+
const { baseUrl, apiKey, issuerId } = config;
|
|
1305
|
+
if (!baseUrl) throw new Error("createPafiEstimatorClient: baseUrl required");
|
|
1306
|
+
if (!apiKey) throw new Error("createPafiEstimatorClient: apiKey required");
|
|
1307
|
+
if (!issuerId) throw new Error("createPafiEstimatorClient: issuerId required");
|
|
1308
|
+
const fetchImpl = config.fetchImpl ?? globalThis.fetch;
|
|
1309
|
+
if (!fetchImpl) {
|
|
1310
|
+
throw new Error(
|
|
1311
|
+
"createPafiEstimatorClient: no fetch implementation available \u2014 pass `fetchImpl`"
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
const url = `${baseUrl.replace(/\/$/, "")}/v1/estimate-gas-fee`;
|
|
1315
|
+
return {
|
|
1316
|
+
async getGasUnits(input) {
|
|
1317
|
+
const body = JSON.stringify({
|
|
1318
|
+
partialUserOp: {
|
|
1319
|
+
sender: input.partialUserOp.sender,
|
|
1320
|
+
// Hex-encode bigint for JSON safety. Sponsor-relayer parses
|
|
1321
|
+
// back to bigint at the DTO layer.
|
|
1322
|
+
nonce: `0x${input.partialUserOp.nonce.toString(16)}`,
|
|
1323
|
+
callData: input.partialUserOp.callData,
|
|
1324
|
+
signature: input.partialUserOp.signature
|
|
1325
|
+
},
|
|
1326
|
+
scenario: input.scenario,
|
|
1327
|
+
contractAddress: input.contractAddress,
|
|
1328
|
+
paymasterAddress: input.paymasterAddress
|
|
1329
|
+
});
|
|
1330
|
+
const res = await fetchImpl(url, {
|
|
1331
|
+
method: "POST",
|
|
1332
|
+
headers: {
|
|
1333
|
+
"content-type": "application/json",
|
|
1334
|
+
authorization: `Bearer ${apiKey}`,
|
|
1335
|
+
"x-issuer-id": issuerId
|
|
1336
|
+
},
|
|
1337
|
+
body
|
|
1338
|
+
});
|
|
1339
|
+
if (!res.ok) {
|
|
1340
|
+
let errBody = null;
|
|
1341
|
+
try {
|
|
1342
|
+
errBody = await res.json();
|
|
1343
|
+
} catch {
|
|
1344
|
+
}
|
|
1345
|
+
throw new PafiEstimatorHttpError(res.status, errBody);
|
|
1346
|
+
}
|
|
1347
|
+
const json = await res.json();
|
|
1348
|
+
return {
|
|
1349
|
+
gasUnits: BigInt(json.gasUnits),
|
|
1350
|
+
source: json.source,
|
|
1351
|
+
expiresAt: json.expiresAt
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1148
1357
|
// src/indexer/types.ts
|
|
1149
1358
|
var InMemoryCursorStore = class {
|
|
1150
1359
|
cursor;
|
|
@@ -2071,11 +2280,29 @@ var PTRedeemHandler = class {
|
|
|
2071
2280
|
}
|
|
2072
2281
|
}
|
|
2073
2282
|
async _handleAfterNonceLock(request, burnNonce) {
|
|
2283
|
+
const previewDeadline = BigInt(
|
|
2284
|
+
Math.floor(this.now() / 1e3) + this.signatureDeadlineSeconds
|
|
2285
|
+
);
|
|
2074
2286
|
let fee;
|
|
2075
2287
|
if (request.feeAmount !== void 0) {
|
|
2076
2288
|
fee = request.feeAmount > 0n ? request.feeAmount : 0n;
|
|
2077
2289
|
} else if (this.feeService) {
|
|
2078
|
-
|
|
2290
|
+
const previewUserOp = this.relayService.previewBurnUserOp({
|
|
2291
|
+
userAddress: request.userAddress,
|
|
2292
|
+
aaNonce: burnNonce,
|
|
2293
|
+
pointTokenAddress: this.pointTokenAddress,
|
|
2294
|
+
amount: request.amount,
|
|
2295
|
+
deadline: previewDeadline
|
|
2296
|
+
});
|
|
2297
|
+
fee = await this.feeService.estimateGasFee({
|
|
2298
|
+
scenario: "burn",
|
|
2299
|
+
contractAddress: this.pointTokenAddress,
|
|
2300
|
+
partialUserOp: {
|
|
2301
|
+
sender: previewUserOp.sender,
|
|
2302
|
+
nonce: previewUserOp.nonce,
|
|
2303
|
+
callData: previewUserOp.callData
|
|
2304
|
+
}
|
|
2305
|
+
});
|
|
2079
2306
|
} else {
|
|
2080
2307
|
fee = 0n;
|
|
2081
2308
|
}
|
|
@@ -2097,9 +2324,7 @@ var PTRedeemHandler = class {
|
|
|
2097
2324
|
`insufficient on-chain PT balance: have ${onChainBalance}, need ${request.amount}`
|
|
2098
2325
|
);
|
|
2099
2326
|
}
|
|
2100
|
-
const deadline =
|
|
2101
|
-
Math.floor(this.now() / 1e3) + this.signatureDeadlineSeconds
|
|
2102
|
-
);
|
|
2327
|
+
const deadline = previewDeadline;
|
|
2103
2328
|
const domain = {
|
|
2104
2329
|
name: this.domain.name,
|
|
2105
2330
|
chainId: this.chainId,
|
|
@@ -2762,7 +2987,23 @@ var PTClaimHandler = class {
|
|
|
2762
2987
|
const signatureDeadline = BigInt(
|
|
2763
2988
|
Math.floor(this.cfg.now() / 1e3) + this.cfg.signatureDeadlineSeconds
|
|
2764
2989
|
);
|
|
2765
|
-
const
|
|
2990
|
+
const previewUserOp = this.cfg.relayService.previewMintUserOp({
|
|
2991
|
+
userAddress: request.userAddress,
|
|
2992
|
+
aaNonce: request.aaNonce,
|
|
2993
|
+
pointTokenAddress: request.pointTokenAddress,
|
|
2994
|
+
amount: request.amount,
|
|
2995
|
+
deadline: signatureDeadline,
|
|
2996
|
+
mintFeeWrapperAddress: resolvedWrapper
|
|
2997
|
+
});
|
|
2998
|
+
const feeAmount = this.cfg.feeService ? await this.cfg.feeService.estimateGasFee({
|
|
2999
|
+
scenario: resolvedWrapper ? "mint-wrapped" : "mint",
|
|
3000
|
+
contractAddress: request.pointTokenAddress,
|
|
3001
|
+
partialUserOp: {
|
|
3002
|
+
sender: previewUserOp.sender,
|
|
3003
|
+
nonce: previewUserOp.nonce,
|
|
3004
|
+
callData: previewUserOp.callData
|
|
3005
|
+
}
|
|
3006
|
+
}) : 0n;
|
|
2766
3007
|
const domain = {
|
|
2767
3008
|
name: this.cfg.pointTokenDomainName,
|
|
2768
3009
|
chainId: request.chainId,
|
|
@@ -4698,7 +4939,7 @@ var MemoryRedemptionHistoryStore = class {
|
|
|
4698
4939
|
};
|
|
4699
4940
|
|
|
4700
4941
|
// src/index.ts
|
|
4701
|
-
var PAFI_ISSUER_SDK_VERSION = true ? "0.
|
|
4942
|
+
var PAFI_ISSUER_SDK_VERSION = true ? "0.20.0" : "dev";
|
|
4702
4943
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4703
4944
|
0 && (module.exports = {
|
|
4704
4945
|
AdapterMisconfiguredError,
|
|
@@ -4731,6 +4972,7 @@ var PAFI_ISSUER_SDK_VERSION = true ? "0.19.0" : "dev";
|
|
|
4731
4972
|
PTRedeemHandler,
|
|
4732
4973
|
PafiBackendClient,
|
|
4733
4974
|
PafiBackendError,
|
|
4975
|
+
PafiEstimatorHttpError,
|
|
4734
4976
|
PafiSdkError,
|
|
4735
4977
|
PendingUserOpForbiddenError,
|
|
4736
4978
|
PendingUserOpNotFoundError,
|
|
@@ -4751,6 +4993,7 @@ var PAFI_ISSUER_SDK_VERSION = true ? "0.19.0" : "dev";
|
|
|
4751
4993
|
buildSdkErrorBody,
|
|
4752
4994
|
createIssuerService,
|
|
4753
4995
|
createNativePtQuoter,
|
|
4996
|
+
createPafiEstimatorClient,
|
|
4754
4997
|
createSdkErrorMapper,
|
|
4755
4998
|
createSubgraphNativeUsdtQuoter,
|
|
4756
4999
|
createSubgraphPoolsProvider,
|