Skip to content

Commit

Permalink
feat(client): multi account light and alchemy consolidation
Browse files Browse the repository at this point in the history
  • Loading branch information
Blu-J committed Jan 10, 2025
1 parent 21acc9f commit 136cc8f
Show file tree
Hide file tree
Showing 32 changed files with 486 additions and 692 deletions.
4 changes: 3 additions & 1 deletion .vitest/vitest.shared.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { join } from "node:path";
import { configDefaults, defineConfig } from "vitest/config";

const typechecking = process.env["TYPECHECK"] === "true";
export const sharedConfig = defineConfig({
test: {
typecheck: {
enabled: typechecking,
only: typechecking,
ignoreSourceErrors: true,
},
alias: {
Expand Down
17 changes: 17 additions & 0 deletions account-kit/infra/src/alchemyTransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import {
createTransport,
http,
type Chain,
type EIP1193RequestFn,
type HttpTransportConfig,
type PublicRpcSchema,
Expand Down Expand Up @@ -65,6 +66,22 @@ export type AlchemyTransport = AlchemyTransportBase & {
config: AlchemyTransportConfig;
};

/**
* A type guard for the transport to determine if it is an Alchemy transport.
* Used in cases where we would like to do switching depending on the transport, where there used
* to be two clients for a alchemy and a non alchemy, and with this switch we don't need the two seperate clients. *
*
* @param {Transport} transport The transport to check
* @param {Chain} chain Chain for the transport to run its function to return the transport config
* @returns {boolean} `true` if the transport is an Alchemy transport, otherwise `false`
*/
export function isAlchemyTransport(
transport: Transport,
chain: Chain
): transport is AlchemyTransport {
return transport({ chain }).config.type === "alchemy";
}

/**
* Creates an Alchemy transport with the specified configuration options.
* When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt.
Expand Down
2 changes: 1 addition & 1 deletion account-kit/infra/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export type * from "./actions/simulateUserOperationChanges.js";
export { simulateUserOperationChanges } from "./actions/simulateUserOperationChanges.js";
export type * from "./actions/types.js";
export type * from "./alchemyTransport.js";
export { alchemy } from "./alchemyTransport.js";
export { alchemy, isAlchemyTransport } from "./alchemyTransport.js";
export type * from "./chains.js";
export {
arbitrum,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { Alchemy, Network } from "alchemy-sdk";
import { avalanche, type Chain } from "viem/chains";
import { createLightAccountAlchemyClient } from "./alchemyClient.js";

Check warning on line 15 in account-kit/smart-contracts/src/light-account/clients/alchemyClient.test.ts

View workflow job for this annotation

GitHub Actions / Lint

'createLightAccountAlchemyClient' is defined but never used
import { createLightAccountClient } from "./client.js";

describe("Light Account Client Tests", () => {
const dummyMnemonic =
Expand Down Expand Up @@ -136,7 +137,7 @@ describe("Light Account Client Tests", () => {
signer: SmartAccountSigner;
chain: Chain;
}) =>
createLightAccountAlchemyClient({
createLightAccountClient({
transport: alchemy({
jwt: "test",
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import type { HttpTransport, SmartAccountSigner } from "@aa-sdk/core";
import {
createAlchemySmartAccountClient,
type AlchemySmartAccountClient,
type AlchemySmartAccountClientConfig,
} from "@account-kit/infra";
import {
createLightAccount,
lightAccountClientActions,
createLightAccountClient,
type CreateLightAccountParams,
type LightAccount,
type LightAccountClientActions,
Expand Down Expand Up @@ -49,7 +47,7 @@ export async function createLightAccountAlchemyClient<
* signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey())
* });
* ```
*
* @deprecated Use createLightAccountClient instead now, it should switch depending on the transport
* @param {AlchemyLightAccountClientConfig} config The configuration for setting up the Alchemy Light Account Client
* @returns {Promise<AlchemySmartAccountClient>} A promise that resolves to an `AlchemySmartAccountClient` object containing the created client
*/
Expand All @@ -59,17 +57,10 @@ export async function createLightAccountAlchemyClient({
chain,
...config
}: AlchemyLightAccountClientConfig): Promise<AlchemySmartAccountClient> {
const account = await createLightAccount({
...config,
return createLightAccountClient({
opts,
transport,
chain,
});

return createAlchemySmartAccountClient({
...config,
transport,
chain,
account,
opts,
}).extend(lightAccountClientActions);
});
}
44 changes: 25 additions & 19 deletions account-kit/smart-contracts/src/light-account/clients/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,18 @@ import {
createLightAccount,
type CreateLightAccountParams,
type LightAccount,
} from "../accounts/account.js";
} from "@account-kit/smart-contracts";
import {
lightAccountClientActions,
type LightAccountClientActions,
} from "../decorators/lightAccount.js";
import {
isAlchemyTransport,
createAlchemySmartAccountClient,
type AlchemySmartAccountClient,
type AlchemyTransport,
} from "@account-kit/infra";
import {
createLightAccountAlchemyClient,
type AlchemyLightAccountClientConfig,
} from "./alchemyClient.js";
import { type AlchemyLightAccountClientConfig } from "./alchemyClient.js";

export type CreateLightAccountClientParams<
TTransport extends Transport | AlchemyTransport = Transport,
Expand Down Expand Up @@ -85,6 +84,19 @@ export function createLightAccountClient<
* signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey())
* });
* ```
* @example
* ```ts
* import { createLightAccountClient } from "@account-kit/smart-contracts";
* import { sepolia, alchemy } from "@account-kit/infra";
* import { LocalAccountSigner } from "@aa-sdk/core";
* import { generatePrivateKey } from "viem"
*
* const lightAlchemyAccountClient = await createLightAccountClient({
* transport: alchemy({ apiKey: "your-api-key" }),
* chain: sepolia,
* signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey())
* });
* ```
*
* @param {CreateLightAccountClientParams} params The parameters for creating a light account client
* @returns {Promise<SmartAccountClient>} A promise that resolves to a `SmartAccountClient` object containing the created account information and methods
Expand All @@ -94,18 +106,19 @@ export async function createLightAccountClient(
): Promise<SmartAccountClient | AlchemySmartAccountClient> {
const { transport, chain } = params;

if (isAlchemyTransport(transport, chain)) {
return await createLightAccountAlchemyClient({
...params,
transport,
});
}

const lightAccount = await createLightAccount({
...params,
transport,
chain,
});
if (isAlchemyTransport(transport, chain)) {
return createAlchemySmartAccountClient({
...params,
transport,
chain,
account: lightAccount,
}).extend(lightAccountClientActions);
}

return createSmartAccountClient({
...params,
Expand All @@ -114,10 +127,3 @@ export async function createLightAccountClient(
account: lightAccount,
}).extend(lightAccountClientActions);
}

function isAlchemyTransport(
transport: Transport,
chain: Chain
): transport is AlchemyTransport {
return transport({ chain }).config.type === "alchemy";
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { Alchemy, Network } from "alchemy-sdk";
import { avalanche, type Chain } from "viem/chains";
import { createMultiOwnerLightAccountAlchemyClient } from "./multiOwnerAlchemyClient.js";

Check warning on line 15 in account-kit/smart-contracts/src/light-account/clients/multiOwnerAlchemyClient.test.ts

View workflow job for this annotation

GitHub Actions / Lint

'createMultiOwnerLightAccountAlchemyClient' is defined but never used
import { createMultiOwnerLightAccountClient } from "./multiOwnerLightAccount.js";

describe("MultiOwnerLightAccount Client Tests", () => {
const dummyMnemonic =
Expand Down Expand Up @@ -138,7 +139,7 @@ describe("MultiOwnerLightAccount Client Tests", () => {
signer: SmartAccountSigner;
chain: Chain;
}) =>
createMultiOwnerLightAccountAlchemyClient({
createMultiOwnerLightAccountClient({
transport: alchemy({
jwt: "test",
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import type { HttpTransport, SmartAccountSigner } from "@aa-sdk/core";
import {
createAlchemySmartAccountClient,
type AlchemySmartAccountClient,
type AlchemySmartAccountClientConfig,
} from "@account-kit/infra";
import {
createMultiOwnerLightAccount,
multiOwnerLightAccountClientActions,
createMultiOwnerLightAccountClient,
type CreateMultiOwnerLightAccountParams,
type MultiOwnerLightAccount,
type MultiOwnerLightAccountClientActions,
Expand Down Expand Up @@ -55,6 +53,7 @@ export async function createMultiOwnerLightAccountAlchemyClient<
* });
* ```
*
* @deprecated Use createMultiOwnerLightAccountAlchemyClient instead now, it should switch depending on the transport
* @param {AlchemyMultiOwnerLightAccountClientConfig} config The configuration for creating the Alchemy client
* @returns {Promise<AlchemySmartAccountClient>} A promise that resolves to an `AlchemySmartAccountClient` object containing the created account information and methods
*/
Expand All @@ -64,17 +63,10 @@ export async function createMultiOwnerLightAccountAlchemyClient({
chain,
...config
}: AlchemyMultiOwnerLightAccountClientConfig): Promise<AlchemySmartAccountClient> {
const account = await createMultiOwnerLightAccount({
...config,
return createMultiOwnerLightAccountClient({
opts,
transport,
chain,
});

return createAlchemySmartAccountClient({
...config,
transport,
chain,
account,
opts,
}).extend(multiOwnerLightAccountClientActions);
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import {
createBundlerClient,
createSmartAccountClientFromExisting,
erc7677Middleware,
LocalAccountSigner,
type Address,
type SmartAccountSigner,
} from "@aa-sdk/core";
import { custom, type Chain } from "viem";
import { generatePrivateKey } from "viem/accounts";
import { setBalance } from "viem/actions";
import { accounts } from "~test/constants.js";
import { local070Instance } from "~test/instances.js";
import { multiOwnerPluginActions } from "../../msca/plugins/multi-owner/index.js";
import { getMSCAUpgradeToData } from "../../msca/utils.js";
import type { LightAccountVersion } from "../types";
import { createMultiOwnerLightAccountClient } from "./multiOwnerLightAccount.js";
import {
alchemy,
alchemyEnhancedApiActions,
arbitrumSepolia,
} from "@account-kit/infra";
import { Alchemy, Network } from "alchemy-sdk";

describe("Types: MultiOwner Light Account Tests", () => {
const instance = local070Instance;
let client: ReturnType<typeof instance.getClient>;

beforeAll(async () => {
client = instance.getClient();
});

const signer: SmartAccountSigner = new LocalAccountSigner(
accounts.fundedAccountOwner
);

it("should upgrade a deployed multi owner light account to msca successfully", async () => {
// create a owner signer to create the account
const throwawaySigner = LocalAccountSigner.privateKeyToAccountSigner(
generatePrivateKey()
);
const throwawayClient = await givenConnectedProvider({
signer: throwawaySigner,
});

const accountAddress = throwawayClient.getAddress();
const ownerAddress = await throwawaySigner.getAddress();

// fund + deploy the throwaway address
await setBalance(client, {
address: accountAddress,
value: 200000000000000000n,
});

const { createMAAccount, ...upgradeToData } = await getMSCAUpgradeToData(
throwawayClient,
{
account: throwawayClient.account,
multiOwnerPluginAddress: "0xcE0000007B008F50d762D155002600004cD6c647",
}
);

await throwawayClient.upgradeAccount({
upgradeTo: upgradeToData,
waitForTx: true,
});

const upgradedClient = createSmartAccountClientFromExisting({
client: createBundlerClient({
chain: instance.chain,
transport: custom(client),
}),
account: await createMAAccount(),
}).extend(multiOwnerPluginActions);

const upgradedAccountAddress = upgradedClient.getAddress();

const owners = await upgradedClient.readOwners({
account: upgradedClient.account,
pluginAddress: "0xcE0000007B008F50d762D155002600004cD6c647",
});

expect(upgradedAccountAddress).toBe(accountAddress);
expect(owners).toContain(ownerAddress);
}, 200000);

it("should have enhanced api properties on the provider", async () => {
const chain = arbitrumSepolia;
const alchemy = new Alchemy({
network: Network.MATIC_MUMBAI,
apiKey: "test",
});

const provider = (
await givenAlchemyConnectedProvider({ signer, chain })
).extend(alchemyEnhancedApiActions(alchemy));

expect(provider.account).toBeDefined();
expect(provider.waitForUserOperationTransaction).toBeDefined();
expect(provider.sendUserOperation).toBeDefined();
expect(provider.core).toBeDefined();
});
const givenAlchemyConnectedProvider = async ({
signer,
chain,
}: {
signer: SmartAccountSigner;
chain: Chain;
}) =>
createMultiOwnerLightAccountClient({
transport: alchemy({
jwt: "test",
}),
chain,
signer,
accountAddress: "0x86f3B0211764971Ad0Fc8C8898d31f5d792faD84",
});

const givenConnectedProvider = ({
signer,
version = "v2.0.0",
accountAddress,
usePaymaster = false,
accountIndex,
}: {
signer: SmartAccountSigner;
version?: LightAccountVersion<"MultiOwnerLightAccount">;
usePaymaster?: boolean;
accountAddress?: Address;
accountIndex?: bigint;
}) =>
createMultiOwnerLightAccountClient({
signer,
accountAddress,
version,
transport: custom(client),
chain: instance.chain,
salt: accountIndex,
...(usePaymaster ? erc7677Middleware() : {}),
});
});
Loading

0 comments on commit 136cc8f

Please sign in to comment.