abstractionkit 0.3.1 → 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/CHANGELOG.md +70 -0
- package/dist/index.cjs +431 -20
- package/dist/index.d.cts +152 -40
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +152 -40
- package/dist/index.d.mts.map +1 -1
- package/dist/index.iife.js +431 -20
- package/dist/index.mjs +428 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,75 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.2
|
|
4
|
+
|
|
5
|
+
### New Features
|
|
6
|
+
|
|
7
|
+
- **`signUserOperationWithSigner(s)` + `ExternalSigner` (capability-oriented signing API)**: new async method on every account class for integrating viem, ethers Signers, hardware wallets, HSMs, MPC, WebAuthn, or Uint8Array-only signers without passing raw private keys. Each account declares its accepted schemes via a static `ACCEPTED_SIGNING_SCHEMES: ReadonlyArray<"hash" | "typedData">`, and incompatible signers fail offline with an actionable error. The method naming mirrors the parameter arity:
|
|
8
|
+
- Safe accounts (multi-signer): `signUserOperationWithSigners(op, signers[], chainId)` — plural.
|
|
9
|
+
- Simple7702 / Calibur (single signer): `signUserOperationWithSigner(op, signer, chainId)` — singular.
|
|
10
|
+
|
|
11
|
+
Call-site is one line:
|
|
12
|
+
```ts
|
|
13
|
+
import { fromViem } from "abstractionkit"
|
|
14
|
+
userOp.signature = await safe.signUserOperationWithSigners(
|
|
15
|
+
userOp, [fromViem(account)], chainId,
|
|
16
|
+
)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- **`ExternalSigner` interface**: `{ address, signHash?, signTypedData? }` discriminated union that enforces at least one of the two methods at compile time. Accepts any signer that matches the shape (viem local account, viem WalletClient, ethers Wallet, hardware wallet, MPC, WebAuthn, Uint8Array-held keys). The library has zero runtime dependency on viem or ethers for this surface.
|
|
20
|
+
- **`fromPrivateKey(pk)` / `fromViem(account)` / `fromEthersWallet(wallet)` / `fromViemWalletClient(client)` adapters**: one-line factories returning an `ExternalSigner`. Structural types only. `fromViem` / `fromViemWalletClient` require viem ≥ 2.0; `fromEthersWallet` requires ethers ≥ 6.0.
|
|
21
|
+
- **`SignHashFn` / `SignTypedDataFn` / `TypedData` / `SigningScheme` / `SignContext` / `MultiOpSignContext` types** exported from the package root for implementers of custom signers.
|
|
22
|
+
- **`SignContext` forwarded to signers, narrowly typed per signing path**: signers receive a context as the second arg of `signHash` / `signTypedData` so custom validator implementations can inspect the userOp. `Signer<C>` is generic over context (default `C = SignContext` for single-op `{userOperation, chainId, entryPoint}`; opt into `ExternalSigner<MultiOpSignContext>` for `signUserOperationsWithSigners`'s `{userOperations[], entryPoint}`). Built-in adapters return `Signer<unknown>` and work everywhere. See `src/signer/types.ts`.
|
|
23
|
+
- **`SafeMultiChainSigAccountV1.signUserOperationsWithSigners`**: new async multi-op variant that signs a Merkle-rooted bundle of UserOperations with a single signature across chains, using `ExternalSigner[]`.
|
|
24
|
+
|
|
25
|
+
### Breaking Changes
|
|
26
|
+
|
|
27
|
+
> **Note on versioning.** The callback-API removal below is a breaking change for callers of `signUserOperationWithSigner`'s prior callback shape on `Calibur7702Account`. Calibur is not yet in use in any production environment; we're communicating directly with the developers currently building against it to coordinate the migration.
|
|
28
|
+
|
|
29
|
+
- **`SafeAccount.baseSignSingleUserOperation` is now `protected static`.** Previously `public static`, which leaked an internal helper into the package surface. All callers should use the version-specific subclass methods that wrap it (`SafeAccountV0_2_0#signUserOperation`, `SafeAccountV0_3_0#signUserOperation`, etc.) — they auto-inject the correct entrypoint and 4337 module addresses. Migration:
|
|
30
|
+
```ts
|
|
31
|
+
// Before:
|
|
32
|
+
const sig = SafeAccount.baseSignSingleUserOperation(
|
|
33
|
+
op, [pk], chainId,
|
|
34
|
+
SafeAccountV0_3_0.DEFAULT_ENTRYPOINT_ADDRESS,
|
|
35
|
+
SafeAccountV0_3_0.DEFAULT_SAFE_4337_MODULE_ADDRESS,
|
|
36
|
+
);
|
|
37
|
+
// After:
|
|
38
|
+
const sig = safeV3.signUserOperation(op, [pk], chainId);
|
|
39
|
+
```
|
|
40
|
+
`baseSignUserOperationWithSigners` (introduced earlier in this Unreleased window) is also `protected static` for the same reason; no migration needed since it was never on a released `latest` tag.
|
|
41
|
+
- **`ViemLocalAccountLike` / `ViemWalletClientLike` / `EthersWalletLike` are no longer exported.** They're internal structural shapes the adapters match against; pass concrete viem / ethers instances directly to `fromViem` / `fromViemWalletClient` / `fromEthersWallet`. If you need to type a wrapper, use `Parameters<typeof fromViem>[0]` (etc.).
|
|
42
|
+
- **Callback signing API removed.** `signUserOperationWithSigner(op, callback, chainId)` as introduced in the original signer PR is gone, along with the `SignerFunction`, `AddressedSignerFunction`, `SignerInput`, `SignerResult`, and `SignerTypedData` types. The callback method name is now reused for the new capability-oriented API on single-signer accounts (Simple7702, Calibur) with a different parameter shape. Migration:
|
|
43
|
+
```ts
|
|
44
|
+
// Before:
|
|
45
|
+
const signer = async ({ userOpHash }) => ({
|
|
46
|
+
signature: wallet.signingKey.sign(userOpHash).serialized,
|
|
47
|
+
});
|
|
48
|
+
userOp.signature = await account.signUserOperationWithSigner(userOp, signer, chainId);
|
|
49
|
+
|
|
50
|
+
// After — Simple7702 / Calibur (single signer):
|
|
51
|
+
import { fromEthersWallet } from "abstractionkit";
|
|
52
|
+
userOp.signature = await account.signUserOperationWithSigner(
|
|
53
|
+
userOp, fromEthersWallet(wallet), chainId,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// After — Safe accounts (multi-signer, plural method name):
|
|
57
|
+
userOp.signature = await safe.signUserOperationWithSigners(
|
|
58
|
+
userOp, [fromEthersWallet(wallet)], chainId,
|
|
59
|
+
);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Migration: Signing with a raw private key
|
|
63
|
+
|
|
64
|
+
The existing sync `signUserOperation(op, pk[] | pk, chainId): string` method on every account **is untouched**. If your code passes a hex private-key string directly, no change needed. The new `signUserOperationWithSigner(s)` methods are Signers-only — they do NOT accept bare pk strings. To sign with a pk string via the new API, wrap explicitly:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { fromPrivateKey } from "abstractionkit";
|
|
68
|
+
userOp.signature = await safe.signUserOperationWithSigners(
|
|
69
|
+
userOp, [fromPrivateKey(pk)], chainId,
|
|
70
|
+
);
|
|
71
|
+
```
|
|
72
|
+
|
|
3
73
|
## 0.3.1
|
|
4
74
|
|
|
5
75
|
### New Features
|
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
|
/**
|
|
@@ -1582,6 +1628,30 @@ var BaseSimple7702Account = class BaseSimple7702Account extends SmartAccount {
|
|
|
1582
1628
|
return new ethers.Wallet(privateKey).signingKey.sign(userOperationHash).serialized;
|
|
1583
1629
|
}
|
|
1584
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
|
+
/**
|
|
1585
1655
|
* Submit a signed UserOperation to a bundler for on-chain inclusion.
|
|
1586
1656
|
* @param userOperation - The signed UserOperation to submit
|
|
1587
1657
|
* @param bundlerRpc - Bundler RPC endpoint
|
|
@@ -1692,6 +1762,18 @@ var Simple7702Account = class Simple7702Account extends BaseSimple7702Account {
|
|
|
1692
1762
|
return this.baseSignUserOperation(useroperation, privateKey, chainId);
|
|
1693
1763
|
}
|
|
1694
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
|
+
/**
|
|
1695
1777
|
* Send a signed {@link UserOperationV8} to a bundler for on-chain inclusion.
|
|
1696
1778
|
* @param userOperation - The signed UserOperation to submit
|
|
1697
1779
|
* @param bundlerRpc - Bundler RPC endpoint
|
|
@@ -1757,6 +1839,15 @@ var Simple7702AccountV09 = class Simple7702AccountV09 extends BaseSimple7702Acco
|
|
|
1757
1839
|
return this.baseSignUserOperation(useroperation, privateKey, chainId);
|
|
1758
1840
|
}
|
|
1759
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
|
+
/**
|
|
1760
1851
|
* Send a signed {@link UserOperationV9} to a bundler for on-chain inclusion.
|
|
1761
1852
|
* @param userOperation - The signed UserOperation to submit
|
|
1762
1853
|
* @param bundlerRpc - Bundler RPC endpoint
|
|
@@ -3161,6 +3252,68 @@ var SafeAccount = class SafeAccount extends SmartAccount {
|
|
|
3161
3252
|
});
|
|
3162
3253
|
}
|
|
3163
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
|
+
/**
|
|
3164
3317
|
* compute the deterministic address for a webauthn proxy verifier based on a
|
|
3165
3318
|
* webauthn public key(x, y)
|
|
3166
3319
|
* @param x - webauthn public key x parameter
|
|
@@ -3807,7 +3960,7 @@ var SafeAccount = class SafeAccount extends SmartAccount {
|
|
|
3807
3960
|
* @param toolVersion - tool version, defaults to current abstractionkit version
|
|
3808
3961
|
* @returns the onchain idenetifier as a hex string (not 0x prefixed)
|
|
3809
3962
|
*/
|
|
3810
|
-
function generateOnChainIdentifier(project, platform = "Web", tool = "abstractionkit", toolVersion = "0.3.
|
|
3963
|
+
function generateOnChainIdentifier(project, platform = "Web", tool = "abstractionkit", toolVersion = "0.3.2") {
|
|
3811
3964
|
const identifierPrefix = "5afe";
|
|
3812
3965
|
const identifierVersion = "00";
|
|
3813
3966
|
const projectHash = (0, ethers.keccak256)("0x" + Buffer.from(project, "utf8").toString("hex")).slice(-20);
|
|
@@ -3884,6 +4037,17 @@ const DEFAULT_WEB_AUTHN_DAIMO_VERIFIER_V_0_2_1 = "0xc2b78104907F722DABAc4C69f826
|
|
|
3884
4037
|
* with the Daimo P256 verifier instead of the FCL P256 verifier
|
|
3885
4038
|
* used by the base SafeAccount class.
|
|
3886
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.
|
|
3887
4051
|
*/
|
|
3888
4052
|
var SafeMultiChainSigAccountV1 = class SafeMultiChainSigAccountV1 extends SafeAccount {
|
|
3889
4053
|
static DEFAULT_ENTRYPOINT_ADDRESS = ENTRYPOINT_V9;
|
|
@@ -4102,6 +4266,23 @@ var SafeMultiChainSigAccountV1 = class SafeMultiChainSigAccountV1 extends SafeAc
|
|
|
4102
4266
|
});
|
|
4103
4267
|
}
|
|
4104
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
|
+
/**
|
|
4105
4286
|
* sign a list of useroperations - multi chain signature
|
|
4106
4287
|
* @param useroperation - useroperation to sign
|
|
4107
4288
|
* @param privateKeys - for the signers
|
|
@@ -4152,6 +4333,86 @@ var SafeMultiChainSigAccountV1 = class SafeMultiChainSigAccountV1 extends SafeAc
|
|
|
4152
4333
|
})];
|
|
4153
4334
|
}
|
|
4154
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
|
+
/**
|
|
4155
4416
|
* Compute the EIP-712 hash of a multi-chain Merkle tree root for a set of UserOperations.
|
|
4156
4417
|
* This hash is what signers sign to approve multiple cross-chain operations at once.
|
|
4157
4418
|
* @param userOperationsToSignsToSign - list of UserOperations with their target chain IDs
|
|
@@ -4628,34 +4889,42 @@ var Calibur7702Account = class Calibur7702Account extends SmartAccount {
|
|
|
4628
4889
|
return Calibur7702Account.wrapSignature(keyHash, ecdsaSig, hookData);
|
|
4629
4890
|
}
|
|
4630
4891
|
/**
|
|
4631
|
-
*
|
|
4632
|
-
*
|
|
4633
|
-
*
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
*
|
|
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.
|
|
4638
4901
|
*
|
|
4639
|
-
*
|
|
4640
|
-
* @
|
|
4641
|
-
*
|
|
4642
|
-
*
|
|
4643
|
-
* @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`.
|
|
4644
4906
|
*
|
|
4645
4907
|
* @example
|
|
4646
|
-
*
|
|
4908
|
+
* import { fromViem, fromEthersWallet } from "abstractionkit";
|
|
4647
4909
|
* userOp.signature = await account.signUserOperationWithSigner(
|
|
4648
|
-
* userOp,
|
|
4649
|
-
* (hash) => walletClient.signMessage({ message: { raw: hash } }),
|
|
4650
|
-
* chainId,
|
|
4910
|
+
* userOp, fromViem(privateKeyToAccount(pk)), chainId,
|
|
4651
4911
|
* );
|
|
4652
4912
|
*/
|
|
4653
4913
|
async signUserOperationWithSigner(userOperation, signer, chainId, overrides = {}) {
|
|
4654
|
-
const
|
|
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
|
+
});
|
|
4655
4925
|
const keyHash = overrides.keyHash ?? ROOT_KEY_HASH;
|
|
4656
4926
|
const hookData = overrides.hookData ?? "0x";
|
|
4657
|
-
|
|
4658
|
-
return Calibur7702Account.wrapSignature(keyHash, ecdsaSig, hookData);
|
|
4927
|
+
return Calibur7702Account.wrapSignature(keyHash, signature, hookData);
|
|
4659
4928
|
}
|
|
4660
4929
|
/**
|
|
4661
4930
|
* Format a WebAuthn (passkey) assertion into Calibur's signature format.
|
|
@@ -5126,6 +5395,88 @@ var Calibur7702Account = class Calibur7702Account extends SmartAccount {
|
|
|
5126
5395
|
}
|
|
5127
5396
|
};
|
|
5128
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 >= 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 >= 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 >= 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
|
|
5129
5480
|
//#region src/account/Safe/modules/SafeModule.ts
|
|
5130
5481
|
/**
|
|
5131
5482
|
* Abstract base class for Safe modules. Provides shared utilities for
|
|
@@ -6129,6 +6480,45 @@ var SafeAccountV0_3_0 = class SafeAccountV0_3_0 extends SafeAccount {
|
|
|
6129
6480
|
signUserOperation(useroperation, privateKeys, chainId, overrides = {}) {
|
|
6130
6481
|
return SafeAccount.baseSignSingleUserOperation(useroperation, privateKeys, chainId, this.entrypointAddress, this.safe4337ModuleAddress, overrides);
|
|
6131
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
|
+
}
|
|
6132
6522
|
};
|
|
6133
6523
|
//#endregion
|
|
6134
6524
|
//#region src/account/Safe/SafeAccountV0_2_0.ts
|
|
@@ -6371,6 +6761,19 @@ var SafeAccountV0_2_0 = class SafeAccountV0_2_0 extends SafeAccount {
|
|
|
6371
6761
|
signUserOperation(useroperation, privateKeys, chainId, overrides = {}) {
|
|
6372
6762
|
return SafeAccount.baseSignSingleUserOperation(useroperation, privateKeys, chainId, this.entrypointAddress, this.safe4337ModuleAddress, overrides);
|
|
6373
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
|
+
}
|
|
6374
6777
|
};
|
|
6375
6778
|
//#endregion
|
|
6376
6779
|
//#region src/account/Safe/SafeAccountV1_5_0_M_0_3_0.ts
|
|
@@ -7536,6 +7939,10 @@ var abstractionkit_exports = /* @__PURE__ */ __exportAll({
|
|
|
7536
7939
|
createWorldIdSignal: () => createWorldIdSignal,
|
|
7537
7940
|
fetchAccountNonce: () => fetchAccountNonce,
|
|
7538
7941
|
fetchGasPrice: () => fetchGasPrice,
|
|
7942
|
+
fromEthersWallet: () => fromEthersWallet,
|
|
7943
|
+
fromPrivateKey: () => fromPrivateKey,
|
|
7944
|
+
fromViem: () => fromViem,
|
|
7945
|
+
fromViemWalletClient: () => fromViemWalletClient,
|
|
7539
7946
|
getBalanceOf: () => getBalanceOf,
|
|
7540
7947
|
getDelegatedAddress: () => getDelegatedAddress,
|
|
7541
7948
|
getDepositInfo: () => getDepositInfo,
|
|
@@ -7616,6 +8023,10 @@ exports.createUserOperationHash = createUserOperationHash;
|
|
|
7616
8023
|
exports.createWorldIdSignal = createWorldIdSignal;
|
|
7617
8024
|
exports.fetchAccountNonce = fetchAccountNonce;
|
|
7618
8025
|
exports.fetchGasPrice = fetchGasPrice;
|
|
8026
|
+
exports.fromEthersWallet = fromEthersWallet;
|
|
8027
|
+
exports.fromPrivateKey = fromPrivateKey;
|
|
8028
|
+
exports.fromViem = fromViem;
|
|
8029
|
+
exports.fromViemWalletClient = fromViemWalletClient;
|
|
7619
8030
|
exports.getBalanceOf = getBalanceOf;
|
|
7620
8031
|
exports.getDelegatedAddress = getDelegatedAddress;
|
|
7621
8032
|
exports.getDepositInfo = getDepositInfo;
|