Skip to content

Commit b677351

Browse files
committed
feat(client): multi account light and alchemy consolidation
1 parent 21acc9f commit b677351

32 files changed

+504
-686
lines changed

.vitest/vitest.shared.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { join } from "node:path";
22
import { configDefaults, defineConfig } from "vitest/config";
3-
3+
const typechecking = process.env["TYPECHECK"] === "true";
44
export const sharedConfig = defineConfig({
55
test: {
66
typecheck: {
7+
enabled: typechecking,
8+
only: typechecking,
79
ignoreSourceErrors: true,
810
},
911
alias: {

account-kit/infra/src/alchemyTransport.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
import {
99
createTransport,
1010
http,
11+
type Chain,
1112
type EIP1193RequestFn,
1213
type HttpTransportConfig,
1314
type PublicRpcSchema,
@@ -65,6 +66,22 @@ export type AlchemyTransport = AlchemyTransportBase & {
6566
config: AlchemyTransportConfig;
6667
};
6768

69+
/**
70+
* A type guard for the transport to determine if it is an Alchemy transport.
71+
* Used in cases where we would like to do switching depending on the transport, where there used
72+
* to be two clients for a alchemy and a non alchemy, and with this switch we don't need the two seperate clients. *
73+
*
74+
* @param {Transport} transport The transport to check
75+
* @param {Chain} chain Chain for the transport to run its function to return the transport config
76+
* @returns {boolean} `true` if the transport is an Alchemy transport, otherwise `false`
77+
*/
78+
export function isAlchemyTransport(
79+
transport: Transport,
80+
chain: Chain
81+
): transport is AlchemyTransport {
82+
return transport({ chain }).config.type === "alchemy";
83+
}
84+
6885
/**
6986
* Creates an Alchemy transport with the specified configuration options.
7087
* When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt.

account-kit/infra/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export type * from "./actions/simulateUserOperationChanges.js";
22
export { simulateUserOperationChanges } from "./actions/simulateUserOperationChanges.js";
33
export type * from "./actions/types.js";
44
export type * from "./alchemyTransport.js";
5-
export { alchemy } from "./alchemyTransport.js";
5+
export { alchemy, isAlchemyTransport } from "./alchemyTransport.js";
66
export type * from "./chains.js";
77
export {
88
arbitrum,

account-kit/smart-contracts/src/light-account/clients/alchemyClient.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import { Alchemy, Network } from "alchemy-sdk";
1414
import { avalanche, type Chain } from "viem/chains";
1515
import { createLightAccountAlchemyClient } from "./alchemyClient.js";
16+
import { createLightAccountClient } from "./client.js";
1617

1718
describe("Light Account Client Tests", () => {
1819
const dummyMnemonic =
@@ -136,7 +137,7 @@ describe("Light Account Client Tests", () => {
136137
signer: SmartAccountSigner;
137138
chain: Chain;
138139
}) =>
139-
createLightAccountAlchemyClient({
140+
createLightAccountClient({
140141
transport: alchemy({
141142
jwt: "test",
142143
}),

account-kit/smart-contracts/src/light-account/clients/alchemyClient.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import type { HttpTransport, SmartAccountSigner } from "@aa-sdk/core";
22
import {
3-
createAlchemySmartAccountClient,
43
type AlchemySmartAccountClient,
54
type AlchemySmartAccountClientConfig,
65
} from "@account-kit/infra";
76
import {
8-
createLightAccount,
9-
lightAccountClientActions,
7+
createLightAccountClient,
108
type CreateLightAccountParams,
119
type LightAccount,
1210
type LightAccountClientActions,
@@ -49,7 +47,7 @@ export async function createLightAccountAlchemyClient<
4947
* signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey())
5048
* });
5149
* ```
52-
*
50+
* @deprecated Use createLightAccountClient instead now, it should switch depending on the transport
5351
* @param {AlchemyLightAccountClientConfig} config The configuration for setting up the Alchemy Light Account Client
5452
* @returns {Promise<AlchemySmartAccountClient>} A promise that resolves to an `AlchemySmartAccountClient` object containing the created client
5553
*/
@@ -59,17 +57,10 @@ export async function createLightAccountAlchemyClient({
5957
chain,
6058
...config
6159
}: AlchemyLightAccountClientConfig): Promise<AlchemySmartAccountClient> {
62-
const account = await createLightAccount({
63-
...config,
60+
return createLightAccountClient({
61+
opts,
6462
transport,
6563
chain,
66-
});
67-
68-
return createAlchemySmartAccountClient({
6964
...config,
70-
transport,
71-
chain,
72-
account,
73-
opts,
74-
}).extend(lightAccountClientActions);
65+
});
7566
}

account-kit/smart-contracts/src/light-account/clients/client.ts

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,18 @@ import {
1111
createLightAccount,
1212
type CreateLightAccountParams,
1313
type LightAccount,
14-
} from "../accounts/account.js";
14+
} from "@account-kit/smart-contracts";
1515
import {
1616
lightAccountClientActions,
1717
type LightAccountClientActions,
1818
} from "../decorators/lightAccount.js";
1919
import {
20+
isAlchemyTransport,
21+
createAlchemySmartAccountClient,
2022
type AlchemySmartAccountClient,
2123
type AlchemyTransport,
2224
} from "@account-kit/infra";
23-
import {
24-
createLightAccountAlchemyClient,
25-
type AlchemyLightAccountClientConfig,
26-
} from "./alchemyClient.js";
25+
import { type AlchemyLightAccountClientConfig } from "./alchemyClient.js";
2726

2827
export type CreateLightAccountClientParams<
2928
TTransport extends Transport | AlchemyTransport = Transport,
@@ -85,6 +84,19 @@ export function createLightAccountClient<
8584
* signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey())
8685
* });
8786
* ```
87+
* @example
88+
* ```ts
89+
* import { createLightAccountClient } from "@account-kit/smart-contracts";
90+
* import { sepolia, alchemy } from "@account-kit/infra";
91+
* import { LocalAccountSigner } from "@aa-sdk/core";
92+
* import { generatePrivateKey } from "viem"
93+
*
94+
* const lightAlchemyAccountClient = await createLightAccountClient({
95+
* transport: alchemy({ apiKey: "your-api-key" }),
96+
* chain: sepolia,
97+
* signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey())
98+
* });
99+
* ```
88100
*
89101
* @param {CreateLightAccountClientParams} params The parameters for creating a light account client
90102
* @returns {Promise<SmartAccountClient>} A promise that resolves to a `SmartAccountClient` object containing the created account information and methods
@@ -95,10 +107,17 @@ export async function createLightAccountClient(
95107
const { transport, chain } = params;
96108

97109
if (isAlchemyTransport(transport, chain)) {
98-
return await createLightAccountAlchemyClient({
110+
const account = await createLightAccount({
99111
...params,
100112
transport,
113+
chain,
101114
});
115+
return createAlchemySmartAccountClient({
116+
...params,
117+
transport,
118+
chain,
119+
account,
120+
}).extend(lightAccountClientActions);
102121
}
103122

104123
const lightAccount = await createLightAccount({
@@ -114,10 +133,3 @@ export async function createLightAccountClient(
114133
account: lightAccount,
115134
}).extend(lightAccountClientActions);
116135
}
117-
118-
function isAlchemyTransport(
119-
transport: Transport,
120-
chain: Chain
121-
): transport is AlchemyTransport {
122-
return transport({ chain }).config.type === "alchemy";
123-
}

account-kit/smart-contracts/src/light-account/clients/multiOwnerAlchemyClient.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import { Alchemy, Network } from "alchemy-sdk";
1414
import { avalanche, type Chain } from "viem/chains";
1515
import { createMultiOwnerLightAccountAlchemyClient } from "./multiOwnerAlchemyClient.js";
16+
import { createMultiOwnerLightAccountClient } from "./multiOwnerLightAccount.js";
1617

1718
describe("MultiOwnerLightAccount Client Tests", () => {
1819
const dummyMnemonic =
@@ -138,7 +139,7 @@ describe("MultiOwnerLightAccount Client Tests", () => {
138139
signer: SmartAccountSigner;
139140
chain: Chain;
140141
}) =>
141-
createMultiOwnerLightAccountAlchemyClient({
142+
createMultiOwnerLightAccountClient({
142143
transport: alchemy({
143144
jwt: "test",
144145
}),

account-kit/smart-contracts/src/light-account/clients/multiOwnerAlchemyClient.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import type { HttpTransport, SmartAccountSigner } from "@aa-sdk/core";
22
import {
3-
createAlchemySmartAccountClient,
43
type AlchemySmartAccountClient,
54
type AlchemySmartAccountClientConfig,
65
} from "@account-kit/infra";
76
import {
8-
createMultiOwnerLightAccount,
9-
multiOwnerLightAccountClientActions,
7+
createMultiOwnerLightAccountClient,
108
type CreateMultiOwnerLightAccountParams,
119
type MultiOwnerLightAccount,
1210
type MultiOwnerLightAccountClientActions,
@@ -55,6 +53,7 @@ export async function createMultiOwnerLightAccountAlchemyClient<
5553
* });
5654
* ```
5755
*
56+
* @deprecated Use createMultiOwnerLightAccountAlchemyClient instead now, it should switch depending on the transport
5857
* @param {AlchemyMultiOwnerLightAccountClientConfig} config The configuration for creating the Alchemy client
5958
* @returns {Promise<AlchemySmartAccountClient>} A promise that resolves to an `AlchemySmartAccountClient` object containing the created account information and methods
6059
*/
@@ -64,17 +63,10 @@ export async function createMultiOwnerLightAccountAlchemyClient({
6463
chain,
6564
...config
6665
}: AlchemyMultiOwnerLightAccountClientConfig): Promise<AlchemySmartAccountClient> {
67-
const account = await createMultiOwnerLightAccount({
68-
...config,
66+
return createMultiOwnerLightAccountClient({
67+
opts,
6968
transport,
7069
chain,
71-
});
72-
73-
return createAlchemySmartAccountClient({
7470
...config,
75-
transport,
76-
chain,
77-
account,
78-
opts,
79-
}).extend(multiOwnerLightAccountClientActions);
71+
});
8072
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import {
2+
createBundlerClient,
3+
createSmartAccountClientFromExisting,
4+
erc7677Middleware,
5+
LocalAccountSigner,
6+
type Address,
7+
type SmartAccountSigner,
8+
} from "@aa-sdk/core";
9+
import { custom, type Chain } from "viem";
10+
import { generatePrivateKey } from "viem/accounts";
11+
import { setBalance } from "viem/actions";
12+
import { accounts } from "~test/constants.js";
13+
import { local070Instance } from "~test/instances.js";
14+
import { multiOwnerPluginActions } from "../../msca/plugins/multi-owner/index.js";
15+
import { getMSCAUpgradeToData } from "../../msca/utils.js";
16+
import type { LightAccountVersion } from "../types";
17+
import { createMultiOwnerLightAccountClient } from "./multiOwnerLightAccount.js";
18+
import {
19+
alchemy,
20+
alchemyEnhancedApiActions,
21+
arbitrumSepolia,
22+
} from "@account-kit/infra";
23+
import { Alchemy, Network } from "alchemy-sdk";
24+
25+
describe("Types: MultiOwner Light Account Tests", () => {
26+
const instance = local070Instance;
27+
let client: ReturnType<typeof instance.getClient>;
28+
29+
beforeAll(async () => {
30+
client = instance.getClient();
31+
});
32+
33+
const signer: SmartAccountSigner = new LocalAccountSigner(
34+
accounts.fundedAccountOwner
35+
);
36+
37+
it("should upgrade a deployed multi owner light account to msca successfully", async () => {
38+
// create a owner signer to create the account
39+
const throwawaySigner = LocalAccountSigner.privateKeyToAccountSigner(
40+
generatePrivateKey()
41+
);
42+
const throwawayClient = await givenConnectedProvider({
43+
signer: throwawaySigner,
44+
});
45+
46+
const accountAddress = throwawayClient.getAddress();
47+
const ownerAddress = await throwawaySigner.getAddress();
48+
49+
// fund + deploy the throwaway address
50+
await setBalance(client, {
51+
address: accountAddress,
52+
value: 200000000000000000n,
53+
});
54+
55+
const { createMAAccount, ...upgradeToData } = await getMSCAUpgradeToData(
56+
throwawayClient,
57+
{
58+
account: throwawayClient.account,
59+
multiOwnerPluginAddress: "0xcE0000007B008F50d762D155002600004cD6c647",
60+
}
61+
);
62+
63+
await throwawayClient.upgradeAccount({
64+
upgradeTo: upgradeToData,
65+
waitForTx: true,
66+
});
67+
68+
const upgradedClient = createSmartAccountClientFromExisting({
69+
client: createBundlerClient({
70+
chain: instance.chain,
71+
transport: custom(client),
72+
}),
73+
account: await createMAAccount(),
74+
}).extend(multiOwnerPluginActions);
75+
76+
const upgradedAccountAddress = upgradedClient.getAddress();
77+
78+
const owners = await upgradedClient.readOwners({
79+
account: upgradedClient.account,
80+
pluginAddress: "0xcE0000007B008F50d762D155002600004cD6c647",
81+
});
82+
83+
expect(upgradedAccountAddress).toBe(accountAddress);
84+
expect(owners).toContain(ownerAddress);
85+
}, 200000);
86+
87+
it("should have enhanced api properties on the provider", async () => {
88+
const chain = arbitrumSepolia;
89+
const alchemy = new Alchemy({
90+
network: Network.MATIC_MUMBAI,
91+
apiKey: "test",
92+
});
93+
94+
const provider = (
95+
await givenAlchemyConnectedProvider({ signer, chain })
96+
).extend(alchemyEnhancedApiActions(alchemy));
97+
98+
expect(provider.account).toBeDefined();
99+
expect(provider.waitForUserOperationTransaction).toBeDefined();
100+
expect(provider.sendUserOperation).toBeDefined();
101+
expect(provider.core).toBeDefined();
102+
});
103+
const givenAlchemyConnectedProvider = async ({
104+
signer,
105+
chain,
106+
}: {
107+
signer: SmartAccountSigner;
108+
chain: Chain;
109+
}) =>
110+
createMultiOwnerLightAccountClient({
111+
transport: alchemy({
112+
jwt: "test",
113+
}),
114+
chain,
115+
signer,
116+
accountAddress: "0x86f3B0211764971Ad0Fc8C8898d31f5d792faD84",
117+
});
118+
119+
const givenConnectedProvider = ({
120+
signer,
121+
version = "v2.0.0",
122+
accountAddress,
123+
usePaymaster = false,
124+
accountIndex,
125+
}: {
126+
signer: SmartAccountSigner;
127+
version?: LightAccountVersion<"MultiOwnerLightAccount">;
128+
usePaymaster?: boolean;
129+
accountAddress?: Address;
130+
accountIndex?: bigint;
131+
}) =>
132+
createMultiOwnerLightAccountClient({
133+
signer,
134+
accountAddress,
135+
version,
136+
transport: custom(client),
137+
chain: instance.chain,
138+
salt: accountIndex,
139+
...(usePaymaster ? erc7677Middleware() : {}),
140+
});
141+
});

0 commit comments

Comments
 (0)