abstractionkit 0.3.0 → 0.3.1
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/CHANGELOG.md +107 -71
- package/README.md +158 -73
- package/dist/index.cjs +418 -20
- package/dist/index.d.cts +81 -22
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +81 -22
- package/dist/index.d.mts.map +1 -1
- package/dist/index.iife.js +418 -20
- package/dist/index.mjs +418 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.iife.js
CHANGED
|
@@ -1115,11 +1115,14 @@ var abstractionkit = (function(exports, ethers) {
|
|
|
1115
1115
|
state_override_set
|
|
1116
1116
|
]);
|
|
1117
1117
|
const res = jsonRpcResult;
|
|
1118
|
-
|
|
1118
|
+
const gasEstimationResult = {
|
|
1119
1119
|
callGasLimit: BigInt(res.callGasLimit),
|
|
1120
1120
|
preVerificationGas: BigInt(res.preVerificationGas),
|
|
1121
1121
|
verificationGasLimit: BigInt(res.verificationGasLimit)
|
|
1122
1122
|
};
|
|
1123
|
+
if (res.paymasterVerificationGasLimit != null) gasEstimationResult.paymasterVerificationGasLimit = BigInt(res.paymasterVerificationGasLimit);
|
|
1124
|
+
if (res.paymasterPostOpGasLimit != null) gasEstimationResult.paymasterPostOpGasLimit = BigInt(res.paymasterPostOpGasLimit);
|
|
1125
|
+
return gasEstimationResult;
|
|
1123
1126
|
} catch (err) {
|
|
1124
1127
|
throw new AbstractionKitError("BUNDLER_ERROR", "bundler eth_estimateUserOperationGas rpc call failed", { cause: ensureError(err) });
|
|
1125
1128
|
}
|
|
@@ -3804,7 +3807,7 @@ var abstractionkit = (function(exports, ethers) {
|
|
|
3804
3807
|
* @param toolVersion - tool version, defaults to current abstractionkit version
|
|
3805
3808
|
* @returns the onchain idenetifier as a hex string (not 0x prefixed)
|
|
3806
3809
|
*/
|
|
3807
|
-
function generateOnChainIdentifier(project, platform = "Web", tool = "abstractionkit", toolVersion = "0.3.
|
|
3810
|
+
function generateOnChainIdentifier(project, platform = "Web", tool = "abstractionkit", toolVersion = "0.3.1") {
|
|
3808
3811
|
const identifierPrefix = "5afe";
|
|
3809
3812
|
const identifierVersion = "00";
|
|
3810
3813
|
const projectHash = (0, ethers.keccak256)("0x" + Buffer.from(project, "utf8").toString("hex")).slice(-20);
|
|
@@ -4192,36 +4195,39 @@ var abstractionkit = (function(exports, ethers) {
|
|
|
4192
4195
|
* @param overrides - overrides for the default values
|
|
4193
4196
|
* @returns signature
|
|
4194
4197
|
*/
|
|
4195
|
-
static formatSignaturesToUseroperationsSignatures(userOperationsToSign, signerSignaturePairs
|
|
4198
|
+
static formatSignaturesToUseroperationsSignatures(userOperationsToSign, signerSignaturePairs) {
|
|
4196
4199
|
if (userOperationsToSign.length < 1) throw new RangeError("There should be at least one userOperationsToSign");
|
|
4197
|
-
const
|
|
4200
|
+
const defaultOverrides = {
|
|
4198
4201
|
eip7212WebAuthnPrecompileVerifier: SafeMultiChainSigAccountV1.DEFAULT_WEB_AUTHN_PRECOMPILE,
|
|
4199
4202
|
eip7212WebAuthnContractVerifier: SafeMultiChainSigAccountV1.DEFAULT_WEB_AUTHN_DAIMO_VERIFIER,
|
|
4200
4203
|
webAuthnSignerFactory: SafeMultiChainSigAccountV1.DEFAULT_WEB_AUTHN_SIGNER_FACTORY,
|
|
4201
4204
|
webAuthnSignerSingleton: SafeMultiChainSigAccountV1.DEFAULT_WEB_AUTHN_SIGNER_SINGLETON,
|
|
4202
4205
|
webAuthnSignerProxyCreationCode: SafeMultiChainSigAccountV1.DEFAULT_WEB_AUTHN_SIGNER_PROXY_CREATION_CODE,
|
|
4203
4206
|
safe4337ModuleAddress: SafeMultiChainSigAccountV1.DEFAULT_SAFE_4337_MODULE_ADDRESS,
|
|
4204
|
-
webAuthnSharedSigner: SafeMultiChainSigAccountV1.DEFAULT_WEB_AUTHN_SHARED_SIGNER
|
|
4205
|
-
...overrides
|
|
4207
|
+
webAuthnSharedSigner: SafeMultiChainSigAccountV1.DEFAULT_WEB_AUTHN_SHARED_SIGNER
|
|
4206
4208
|
};
|
|
4207
4209
|
if (userOperationsToSign.length === 1) return [SafeAccount.formatSignaturesToUseroperationSignature(signerSignaturePairs, {
|
|
4208
|
-
...
|
|
4210
|
+
...defaultOverrides,
|
|
4211
|
+
...userOperationsToSign[0].overrides,
|
|
4212
|
+
validAfter: userOperationsToSign[0].validAfter,
|
|
4213
|
+
validUntil: userOperationsToSign[0].validUntil,
|
|
4209
4214
|
isMultiChainSignature: true
|
|
4210
4215
|
})];
|
|
4211
4216
|
const userOperationsHashes = [];
|
|
4212
|
-
userOperationsToSign.forEach((
|
|
4213
|
-
const userOperationHash = SafeAccount.getUserOperationEip712Hash_V9(
|
|
4214
|
-
validAfter:
|
|
4215
|
-
validUntil:
|
|
4216
|
-
safe4337ModuleAddress:
|
|
4217
|
+
userOperationsToSign.forEach((userOperationToSign, _index) => {
|
|
4218
|
+
const userOperationHash = SafeAccount.getUserOperationEip712Hash_V9(userOperationToSign.userOperation, userOperationToSign.chainId, {
|
|
4219
|
+
validAfter: userOperationToSign.validAfter,
|
|
4220
|
+
validUntil: userOperationToSign.validUntil,
|
|
4221
|
+
safe4337ModuleAddress: userOperationToSign.overrides?.safe4337ModuleAddress ?? defaultOverrides.safe4337ModuleAddress
|
|
4217
4222
|
});
|
|
4218
4223
|
userOperationsHashes.push(userOperationHash);
|
|
4219
4224
|
});
|
|
4220
4225
|
const [_root, proofs] = generateMerkleProofs(userOperationsHashes);
|
|
4221
4226
|
const userOpSignatures = [];
|
|
4222
|
-
userOperationsToSign.forEach((
|
|
4227
|
+
userOperationsToSign.forEach((userOperationToSign, index) => {
|
|
4223
4228
|
userOpSignatures.push(SafeAccount.formatSignaturesToUseroperationSignature(signerSignaturePairs, {
|
|
4224
|
-
...
|
|
4229
|
+
...defaultOverrides,
|
|
4230
|
+
...userOperationToSign.overrides,
|
|
4225
4231
|
isMultiChainSignature: true,
|
|
4226
4232
|
multiChainMerkleProof: proofs[index]
|
|
4227
4233
|
}));
|
|
@@ -6523,9 +6529,9 @@ var abstractionkit = (function(exports, ethers) {
|
|
|
6523
6529
|
/** Buffer added to verificationGasLimit for paymasterAndData verification overhead */
|
|
6524
6530
|
const PAYMASTER_V06_VERIFICATION_OVERHEAD = 40000n;
|
|
6525
6531
|
/** Max value for uint256 */
|
|
6526
|
-
const UINT256_MAX = 115792089237316195423570985008687907853269984665640564039457584007913129639935n;
|
|
6532
|
+
const UINT256_MAX$1 = 115792089237316195423570985008687907853269984665640564039457584007913129639935n;
|
|
6527
6533
|
/** Multiplier for token approve amount to cover paymasterAndData cost variance */
|
|
6528
|
-
const TOKEN_APPROVE_AMOUNT_MULTIPLIER = 2n;
|
|
6534
|
+
const TOKEN_APPROVE_AMOUNT_MULTIPLIER$1 = 2n;
|
|
6529
6535
|
/**
|
|
6530
6536
|
* ERC-20 tokens that require resetting their allowance to 0 before setting a
|
|
6531
6537
|
* new approval amount (e.g. USDT on mainnet).
|
|
@@ -6535,7 +6541,7 @@ var abstractionkit = (function(exports, ethers) {
|
|
|
6535
6541
|
* please open an issue at https://github.com/candidelabs/abstractionkit/issues
|
|
6536
6542
|
* or use the `resetApproval` override as a workaround.
|
|
6537
6543
|
*/
|
|
6538
|
-
const TOKENS_REQUIRING_ALLOWANCE_RESET = ["0xdac17f958d2ee523a2206206994597c13d831ec7"];
|
|
6544
|
+
const TOKENS_REQUIRING_ALLOWANCE_RESET$1 = ["0xdac17f958d2ee523a2206206994597c13d831ec7"];
|
|
6539
6545
|
/**
|
|
6540
6546
|
* Client for the Candide Paymaster service.
|
|
6541
6547
|
* Supports both gas sponsorship (sponsor paymaster) and ERC-20 token payment for gas (token paymaster).
|
|
@@ -6875,12 +6881,12 @@ var abstractionkit = (function(exports, ethers) {
|
|
|
6875
6881
|
if (epData == null) throw new RangeError(`UserOperation for entrypoint ${entrypoint} is not supported`);
|
|
6876
6882
|
this.setDummyPaymasterFields(userOp, epData);
|
|
6877
6883
|
const oldCallData = userOp.callData;
|
|
6878
|
-
const requiresAllowanceReset = overrides?.resetApproval ?? TOKENS_REQUIRING_ALLOWANCE_RESET.includes(context.token.toLowerCase());
|
|
6879
|
-
let callDataWithApprove = smartAccount.prependTokenPaymasterApproveToCallData(userOp.callData, context.token, epData.paymasterMetadata.address, UINT256_MAX);
|
|
6884
|
+
const requiresAllowanceReset = overrides?.resetApproval ?? TOKENS_REQUIRING_ALLOWANCE_RESET$1.includes(context.token.toLowerCase());
|
|
6885
|
+
let callDataWithApprove = smartAccount.prependTokenPaymasterApproveToCallData(userOp.callData, context.token, epData.paymasterMetadata.address, UINT256_MAX$1);
|
|
6880
6886
|
if (requiresAllowanceReset) callDataWithApprove = smartAccount.prependTokenPaymasterApproveToCallData(callDataWithApprove, context.token, epData.paymasterMetadata.address, 0n);
|
|
6881
6887
|
userOp.callData = callDataWithApprove;
|
|
6882
6888
|
await this.estimateAndApplyGasLimits(userOp, bundlerRpc, entrypoint, overrides ?? {});
|
|
6883
|
-
const approveAmount = await this.calculateUserOperationErc20TokenMaxGasCost(smartAccount, userOp, context.token) * TOKEN_APPROVE_AMOUNT_MULTIPLIER;
|
|
6889
|
+
const approveAmount = await this.calculateUserOperationErc20TokenMaxGasCost(smartAccount, userOp, context.token) * TOKEN_APPROVE_AMOUNT_MULTIPLIER$1;
|
|
6884
6890
|
callDataWithApprove = smartAccount.prependTokenPaymasterApproveToCallData(oldCallData, context.token, epData.paymasterMetadata.address, approveAmount);
|
|
6885
6891
|
if (requiresAllowanceReset) callDataWithApprove = smartAccount.prependTokenPaymasterApproveToCallData(callDataWithApprove, context.token, epData.paymasterMetadata.address, 0n);
|
|
6886
6892
|
userOp.callData = callDataWithApprove;
|
|
@@ -6960,6 +6966,396 @@ var abstractionkit = (function(exports, ethers) {
|
|
|
6960
6966
|
}
|
|
6961
6967
|
};
|
|
6962
6968
|
//#endregion
|
|
6969
|
+
//#region src/paymaster/Erc7677Paymaster.ts
|
|
6970
|
+
/** Max value for uint256 */
|
|
6971
|
+
const UINT256_MAX = 115792089237316195423570985008687907853269984665640564039457584007913129639935n;
|
|
6972
|
+
/** Multiplier for token approve amount to cover paymasterAndData cost variance */
|
|
6973
|
+
const TOKEN_APPROVE_AMOUNT_MULTIPLIER = 2n;
|
|
6974
|
+
/**
|
|
6975
|
+
* ERC-20 tokens that require resetting their allowance to 0 before setting a
|
|
6976
|
+
* new approval amount (e.g. USDT on mainnet).
|
|
6977
|
+
*/
|
|
6978
|
+
const TOKENS_REQUIRING_ALLOWANCE_RESET = ["0xdac17f958d2ee523a2206206994597c13d831ec7"];
|
|
6979
|
+
/**
|
|
6980
|
+
* Time-to-live for cached Candide `pm_supportedERC20Tokens` responses, applied
|
|
6981
|
+
* only when the fetch is initiated for an exchange-rate lookup. Stub-data
|
|
6982
|
+
* lookups (paymaster address + dummyPaymasterAndData) reuse the cache
|
|
6983
|
+
* indefinitely since those fields are effectively static per EP.
|
|
6984
|
+
*/
|
|
6985
|
+
const CANDIDE_TOKEN_QUOTE_TTL_MS = 45e3;
|
|
6986
|
+
var Erc7677Paymaster = class Erc7677Paymaster extends Paymaster {
|
|
6987
|
+
/** The paymaster JSON-RPC endpoint URL */
|
|
6988
|
+
rpcUrl;
|
|
6989
|
+
/** Cached chain ID (hex string). Passed via constructor or resolved from the bundler at first use. */
|
|
6990
|
+
chainId;
|
|
6991
|
+
/** Detected or explicitly set paymaster provider. `null` means no provider-specific features. */
|
|
6992
|
+
provider;
|
|
6993
|
+
/**
|
|
6994
|
+
* Cached Candide `pm_supportedERC20Tokens` response, keyed by lowercase
|
|
6995
|
+
* entrypoint. Used for both token quotes and stub data to avoid a second
|
|
6996
|
+
* round-trip (`pm_getPaymasterStubData`) for Candide-hosted paymasters.
|
|
6997
|
+
*
|
|
6998
|
+
* The cache is indefinite for stub-data lookups but has a TTL for
|
|
6999
|
+
* exchange-rate lookups — see {@link CANDIDE_TOKEN_QUOTE_TTL_MS}.
|
|
7000
|
+
*/
|
|
7001
|
+
candideCache = /* @__PURE__ */ new Map();
|
|
7002
|
+
/**
|
|
7003
|
+
* Detect the paymaster provider from the RPC URL hostname.
|
|
7004
|
+
* Returns `null` for unknown hosts or malformed URLs.
|
|
7005
|
+
*
|
|
7006
|
+
* Hostname-based (not substring) so that proxies or paths containing a
|
|
7007
|
+
* provider name (e.g. `https://my-proxy.com/pimlico-compat/...`) are not
|
|
7008
|
+
* misidentified.
|
|
7009
|
+
*/
|
|
7010
|
+
static detectProvider(rpcUrl) {
|
|
7011
|
+
let host;
|
|
7012
|
+
try {
|
|
7013
|
+
host = new URL(rpcUrl).hostname.toLowerCase();
|
|
7014
|
+
} catch {
|
|
7015
|
+
return null;
|
|
7016
|
+
}
|
|
7017
|
+
if (host === "pimlico.io" || host.endsWith(".pimlico.io")) return "pimlico";
|
|
7018
|
+
if (host === "candide.dev" || host.endsWith(".candide.dev")) return "candide";
|
|
7019
|
+
return null;
|
|
7020
|
+
}
|
|
7021
|
+
/**
|
|
7022
|
+
* @param rpcUrl - Paymaster JSON-RPC endpoint. Can be the same URL as the
|
|
7023
|
+
* bundler when the provider bundles both (Candide, Pimlico, Alchemy);
|
|
7024
|
+
* can also be a separate paymaster-only endpoint.
|
|
7025
|
+
* @param options
|
|
7026
|
+
* @param options.chainId - Optional chain id as a bigint (e.g. `1n` for
|
|
7027
|
+
* mainnet). When provided, avoids a lookup at first use. Otherwise,
|
|
7028
|
+
* resolved from the bundler via `eth_chainId` on the first call.
|
|
7029
|
+
* @param options.provider - Paymaster provider. `"auto"` (default) detects
|
|
7030
|
+
* from the RPC URL. Set explicitly to override, or `null` to disable.
|
|
7031
|
+
*/
|
|
7032
|
+
constructor(rpcUrl, options = {}) {
|
|
7033
|
+
super();
|
|
7034
|
+
this.rpcUrl = rpcUrl;
|
|
7035
|
+
this.chainId = options.chainId != null ? "0x" + options.chainId.toString(16) : null;
|
|
7036
|
+
if (options.provider === void 0 || options.provider === "auto") this.provider = Erc7677Paymaster.detectProvider(rpcUrl);
|
|
7037
|
+
else this.provider = options.provider;
|
|
7038
|
+
}
|
|
7039
|
+
/**
|
|
7040
|
+
* Resolve the chain id, querying the bundler if not provided at construction.
|
|
7041
|
+
*/
|
|
7042
|
+
async getChainId(bundlerRpc) {
|
|
7043
|
+
if (this.chainId != null) return this.chainId;
|
|
7044
|
+
const id = await new Bundler(bundlerRpc).chainId();
|
|
7045
|
+
this.chainId = id;
|
|
7046
|
+
return id;
|
|
7047
|
+
}
|
|
7048
|
+
/**
|
|
7049
|
+
* Determine the EntryPoint address from the UserOperation shape.
|
|
7050
|
+
* V6 ops have `initCode`, V8+ ops have `eip7702Auth`, V7 is the default.
|
|
7051
|
+
*/
|
|
7052
|
+
resolveEntrypoint(smartAccount, userOperation) {
|
|
7053
|
+
if (smartAccount.entrypointAddress != null && smartAccount.entrypointAddress.trim() !== "") return smartAccount.entrypointAddress;
|
|
7054
|
+
if ("initCode" in userOperation) return ENTRYPOINT_V6;
|
|
7055
|
+
if ("eip7702Auth" in userOperation) return ENTRYPOINT_V8;
|
|
7056
|
+
return ENTRYPOINT_V7;
|
|
7057
|
+
}
|
|
7058
|
+
/**
|
|
7059
|
+
* Low-level ERC-7677 `pm_getPaymasterStubData` call.
|
|
7060
|
+
* Returns dummy paymaster fields intended for gas estimation.
|
|
7061
|
+
*
|
|
7062
|
+
* Most consumers should prefer {@link createPaymasterUserOperation}, which
|
|
7063
|
+
* runs the full stub → estimate → final pipeline. Use this directly if you
|
|
7064
|
+
* need to drive the flow manually.
|
|
7065
|
+
*/
|
|
7066
|
+
async getPaymasterStubData(userOperation, entrypoint, chainIdHex, context = {}) {
|
|
7067
|
+
try {
|
|
7068
|
+
return await sendJsonRpcRequest(this.rpcUrl, "pm_getPaymasterStubData", [
|
|
7069
|
+
userOperation,
|
|
7070
|
+
entrypoint,
|
|
7071
|
+
chainIdHex,
|
|
7072
|
+
context
|
|
7073
|
+
]);
|
|
7074
|
+
} catch (err) {
|
|
7075
|
+
throw new AbstractionKitError("PAYMASTER_ERROR", "pm_getPaymasterStubData failed", { cause: ensureError(err) });
|
|
7076
|
+
}
|
|
7077
|
+
}
|
|
7078
|
+
/**
|
|
7079
|
+
* Low-level ERC-7677 `pm_getPaymasterData` call.
|
|
7080
|
+
* Returns the final signed paymaster fields.
|
|
7081
|
+
*/
|
|
7082
|
+
async getPaymasterData(userOperation, entrypoint, chainIdHex, context = {}) {
|
|
7083
|
+
try {
|
|
7084
|
+
return await sendJsonRpcRequest(this.rpcUrl, "pm_getPaymasterData", [
|
|
7085
|
+
userOperation,
|
|
7086
|
+
entrypoint,
|
|
7087
|
+
chainIdHex,
|
|
7088
|
+
context
|
|
7089
|
+
]);
|
|
7090
|
+
} catch (err) {
|
|
7091
|
+
throw new AbstractionKitError("PAYMASTER_ERROR", "pm_getPaymasterData failed", { cause: ensureError(err) });
|
|
7092
|
+
}
|
|
7093
|
+
}
|
|
7094
|
+
/**
|
|
7095
|
+
* Send an arbitrary JSON-RPC request through the paymaster endpoint.
|
|
7096
|
+
* Useful for provider-specific methods that fall outside the ERC-7677 spec.
|
|
7097
|
+
*
|
|
7098
|
+
* @param method - The JSON-RPC method name
|
|
7099
|
+
* @param params - The JSON-RPC params array
|
|
7100
|
+
* @returns The `result` field from the JSON-RPC response
|
|
7101
|
+
*/
|
|
7102
|
+
async sendRPCRequest(method, params = []) {
|
|
7103
|
+
try {
|
|
7104
|
+
return await sendJsonRpcRequest(this.rpcUrl, method, params);
|
|
7105
|
+
} catch (err) {
|
|
7106
|
+
throw new AbstractionKitError("PAYMASTER_ERROR", `sendRPCRequest(${method}) failed`, { cause: ensureError(err) });
|
|
7107
|
+
}
|
|
7108
|
+
}
|
|
7109
|
+
/**
|
|
7110
|
+
* Runs the full ERC-7677 pipeline and returns a UserOperation with paymaster
|
|
7111
|
+
* fields populated. The caller is responsible for signing and sending.
|
|
7112
|
+
*
|
|
7113
|
+
* @param smartAccount - Provides the target EntryPoint; not mutated.
|
|
7114
|
+
* @param userOperation - Starting UserOperation. Not mutated — a shallow copy is returned.
|
|
7115
|
+
* @param bundlerRpc - Bundler URL used for gas estimation and, if
|
|
7116
|
+
* `options.chainId` was not provided to the constructor, chain-id lookup.
|
|
7117
|
+
* @param context - Provider-specific paymaster context
|
|
7118
|
+
* (e.g. `{ sponsorshipPolicyId }` or `{ token }`).
|
|
7119
|
+
* @param overrides - Gas estimation overrides and state-override set.
|
|
7120
|
+
*
|
|
7121
|
+
* @returns The UserOperation with paymaster + gas fields populated.
|
|
7122
|
+
*/
|
|
7123
|
+
async createPaymasterUserOperation(smartAccount, userOperation, bundlerRpc, context = {}, overrides = {}) {
|
|
7124
|
+
try {
|
|
7125
|
+
const userOp = { ...userOperation };
|
|
7126
|
+
const entrypoint = overrides.entrypoint ?? this.resolveEntrypoint(smartAccount, userOp);
|
|
7127
|
+
const chainIdHex = await this.getChainId(bundlerRpc);
|
|
7128
|
+
if (context.token != null && typeof context.token === "string") return this.tokenPaymasterFlow(smartAccount, userOp, context.token, bundlerRpc, entrypoint, chainIdHex, context, overrides);
|
|
7129
|
+
return this.sponsoredFlow(userOp, bundlerRpc, entrypoint, chainIdHex, context, overrides);
|
|
7130
|
+
} catch (err) {
|
|
7131
|
+
const error = ensureError(err);
|
|
7132
|
+
if (error instanceof AbstractionKitError) throw error;
|
|
7133
|
+
throw new AbstractionKitError("PAYMASTER_ERROR", "createPaymasterUserOperation failed", { cause: error });
|
|
7134
|
+
}
|
|
7135
|
+
}
|
|
7136
|
+
/**
|
|
7137
|
+
* Merge paymaster fields into a UserOperation. Handles both v0.6
|
|
7138
|
+
* (`paymasterAndData`) and v0.7+ split fields.
|
|
7139
|
+
*/
|
|
7140
|
+
applyPaymasterFields(userOp, fields) {
|
|
7141
|
+
if ("initCode" in userOp) {
|
|
7142
|
+
if (fields.paymasterAndData != null) userOp.paymasterAndData = fields.paymasterAndData;
|
|
7143
|
+
return;
|
|
7144
|
+
}
|
|
7145
|
+
if (fields.paymaster != null) userOp.paymaster = fields.paymaster;
|
|
7146
|
+
if (fields.paymasterData != null) userOp.paymasterData = fields.paymasterData;
|
|
7147
|
+
if (fields.paymasterVerificationGasLimit != null) userOp.paymasterVerificationGasLimit = BigInt(fields.paymasterVerificationGasLimit);
|
|
7148
|
+
if (fields.paymasterPostOpGasLimit != null) userOp.paymasterPostOpGasLimit = BigInt(fields.paymasterPostOpGasLimit);
|
|
7149
|
+
}
|
|
7150
|
+
/**
|
|
7151
|
+
* Estimate gas limits via the bundler and apply them (with multipliers).
|
|
7152
|
+
* Reads paymaster gas fields back from the bundler when present — some
|
|
7153
|
+
* providers' `pm_getPaymasterStubData` returns `paymasterPostOpGasLimit: 0x1`
|
|
7154
|
+
* as a placeholder, relying on the bundler's estimate for the real value.
|
|
7155
|
+
*
|
|
7156
|
+
* Mirrors CandidePaymaster.estimateAndApplyGasLimits default multipliers
|
|
7157
|
+
* (5%/10%/10% on preVerification/verification/call) for consistent UX.
|
|
7158
|
+
*/
|
|
7159
|
+
async estimateAndApplyGasLimits(userOp, bundlerRpc, entrypoint, overrides) {
|
|
7160
|
+
let preVerificationGas = userOp.preVerificationGas;
|
|
7161
|
+
let verificationGasLimit = userOp.verificationGasLimit;
|
|
7162
|
+
let callGasLimit = userOp.callGasLimit;
|
|
7163
|
+
if (overrides.preVerificationGas == null || overrides.verificationGasLimit == null || overrides.callGasLimit == null) {
|
|
7164
|
+
if (bundlerRpc == null) throw new AbstractionKitError("BAD_DATA", "bundlerRpc can't be null if preVerificationGas, verificationGasLimit and callGasLimit are not overridden");
|
|
7165
|
+
const bundler = new Bundler(bundlerRpc);
|
|
7166
|
+
userOp.callGasLimit = 0n;
|
|
7167
|
+
userOp.verificationGasLimit = 0n;
|
|
7168
|
+
userOp.preVerificationGas = 0n;
|
|
7169
|
+
const skipFeeZeroing = this.provider === "pimlico";
|
|
7170
|
+
const inputMaxFeePerGas = userOp.maxFeePerGas;
|
|
7171
|
+
const inputMaxPriorityFeePerGas = userOp.maxPriorityFeePerGas;
|
|
7172
|
+
if (!skipFeeZeroing) {
|
|
7173
|
+
userOp.maxFeePerGas = 0n;
|
|
7174
|
+
userOp.maxPriorityFeePerGas = 0n;
|
|
7175
|
+
}
|
|
7176
|
+
const estimation = await bundler.estimateUserOperationGas(userOp, entrypoint, overrides.state_override_set);
|
|
7177
|
+
if (!skipFeeZeroing) {
|
|
7178
|
+
userOp.maxFeePerGas = inputMaxFeePerGas;
|
|
7179
|
+
userOp.maxPriorityFeePerGas = inputMaxPriorityFeePerGas;
|
|
7180
|
+
}
|
|
7181
|
+
if (estimation.preVerificationGas > preVerificationGas) preVerificationGas = estimation.preVerificationGas;
|
|
7182
|
+
if (estimation.verificationGasLimit > verificationGasLimit) verificationGasLimit = estimation.verificationGasLimit;
|
|
7183
|
+
if (estimation.callGasLimit > callGasLimit) callGasLimit = estimation.callGasLimit;
|
|
7184
|
+
if ("paymaster" in userOp && estimation.paymasterVerificationGasLimit != null) userOp.paymasterVerificationGasLimit = estimation.paymasterVerificationGasLimit;
|
|
7185
|
+
if ("paymaster" in userOp && estimation.paymasterPostOpGasLimit != null) userOp.paymasterPostOpGasLimit = estimation.paymasterPostOpGasLimit;
|
|
7186
|
+
}
|
|
7187
|
+
if (typeof overrides.preVerificationGas === "bigint" && overrides.preVerificationGas < 0n) throw new RangeError("preVerificationGas override can't be negative");
|
|
7188
|
+
if (typeof overrides.verificationGasLimit === "bigint" && overrides.verificationGasLimit < 0n) throw new RangeError("verificationGasLimit override can't be negative");
|
|
7189
|
+
if (typeof overrides.callGasLimit === "bigint" && overrides.callGasLimit < 0n) throw new RangeError("callGasLimit override can't be negative");
|
|
7190
|
+
const applyMultiplier = (value, multiplier) => value + value * BigInt(Math.round((multiplier ?? 0) * 100)) / 10000n;
|
|
7191
|
+
userOp.preVerificationGas = overrides.preVerificationGas ?? applyMultiplier(preVerificationGas, overrides.preVerificationGasPercentageMultiplier ?? 5);
|
|
7192
|
+
userOp.verificationGasLimit = overrides.verificationGasLimit ?? applyMultiplier(verificationGasLimit, overrides.verificationGasLimitPercentageMultiplier ?? 10);
|
|
7193
|
+
userOp.callGasLimit = overrides.callGasLimit ?? applyMultiplier(callGasLimit, overrides.callGasLimitPercentageMultiplier ?? 10);
|
|
7194
|
+
if (entrypoint.toLowerCase() === "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789".toLowerCase()) userOp.verificationGasLimit += 40000n;
|
|
7195
|
+
}
|
|
7196
|
+
/**
|
|
7197
|
+
* Fetch token exchange rate and paymaster address via Pimlico's
|
|
7198
|
+
* `pimlico_getTokenQuotes` RPC.
|
|
7199
|
+
*
|
|
7200
|
+
* @returns `exchangeRate` as a bigint scaled by 10^18 (the value of 1 ETH
|
|
7201
|
+
* expressed in the token's smallest unit). Used to compute the token
|
|
7202
|
+
* approval amount via `(exchangeRate * gasCostWei) / 10^18`.
|
|
7203
|
+
*/
|
|
7204
|
+
async fetchPimlicoTokenQuote(tokenAddress, entrypoint, chainIdHex) {
|
|
7205
|
+
const quotes = (await sendJsonRpcRequest(this.rpcUrl, "pimlico_getTokenQuotes", [
|
|
7206
|
+
{ tokens: [tokenAddress] },
|
|
7207
|
+
entrypoint,
|
|
7208
|
+
chainIdHex
|
|
7209
|
+
]))?.quotes;
|
|
7210
|
+
if (!Array.isArray(quotes) || quotes.length === 0) throw new AbstractionKitError("PAYMASTER_ERROR", `pimlico_getTokenQuotes returned no quotes for token ${tokenAddress}`);
|
|
7211
|
+
const quote = quotes.find((q) => q.token.toLowerCase() === tokenAddress.toLowerCase());
|
|
7212
|
+
if (quote == null) throw new AbstractionKitError("PAYMASTER_ERROR", `pimlico_getTokenQuotes did not include token ${tokenAddress}`);
|
|
7213
|
+
return {
|
|
7214
|
+
exchangeRate: BigInt(quote.exchangeRate),
|
|
7215
|
+
paymasterAddress: quote.paymaster
|
|
7216
|
+
};
|
|
7217
|
+
}
|
|
7218
|
+
/**
|
|
7219
|
+
* Fetch (and cache) Candide's `pm_supportedERC20Tokens` response for the
|
|
7220
|
+
* given entrypoint. The response carries both exchange rates and the
|
|
7221
|
+
* `dummyPaymasterAndData` used for gas estimation, so one round-trip
|
|
7222
|
+
* suffices for the entire paymaster flow.
|
|
7223
|
+
*
|
|
7224
|
+
* @param options.enforceTTL - When true, re-fetches if the cached entry is
|
|
7225
|
+
* older than {@link CANDIDE_TOKEN_QUOTE_TTL_MS}. Set by exchange-rate
|
|
7226
|
+
* lookups (where staleness matters). Stub-data lookups leave this false
|
|
7227
|
+
* and reuse the cache indefinitely — the paymaster address and
|
|
7228
|
+
* `dummyPaymasterAndData` are effectively static per paymaster version.
|
|
7229
|
+
*/
|
|
7230
|
+
async fetchCandideSupportedTokens(entrypoint, options = {}) {
|
|
7231
|
+
const key = entrypoint.toLowerCase();
|
|
7232
|
+
const cached = this.candideCache.get(key);
|
|
7233
|
+
const isStale = cached != null && options.enforceTTL === true && Date.now() - cached.fetchedAt > CANDIDE_TOKEN_QUOTE_TTL_MS;
|
|
7234
|
+
if (cached != null && !isStale) return cached.data;
|
|
7235
|
+
const result = await sendJsonRpcRequest(this.rpcUrl, "pm_supportedERC20Tokens", [entrypoint]);
|
|
7236
|
+
this.candideCache.set(key, {
|
|
7237
|
+
data: result,
|
|
7238
|
+
fetchedAt: Date.now()
|
|
7239
|
+
});
|
|
7240
|
+
return result;
|
|
7241
|
+
}
|
|
7242
|
+
/**
|
|
7243
|
+
* Fetch token exchange rate and paymaster address via Candide's
|
|
7244
|
+
* `pm_supportedERC20Tokens` RPC.
|
|
7245
|
+
*
|
|
7246
|
+
* @returns `exchangeRate` as a bigint scaled by 10^18 (the value of 1 ETH
|
|
7247
|
+
* expressed in the token's smallest unit). Used to compute the token
|
|
7248
|
+
* approval amount via `(exchangeRate * gasCostWei) / 10^18`.
|
|
7249
|
+
*/
|
|
7250
|
+
async fetchCandideTokenQuote(tokenAddress, entrypoint) {
|
|
7251
|
+
const result = await this.fetchCandideSupportedTokens(entrypoint, { enforceTTL: true });
|
|
7252
|
+
const token = result.tokens?.find((t) => t.address.toLowerCase() === tokenAddress.toLowerCase());
|
|
7253
|
+
if (token == null) throw new AbstractionKitError("PAYMASTER_ERROR", `${tokenAddress} token is not supported by the Candide paymaster`);
|
|
7254
|
+
return {
|
|
7255
|
+
exchangeRate: BigInt(token.exchangeRate),
|
|
7256
|
+
paymasterAddress: result.paymasterMetadata.address
|
|
7257
|
+
};
|
|
7258
|
+
}
|
|
7259
|
+
/**
|
|
7260
|
+
* Convert Candide's `dummyPaymasterAndData` metadata into a stub result
|
|
7261
|
+
* compatible with {@link applyPaymasterFields}. Handles both v0.6
|
|
7262
|
+
* (concatenated hex string) and v0.7+ (structured) shapes.
|
|
7263
|
+
*/
|
|
7264
|
+
candideStubFromMetadata(metadata) {
|
|
7265
|
+
const dummy = metadata.dummyPaymasterAndData;
|
|
7266
|
+
if (typeof dummy === "string") return { paymasterAndData: dummy };
|
|
7267
|
+
return {
|
|
7268
|
+
paymaster: dummy.paymaster,
|
|
7269
|
+
paymasterData: dummy.paymasterData,
|
|
7270
|
+
paymasterVerificationGasLimit: dummy.paymasterVerificationGasLimit,
|
|
7271
|
+
paymasterPostOpGasLimit: dummy.paymasterPostOpGasLimit
|
|
7272
|
+
};
|
|
7273
|
+
}
|
|
7274
|
+
/**
|
|
7275
|
+
* Get stub paymaster data. For Candide-hosted paymasters this derives the
|
|
7276
|
+
* stub from the cached `pm_supportedERC20Tokens` response (no extra
|
|
7277
|
+
* round-trip). For other providers, falls back to `pm_getPaymasterStubData`.
|
|
7278
|
+
*/
|
|
7279
|
+
async getStubData(userOperation, entrypoint, chainIdHex, context) {
|
|
7280
|
+
if (this.provider === "candide") {
|
|
7281
|
+
const response = await this.fetchCandideSupportedTokens(entrypoint);
|
|
7282
|
+
return this.candideStubFromMetadata(response.paymasterMetadata);
|
|
7283
|
+
}
|
|
7284
|
+
return this.getPaymasterStubData(userOperation, entrypoint, chainIdHex, context);
|
|
7285
|
+
}
|
|
7286
|
+
/**
|
|
7287
|
+
* Route to the correct provider-specific token quote fetcher.
|
|
7288
|
+
* Returns `null` when no provider is configured.
|
|
7289
|
+
*/
|
|
7290
|
+
async fetchProviderTokenQuote(tokenAddress, entrypoint, chainIdHex) {
|
|
7291
|
+
switch (this.provider) {
|
|
7292
|
+
case "pimlico": return this.fetchPimlicoTokenQuote(tokenAddress, entrypoint, chainIdHex);
|
|
7293
|
+
case "candide": return this.fetchCandideTokenQuote(tokenAddress, entrypoint);
|
|
7294
|
+
default: return null;
|
|
7295
|
+
}
|
|
7296
|
+
}
|
|
7297
|
+
/**
|
|
7298
|
+
* Internal token paymaster pipeline. Called from `createPaymasterUserOperation`
|
|
7299
|
+
* when `context.token` is set and the smart account supports approval prepending.
|
|
7300
|
+
*
|
|
7301
|
+
* Three cases:
|
|
7302
|
+
* - **Provider detected**: exchange rate + paymaster address from provider RPC.
|
|
7303
|
+
* - **No provider, `context.exchangeRate` set**: uses provided rate, paymaster
|
|
7304
|
+
* address from stub.
|
|
7305
|
+
* - **No provider, no rate**: falls through to the regular sponsored flow
|
|
7306
|
+
* (developer already handled approval).
|
|
7307
|
+
*/
|
|
7308
|
+
async tokenPaymasterFlow(smartAccount, userOp, tokenAddress, bundlerRpc, entrypoint, chainIdHex, context, overrides) {
|
|
7309
|
+
let exchangeRate;
|
|
7310
|
+
let paymasterAddress = null;
|
|
7311
|
+
const providerQuote = await this.fetchProviderTokenQuote(tokenAddress, entrypoint, chainIdHex);
|
|
7312
|
+
if (providerQuote != null) {
|
|
7313
|
+
exchangeRate = providerQuote.exchangeRate;
|
|
7314
|
+
paymasterAddress = providerQuote.paymasterAddress;
|
|
7315
|
+
} else if (context.exchangeRate != null) {
|
|
7316
|
+
try {
|
|
7317
|
+
exchangeRate = BigInt(context.exchangeRate);
|
|
7318
|
+
} catch (err) {
|
|
7319
|
+
throw new AbstractionKitError("PAYMASTER_ERROR", `context.exchangeRate could not be parsed as a bigint: ${String(context.exchangeRate)}`, { cause: ensureError(err) });
|
|
7320
|
+
}
|
|
7321
|
+
if (exchangeRate <= 0n) throw new AbstractionKitError("PAYMASTER_ERROR", `context.exchangeRate must be > 0, got ${exchangeRate}`);
|
|
7322
|
+
} else return this.sponsoredFlow(userOp, bundlerRpc, entrypoint, chainIdHex, context, overrides);
|
|
7323
|
+
const stub = await this.getStubData(userOp, entrypoint, chainIdHex, context);
|
|
7324
|
+
this.applyPaymasterFields(userOp, stub);
|
|
7325
|
+
if (paymasterAddress == null) if (context.paymasterAddress != null) paymasterAddress = context.paymasterAddress;
|
|
7326
|
+
else if ("initCode" in userOp && stub.paymasterAndData != null) paymasterAddress = "0x" + stub.paymasterAndData.slice(2, 42);
|
|
7327
|
+
else if (stub.paymaster != null) paymasterAddress = stub.paymaster;
|
|
7328
|
+
else throw new AbstractionKitError("PAYMASTER_ERROR", "pm_getPaymasterStubData did not return a paymaster address. Pass paymasterAddress in the context or set a provider.");
|
|
7329
|
+
const originalCallData = userOp.callData;
|
|
7330
|
+
const requiresAllowanceReset = overrides.resetApproval ?? TOKENS_REQUIRING_ALLOWANCE_RESET.includes(tokenAddress.toLowerCase());
|
|
7331
|
+
let callDataWithApprove = smartAccount.prependTokenPaymasterApproveToCallData(userOp.callData, tokenAddress, paymasterAddress, UINT256_MAX);
|
|
7332
|
+
if (requiresAllowanceReset) callDataWithApprove = smartAccount.prependTokenPaymasterApproveToCallData(callDataWithApprove, tokenAddress, paymasterAddress, 0n);
|
|
7333
|
+
userOp.callData = callDataWithApprove;
|
|
7334
|
+
await this.estimateAndApplyGasLimits(userOp, bundlerRpc, entrypoint, overrides);
|
|
7335
|
+
const maxGasCostWei = calculateUserOperationMaxGasCost(userOp);
|
|
7336
|
+
const approveAmount = exchangeRate * maxGasCostWei / 10n ** 18n * TOKEN_APPROVE_AMOUNT_MULTIPLIER;
|
|
7337
|
+
callDataWithApprove = smartAccount.prependTokenPaymasterApproveToCallData(originalCallData, tokenAddress, paymasterAddress, approveAmount);
|
|
7338
|
+
if (requiresAllowanceReset) callDataWithApprove = smartAccount.prependTokenPaymasterApproveToCallData(callDataWithApprove, tokenAddress, paymasterAddress, 0n);
|
|
7339
|
+
userOp.callData = callDataWithApprove;
|
|
7340
|
+
const final = await this.getPaymasterData(userOp, entrypoint, chainIdHex, context);
|
|
7341
|
+
this.applyPaymasterFields(userOp, final);
|
|
7342
|
+
return userOp;
|
|
7343
|
+
}
|
|
7344
|
+
/**
|
|
7345
|
+
* The regular (non-token) sponsored flow: stub → estimate → final.
|
|
7346
|
+
* Extracted to allow `tokenPaymasterFlow` to fall through to it for Case C.
|
|
7347
|
+
*/
|
|
7348
|
+
async sponsoredFlow(userOp, bundlerRpc, entrypoint, chainIdHex, context, overrides) {
|
|
7349
|
+
const stub = await this.getStubData(userOp, entrypoint, chainIdHex, context);
|
|
7350
|
+
this.applyPaymasterFields(userOp, stub);
|
|
7351
|
+
await this.estimateAndApplyGasLimits(userOp, bundlerRpc, entrypoint, overrides);
|
|
7352
|
+
if (stub.isFinal === true) return userOp;
|
|
7353
|
+
const final = await this.getPaymasterData(userOp, entrypoint, chainIdHex, context);
|
|
7354
|
+
this.applyPaymasterFields(userOp, final);
|
|
7355
|
+
return userOp;
|
|
7356
|
+
}
|
|
7357
|
+
};
|
|
7358
|
+
//#endregion
|
|
6963
7359
|
//#region src/paymaster/AllowAllPaymaster.ts
|
|
6964
7360
|
/**
|
|
6965
7361
|
* A paymaster that sponsors all UserOperations unconditionally.
|
|
@@ -7105,6 +7501,7 @@ var abstractionkit = (function(exports, ethers) {
|
|
|
7105
7501
|
ENTRYPOINT_V9: () => ENTRYPOINT_V9,
|
|
7106
7502
|
EOADummySignerSignaturePair: () => EOADummySignerSignaturePair,
|
|
7107
7503
|
EXECUTE_RECOVERY_PRIMARY_TYPE: () => EXECUTE_RECOVERY_PRIMARY_TYPE,
|
|
7504
|
+
Erc7677Paymaster: () => Erc7677Paymaster,
|
|
7108
7505
|
ExperimentalAllowAllParallelPaymaster: () => ExperimentalAllowAllParallelPaymaster,
|
|
7109
7506
|
GasOption: () => GasOption,
|
|
7110
7507
|
Operation: () => Operation,
|
|
@@ -7178,6 +7575,7 @@ var abstractionkit = (function(exports, ethers) {
|
|
|
7178
7575
|
exports.ENTRYPOINT_V9 = ENTRYPOINT_V9;
|
|
7179
7576
|
exports.EOADummySignerSignaturePair = EOADummySignerSignaturePair;
|
|
7180
7577
|
exports.EXECUTE_RECOVERY_PRIMARY_TYPE = EXECUTE_RECOVERY_PRIMARY_TYPE;
|
|
7578
|
+
exports.Erc7677Paymaster = Erc7677Paymaster;
|
|
7181
7579
|
exports.ExperimentalAllowAllParallelPaymaster = ExperimentalAllowAllParallelPaymaster;
|
|
7182
7580
|
exports.GasOption = GasOption;
|
|
7183
7581
|
exports.Operation = Operation;
|