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.
@@ -1115,11 +1115,14 @@ var abstractionkit = (function(exports, ethers) {
1115
1115
  state_override_set
1116
1116
  ]);
1117
1117
  const res = jsonRpcResult;
1118
- return {
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.0") {
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, overrides = {}) {
4198
+ static formatSignaturesToUseroperationsSignatures(userOperationsToSign, signerSignaturePairs) {
4196
4199
  if (userOperationsToSign.length < 1) throw new RangeError("There should be at least one userOperationsToSign");
4197
- const resolvedOverrides = {
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
- ...resolvedOverrides,
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((userOperationsToSign, _index) => {
4213
- const userOperationHash = SafeAccount.getUserOperationEip712Hash_V9(userOperationsToSign.userOperation, userOperationsToSign.chainId, {
4214
- validAfter: userOperationsToSign.validAfter,
4215
- validUntil: userOperationsToSign.validUntil,
4216
- safe4337ModuleAddress: resolvedOverrides.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((_userOperationsToSign, index) => {
4227
+ userOperationsToSign.forEach((userOperationToSign, index) => {
4223
4228
  userOpSignatures.push(SafeAccount.formatSignaturesToUseroperationSignature(signerSignaturePairs, {
4224
- ...resolvedOverrides,
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;