Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(client): multi account light and alchemy consolidation #1264

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -12,7 +12,8 @@
} from "@account-kit/infra";
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 @@
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 @@ -12,7 +12,8 @@
} from "@account-kit/infra";
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 @@
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
Loading