abstractionkit 0.3.0 → 0.3.2

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 CHANGED
@@ -805,6 +805,52 @@ async function handlefetchGasPrice(providerRpc, polygonGasStation, gasLevel = Ga
805
805
  return [maxFeePerGas, maxPriorityFeePerGas];
806
806
  }
807
807
  //#endregion
808
+ //#region src/signer/negotiate.ts
809
+ /**
810
+ * Pick the best mutually-supported signing scheme for one signer against an
811
+ * account's accepted schemes. Later in the `accepted` array = lower preference;
812
+ * the account ranks by preference.
813
+ *
814
+ * Throws a detailed {@link AbstractionKitError} if no scheme overlaps,
815
+ * citing the signer's address, what the account accepts, and what the
816
+ * signer can do.
817
+ */
818
+ function pickScheme(signer, accepted, context) {
819
+ const signerCan = [];
820
+ if (typeof signer.signTypedData === "function") signerCan.push("typedData");
821
+ if (typeof signer.signHash === "function") signerCan.push("hash");
822
+ for (const scheme of accepted) if (signerCan.includes(scheme)) return scheme;
823
+ throw new AbstractionKitError("BAD_DATA", buildMismatchMessage({
824
+ accountName: context.accountName,
825
+ signerIndex: context.signerIndex,
826
+ signerAddress: signer.address,
827
+ accepted,
828
+ signerCan
829
+ }));
830
+ }
831
+ function buildMismatchMessage(params) {
832
+ const { accountName, signerIndex, signerAddress, accepted, signerCan } = params;
833
+ const canStr = signerCan.length > 0 ? signerCan.join(", ") : "none";
834
+ return `No compatible signing scheme for signer[${signerIndex}] ${signerAddress}. ${accountName} accepts: [${accepted.join(", ")}]; signer provides: [${canStr}]. ` + (signerCan.length === 0 ? "Signer must implement at least one of `signHash` or `signTypedData`. " : "") + "Hint: `fromViem` / `fromEthersWallet` give both; `fromViemWalletClient` gives only `typedData` (use Safe for JSON-RPC wallets).";
835
+ }
836
+ /**
837
+ * Invoke a signer for one scheme. Keeps the dispatch in one place so the
838
+ * account-side code stays linear. `typedData` is optional: accounts that
839
+ * only accept the `"hash"` scheme (Simple7702, Calibur) pass just `hash`.
840
+ *
841
+ * `context` is always forwarded to the signer so power-user implementations
842
+ * can inspect the userOp.
843
+ */
844
+ async function invokeSigner(signer, scheme, payload) {
845
+ if (scheme === "typedData") {
846
+ if (typeof signer.signTypedData !== "function") throw new AbstractionKitError("BAD_DATA", `signer ${signer.address} is missing signTypedData`);
847
+ if (!payload.typedData) throw new AbstractionKitError("BAD_DATA", `scheme "typedData" selected but no typedData payload provided`);
848
+ return signer.signTypedData(payload.typedData, payload.context);
849
+ }
850
+ if (typeof signer.signHash !== "function") throw new AbstractionKitError("BAD_DATA", `signer ${signer.address} is missing signHash`);
851
+ return signer.signHash(payload.hash, payload.context);
852
+ }
853
+ //#endregion
808
854
  //#region src/utils7702.ts
809
855
  const SET_CODE_TX_TYPE = "0x04";
810
856
  /**
@@ -1115,11 +1161,14 @@ var Bundler = class {
1115
1161
  state_override_set
1116
1162
  ]);
1117
1163
  const res = jsonRpcResult;
1118
- return {
1164
+ const gasEstimationResult = {
1119
1165
  callGasLimit: BigInt(res.callGasLimit),
1120
1166
  preVerificationGas: BigInt(res.preVerificationGas),
1121
1167
  verificationGasLimit: BigInt(res.verificationGasLimit)
1122
1168
  };
1169
+ if (res.paymasterVerificationGasLimit != null) gasEstimationResult.paymasterVerificationGasLimit = BigInt(res.paymasterVerificationGasLimit);
1170
+ if (res.paymasterPostOpGasLimit != null) gasEstimationResult.paymasterPostOpGasLimit = BigInt(res.paymasterPostOpGasLimit);
1171
+ return gasEstimationResult;
1123
1172
  } catch (err) {
1124
1173
  throw new AbstractionKitError("BUNDLER_ERROR", "bundler eth_estimateUserOperationGas rpc call failed", { cause: ensureError(err) });
1125
1174
  }
@@ -1579,6 +1628,30 @@ var BaseSimple7702Account = class BaseSimple7702Account extends SmartAccount {
1579
1628
  return new ethers.Wallet(privateKey).signingKey.sign(userOperationHash).serialized;
1580
1629
  }
1581
1630
  /**
1631
+ * Schemes Simple7702 accepts from a Signer. Only raw-hash ECDSA, since
1632
+ * the delegatee verifies a plain signature over the userOp hash.
1633
+ */
1634
+ static ACCEPTED_SIGNING_SCHEMES = ["hash"];
1635
+ /**
1636
+ * Sign a UserOperation with an {@link AkSigner}. Signer must implement
1637
+ * `signHash`, since Simple7702 only verifies raw ECDSA over the userOp
1638
+ * hash. JSON-RPC wallets and anything that only provides `signTypedData`
1639
+ * fail offline with a specific error.
1640
+ */
1641
+ async baseSignUserOperationWithSigner(useroperation, signer, chainId) {
1642
+ return invokeSigner(signer, pickScheme(signer, BaseSimple7702Account.ACCEPTED_SIGNING_SCHEMES, {
1643
+ accountName: "Simple7702 (raw ECDSA over userOpHash)",
1644
+ signerIndex: 0
1645
+ }), {
1646
+ hash: createUserOperationHash(useroperation, this.entrypointAddress, chainId),
1647
+ context: {
1648
+ userOperation: useroperation,
1649
+ chainId,
1650
+ entryPoint: this.entrypointAddress
1651
+ }
1652
+ });
1653
+ }
1654
+ /**
1582
1655
  * Submit a signed UserOperation to a bundler for on-chain inclusion.
1583
1656
  * @param userOperation - The signed UserOperation to submit
1584
1657
  * @param bundlerRpc - Bundler RPC endpoint
@@ -1689,6 +1762,18 @@ var Simple7702Account = class Simple7702Account extends BaseSimple7702Account {
1689
1762
  return this.baseSignUserOperation(useroperation, privateKey, chainId);
1690
1763
  }
1691
1764
  /**
1765
+ * Sign a {@link UserOperationV8} using an {@link ExternalSigner}.
1766
+ * Simple7702 only accepts raw-hash ECDSA; signers without `signHash`
1767
+ * fail offline with an actionable error.
1768
+ *
1769
+ * For signing with a raw private-key string, use the sync
1770
+ * {@link signUserOperation} method, or wrap explicitly with
1771
+ * `fromPrivateKey(pk)`.
1772
+ */
1773
+ async signUserOperationWithSigner(useroperation, signer, chainId) {
1774
+ return this.baseSignUserOperationWithSigner(useroperation, signer, chainId);
1775
+ }
1776
+ /**
1692
1777
  * Send a signed {@link UserOperationV8} to a bundler for on-chain inclusion.
1693
1778
  * @param userOperation - The signed UserOperation to submit
1694
1779
  * @param bundlerRpc - Bundler RPC endpoint
@@ -1754,6 +1839,15 @@ var Simple7702AccountV09 = class Simple7702AccountV09 extends BaseSimple7702Acco
1754
1839
  return this.baseSignUserOperation(useroperation, privateKey, chainId);
1755
1840
  }
1756
1841
  /**
1842
+ * Sign a {@link UserOperationV9} using an {@link ExternalSigner}.
1843
+ * Simple7702 only accepts raw-hash ECDSA; signers without `signHash`
1844
+ * fail offline with an actionable error. For a raw pk string, use the
1845
+ * sync {@link signUserOperation} method or wrap with `fromPrivateKey`.
1846
+ */
1847
+ async signUserOperationWithSigner(useroperation, signer, chainId) {
1848
+ return this.baseSignUserOperationWithSigner(useroperation, signer, chainId);
1849
+ }
1850
+ /**
1757
1851
  * Send a signed {@link UserOperationV9} to a bundler for on-chain inclusion.
1758
1852
  * @param userOperation - The signed UserOperation to submit
1759
1853
  * @param bundlerRpc - Bundler RPC endpoint
@@ -3158,6 +3252,68 @@ var SafeAccount = class SafeAccount extends SmartAccount {
3158
3252
  });
3159
3253
  }
3160
3254
  /**
3255
+ * Schemes Safe accepts from a {@link Signer}, in preference order.
3256
+ * `typedData` is preferred because wallets can display structured fields
3257
+ * rather than a hex blob; `hash` is accepted as a fallback for signers
3258
+ * that only support raw ECDSA.
3259
+ */
3260
+ static ACCEPTED_SIGNING_SCHEMES = ["typedData", "hash"];
3261
+ /**
3262
+ * Sign a UserOperation using one or more {@link Signer}s. This is the
3263
+ * capability-oriented signing path: each signer declares what it can do
3264
+ * (`signHash`, `signTypedData`, both) and the account picks the best
3265
+ * match per signer. Incompatible signers fail offline with an actionable
3266
+ * error rather than a silent bundler rejection.
3267
+ *
3268
+ * Signers are invoked in parallel. For interactive wallets that share a
3269
+ * popup session, sequence the prompts inside your Signer implementation.
3270
+ *
3271
+ * @param useroperation - UserOperation to sign
3272
+ * @param signers - Signer instances (`fromViem(account)`, `fromEthersWallet(wallet)`, etc.)
3273
+ * @param chainId - target chain id
3274
+ * @param entrypointAddress - target EntryPoint
3275
+ * @param safe4337ModuleAddress - Safe 4337 module
3276
+ * @param overrides - optional validAfter / validUntil / multi-chain flag
3277
+ * @returns formatted signature
3278
+ */
3279
+ static async baseSignUserOperationWithSigners(useroperation, signers, chainId, entrypointAddress, safe4337ModuleAddress, context, overrides = {}) {
3280
+ const validAfter = overrides.validAfter ?? 0n;
3281
+ const validUntil = overrides.validUntil ?? 0n;
3282
+ if (signers.length < 1) throw new RangeError("There should be at least one signer");
3283
+ const typedDataRaw = SafeAccount.getUserOperationEip712Data(useroperation, chainId, {
3284
+ validAfter,
3285
+ validUntil,
3286
+ entrypointAddress,
3287
+ safe4337ModuleAddress
3288
+ });
3289
+ const userOpHash = ethers.TypedDataEncoder.hash(typedDataRaw.domain, typedDataRaw.types, typedDataRaw.messageValue);
3290
+ const { EIP712Domain: _drop, ...primaryTypes } = typedDataRaw.types;
3291
+ const typedData = {
3292
+ domain: typedDataRaw.domain,
3293
+ types: primaryTypes,
3294
+ primaryType: EIP712_SAFE_OPERATION_PRIMARY_TYPE,
3295
+ message: typedDataRaw.messageValue
3296
+ };
3297
+ const normalizedAddresses = signers.map((signer) => (0, ethers.getAddress)(signer.address));
3298
+ const schemes = signers.map((signer, signerIndex) => pickScheme(signer, SafeAccount.ACCEPTED_SIGNING_SCHEMES, {
3299
+ accountName: "Safe (EIP-712 or raw hash over SafeOp digest)",
3300
+ signerIndex
3301
+ }));
3302
+ const signerSignaturePairs = (await Promise.all(signers.map((signer, i) => invokeSigner(signer, schemes[i], {
3303
+ hash: userOpHash,
3304
+ typedData,
3305
+ context
3306
+ })))).map((signature, i) => ({
3307
+ signer: normalizedAddresses[i],
3308
+ signature
3309
+ }));
3310
+ return SafeAccount.formatSignaturesToUseroperationSignature(signerSignaturePairs, {
3311
+ validAfter,
3312
+ validUntil,
3313
+ isMultiChainSignature: overrides.isMultiChainSignature
3314
+ });
3315
+ }
3316
+ /**
3161
3317
  * compute the deterministic address for a webauthn proxy verifier based on a
3162
3318
  * webauthn public key(x, y)
3163
3319
  * @param x - webauthn public key x parameter
@@ -3804,7 +3960,7 @@ var SafeAccount = class SafeAccount extends SmartAccount {
3804
3960
  * @param toolVersion - tool version, defaults to current abstractionkit version
3805
3961
  * @returns the onchain idenetifier as a hex string (not 0x prefixed)
3806
3962
  */
3807
- function generateOnChainIdentifier(project, platform = "Web", tool = "abstractionkit", toolVersion = "0.3.0") {
3963
+ function generateOnChainIdentifier(project, platform = "Web", tool = "abstractionkit", toolVersion = "0.3.2") {
3808
3964
  const identifierPrefix = "5afe";
3809
3965
  const identifierVersion = "00";
3810
3966
  const projectHash = (0, ethers.keccak256)("0x" + Buffer.from(project, "utf8").toString("hex")).slice(-20);
@@ -3881,6 +4037,17 @@ const DEFAULT_WEB_AUTHN_DAIMO_VERIFIER_V_0_2_1 = "0xc2b78104907F722DABAc4C69f826
3881
4037
  * with the Daimo P256 verifier instead of the FCL P256 verifier
3882
4038
  * used by the base SafeAccount class.
3883
4039
  * @see {@link https://github.com/safe-fndn/safe-modules/blob/04e65efbce634e776cc8c1fbe90061f09e09a71b/modules/passkey/CHANGELOG.md?plain=1#L23}
4040
+ *
4041
+ * @remarks Signer typing on this class is asymmetric:
4042
+ * - {@link signUserOperationWithSigners} (singular Operation) signs one op
4043
+ * and uses {@link SignContext} like every other account.
4044
+ * - {@link signUserOperationsWithSigners} (plural Operations) signs a bundle
4045
+ * under one signature and uses {@link MultiOpSignContext}.
4046
+ *
4047
+ * To author one signer that works on both methods, type it as
4048
+ * `ExternalSigner<unknown>` (the shape returned by the built-in adapters).
4049
+ * The two narrow context types exist so signers that DO read the context
4050
+ * get accurate, non-optional fields per path.
3884
4051
  */
3885
4052
  var SafeMultiChainSigAccountV1 = class SafeMultiChainSigAccountV1 extends SafeAccount {
3886
4053
  static DEFAULT_ENTRYPOINT_ADDRESS = ENTRYPOINT_V9;
@@ -4099,6 +4266,23 @@ var SafeMultiChainSigAccountV1 = class SafeMultiChainSigAccountV1 extends SafeAc
4099
4266
  });
4100
4267
  }
4101
4268
  /**
4269
+ * Sign a single UserOperation for multi-chain using one or more
4270
+ * {@link AkSigner} instances. See
4271
+ * {@link SafeAccountV0_3_0.signUserOperationWithSigners} for the full
4272
+ * design rationale. Sets the multi-chain flag automatically.
4273
+ */
4274
+ signUserOperationWithSigners(userOperation, signers, chainId, overrides = {}) {
4275
+ const context = {
4276
+ userOperation,
4277
+ chainId,
4278
+ entryPoint: this.entrypointAddress
4279
+ };
4280
+ return SafeAccount.baseSignUserOperationWithSigners(userOperation, signers, chainId, this.entrypointAddress, this.safe4337ModuleAddress, context, {
4281
+ ...overrides,
4282
+ isMultiChainSignature: true
4283
+ });
4284
+ }
4285
+ /**
4102
4286
  * sign a list of useroperations - multi chain signature
4103
4287
  * @param useroperation - useroperation to sign
4104
4288
  * @param privateKeys - for the signers
@@ -4149,6 +4333,86 @@ var SafeMultiChainSigAccountV1 = class SafeMultiChainSigAccountV1 extends SafeAc
4149
4333
  })];
4150
4334
  }
4151
4335
  /**
4336
+ * Sign a list of UserOperations with a single multi-chain signature,
4337
+ * using {@link AkSigner} instances typed for {@link MultiOpSignContext}
4338
+ * (viem, ethers, hardware wallet, HSM, MPC, Uint8Array-only). Each
4339
+ * signer signs the Merkle root of the UserOperation EIP-712 hashes via
4340
+ * raw-hash signing. `signTypedData` isn't exposed here because the
4341
+ * Merkle root is opaque and has no meaningful typed-data display.
4342
+ *
4343
+ * Signers always receive {@link MultiOpSignContext} regardless of bundle
4344
+ * length, so multi-op-typed signers can rely on `ctx.userOperations`
4345
+ * being defined. Pre-built adapters `fromPrivateKey`, `fromViem`, and
4346
+ * `fromEthersWallet` return a universal `Signer<unknown>` and work
4347
+ * here without retyping; user-defined single-op signers
4348
+ * (`Signer<SignContext>`) do not — they would receive a context shape
4349
+ * they didn't declare. `fromViemWalletClient` is **not** usable on the
4350
+ * multi-op Merkle path: it only exposes `signTypedData`, and the
4351
+ * Merkle root has no meaningful typed-data display. {@link pickScheme}
4352
+ * rejects it offline with an actionable error.
4353
+ *
4354
+ * @param userOperationsToSign - UserOperations + chain IDs + validity windows
4355
+ * @param signers - one Signer per owner (any order; sorted by address on-chain)
4356
+ * @returns one signature per input UserOperation, in the same order
4357
+ */
4358
+ async signUserOperationsWithSigners(userOperationsToSign, signers) {
4359
+ if (userOperationsToSign.length < 1) throw new RangeError("There should be at least one userOperationsToSign");
4360
+ if (signers.length < 1) throw new RangeError("There should be at least one signer");
4361
+ const context = {
4362
+ userOperations: userOperationsToSign.map((u) => ({
4363
+ userOperation: u.userOperation,
4364
+ chainId: u.chainId
4365
+ })),
4366
+ entryPoint: this.entrypointAddress
4367
+ };
4368
+ if (userOperationsToSign.length > 1) {
4369
+ const userOperationsHashes = [];
4370
+ userOperationsToSign.forEach((uopToSign) => {
4371
+ const userOperationHash = SafeAccount.getUserOperationEip712Hash_V9(uopToSign.userOperation, uopToSign.chainId, {
4372
+ validAfter: uopToSign.validAfter,
4373
+ validUntil: uopToSign.validUntil,
4374
+ safe4337ModuleAddress: this.safe4337ModuleAddress,
4375
+ entrypointAddress: this.entrypointAddress
4376
+ });
4377
+ userOperationsHashes.push(userOperationHash);
4378
+ });
4379
+ const [root, proofs] = generateMerkleProofs(userOperationsHashes);
4380
+ const merkleTreeRootHash = ethers.TypedDataEncoder.hash({ verifyingContract: this.safe4337ModuleAddress }, EIP712_MULTI_CHAIN_OPERATIONS_TYPE, { merkleTreeRoot: root });
4381
+ const normalizedAddresses = signers.map((signer) => (0, ethers.getAddress)(signer.address));
4382
+ signers.forEach((signer, i) => {
4383
+ pickScheme(signer, ["hash"], {
4384
+ accountName: "SafeMultiChainSigAccountV1 (multi-op Merkle root)",
4385
+ signerIndex: i
4386
+ });
4387
+ });
4388
+ const signatures = await Promise.all(signers.map((signer) => invokeSigner(signer, "hash", {
4389
+ hash: merkleTreeRootHash,
4390
+ context
4391
+ })));
4392
+ const signerSignaturePairs = signers.map((_signer, i) => ({
4393
+ signer: normalizedAddresses[i],
4394
+ signature: signatures[i]
4395
+ }));
4396
+ const userOpSignatures = [];
4397
+ userOperationsToSign.forEach((uopToSign, index) => {
4398
+ userOpSignatures.push(SafeAccount.formatSignaturesToUseroperationSignature(signerSignaturePairs, {
4399
+ validAfter: uopToSign.validAfter,
4400
+ validUntil: uopToSign.validUntil,
4401
+ isMultiChainSignature: true,
4402
+ multiChainMerkleProof: proofs[index]
4403
+ }));
4404
+ });
4405
+ return userOpSignatures;
4406
+ } else {
4407
+ const u = userOperationsToSign[0];
4408
+ return [await SafeAccount.baseSignUserOperationWithSigners(u.userOperation, signers, u.chainId, this.entrypointAddress, this.safe4337ModuleAddress, context, {
4409
+ validAfter: u.validAfter,
4410
+ validUntil: u.validUntil,
4411
+ isMultiChainSignature: true
4412
+ })];
4413
+ }
4414
+ }
4415
+ /**
4152
4416
  * Compute the EIP-712 hash of a multi-chain Merkle tree root for a set of UserOperations.
4153
4417
  * This hash is what signers sign to approve multiple cross-chain operations at once.
4154
4418
  * @param userOperationsToSignsToSign - list of UserOperations with their target chain IDs
@@ -4192,36 +4456,39 @@ var SafeMultiChainSigAccountV1 = class SafeMultiChainSigAccountV1 extends SafeAc
4192
4456
  * @param overrides - overrides for the default values
4193
4457
  * @returns signature
4194
4458
  */
4195
- static formatSignaturesToUseroperationsSignatures(userOperationsToSign, signerSignaturePairs, overrides = {}) {
4459
+ static formatSignaturesToUseroperationsSignatures(userOperationsToSign, signerSignaturePairs) {
4196
4460
  if (userOperationsToSign.length < 1) throw new RangeError("There should be at least one userOperationsToSign");
4197
- const resolvedOverrides = {
4461
+ const defaultOverrides = {
4198
4462
  eip7212WebAuthnPrecompileVerifier: SafeMultiChainSigAccountV1.DEFAULT_WEB_AUTHN_PRECOMPILE,
4199
4463
  eip7212WebAuthnContractVerifier: SafeMultiChainSigAccountV1.DEFAULT_WEB_AUTHN_DAIMO_VERIFIER,
4200
4464
  webAuthnSignerFactory: SafeMultiChainSigAccountV1.DEFAULT_WEB_AUTHN_SIGNER_FACTORY,
4201
4465
  webAuthnSignerSingleton: SafeMultiChainSigAccountV1.DEFAULT_WEB_AUTHN_SIGNER_SINGLETON,
4202
4466
  webAuthnSignerProxyCreationCode: SafeMultiChainSigAccountV1.DEFAULT_WEB_AUTHN_SIGNER_PROXY_CREATION_CODE,
4203
4467
  safe4337ModuleAddress: SafeMultiChainSigAccountV1.DEFAULT_SAFE_4337_MODULE_ADDRESS,
4204
- webAuthnSharedSigner: SafeMultiChainSigAccountV1.DEFAULT_WEB_AUTHN_SHARED_SIGNER,
4205
- ...overrides
4468
+ webAuthnSharedSigner: SafeMultiChainSigAccountV1.DEFAULT_WEB_AUTHN_SHARED_SIGNER
4206
4469
  };
4207
4470
  if (userOperationsToSign.length === 1) return [SafeAccount.formatSignaturesToUseroperationSignature(signerSignaturePairs, {
4208
- ...resolvedOverrides,
4471
+ ...defaultOverrides,
4472
+ ...userOperationsToSign[0].overrides,
4473
+ validAfter: userOperationsToSign[0].validAfter,
4474
+ validUntil: userOperationsToSign[0].validUntil,
4209
4475
  isMultiChainSignature: true
4210
4476
  })];
4211
4477
  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
4478
+ userOperationsToSign.forEach((userOperationToSign, _index) => {
4479
+ const userOperationHash = SafeAccount.getUserOperationEip712Hash_V9(userOperationToSign.userOperation, userOperationToSign.chainId, {
4480
+ validAfter: userOperationToSign.validAfter,
4481
+ validUntil: userOperationToSign.validUntil,
4482
+ safe4337ModuleAddress: userOperationToSign.overrides?.safe4337ModuleAddress ?? defaultOverrides.safe4337ModuleAddress
4217
4483
  });
4218
4484
  userOperationsHashes.push(userOperationHash);
4219
4485
  });
4220
4486
  const [_root, proofs] = generateMerkleProofs(userOperationsHashes);
4221
4487
  const userOpSignatures = [];
4222
- userOperationsToSign.forEach((_userOperationsToSign, index) => {
4488
+ userOperationsToSign.forEach((userOperationToSign, index) => {
4223
4489
  userOpSignatures.push(SafeAccount.formatSignaturesToUseroperationSignature(signerSignaturePairs, {
4224
- ...resolvedOverrides,
4490
+ ...defaultOverrides,
4491
+ ...userOperationToSign.overrides,
4225
4492
  isMultiChainSignature: true,
4226
4493
  multiChainMerkleProof: proofs[index]
4227
4494
  }));
@@ -4622,34 +4889,42 @@ var Calibur7702Account = class Calibur7702Account extends SmartAccount {
4622
4889
  return Calibur7702Account.wrapSignature(keyHash, ecdsaSig, hookData);
4623
4890
  }
4624
4891
  /**
4625
- * Sign a UserOperation with an external signer (viem, ethers Signer,
4626
- * hardware wallet, MPC signer, etc.).
4627
- * Computes the UserOperation hash and wraps the returned signature in
4628
- * Calibur's format: `abi.encode(keyHash, ecdsaSig, hookData)`.
4892
+ * Schemes Calibur accepts from a Signer. Only raw-hash ECDSA, since
4893
+ * the account verifies a plain signature over the userOp hash, then
4894
+ * wraps with `(keyHash, signature, hookData)`.
4895
+ */
4896
+ static ACCEPTED_SIGNING_SCHEMES = ["hash"];
4897
+ /**
4898
+ * Sign a UserOperation using an {@link ExternalSigner}. Calibur only
4899
+ * accepts raw-hash ECDSA; signers without `signHash` fail offline with
4900
+ * an actionable error.
4629
4901
  *
4630
- * By default signs with the root key. To sign with a registered
4631
- * secondary key, pass its key hash via `overrides.keyHash`.
4632
- *
4633
- * @param userOperation - The UserOperation to sign
4634
- * @param signer - Async signing function: `(hash: string) => Promise<string>`
4635
- * @param chainId - Target chain ID
4636
- * @param overrides - Optional overrides (keyHash for secondary keys, hookData)
4637
- * @returns Promise resolving to the hex-encoded wrapped signature
4902
+ * For signing with a raw private-key string, use the sync
4903
+ * {@link signUserOperation} method, or wrap explicitly with
4904
+ * `fromPrivateKey(pk)`. For secondary (non-root) keys, pass the key
4905
+ * hash via `overrides.keyHash`.
4638
4906
  *
4639
4907
  * @example
4640
- * // Sign with a viem wallet client
4908
+ * import { fromViem, fromEthersWallet } from "abstractionkit";
4641
4909
  * userOp.signature = await account.signUserOperationWithSigner(
4642
- * userOp,
4643
- * (hash) => walletClient.signMessage({ message: { raw: hash } }),
4644
- * chainId,
4910
+ * userOp, fromViem(privateKeyToAccount(pk)), chainId,
4645
4911
  * );
4646
4912
  */
4647
4913
  async signUserOperationWithSigner(userOperation, signer, chainId, overrides = {}) {
4648
- const userOperationHash = createUserOperationHash(userOperation, this.entrypointAddress, chainId);
4914
+ const signature = await invokeSigner(signer, pickScheme(signer, Calibur7702Account.ACCEPTED_SIGNING_SCHEMES, {
4915
+ accountName: "Calibur (raw ECDSA over userOpHash)",
4916
+ signerIndex: 0
4917
+ }), {
4918
+ hash: createUserOperationHash(userOperation, this.entrypointAddress, chainId),
4919
+ context: {
4920
+ userOperation,
4921
+ chainId,
4922
+ entryPoint: this.entrypointAddress
4923
+ }
4924
+ });
4649
4925
  const keyHash = overrides.keyHash ?? ROOT_KEY_HASH;
4650
4926
  const hookData = overrides.hookData ?? "0x";
4651
- const ecdsaSig = await signer(userOperationHash);
4652
- return Calibur7702Account.wrapSignature(keyHash, ecdsaSig, hookData);
4927
+ return Calibur7702Account.wrapSignature(keyHash, signature, hookData);
4653
4928
  }
4654
4929
  /**
4655
4930
  * Format a WebAuthn (passkey) assertion into Calibur's signature format.
@@ -5120,6 +5395,88 @@ var Calibur7702Account = class Calibur7702Account extends SmartAccount {
5120
5395
  }
5121
5396
  };
5122
5397
  //#endregion
5398
+ //#region src/signer/adapters.ts
5399
+ /**
5400
+ * Build a Signer from a raw private key. Uses the library's existing
5401
+ * ethers dependency internally, so no additional packages are required on
5402
+ * the caller side. Supports both raw-hash and typed-data signing.
5403
+ *
5404
+ * Prefer this when all you have is a private key (test suites, server-side
5405
+ * scripts, scripts with env-injected keys, etc.). If you already hold a
5406
+ * viem Account or ethers Wallet from elsewhere in your app, pass it to
5407
+ * {@link fromViem} or {@link fromEthersWallet} instead.
5408
+ *
5409
+ * @example
5410
+ * import { fromPrivateKey } from "abstractionkit";
5411
+ * const signer = fromPrivateKey(process.env.PRIVATE_KEY!);
5412
+ * userOp.signature = await safe.signUserOperationWithSigners(userOp, [signer], chainId);
5413
+ */
5414
+ function fromPrivateKey(privateKey) {
5415
+ const wallet = new ethers.Wallet(privateKey);
5416
+ return {
5417
+ address: (0, ethers.getAddress)(wallet.address),
5418
+ signHash: async (hash) => wallet.signingKey.sign(hash).serialized,
5419
+ signTypedData: async (td) => await wallet.signTypedData(td.domain, td.types, td.message)
5420
+ };
5421
+ }
5422
+ /**
5423
+ * Adapt a viem Local Account (e.g. `privateKeyToAccount(pk)`) to a Signer.
5424
+ * Supports both raw-hash and typed-data signing.
5425
+ *
5426
+ * @remarks Requires viem &gt;= 2.0.
5427
+ */
5428
+ function fromViem(account) {
5429
+ return {
5430
+ address: account.address,
5431
+ signHash: (hash) => account.sign({ hash }),
5432
+ signTypedData: (td) => account.signTypedData({
5433
+ domain: td.domain,
5434
+ types: td.types,
5435
+ primaryType: td.primaryType,
5436
+ message: td.message
5437
+ })
5438
+ };
5439
+ }
5440
+ /**
5441
+ * Adapt a viem WalletClient to a Signer. WalletClient is the client-style
5442
+ * API dApps use to drive browser / JSON-RPC wallets, so only typed-data
5443
+ * signing is exposed (JSON-RPC wallets can't sign raw hashes).
5444
+ *
5445
+ * Requires the client to have been constructed with an `account` (local or
5446
+ * JSON-RPC). For local accounts, pass that directly to `fromViem` instead
5447
+ * if you want raw-hash fallback.
5448
+ *
5449
+ * @remarks Requires viem &gt;= 2.0.
5450
+ */
5451
+ function fromViemWalletClient(client) {
5452
+ if (!client.account) throw new Error("fromViemWalletClient: client has no `account` configured. Construct with `createWalletClient({ account, transport, chain })`.");
5453
+ const account = client.account;
5454
+ const signTypedData = client.signTypedData;
5455
+ return {
5456
+ address: account.address,
5457
+ signTypedData: (td) => signTypedData({
5458
+ account,
5459
+ domain: td.domain,
5460
+ types: td.types,
5461
+ primaryType: td.primaryType,
5462
+ message: td.message
5463
+ })
5464
+ };
5465
+ }
5466
+ /**
5467
+ * Adapt an ethers `Wallet` / `HDNodeWallet` to a Signer. Supports both
5468
+ * raw-hash and typed-data signing.
5469
+ *
5470
+ * @remarks Requires ethers &gt;= 6.0.
5471
+ */
5472
+ function fromEthersWallet(wallet) {
5473
+ return {
5474
+ address: wallet.address,
5475
+ signHash: async (hash) => wallet.signingKey.sign(hash).serialized,
5476
+ signTypedData: async (td) => await wallet.signTypedData(td.domain, td.types, td.message)
5477
+ };
5478
+ }
5479
+ //#endregion
5123
5480
  //#region src/account/Safe/modules/SafeModule.ts
5124
5481
  /**
5125
5482
  * Abstract base class for Safe modules. Provides shared utilities for
@@ -6123,6 +6480,45 @@ var SafeAccountV0_3_0 = class SafeAccountV0_3_0 extends SafeAccount {
6123
6480
  signUserOperation(useroperation, privateKeys, chainId, overrides = {}) {
6124
6481
  return SafeAccount.baseSignSingleUserOperation(useroperation, privateKeys, chainId, this.entrypointAddress, this.safe4337ModuleAddress, overrides);
6125
6482
  }
6483
+ /**
6484
+ * Sign a UserOperation using one or more {@link ExternalSigner} instances.
6485
+ * Capability-oriented entry point: each Signer declares what it can do
6486
+ * (`signHash`, `signTypedData`, both) and the account picks the best
6487
+ * match per signer. Incompatible signers fail offline with an actionable
6488
+ * error — no silent bundler rejections.
6489
+ *
6490
+ * This method is for external signers only (viem, ethers, hardware
6491
+ * wallets, MPC, HSMs, Uint8Array-only signers). If you just have a raw
6492
+ * private-key string, use the sync {@link signUserOperation} method
6493
+ * instead, or wrap explicitly with `fromPrivateKey(pk)`.
6494
+ *
6495
+ * Prebuilt adapters: `fromViem`, `fromEthersWallet`,
6496
+ * `fromViemWalletClient`, `fromPrivateKey`. Custom signers just need to
6497
+ * match the `ExternalSigner` shape.
6498
+ *
6499
+ * @example
6500
+ * import { fromViem } from "abstractionkit";
6501
+ * import { privateKeyToAccount } from "viem/accounts";
6502
+ *
6503
+ * const signer = fromViem(privateKeyToAccount(pk));
6504
+ * userOp.signature = await account.signUserOperationWithSigners(
6505
+ * userOp, [signer], chainId,
6506
+ * );
6507
+ *
6508
+ * @param useroperation - UserOperation to sign
6509
+ * @param signers - one ExternalSigner per owner (any order)
6510
+ * @param chainId - target chain ID
6511
+ * @param overrides - optional validAfter / validUntil / multi-chain flag
6512
+ * @returns Promise resolving to the formatted signature string
6513
+ */
6514
+ signUserOperationWithSigners(useroperation, signers, chainId, overrides = {}) {
6515
+ const context = {
6516
+ userOperation: useroperation,
6517
+ chainId,
6518
+ entryPoint: this.entrypointAddress
6519
+ };
6520
+ return SafeAccount.baseSignUserOperationWithSigners(useroperation, signers, chainId, this.entrypointAddress, this.safe4337ModuleAddress, context, overrides);
6521
+ }
6126
6522
  };
6127
6523
  //#endregion
6128
6524
  //#region src/account/Safe/SafeAccountV0_2_0.ts
@@ -6365,6 +6761,19 @@ var SafeAccountV0_2_0 = class SafeAccountV0_2_0 extends SafeAccount {
6365
6761
  signUserOperation(useroperation, privateKeys, chainId, overrides = {}) {
6366
6762
  return SafeAccount.baseSignSingleUserOperation(useroperation, privateKeys, chainId, this.entrypointAddress, this.safe4337ModuleAddress, overrides);
6367
6763
  }
6764
+ /**
6765
+ * Sign a UserOperation using one or more {@link AkSigner} instances.
6766
+ * See {@link SafeAccountV0_3_0.signUserOperationWithSigners} for full
6767
+ * design rationale and examples.
6768
+ */
6769
+ signUserOperationWithSigners(useroperation, signers, chainId, overrides = {}) {
6770
+ const context = {
6771
+ userOperation: useroperation,
6772
+ chainId,
6773
+ entryPoint: this.entrypointAddress
6774
+ };
6775
+ return SafeAccount.baseSignUserOperationWithSigners(useroperation, signers, chainId, this.entrypointAddress, this.safe4337ModuleAddress, context, overrides);
6776
+ }
6368
6777
  };
6369
6778
  //#endregion
6370
6779
  //#region src/account/Safe/SafeAccountV1_5_0_M_0_3_0.ts
@@ -6523,9 +6932,9 @@ var Paymaster = class {};
6523
6932
  /** Buffer added to verificationGasLimit for paymasterAndData verification overhead */
6524
6933
  const PAYMASTER_V06_VERIFICATION_OVERHEAD = 40000n;
6525
6934
  /** Max value for uint256 */
6526
- const UINT256_MAX = 115792089237316195423570985008687907853269984665640564039457584007913129639935n;
6935
+ const UINT256_MAX$1 = 115792089237316195423570985008687907853269984665640564039457584007913129639935n;
6527
6936
  /** Multiplier for token approve amount to cover paymasterAndData cost variance */
6528
- const TOKEN_APPROVE_AMOUNT_MULTIPLIER = 2n;
6937
+ const TOKEN_APPROVE_AMOUNT_MULTIPLIER$1 = 2n;
6529
6938
  /**
6530
6939
  * ERC-20 tokens that require resetting their allowance to 0 before setting a
6531
6940
  * new approval amount (e.g. USDT on mainnet).
@@ -6535,7 +6944,7 @@ const TOKEN_APPROVE_AMOUNT_MULTIPLIER = 2n;
6535
6944
  * please open an issue at https://github.com/candidelabs/abstractionkit/issues
6536
6945
  * or use the `resetApproval` override as a workaround.
6537
6946
  */
6538
- const TOKENS_REQUIRING_ALLOWANCE_RESET = ["0xdac17f958d2ee523a2206206994597c13d831ec7"];
6947
+ const TOKENS_REQUIRING_ALLOWANCE_RESET$1 = ["0xdac17f958d2ee523a2206206994597c13d831ec7"];
6539
6948
  /**
6540
6949
  * Client for the Candide Paymaster service.
6541
6950
  * Supports both gas sponsorship (sponsor paymaster) and ERC-20 token payment for gas (token paymaster).
@@ -6875,12 +7284,12 @@ var CandidePaymaster = class CandidePaymaster extends Paymaster {
6875
7284
  if (epData == null) throw new RangeError(`UserOperation for entrypoint ${entrypoint} is not supported`);
6876
7285
  this.setDummyPaymasterFields(userOp, epData);
6877
7286
  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);
7287
+ const requiresAllowanceReset = overrides?.resetApproval ?? TOKENS_REQUIRING_ALLOWANCE_RESET$1.includes(context.token.toLowerCase());
7288
+ let callDataWithApprove = smartAccount.prependTokenPaymasterApproveToCallData(userOp.callData, context.token, epData.paymasterMetadata.address, UINT256_MAX$1);
6880
7289
  if (requiresAllowanceReset) callDataWithApprove = smartAccount.prependTokenPaymasterApproveToCallData(callDataWithApprove, context.token, epData.paymasterMetadata.address, 0n);
6881
7290
  userOp.callData = callDataWithApprove;
6882
7291
  await this.estimateAndApplyGasLimits(userOp, bundlerRpc, entrypoint, overrides ?? {});
6883
- const approveAmount = await this.calculateUserOperationErc20TokenMaxGasCost(smartAccount, userOp, context.token) * TOKEN_APPROVE_AMOUNT_MULTIPLIER;
7292
+ const approveAmount = await this.calculateUserOperationErc20TokenMaxGasCost(smartAccount, userOp, context.token) * TOKEN_APPROVE_AMOUNT_MULTIPLIER$1;
6884
7293
  callDataWithApprove = smartAccount.prependTokenPaymasterApproveToCallData(oldCallData, context.token, epData.paymasterMetadata.address, approveAmount);
6885
7294
  if (requiresAllowanceReset) callDataWithApprove = smartAccount.prependTokenPaymasterApproveToCallData(callDataWithApprove, context.token, epData.paymasterMetadata.address, 0n);
6886
7295
  userOp.callData = callDataWithApprove;
@@ -6960,6 +7369,396 @@ var CandidePaymaster = class CandidePaymaster extends Paymaster {
6960
7369
  }
6961
7370
  };
6962
7371
  //#endregion
7372
+ //#region src/paymaster/Erc7677Paymaster.ts
7373
+ /** Max value for uint256 */
7374
+ const UINT256_MAX = 115792089237316195423570985008687907853269984665640564039457584007913129639935n;
7375
+ /** Multiplier for token approve amount to cover paymasterAndData cost variance */
7376
+ const TOKEN_APPROVE_AMOUNT_MULTIPLIER = 2n;
7377
+ /**
7378
+ * ERC-20 tokens that require resetting their allowance to 0 before setting a
7379
+ * new approval amount (e.g. USDT on mainnet).
7380
+ */
7381
+ const TOKENS_REQUIRING_ALLOWANCE_RESET = ["0xdac17f958d2ee523a2206206994597c13d831ec7"];
7382
+ /**
7383
+ * Time-to-live for cached Candide `pm_supportedERC20Tokens` responses, applied
7384
+ * only when the fetch is initiated for an exchange-rate lookup. Stub-data
7385
+ * lookups (paymaster address + dummyPaymasterAndData) reuse the cache
7386
+ * indefinitely since those fields are effectively static per EP.
7387
+ */
7388
+ const CANDIDE_TOKEN_QUOTE_TTL_MS = 45e3;
7389
+ var Erc7677Paymaster = class Erc7677Paymaster extends Paymaster {
7390
+ /** The paymaster JSON-RPC endpoint URL */
7391
+ rpcUrl;
7392
+ /** Cached chain ID (hex string). Passed via constructor or resolved from the bundler at first use. */
7393
+ chainId;
7394
+ /** Detected or explicitly set paymaster provider. `null` means no provider-specific features. */
7395
+ provider;
7396
+ /**
7397
+ * Cached Candide `pm_supportedERC20Tokens` response, keyed by lowercase
7398
+ * entrypoint. Used for both token quotes and stub data to avoid a second
7399
+ * round-trip (`pm_getPaymasterStubData`) for Candide-hosted paymasters.
7400
+ *
7401
+ * The cache is indefinite for stub-data lookups but has a TTL for
7402
+ * exchange-rate lookups — see {@link CANDIDE_TOKEN_QUOTE_TTL_MS}.
7403
+ */
7404
+ candideCache = /* @__PURE__ */ new Map();
7405
+ /**
7406
+ * Detect the paymaster provider from the RPC URL hostname.
7407
+ * Returns `null` for unknown hosts or malformed URLs.
7408
+ *
7409
+ * Hostname-based (not substring) so that proxies or paths containing a
7410
+ * provider name (e.g. `https://my-proxy.com/pimlico-compat/...`) are not
7411
+ * misidentified.
7412
+ */
7413
+ static detectProvider(rpcUrl) {
7414
+ let host;
7415
+ try {
7416
+ host = new URL(rpcUrl).hostname.toLowerCase();
7417
+ } catch {
7418
+ return null;
7419
+ }
7420
+ if (host === "pimlico.io" || host.endsWith(".pimlico.io")) return "pimlico";
7421
+ if (host === "candide.dev" || host.endsWith(".candide.dev")) return "candide";
7422
+ return null;
7423
+ }
7424
+ /**
7425
+ * @param rpcUrl - Paymaster JSON-RPC endpoint. Can be the same URL as the
7426
+ * bundler when the provider bundles both (Candide, Pimlico, Alchemy);
7427
+ * can also be a separate paymaster-only endpoint.
7428
+ * @param options
7429
+ * @param options.chainId - Optional chain id as a bigint (e.g. `1n` for
7430
+ * mainnet). When provided, avoids a lookup at first use. Otherwise,
7431
+ * resolved from the bundler via `eth_chainId` on the first call.
7432
+ * @param options.provider - Paymaster provider. `"auto"` (default) detects
7433
+ * from the RPC URL. Set explicitly to override, or `null` to disable.
7434
+ */
7435
+ constructor(rpcUrl, options = {}) {
7436
+ super();
7437
+ this.rpcUrl = rpcUrl;
7438
+ this.chainId = options.chainId != null ? "0x" + options.chainId.toString(16) : null;
7439
+ if (options.provider === void 0 || options.provider === "auto") this.provider = Erc7677Paymaster.detectProvider(rpcUrl);
7440
+ else this.provider = options.provider;
7441
+ }
7442
+ /**
7443
+ * Resolve the chain id, querying the bundler if not provided at construction.
7444
+ */
7445
+ async getChainId(bundlerRpc) {
7446
+ if (this.chainId != null) return this.chainId;
7447
+ const id = await new Bundler(bundlerRpc).chainId();
7448
+ this.chainId = id;
7449
+ return id;
7450
+ }
7451
+ /**
7452
+ * Determine the EntryPoint address from the UserOperation shape.
7453
+ * V6 ops have `initCode`, V8+ ops have `eip7702Auth`, V7 is the default.
7454
+ */
7455
+ resolveEntrypoint(smartAccount, userOperation) {
7456
+ if (smartAccount.entrypointAddress != null && smartAccount.entrypointAddress.trim() !== "") return smartAccount.entrypointAddress;
7457
+ if ("initCode" in userOperation) return ENTRYPOINT_V6;
7458
+ if ("eip7702Auth" in userOperation) return ENTRYPOINT_V8;
7459
+ return ENTRYPOINT_V7;
7460
+ }
7461
+ /**
7462
+ * Low-level ERC-7677 `pm_getPaymasterStubData` call.
7463
+ * Returns dummy paymaster fields intended for gas estimation.
7464
+ *
7465
+ * Most consumers should prefer {@link createPaymasterUserOperation}, which
7466
+ * runs the full stub → estimate → final pipeline. Use this directly if you
7467
+ * need to drive the flow manually.
7468
+ */
7469
+ async getPaymasterStubData(userOperation, entrypoint, chainIdHex, context = {}) {
7470
+ try {
7471
+ return await sendJsonRpcRequest(this.rpcUrl, "pm_getPaymasterStubData", [
7472
+ userOperation,
7473
+ entrypoint,
7474
+ chainIdHex,
7475
+ context
7476
+ ]);
7477
+ } catch (err) {
7478
+ throw new AbstractionKitError("PAYMASTER_ERROR", "pm_getPaymasterStubData failed", { cause: ensureError(err) });
7479
+ }
7480
+ }
7481
+ /**
7482
+ * Low-level ERC-7677 `pm_getPaymasterData` call.
7483
+ * Returns the final signed paymaster fields.
7484
+ */
7485
+ async getPaymasterData(userOperation, entrypoint, chainIdHex, context = {}) {
7486
+ try {
7487
+ return await sendJsonRpcRequest(this.rpcUrl, "pm_getPaymasterData", [
7488
+ userOperation,
7489
+ entrypoint,
7490
+ chainIdHex,
7491
+ context
7492
+ ]);
7493
+ } catch (err) {
7494
+ throw new AbstractionKitError("PAYMASTER_ERROR", "pm_getPaymasterData failed", { cause: ensureError(err) });
7495
+ }
7496
+ }
7497
+ /**
7498
+ * Send an arbitrary JSON-RPC request through the paymaster endpoint.
7499
+ * Useful for provider-specific methods that fall outside the ERC-7677 spec.
7500
+ *
7501
+ * @param method - The JSON-RPC method name
7502
+ * @param params - The JSON-RPC params array
7503
+ * @returns The `result` field from the JSON-RPC response
7504
+ */
7505
+ async sendRPCRequest(method, params = []) {
7506
+ try {
7507
+ return await sendJsonRpcRequest(this.rpcUrl, method, params);
7508
+ } catch (err) {
7509
+ throw new AbstractionKitError("PAYMASTER_ERROR", `sendRPCRequest(${method}) failed`, { cause: ensureError(err) });
7510
+ }
7511
+ }
7512
+ /**
7513
+ * Runs the full ERC-7677 pipeline and returns a UserOperation with paymaster
7514
+ * fields populated. The caller is responsible for signing and sending.
7515
+ *
7516
+ * @param smartAccount - Provides the target EntryPoint; not mutated.
7517
+ * @param userOperation - Starting UserOperation. Not mutated — a shallow copy is returned.
7518
+ * @param bundlerRpc - Bundler URL used for gas estimation and, if
7519
+ * `options.chainId` was not provided to the constructor, chain-id lookup.
7520
+ * @param context - Provider-specific paymaster context
7521
+ * (e.g. `{ sponsorshipPolicyId }` or `{ token }`).
7522
+ * @param overrides - Gas estimation overrides and state-override set.
7523
+ *
7524
+ * @returns The UserOperation with paymaster + gas fields populated.
7525
+ */
7526
+ async createPaymasterUserOperation(smartAccount, userOperation, bundlerRpc, context = {}, overrides = {}) {
7527
+ try {
7528
+ const userOp = { ...userOperation };
7529
+ const entrypoint = overrides.entrypoint ?? this.resolveEntrypoint(smartAccount, userOp);
7530
+ const chainIdHex = await this.getChainId(bundlerRpc);
7531
+ if (context.token != null && typeof context.token === "string") return this.tokenPaymasterFlow(smartAccount, userOp, context.token, bundlerRpc, entrypoint, chainIdHex, context, overrides);
7532
+ return this.sponsoredFlow(userOp, bundlerRpc, entrypoint, chainIdHex, context, overrides);
7533
+ } catch (err) {
7534
+ const error = ensureError(err);
7535
+ if (error instanceof AbstractionKitError) throw error;
7536
+ throw new AbstractionKitError("PAYMASTER_ERROR", "createPaymasterUserOperation failed", { cause: error });
7537
+ }
7538
+ }
7539
+ /**
7540
+ * Merge paymaster fields into a UserOperation. Handles both v0.6
7541
+ * (`paymasterAndData`) and v0.7+ split fields.
7542
+ */
7543
+ applyPaymasterFields(userOp, fields) {
7544
+ if ("initCode" in userOp) {
7545
+ if (fields.paymasterAndData != null) userOp.paymasterAndData = fields.paymasterAndData;
7546
+ return;
7547
+ }
7548
+ if (fields.paymaster != null) userOp.paymaster = fields.paymaster;
7549
+ if (fields.paymasterData != null) userOp.paymasterData = fields.paymasterData;
7550
+ if (fields.paymasterVerificationGasLimit != null) userOp.paymasterVerificationGasLimit = BigInt(fields.paymasterVerificationGasLimit);
7551
+ if (fields.paymasterPostOpGasLimit != null) userOp.paymasterPostOpGasLimit = BigInt(fields.paymasterPostOpGasLimit);
7552
+ }
7553
+ /**
7554
+ * Estimate gas limits via the bundler and apply them (with multipliers).
7555
+ * Reads paymaster gas fields back from the bundler when present — some
7556
+ * providers' `pm_getPaymasterStubData` returns `paymasterPostOpGasLimit: 0x1`
7557
+ * as a placeholder, relying on the bundler's estimate for the real value.
7558
+ *
7559
+ * Mirrors CandidePaymaster.estimateAndApplyGasLimits default multipliers
7560
+ * (5%/10%/10% on preVerification/verification/call) for consistent UX.
7561
+ */
7562
+ async estimateAndApplyGasLimits(userOp, bundlerRpc, entrypoint, overrides) {
7563
+ let preVerificationGas = userOp.preVerificationGas;
7564
+ let verificationGasLimit = userOp.verificationGasLimit;
7565
+ let callGasLimit = userOp.callGasLimit;
7566
+ if (overrides.preVerificationGas == null || overrides.verificationGasLimit == null || overrides.callGasLimit == null) {
7567
+ if (bundlerRpc == null) throw new AbstractionKitError("BAD_DATA", "bundlerRpc can't be null if preVerificationGas, verificationGasLimit and callGasLimit are not overridden");
7568
+ const bundler = new Bundler(bundlerRpc);
7569
+ userOp.callGasLimit = 0n;
7570
+ userOp.verificationGasLimit = 0n;
7571
+ userOp.preVerificationGas = 0n;
7572
+ const skipFeeZeroing = this.provider === "pimlico";
7573
+ const inputMaxFeePerGas = userOp.maxFeePerGas;
7574
+ const inputMaxPriorityFeePerGas = userOp.maxPriorityFeePerGas;
7575
+ if (!skipFeeZeroing) {
7576
+ userOp.maxFeePerGas = 0n;
7577
+ userOp.maxPriorityFeePerGas = 0n;
7578
+ }
7579
+ const estimation = await bundler.estimateUserOperationGas(userOp, entrypoint, overrides.state_override_set);
7580
+ if (!skipFeeZeroing) {
7581
+ userOp.maxFeePerGas = inputMaxFeePerGas;
7582
+ userOp.maxPriorityFeePerGas = inputMaxPriorityFeePerGas;
7583
+ }
7584
+ if (estimation.preVerificationGas > preVerificationGas) preVerificationGas = estimation.preVerificationGas;
7585
+ if (estimation.verificationGasLimit > verificationGasLimit) verificationGasLimit = estimation.verificationGasLimit;
7586
+ if (estimation.callGasLimit > callGasLimit) callGasLimit = estimation.callGasLimit;
7587
+ if ("paymaster" in userOp && estimation.paymasterVerificationGasLimit != null) userOp.paymasterVerificationGasLimit = estimation.paymasterVerificationGasLimit;
7588
+ if ("paymaster" in userOp && estimation.paymasterPostOpGasLimit != null) userOp.paymasterPostOpGasLimit = estimation.paymasterPostOpGasLimit;
7589
+ }
7590
+ if (typeof overrides.preVerificationGas === "bigint" && overrides.preVerificationGas < 0n) throw new RangeError("preVerificationGas override can't be negative");
7591
+ if (typeof overrides.verificationGasLimit === "bigint" && overrides.verificationGasLimit < 0n) throw new RangeError("verificationGasLimit override can't be negative");
7592
+ if (typeof overrides.callGasLimit === "bigint" && overrides.callGasLimit < 0n) throw new RangeError("callGasLimit override can't be negative");
7593
+ const applyMultiplier = (value, multiplier) => value + value * BigInt(Math.round((multiplier ?? 0) * 100)) / 10000n;
7594
+ userOp.preVerificationGas = overrides.preVerificationGas ?? applyMultiplier(preVerificationGas, overrides.preVerificationGasPercentageMultiplier ?? 5);
7595
+ userOp.verificationGasLimit = overrides.verificationGasLimit ?? applyMultiplier(verificationGasLimit, overrides.verificationGasLimitPercentageMultiplier ?? 10);
7596
+ userOp.callGasLimit = overrides.callGasLimit ?? applyMultiplier(callGasLimit, overrides.callGasLimitPercentageMultiplier ?? 10);
7597
+ if (entrypoint.toLowerCase() === "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789".toLowerCase()) userOp.verificationGasLimit += 40000n;
7598
+ }
7599
+ /**
7600
+ * Fetch token exchange rate and paymaster address via Pimlico's
7601
+ * `pimlico_getTokenQuotes` RPC.
7602
+ *
7603
+ * @returns `exchangeRate` as a bigint scaled by 10^18 (the value of 1 ETH
7604
+ * expressed in the token's smallest unit). Used to compute the token
7605
+ * approval amount via `(exchangeRate * gasCostWei) / 10^18`.
7606
+ */
7607
+ async fetchPimlicoTokenQuote(tokenAddress, entrypoint, chainIdHex) {
7608
+ const quotes = (await sendJsonRpcRequest(this.rpcUrl, "pimlico_getTokenQuotes", [
7609
+ { tokens: [tokenAddress] },
7610
+ entrypoint,
7611
+ chainIdHex
7612
+ ]))?.quotes;
7613
+ if (!Array.isArray(quotes) || quotes.length === 0) throw new AbstractionKitError("PAYMASTER_ERROR", `pimlico_getTokenQuotes returned no quotes for token ${tokenAddress}`);
7614
+ const quote = quotes.find((q) => q.token.toLowerCase() === tokenAddress.toLowerCase());
7615
+ if (quote == null) throw new AbstractionKitError("PAYMASTER_ERROR", `pimlico_getTokenQuotes did not include token ${tokenAddress}`);
7616
+ return {
7617
+ exchangeRate: BigInt(quote.exchangeRate),
7618
+ paymasterAddress: quote.paymaster
7619
+ };
7620
+ }
7621
+ /**
7622
+ * Fetch (and cache) Candide's `pm_supportedERC20Tokens` response for the
7623
+ * given entrypoint. The response carries both exchange rates and the
7624
+ * `dummyPaymasterAndData` used for gas estimation, so one round-trip
7625
+ * suffices for the entire paymaster flow.
7626
+ *
7627
+ * @param options.enforceTTL - When true, re-fetches if the cached entry is
7628
+ * older than {@link CANDIDE_TOKEN_QUOTE_TTL_MS}. Set by exchange-rate
7629
+ * lookups (where staleness matters). Stub-data lookups leave this false
7630
+ * and reuse the cache indefinitely — the paymaster address and
7631
+ * `dummyPaymasterAndData` are effectively static per paymaster version.
7632
+ */
7633
+ async fetchCandideSupportedTokens(entrypoint, options = {}) {
7634
+ const key = entrypoint.toLowerCase();
7635
+ const cached = this.candideCache.get(key);
7636
+ const isStale = cached != null && options.enforceTTL === true && Date.now() - cached.fetchedAt > CANDIDE_TOKEN_QUOTE_TTL_MS;
7637
+ if (cached != null && !isStale) return cached.data;
7638
+ const result = await sendJsonRpcRequest(this.rpcUrl, "pm_supportedERC20Tokens", [entrypoint]);
7639
+ this.candideCache.set(key, {
7640
+ data: result,
7641
+ fetchedAt: Date.now()
7642
+ });
7643
+ return result;
7644
+ }
7645
+ /**
7646
+ * Fetch token exchange rate and paymaster address via Candide's
7647
+ * `pm_supportedERC20Tokens` RPC.
7648
+ *
7649
+ * @returns `exchangeRate` as a bigint scaled by 10^18 (the value of 1 ETH
7650
+ * expressed in the token's smallest unit). Used to compute the token
7651
+ * approval amount via `(exchangeRate * gasCostWei) / 10^18`.
7652
+ */
7653
+ async fetchCandideTokenQuote(tokenAddress, entrypoint) {
7654
+ const result = await this.fetchCandideSupportedTokens(entrypoint, { enforceTTL: true });
7655
+ const token = result.tokens?.find((t) => t.address.toLowerCase() === tokenAddress.toLowerCase());
7656
+ if (token == null) throw new AbstractionKitError("PAYMASTER_ERROR", `${tokenAddress} token is not supported by the Candide paymaster`);
7657
+ return {
7658
+ exchangeRate: BigInt(token.exchangeRate),
7659
+ paymasterAddress: result.paymasterMetadata.address
7660
+ };
7661
+ }
7662
+ /**
7663
+ * Convert Candide's `dummyPaymasterAndData` metadata into a stub result
7664
+ * compatible with {@link applyPaymasterFields}. Handles both v0.6
7665
+ * (concatenated hex string) and v0.7+ (structured) shapes.
7666
+ */
7667
+ candideStubFromMetadata(metadata) {
7668
+ const dummy = metadata.dummyPaymasterAndData;
7669
+ if (typeof dummy === "string") return { paymasterAndData: dummy };
7670
+ return {
7671
+ paymaster: dummy.paymaster,
7672
+ paymasterData: dummy.paymasterData,
7673
+ paymasterVerificationGasLimit: dummy.paymasterVerificationGasLimit,
7674
+ paymasterPostOpGasLimit: dummy.paymasterPostOpGasLimit
7675
+ };
7676
+ }
7677
+ /**
7678
+ * Get stub paymaster data. For Candide-hosted paymasters this derives the
7679
+ * stub from the cached `pm_supportedERC20Tokens` response (no extra
7680
+ * round-trip). For other providers, falls back to `pm_getPaymasterStubData`.
7681
+ */
7682
+ async getStubData(userOperation, entrypoint, chainIdHex, context) {
7683
+ if (this.provider === "candide") {
7684
+ const response = await this.fetchCandideSupportedTokens(entrypoint);
7685
+ return this.candideStubFromMetadata(response.paymasterMetadata);
7686
+ }
7687
+ return this.getPaymasterStubData(userOperation, entrypoint, chainIdHex, context);
7688
+ }
7689
+ /**
7690
+ * Route to the correct provider-specific token quote fetcher.
7691
+ * Returns `null` when no provider is configured.
7692
+ */
7693
+ async fetchProviderTokenQuote(tokenAddress, entrypoint, chainIdHex) {
7694
+ switch (this.provider) {
7695
+ case "pimlico": return this.fetchPimlicoTokenQuote(tokenAddress, entrypoint, chainIdHex);
7696
+ case "candide": return this.fetchCandideTokenQuote(tokenAddress, entrypoint);
7697
+ default: return null;
7698
+ }
7699
+ }
7700
+ /**
7701
+ * Internal token paymaster pipeline. Called from `createPaymasterUserOperation`
7702
+ * when `context.token` is set and the smart account supports approval prepending.
7703
+ *
7704
+ * Three cases:
7705
+ * - **Provider detected**: exchange rate + paymaster address from provider RPC.
7706
+ * - **No provider, `context.exchangeRate` set**: uses provided rate, paymaster
7707
+ * address from stub.
7708
+ * - **No provider, no rate**: falls through to the regular sponsored flow
7709
+ * (developer already handled approval).
7710
+ */
7711
+ async tokenPaymasterFlow(smartAccount, userOp, tokenAddress, bundlerRpc, entrypoint, chainIdHex, context, overrides) {
7712
+ let exchangeRate;
7713
+ let paymasterAddress = null;
7714
+ const providerQuote = await this.fetchProviderTokenQuote(tokenAddress, entrypoint, chainIdHex);
7715
+ if (providerQuote != null) {
7716
+ exchangeRate = providerQuote.exchangeRate;
7717
+ paymasterAddress = providerQuote.paymasterAddress;
7718
+ } else if (context.exchangeRate != null) {
7719
+ try {
7720
+ exchangeRate = BigInt(context.exchangeRate);
7721
+ } catch (err) {
7722
+ throw new AbstractionKitError("PAYMASTER_ERROR", `context.exchangeRate could not be parsed as a bigint: ${String(context.exchangeRate)}`, { cause: ensureError(err) });
7723
+ }
7724
+ if (exchangeRate <= 0n) throw new AbstractionKitError("PAYMASTER_ERROR", `context.exchangeRate must be > 0, got ${exchangeRate}`);
7725
+ } else return this.sponsoredFlow(userOp, bundlerRpc, entrypoint, chainIdHex, context, overrides);
7726
+ const stub = await this.getStubData(userOp, entrypoint, chainIdHex, context);
7727
+ this.applyPaymasterFields(userOp, stub);
7728
+ if (paymasterAddress == null) if (context.paymasterAddress != null) paymasterAddress = context.paymasterAddress;
7729
+ else if ("initCode" in userOp && stub.paymasterAndData != null) paymasterAddress = "0x" + stub.paymasterAndData.slice(2, 42);
7730
+ else if (stub.paymaster != null) paymasterAddress = stub.paymaster;
7731
+ else throw new AbstractionKitError("PAYMASTER_ERROR", "pm_getPaymasterStubData did not return a paymaster address. Pass paymasterAddress in the context or set a provider.");
7732
+ const originalCallData = userOp.callData;
7733
+ const requiresAllowanceReset = overrides.resetApproval ?? TOKENS_REQUIRING_ALLOWANCE_RESET.includes(tokenAddress.toLowerCase());
7734
+ let callDataWithApprove = smartAccount.prependTokenPaymasterApproveToCallData(userOp.callData, tokenAddress, paymasterAddress, UINT256_MAX);
7735
+ if (requiresAllowanceReset) callDataWithApprove = smartAccount.prependTokenPaymasterApproveToCallData(callDataWithApprove, tokenAddress, paymasterAddress, 0n);
7736
+ userOp.callData = callDataWithApprove;
7737
+ await this.estimateAndApplyGasLimits(userOp, bundlerRpc, entrypoint, overrides);
7738
+ const maxGasCostWei = calculateUserOperationMaxGasCost(userOp);
7739
+ const approveAmount = exchangeRate * maxGasCostWei / 10n ** 18n * TOKEN_APPROVE_AMOUNT_MULTIPLIER;
7740
+ callDataWithApprove = smartAccount.prependTokenPaymasterApproveToCallData(originalCallData, tokenAddress, paymasterAddress, approveAmount);
7741
+ if (requiresAllowanceReset) callDataWithApprove = smartAccount.prependTokenPaymasterApproveToCallData(callDataWithApprove, tokenAddress, paymasterAddress, 0n);
7742
+ userOp.callData = callDataWithApprove;
7743
+ const final = await this.getPaymasterData(userOp, entrypoint, chainIdHex, context);
7744
+ this.applyPaymasterFields(userOp, final);
7745
+ return userOp;
7746
+ }
7747
+ /**
7748
+ * The regular (non-token) sponsored flow: stub → estimate → final.
7749
+ * Extracted to allow `tokenPaymasterFlow` to fall through to it for Case C.
7750
+ */
7751
+ async sponsoredFlow(userOp, bundlerRpc, entrypoint, chainIdHex, context, overrides) {
7752
+ const stub = await this.getStubData(userOp, entrypoint, chainIdHex, context);
7753
+ this.applyPaymasterFields(userOp, stub);
7754
+ await this.estimateAndApplyGasLimits(userOp, bundlerRpc, entrypoint, overrides);
7755
+ if (stub.isFinal === true) return userOp;
7756
+ const final = await this.getPaymasterData(userOp, entrypoint, chainIdHex, context);
7757
+ this.applyPaymasterFields(userOp, final);
7758
+ return userOp;
7759
+ }
7760
+ };
7761
+ //#endregion
6963
7762
  //#region src/paymaster/AllowAllPaymaster.ts
6964
7763
  /**
6965
7764
  * A paymaster that sponsors all UserOperations unconditionally.
@@ -7105,6 +7904,7 @@ var abstractionkit_exports = /* @__PURE__ */ __exportAll({
7105
7904
  ENTRYPOINT_V9: () => ENTRYPOINT_V9,
7106
7905
  EOADummySignerSignaturePair: () => EOADummySignerSignaturePair,
7107
7906
  EXECUTE_RECOVERY_PRIMARY_TYPE: () => EXECUTE_RECOVERY_PRIMARY_TYPE,
7907
+ Erc7677Paymaster: () => Erc7677Paymaster,
7108
7908
  ExperimentalAllowAllParallelPaymaster: () => ExperimentalAllowAllParallelPaymaster,
7109
7909
  GasOption: () => GasOption,
7110
7910
  Operation: () => Operation,
@@ -7139,6 +7939,10 @@ var abstractionkit_exports = /* @__PURE__ */ __exportAll({
7139
7939
  createWorldIdSignal: () => createWorldIdSignal,
7140
7940
  fetchAccountNonce: () => fetchAccountNonce,
7141
7941
  fetchGasPrice: () => fetchGasPrice,
7942
+ fromEthersWallet: () => fromEthersWallet,
7943
+ fromPrivateKey: () => fromPrivateKey,
7944
+ fromViem: () => fromViem,
7945
+ fromViemWalletClient: () => fromViemWalletClient,
7142
7946
  getBalanceOf: () => getBalanceOf,
7143
7947
  getDelegatedAddress: () => getDelegatedAddress,
7144
7948
  getDepositInfo: () => getDepositInfo,
@@ -7178,6 +7982,7 @@ exports.ENTRYPOINT_V8 = ENTRYPOINT_V8;
7178
7982
  exports.ENTRYPOINT_V9 = ENTRYPOINT_V9;
7179
7983
  exports.EOADummySignerSignaturePair = EOADummySignerSignaturePair;
7180
7984
  exports.EXECUTE_RECOVERY_PRIMARY_TYPE = EXECUTE_RECOVERY_PRIMARY_TYPE;
7985
+ exports.Erc7677Paymaster = Erc7677Paymaster;
7181
7986
  exports.ExperimentalAllowAllParallelPaymaster = ExperimentalAllowAllParallelPaymaster;
7182
7987
  exports.GasOption = GasOption;
7183
7988
  exports.Operation = Operation;
@@ -7218,6 +8023,10 @@ exports.createUserOperationHash = createUserOperationHash;
7218
8023
  exports.createWorldIdSignal = createWorldIdSignal;
7219
8024
  exports.fetchAccountNonce = fetchAccountNonce;
7220
8025
  exports.fetchGasPrice = fetchGasPrice;
8026
+ exports.fromEthersWallet = fromEthersWallet;
8027
+ exports.fromPrivateKey = fromPrivateKey;
8028
+ exports.fromViem = fromViem;
8029
+ exports.fromViemWalletClient = fromViemWalletClient;
7221
8030
  exports.getBalanceOf = getBalanceOf;
7222
8031
  exports.getDelegatedAddress = getDelegatedAddress;
7223
8032
  exports.getDepositInfo = getDepositInfo;