diff --git a/packages/accounts/package.json b/packages/accounts/package.json index 5128b80e5..034179d2c 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -39,7 +39,6 @@ "test:run-e2e": "vitest run --config vitest.config.e2e.ts" }, "devDependencies": { - "@alchemy/aa-alchemy": "^1.2.0", "@alchemy/aa-core": "^1.2.0", "typescript": "^5.0.4", "typescript-template": "*", diff --git a/packages/accounts/src/index.ts b/packages/accounts/src/index.ts index 15e958c25..d9b48f31d 100644 --- a/packages/accounts/src/index.ts +++ b/packages/accounts/src/index.ts @@ -19,4 +19,10 @@ export type { KernelBaseValidatorParams } from "./kernel-zerodev/validator/base. //light-account exports export { LightSmartContractAccount } from "./light-account/account.js"; +export { createLightAccountProvider } from "./light-account/provider.js"; +export { + LightAccountFactoryConfigSchema, + LightAccountProviderConfigSchema, +} from "./light-account/schema.js"; +export type { LightAccountProviderConfig } from "./light-account/types.js"; export { getDefaultLightAccountFactoryAddress } from "./light-account/utils.js"; diff --git a/packages/accounts/src/light-account/__tests__/account.test.ts b/packages/accounts/src/light-account/__tests__/account.test.ts index 2abd18777..e97295593 100644 --- a/packages/accounts/src/light-account/__tests__/account.test.ts +++ b/packages/accounts/src/light-account/__tests__/account.test.ts @@ -1,13 +1,12 @@ import { LocalAccountSigner, - SmartAccountProvider, type BatchUserOperationCallData, type SmartAccountSigner, } from "@alchemy/aa-core"; import { polygonMumbai, type Chain } from "viem/chains"; import { describe, it } from "vitest"; import { LightSmartContractAccount } from "../account.js"; -import { getDefaultLightAccountFactoryAddress } from "../utils.js"; +import { createLightAccountProvider } from "../provider.js"; const chain = polygonMumbai; @@ -82,22 +81,10 @@ const givenConnectedProvider = ({ }: { owner: SmartAccountSigner; chain: Chain; -}) => { - return new SmartAccountProvider({ - rpcProvider: `${chain.rpcUrls.alchemy.http[0]}/${"test"}`, +}) => + createLightAccountProvider({ + owner, chain, - }).connect((provider) => { - const account = new LightSmartContractAccount({ - chain, - owner, - factoryAddress: getDefaultLightAccountFactoryAddress(chain), - rpcClient: provider, - }); - - account.getAddress = vi.fn( - async () => "0xb856DBD4fA1A79a46D426f537455e7d3E79ab7c4" - ); - - return account; + rpcProvider: `${chain.rpcUrls.alchemy.http[0]}/${"test"}`, + accountAddress: "0xb856DBD4fA1A79a46D426f537455e7d3E79ab7c4", }); -}; diff --git a/packages/accounts/src/light-account/e2e-tests/light-account.test.ts b/packages/accounts/src/light-account/e2e-tests/light-account.test.ts index 48afbc762..4a94d0c7c 100644 --- a/packages/accounts/src/light-account/e2e-tests/light-account.test.ts +++ b/packages/accounts/src/light-account/e2e-tests/light-account.test.ts @@ -1,4 +1,3 @@ -import { AlchemyProvider } from "@alchemy/aa-alchemy"; import { LocalAccountSigner, Logger, @@ -8,6 +7,7 @@ import { } from "@alchemy/aa-core"; import { isAddress, + toHex, type Address, type Chain, type Hash, @@ -16,13 +16,12 @@ import { import { generatePrivateKey } from "viem/accounts"; import { sepolia } from "viem/chains"; import { - getDefaultLightAccountFactoryAddress, + createLightAccountProvider, LightSmartContractAccount, } from "../../index.js"; import { API_KEY, LIGHT_ACCOUNT_OWNER_MNEMONIC, - PAYMASTER_POLICY_ID, UNDEPLOYED_OWNER_MNEMONIC, } from "./constants.js"; @@ -155,6 +154,11 @@ describe("Light Account Tests", () => { }); it("should transfer ownership successfully", async () => { + const provider = givenConnectedProvider({ + owner, + chain, + }); + // create a throwaway address const throwawayOwner = LocalAccountSigner.privateKeyToAccountSigner( generatePrivateKey() @@ -164,24 +168,32 @@ describe("Light Account Tests", () => { chain, }); + const oldOwner = await throwawayOwner.getAddress(); + + // fund the throwaway address + await provider.sendTransaction({ + from: await provider.getAddress(), + to: await throwawayProvider.getAddress(), + data: "0x", + value: toHex(1000000000000000n), + }); + // create new owner and transfer ownership const newThrowawayOwner = LocalAccountSigner.privateKeyToAccountSigner( generatePrivateKey() ); - const result = await LightSmartContractAccount.transferOwnership( + await LightSmartContractAccount.transferOwnership( throwawayProvider, - newThrowawayOwner + newThrowawayOwner, + true ); - const txnHash = throwawayProvider.waitForUserOperationTransaction(result); - await expect(txnHash).resolves.not.toThrowError(); + const newOwnerViaProvider = + await throwawayProvider.account.getOwnerAddress(); + const newOwner = await newThrowawayOwner.getAddress(); - expect(await throwawayProvider.account.getOwnerAddress()).not.toBe( - await throwawayOwner.getAddress() - ); - expect(await throwawayProvider.account.getOwnerAddress()).toBe( - await newThrowawayOwner.getAddress() - ); + expect(newOwnerViaProvider).not.toBe(oldOwner); + expect(newOwnerViaProvider).toBe(newOwner); }, 100000); }); @@ -196,29 +208,16 @@ const givenConnectedProvider = ({ accountAddress?: Address; feeOptions?: UserOperationFeeOptions; }) => { - const provider = new AlchemyProvider({ - apiKey: API_KEY!, + const provider = createLightAccountProvider({ + rpcProvider: `${chain.rpcUrls.alchemy.http[0]}/${API_KEY!}`, chain, + owner, + accountAddress, opts: { - txMaxRetries: 10, - feeOptions: { - maxFeePerGas: { percentage: 100 }, - maxPriorityFeePerGas: { percentage: 100 }, - ...feeOptions, - }, + feeOptions, + txMaxRetries: 100, }, - }).connect( - (rpcClient) => - new LightSmartContractAccount({ - chain, - owner, - factoryAddress: getDefaultLightAccountFactoryAddress(chain), - rpcClient, - accountAddress, - }) - ); - provider.withAlchemyGasManager({ - policyId: PAYMASTER_POLICY_ID, }); + return provider; }; diff --git a/packages/accounts/src/light-account/provider.ts b/packages/accounts/src/light-account/provider.ts new file mode 100644 index 000000000..9a9788a74 --- /dev/null +++ b/packages/accounts/src/light-account/provider.ts @@ -0,0 +1,31 @@ +import { SmartAccountProvider } from "@alchemy/aa-core"; +import { LightSmartContractAccount } from "./account.js"; +import { LightAccountProviderConfigSchema } from "./schema.js"; +import type { LightAccountProviderConfig } from "./types.js"; +import { getDefaultLightAccountFactoryAddress } from "./utils.js"; + +export const createLightAccountProvider = ( + config_: LightAccountProviderConfig +): SmartAccountProvider & { account: LightSmartContractAccount } => { + const config = LightAccountProviderConfigSchema.parse(config_); + + return new SmartAccountProvider({ + rpcProvider: config.rpcProvider, + chain: config.chain, + entryPointAddress: config.entryPointAddress, + opts: config.opts, + }).connect( + (rpcClient) => + new LightSmartContractAccount({ + rpcClient, + initCode: config.initCode, + owner: config.owner, + chain: config.chain, + entryPointAddress: config.entryPointAddress, + factoryAddress: + config.factoryAddress ?? + getDefaultLightAccountFactoryAddress(config.chain), + accountAddress: config.accountAddress, + }) + ); +}; diff --git a/packages/accounts/src/light-account/schema.ts b/packages/accounts/src/light-account/schema.ts new file mode 100644 index 000000000..6a7ef76eb --- /dev/null +++ b/packages/accounts/src/light-account/schema.ts @@ -0,0 +1,25 @@ +import { + SignerSchema, + createSmartAccountProviderConfigSchema, +} from "@alchemy/aa-core"; +import { Address } from "abitype/zod"; +import { isHex } from "viem"; +import { z } from "zod"; + +export const LightAccountFactoryConfigSchema = z.object({ + owner: SignerSchema, + accountAddress: Address.optional().describe( + "Optional override for the account address." + ), + initCode: z + .string() + .refine(isHex, "initCode must be a valid hex.") + .optional() + .describe("Optional override for the account init code."), + factoryAddress: Address.optional().describe( + "Optional override for the factory address which deploys the smart account." + ), +}); + +export const LightAccountProviderConfigSchema = + createSmartAccountProviderConfigSchema().and(LightAccountFactoryConfigSchema); diff --git a/packages/accounts/src/light-account/types.ts b/packages/accounts/src/light-account/types.ts new file mode 100644 index 000000000..a116f4ed1 --- /dev/null +++ b/packages/accounts/src/light-account/types.ts @@ -0,0 +1,6 @@ +import { z } from "zod"; +import { LightAccountProviderConfigSchema } from "./schema.js"; + +export type LightAccountProviderConfig = z.input< + typeof LightAccountProviderConfigSchema +>; diff --git a/packages/alchemy/e2e-tests/constants.ts b/packages/alchemy/e2e-tests/constants.ts index bd350fcd3..c82fcd13b 100644 --- a/packages/alchemy/e2e-tests/constants.ts +++ b/packages/alchemy/e2e-tests/constants.ts @@ -1,3 +1,4 @@ export const API_KEY = process.env.API_KEY!; -export const OWNER_MNEMONIC = process.env.OWNER_MNEMONIC!; export const PAYMASTER_POLICY_ID = process.env.PAYMASTER_POLICY_ID!; +export const LIGHT_ACCOUNT_OWNER_MNEMONIC = + process.env.LIGHT_ACCOUNT_OWNER_MNEMONIC!; diff --git a/packages/alchemy/e2e-tests/simple-account.test.ts b/packages/alchemy/e2e-tests/light-account.test.ts similarity index 83% rename from packages/alchemy/e2e-tests/simple-account.test.ts rename to packages/alchemy/e2e-tests/light-account.test.ts index 651d770cb..84524d136 100644 --- a/packages/alchemy/e2e-tests/simple-account.test.ts +++ b/packages/alchemy/e2e-tests/light-account.test.ts @@ -1,33 +1,26 @@ import type { UserOperationOverrides } from "@alchemy/aa-core"; import { - LogLevel, - Logger, - SimpleSmartContractAccount, - getDefaultSimpleAccountFactoryAddress, type SmartAccountSigner, type UserOperationFeeOptions, } from "@alchemy/aa-core"; import { Alchemy, Network } from "alchemy-sdk"; -import { - toHex, - type Address, - type Chain, - type HDAccount, - type Hash, -} from "viem"; +import { toHex, type Address, type Chain, type Hash } from "viem"; import { mnemonicToAccount } from "viem/accounts"; import { sepolia } from "viem/chains"; -import { AlchemyProvider } from "../src/provider.js"; -import { API_KEY, OWNER_MNEMONIC, PAYMASTER_POLICY_ID } from "./constants.js"; +import { createLightAccountAlchemyProvider } from "../src/index.js"; +import { + API_KEY, + LIGHT_ACCOUNT_OWNER_MNEMONIC, + PAYMASTER_POLICY_ID, +} from "./constants.js"; const chain = sepolia; const network = Network.ETH_SEPOLIA; -Logger.setLogLevel(LogLevel.DEBUG); - -describe("Simple Account Tests", () => { - const ownerAccount = mnemonicToAccount(OWNER_MNEMONIC); - const owner: SmartAccountSigner = { +describe("Light Account Tests", () => { + const ownerAccount = mnemonicToAccount(LIGHT_ACCOUNT_OWNER_MNEMONIC); + const owner: SmartAccountSigner = { + inner: ownerAccount, signMessage: async (msg) => ownerAccount.signMessage({ message: { raw: toHex(msg) }, @@ -35,13 +28,12 @@ describe("Simple Account Tests", () => { signTypedData: async () => "0xHash", getAddress: async () => ownerAccount.address, signerType: "aa-sdk-tests", - inner: ownerAccount, }; it("should successfully get counterfactual address", async () => { const provider = givenConnectedProvider({ owner, chain }); expect(await provider.getAddress()).toMatchInlineSnapshot( - `"0xb856DBD4fA1A79a46D426f537455e7d3E79ab7c4"` + `"0x1a3a89cd46f124EF40848966c2D7074a575dbC27"` ); }); @@ -222,8 +214,8 @@ describe("Simple Account Tests", () => { const address = await provider.getAddress(); const balances = await provider.core.getTokenBalances(address); - expect(balances.tokenBalances.length).toMatchInlineSnapshot(`4`); - }, 100000); + expect(balances.tokenBalances.length).toMatchInlineSnapshot("1"); + }, 50000); it("should get owned nfts for the smart account", async () => { const alchemy = new Alchemy({ @@ -257,29 +249,8 @@ describe("Simple Account Tests", () => { value: 1n, }); - expect(simulatedAssetChanges.changes.length).toEqual(2); - expect( - simulatedAssetChanges.changes.map((change) => ({ - assertType: change.assetType, - changeType: change.changeType, - from: change.from.toLowerCase(), - to: change.to.toLowerCase(), - })) - ).toEqual([ - { - assertType: "NATIVE", - changeType: "TRANSFER", - from: (await provider.getAddress()).toLowerCase(), - to: provider.getEntryPointAddress().toLowerCase(), - }, - { - assertType: "NATIVE", - changeType: "TRANSFER", - from: (await provider.getAddress()).toLowerCase(), - to: provider.getEntryPointAddress().toLowerCase(), - }, - ]); - }, 100000); + expect(simulatedAssetChanges.changes.length).toMatchInlineSnapshot("2"); + }, 50000); it("should simulate as part of middleware stack when added to provider", async () => { const provider = givenConnectedProvider({ @@ -310,9 +281,10 @@ const givenConnectedProvider = ({ accountAddress?: Address; feeOptions?: UserOperationFeeOptions; }) => - new AlchemyProvider({ + createLightAccountAlchemyProvider({ apiKey: API_KEY!, chain, + owner, opts: { txMaxRetries: 10, feeOptions: { @@ -321,13 +293,5 @@ const givenConnectedProvider = ({ maxPriorityFeePerGas: { percentage: 50 }, }, }, - }).connect( - (provider) => - new SimpleSmartContractAccount({ - chain, - owner, - factoryAddress: getDefaultSimpleAccountFactoryAddress(chain), - rpcClient: provider, - accountAddress, - }) - ); + accountAddress, + }); diff --git a/packages/alchemy/package.json b/packages/alchemy/package.json index 3dc9f8037..3b4f76d50 100644 --- a/packages/alchemy/package.json +++ b/packages/alchemy/package.json @@ -39,6 +39,7 @@ "test:run-e2e": "vitest run --config vitest.config.e2e.ts" }, "devDependencies": { + "@alchemy/aa-accounts": "^1.2.0", "@alchemy/aa-core": "^1.2.0", "typescript": "^5.0.4", "typescript-template": "*", @@ -62,6 +63,7 @@ "homepage": "https://github.com/alchemyplatform/aa-sdk#readme", "gitHead": "ee46e8bb857de3b631044fa70714ea706d9e317d", "optionalDependencies": { + "@alchemy/aa-accounts": "^1.2.0", "alchemy-sdk": "^3.0.0" } } diff --git a/packages/alchemy/src/index.ts b/packages/alchemy/src/index.ts index c722ae0cc..59a030dbe 100644 --- a/packages/alchemy/src/index.ts +++ b/packages/alchemy/src/index.ts @@ -3,5 +3,9 @@ export { withAlchemyGasManager } from "./middleware/gas-manager.js"; export { SupportedChains } from "./chains.js"; -export { AlchemyProvider } from "./provider.js"; +export { + AlchemyProvider, + createLightAccountAlchemyProvider, +} from "./provider/index.js"; +export { AlchemyProviderConfigSchema } from "./schema.js"; export type { AlchemyProviderConfig } from "./type.js"; diff --git a/packages/alchemy/src/middleware/gas-fees.ts b/packages/alchemy/src/middleware/gas-fees.ts index 92992dbd9..34affb313 100644 --- a/packages/alchemy/src/middleware/gas-fees.ts +++ b/packages/alchemy/src/middleware/gas-fees.ts @@ -1,5 +1,5 @@ import { applyUserOpOverrideOrFeeOption } from "@alchemy/aa-core"; -import type { AlchemyProvider } from "../provider.js"; +import type { AlchemyProvider } from "../provider/base.js"; import type { ClientWithAlchemyMethods } from "./client.js"; export const withAlchemyGasFeeEstimator = ( diff --git a/packages/alchemy/src/middleware/gas-manager.ts b/packages/alchemy/src/middleware/gas-manager.ts index b1fc6eabb..b21a55e3d 100644 --- a/packages/alchemy/src/middleware/gas-manager.ts +++ b/packages/alchemy/src/middleware/gas-manager.ts @@ -11,7 +11,7 @@ import { type UserOperationRequest, } from "@alchemy/aa-core"; import { fromHex } from "viem"; -import type { AlchemyProvider } from "../provider.js"; +import type { AlchemyProvider } from "../provider/base.js"; import type { ClientWithAlchemyMethods } from "./client.js"; import type { RequestGasAndPaymasterAndDataOverrides } from "./types/index.js"; diff --git a/packages/alchemy/src/middleware/simulate-uo.ts b/packages/alchemy/src/middleware/simulate-uo.ts index 0f50a64a7..a4e7ee34f 100644 --- a/packages/alchemy/src/middleware/simulate-uo.ts +++ b/packages/alchemy/src/middleware/simulate-uo.ts @@ -3,7 +3,7 @@ import { resolveProperties, type UserOperationStruct, } from "@alchemy/aa-core"; -import type { AlchemyProvider } from "../provider.js"; +import type { AlchemyProvider } from "../provider/index.js"; import type { ClientWithAlchemyMethods } from "./client.js"; export const withAlchemyUserOpSimulation =

( diff --git a/packages/alchemy/src/__tests__/__snapshots__/provider.test.ts.snap b/packages/alchemy/src/provider/__tests__/__snapshots__/light-account.test.ts.snap similarity index 100% rename from packages/alchemy/src/__tests__/__snapshots__/provider.test.ts.snap rename to packages/alchemy/src/provider/__tests__/__snapshots__/light-account.test.ts.snap diff --git a/packages/alchemy/src/__tests__/provider.test.ts b/packages/alchemy/src/provider/__tests__/light-account.test.ts similarity index 88% rename from packages/alchemy/src/__tests__/provider.test.ts rename to packages/alchemy/src/provider/__tests__/light-account.test.ts index f37e1a79f..f396995bb 100644 --- a/packages/alchemy/src/__tests__/provider.test.ts +++ b/packages/alchemy/src/provider/__tests__/light-account.test.ts @@ -1,6 +1,5 @@ import * as AACoreModule from "@alchemy/aa-core"; import { - SimpleSmartContractAccount, getDefaultEntryPointAddress, type BatchUserOperationCallData, type SmartAccountSigner, @@ -9,7 +8,8 @@ import { Alchemy, Network } from "alchemy-sdk"; import { toHex, type Address, type Chain, type HDAccount } from "viem"; import { mnemonicToAccount } from "viem/accounts"; import { polygonMumbai } from "viem/chains"; -import { AlchemyProvider } from "../provider.js"; +import { AlchemyProvider } from "../base.js"; +import { createLightAccountAlchemyProvider } from "../light-account.js"; describe("Alchemy Provider Tests", () => { const dummyMnemonic = @@ -140,27 +140,9 @@ const givenConnectedProvider = ({ }: { owner: SmartAccountSigner; chain: Chain; -}) => { - const dummyEntryPointAddress = - "0x1234567890123456789012345678901234567890" as Address; - - return new AlchemyProvider({ - rpcUrl: chain.rpcUrls.alchemy.http[0], +}) => + createLightAccountAlchemyProvider({ jwt: "test", + owner, chain, - }).connect((provider) => { - const account = new SimpleSmartContractAccount({ - entryPointAddress: dummyEntryPointAddress, - chain, - owner, - factoryAddress: AACoreModule.getDefaultSimpleAccountFactoryAddress(chain), - rpcClient: provider, - }); - - account.getAddress = vi.fn( - async () => "0xb856DBD4fA1A79a46D426f537455e7d3E79ab7c4" - ); - - return account; }); -}; diff --git a/packages/alchemy/src/provider.ts b/packages/alchemy/src/provider/base.ts similarity index 82% rename from packages/alchemy/src/provider.ts rename to packages/alchemy/src/provider/base.ts index 607e54572..48f284dbc 100644 --- a/packages/alchemy/src/provider.ts +++ b/packages/alchemy/src/provider/base.ts @@ -8,20 +8,20 @@ import { } from "@alchemy/aa-core"; import { Alchemy } from "alchemy-sdk"; import { type HttpTransport } from "viem"; -import { SupportedChains } from "./chains.js"; -import { getDefaultUserOperationFeeOptions } from "./defaults.js"; -import type { ClientWithAlchemyMethods } from "./middleware/client.js"; -import { withAlchemyGasFeeEstimator } from "./middleware/gas-fees.js"; +import { SupportedChains } from "../chains.js"; +import { getDefaultUserOperationFeeOptions } from "../defaults.js"; +import type { ClientWithAlchemyMethods } from "../middleware/client.js"; +import { withAlchemyGasFeeEstimator } from "../middleware/gas-fees.js"; import { withAlchemyGasManager, type AlchemyGasManagerConfig, -} from "./middleware/gas-manager.js"; -import { withAlchemyUserOpSimulation } from "./middleware/simulate-uo.js"; +} from "../middleware/gas-manager.js"; +import { withAlchemyUserOpSimulation } from "../middleware/simulate-uo.js"; import { AlchemyProviderConfigSchema, AlchemySdkClientSchema, -} from "./schema.js"; -import type { AlchemyProviderConfig } from "./type.js"; +} from "../schema.js"; +import type { AlchemyProviderConfig } from "../type.js"; export class AlchemyProvider extends SmartAccountProvider { private rpcUrl: string; @@ -109,9 +109,9 @@ export class AlchemyProvider extends SmartAccountProvider { } /** - * This methods adds the Alchemy UserOperation Simulation middleware to the provider. + * This method adds the Alchemy UserOperation Simulation middleware to the provider. * - * @returns {AlchemyProvider} - a new AlchemyProvider with the UserOperation Simulation middleware + * @returns {AlchemyProvider} - a new AlchemyProvider with UserOperation Simulation middleware */ withAlchemyUserOpSimulation(): this { if (!this.isConnected()) { @@ -124,14 +124,17 @@ export class AlchemyProvider extends SmartAccountProvider { } /** - * This methods adds Alchemy Enhanced APIs to the provider, via an optional dependency on `alchemy-sdk`. + * This method adds Alchemy Enhanced APIs to the provider, via an optional dependency on + * `alchemy-sdk`. * @see: https://github.com/alchemyplatform/alchemy-sdk-js * - * The Alchemy SDK client must be configured with the same API key and network as the AlchemyProvider. - * This method validates such at runtime. + * The Alchemy SDK client must be configured with the same API key and network as the + * AlchemyProvider. This method validates such at runtime. + * + * Additionally, since the Alchemy SDK client does not yet support JWT authentication, + * AlchemyProvider initialized with JWTs cannot use this method. They must be initialized with an + * API key or RPC URL. * - * Additionally, since the Alchemy SDK client does not yet support JWT authentication, AlchemyProviders initialized with JWTs cannot use this method. - * They must be initialized with an API key or RPC URL. * There is an open issue on the Alchemy SDK repo to add JWT support in the meantime. * @see: https://github.com/alchemyplatform/alchemy-sdk-js/issues/386 * diff --git a/packages/alchemy/src/provider/index.ts b/packages/alchemy/src/provider/index.ts new file mode 100644 index 000000000..1919a474c --- /dev/null +++ b/packages/alchemy/src/provider/index.ts @@ -0,0 +1,2 @@ +export { AlchemyProvider } from "./base.js"; +export { createLightAccountAlchemyProvider } from "./light-account.js"; diff --git a/packages/alchemy/src/provider/light-account.ts b/packages/alchemy/src/provider/light-account.ts new file mode 100644 index 000000000..3c89ab9d4 --- /dev/null +++ b/packages/alchemy/src/provider/light-account.ts @@ -0,0 +1,36 @@ +import { + getDefaultLightAccountFactoryAddress, + LightSmartContractAccount, +} from "@alchemy/aa-accounts"; +import { LightAccountAlchemyProviderConfigSchema } from "../schema.js"; +import type { LightAccountAlchemyProviderConfig } from "../type.js"; +import { AlchemyProvider } from "./base.js"; + +/** + * This method improves the developer experience of connecting a Light Account to an + * AlchemyProvider via an optional dependency on the `@alchemy/aa-accounts` package. + * @see: https://github.com/alchemyplatform/aa-sdk/tree/development/packages/accounts + * + * @param config_ - the AlchemyProvider configuration with additional pamaeters for Light Account + * @returns - a new AlchemyProvider connected to a Light Account + */ +export const createLightAccountAlchemyProvider = ( + config_: LightAccountAlchemyProviderConfig +): AlchemyProvider & { account: LightSmartContractAccount } => { + const config = LightAccountAlchemyProviderConfigSchema.parse(config_); + + return new AlchemyProvider(config).connect( + (rpcClient) => + new LightSmartContractAccount({ + rpcClient, + initCode: config.initCode, + owner: config.owner, + chain: config.chain, + entryPointAddress: config.entryPointAddress, + factoryAddress: + config.factoryAddress ?? + getDefaultLightAccountFactoryAddress(config.chain), + accountAddress: config.accountAddress, + }) + ); +}; diff --git a/packages/alchemy/src/schema.ts b/packages/alchemy/src/schema.ts index a36c4e30e..108725df6 100644 --- a/packages/alchemy/src/schema.ts +++ b/packages/alchemy/src/schema.ts @@ -1,3 +1,4 @@ +import { LightAccountFactoryConfigSchema } from "@alchemy/aa-accounts"; import { createSmartAccountProviderConfigSchema } from "@alchemy/aa-core"; import { Alchemy } from "alchemy-sdk"; import z from "zod"; @@ -31,3 +32,6 @@ export const AlchemyProviderConfigSchema = .and(ConnectionConfigSchema); export const AlchemySdkClientSchema = z.instanceof(Alchemy); + +export const LightAccountAlchemyProviderConfigSchema = + AlchemyProviderConfigSchema.and(LightAccountFactoryConfigSchema); diff --git a/packages/alchemy/src/type.ts b/packages/alchemy/src/type.ts index 306ef79ae..7d038537d 100644 --- a/packages/alchemy/src/type.ts +++ b/packages/alchemy/src/type.ts @@ -2,8 +2,13 @@ import { z } from "zod"; import type { AlchemyProviderConfigSchema, ConnectionConfigSchema, -} from "./schema"; + LightAccountAlchemyProviderConfigSchema, +} from "./schema.js"; export type ConnectionConfig = z.infer; export type AlchemyProviderConfig = z.infer; + +export type LightAccountAlchemyProviderConfig = z.infer< + typeof LightAccountAlchemyProviderConfigSchema +>; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index c6dada652..73b2c0d98 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -7,7 +7,10 @@ export { SimpleAccountAbi } from "./abis/SimpleAccountAbi.js"; export { SimpleAccountFactoryAbi } from "./abis/SimpleAccountFactoryAbi.js"; export { BaseSmartContractAccount } from "./account/base.js"; -export { createBaseSmartAccountParamsSchema } from "./account/schema.js"; +export { + SimpleSmartAccountParamsSchema, + createBaseSmartAccountParamsSchema, +} from "./account/schema.js"; export { SimpleSmartContractAccount } from "./account/simple.js"; export type * from "./account/types.js"; export type { BaseSmartAccountParams } from "./account/types.js"; diff --git a/site/.vitepress/config.ts b/site/.vitepress/config.ts index d66b359e9..857807599 100644 --- a/site/.vitepress/config.ts +++ b/site/.vitepress/config.ts @@ -530,6 +530,10 @@ export default defineConfig({ text: "constructor", link: "/constructor", }, + { + text: "factory", + link: "/light-account-factory", + }, { text: "gasEstimator", link: "/gasEstimator" }, { text: "simulateUserOperationAssetChanges", @@ -608,6 +612,10 @@ export default defineConfig({ text: "constructor", link: "/constructor", }, + { + text: "provider", + link: "/provider", + }, { text: "signMessageWith6492", link: "/signMessageWith6492", diff --git a/site/packages/aa-accounts/light-account/constructor.md b/site/packages/aa-accounts/light-account/constructor.md index f4034bfdc..cb67fcad4 100644 --- a/site/packages/aa-accounts/light-account/constructor.md +++ b/site/packages/aa-accounts/light-account/constructor.md @@ -73,6 +73,6 @@ A new instance of a `LightSmartContractAccount`. - `entryPointAddress: Address | undefined` -- [optional] entry point contract address. If not provided, the entry point contract address for the provider is the connected account's entry point contract, or if not connected, falls back to the default entry point contract for the chain. See [getDefaultEntryPointAddress](/packages/aa-core/utils/getDefaultEntryPointAddress.html#getdefaultentrypointaddress). -- `accountAddress: Address | undefined` -- the owner EOA address responsible for signing user operations on behalf of the smart account. +- `accountAddress: Address | undefined` -- [optional] a smart account address override that this object will manage instead of generating its own. - `index: bigint | undefined` -- [optional] additional salt value used when creating the smart account. diff --git a/site/packages/aa-accounts/light-account/provider.md b/site/packages/aa-accounts/light-account/provider.md new file mode 100644 index 000000000..10f9a23fc --- /dev/null +++ b/site/packages/aa-accounts/light-account/provider.md @@ -0,0 +1,65 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: LightSmartContractAccount • createLightAccountProvider + - - meta + - name: description + content: Overview of the createLightAccountProvider factory in aa-accounts + - - meta + - property: og:description + content: Overview of the createLightAccountProvider factory in aa-accounts +--- + +# createLightAccountProvider + +`createLightAccountProvider` is a factory that improves the developer experience of connecting a Light Account to a `SmartAccountProvider`. You can use this to directly instantiate a `SmartAccountProvider` already connected to a Light Account in one line of code. + +## Usage + +::: code-group + +<<< @/snippets/light-account-provider.ts +::: + +## Returns + +### `Promise` + +A Promise containing a new `SmartAccountProvider` connected to a Light Account. + +## Parameters + +### `config: LightAccountProviderConfig` + +- `rpcProvider: string | PublicErc4337Client` -- a JSON-RPC URL, or a viem Client that supports ERC-4337 methods and Viem public actions. See [createPublicErc4337Client](/packages/aa-core/client/createPublicErc4337Client.md). + +- `chain: Chain` -- the chain on which to create the provider. + +- `owner: SmartAccountSigner` -- the owner EOA address responsible for signing user operations on behalf of the smart account. + +- `entryPointAddress: Address | undefined` -- [optional] the entry point contract address. If not provided, the entry point contract address for the provider is the connected account's entry point contract, or if not connected, falls back to the default entry point contract for the chain. See [getDefaultEntryPointAddress](/packages/aa-core/utils/getDefaultEntryPointAddress.html#getdefaultentrypointaddress). + +- `opts: SmartAccountProviderOpts | undefined` -- [optional] overrides on provider config variables having to do with fetching transaction receipts and fee computation. + + - `txMaxRetries: string | undefined` -- [optional] the maximum number of times to try fetching a transaction receipt before giving up (default: 5). + + - `txRetryIntervalMs: string | undefined` -- [optional] the interval in milliseconds to wait between retries while waiting for transaction receipts (default: 2_000). + + - `txRetryMulitplier: string | undefined` -- [optional] the mulitplier on interval length to wait between retries while waiting for transaction receipts (default: 1.5). + + - `feeOptions:` [`UserOperationFeeOptions`](/packages/aa-core/types/userOperationFeeOptions.md) `| undefined` --[optional] user operation fee options to be used for gas estimation, set at the global level on the provider. + If not set, default fee options for the chain are used. Available fields in `feeOptions` include `maxFeePerGas`, `maxPriorityFeePerGas`, `callGasLimit`, `preVerificationGas`, `verificationGasLimit` where each field is of type [`UserOperationFeeOptionsField`](/packages/aa-core/types/userOperationFeeOptionsField.md). + + - `maxFeePerGas`: `UserOperationFeeOptionsField` + - `maxPriorityFeePerGas`: `UserOperationFeeOptionsField` + - `callGasLimit`: `UserOperationFeeOptionsField` + - `verificationGasLimit`: `UserOperationFeeOptionsField` + - `preVerificationGas`: `UserOperationFeeOptionsField` + +- `factoryAddress: Address | undefined` -- [optional] the factory address for the smart account implementation, which is required for creating the account if not already deployed. Defaults to the [getDefaultLightAccountFactoryAddress](/packages/aa-accounts/utils/getDefaultLightAccountFactoryAddress.md) + +- `initCode: Hex | undefined` -- [optional] the initCode for deploying the smart account with which the provider will connect. + +- `accountAddress: Address | undefined` -- [optional] a smart account address override that this object will manage instead of generating its own. diff --git a/site/packages/aa-alchemy/provider/light-account-factory.md b/site/packages/aa-alchemy/provider/light-account-factory.md new file mode 100644 index 000000000..c9fbd1299 --- /dev/null +++ b/site/packages/aa-alchemy/provider/light-account-factory.md @@ -0,0 +1,69 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: AlchemyProvider • createLightAccountAlchemyProvider + - - meta + - name: description + content: Overview of the createLightAccountAlchemyProvider factory in aa-alchemy + - - meta + - property: og:description + content: Overview of the createLightAccountAlchemyProvider factory in aa-alchemy +--- + +# createLightAccountAlchemyProvider + +`createLightAccountAlchemyProvider` is a factory that improves the developer experience of connecting a Light Account to an `AlchemyProvider` via an optional dependency on the [`@alchemy/aa-accounts`](https://github.com/alchemyplatform/aa-sdk/tree/development/packages/accounts) package. You can use this to directly instantiate an `AlchemyProvider` already connected to a Light Account in one line of code. + +## Usage + +::: code-group + +<<< @/snippets/light-account-alchemy-provider.ts +::: + +## Returns + +### `Promise` + +A Promise containing a new `AlchemyProvider` connected to a Light Account. + +## Parameters + +### `config: AlchemyProviderConfig` + +- `rpcUrl: string | undefined | never` -- a JSON-RPC URL. This is required if there is no `apiKey`. + +- `apiKey: string | undefined | never` -- an Alchemy API Key. This is required if there is no `rpcUrl` or `jwt`. + +- `jwt: string | undefined | never` -- an Alchemy JWT (JSON web token). This is required if there is no `apiKey`. + +- `chain: Chain` -- the chain on which to create the provider. + +- `owner: SmartAccountSigner` -- the owner EOA address responsible for signing user operations on behalf of the smart account. + +- `entryPointAddress: Address | undefined` -- [optional] the entry point contract address. If not provided, the entry point contract address for the provider is the connected account's entry point contract, or if not connected, falls back to the default entry point contract for the chain. See [getDefaultEntryPointAddress](/packages/aa-core/utils/getDefaultEntryPointAddress.html#getdefaultentrypointaddress). + +- `opts: SmartAccountProviderOpts | undefined` -- [optional] overrides on provider config variables having to do with fetching transaction receipts and fee computation. + + - `txMaxRetries: string | undefined` -- [optional] the maximum number of times to try fetching a transaction receipt before giving up (default: 5). + + - `txRetryIntervalMs: string | undefined` -- [optional] the interval in milliseconds to wait between retries while waiting for transaction receipts (default: 2_000). + + - `txRetryMulitplier: string | undefined` -- [optional] the mulitplier on interval length to wait between retries while waiting for transaction receipts (default: 1.5). + + - `feeOptions:` [`UserOperationFeeOptions`](/packages/aa-core/types/userOperationFeeOptions.md) `| undefined` --[optional] user operation fee options to be used for gas estimation, set at the global level on the provider. + If not set, default fee options for the chain are used. Available fields in `feeOptions` include `maxFeePerGas`, `maxPriorityFeePerGas`, `callGasLimit`, `preVerificationGas`, `verificationGasLimit` where each field is of type [`UserOperationFeeOptionsField`](/packages/aa-core/types/userOperationFeeOptionsField.md). + + - `maxFeePerGas`: `UserOperationFeeOptionsField` + - `maxPriorityFeePerGas`: `UserOperationFeeOptionsField` + - `callGasLimit`: `UserOperationFeeOptionsField` + - `verificationGasLimit`: `UserOperationFeeOptionsField` + - `preVerificationGas`: `UserOperationFeeOptionsField` + +- `factoryAddress: Address | undefined` -- [optional] the factory address for the smart account implementation, which is required for creating the account if not already deployed. Defaults to the [getDefaultLightAccountFactoryAddress](/packages/aa-accounts/utils/getDefaultLightAccountFactoryAddress.md) + +- `initCode: Hex | undefined` -- [optional] the initCode for deploying the smart account with which the provider will connect. + +- `accountAddress: Address | undefined` -- [optional] [optional] a smart account address override that this object will manage instead of generating its own. diff --git a/site/packages/aa-core/accounts/constructor.md b/site/packages/aa-core/accounts/constructor.md index deaef158d..5221a3e71 100644 --- a/site/packages/aa-core/accounts/constructor.md +++ b/site/packages/aa-core/accounts/constructor.md @@ -72,6 +72,6 @@ A new instance of a `SimpleSmartContractAccount`. - `entryPointAddress: Address | undefined` -- [optional] entry point contract address. If not provided, the entry point contract address for the provider is the connected account's entry point contract, or if not connected, falls back to the default entry point contract for the chain. See [getDefaultEntryPointAddress](/packages/aa-core/utils/getDefaultEntryPointAddress.html#getdefaultentrypointaddress). -- `accountAddress: Address | undefined` -- the owner EOA address responsible for signing user operations on behalf of the smart account. +- `accountAddress: Address | undefined` -- [optional] a smart account address override that this object will manage instead of generating its own. - `index: bigint | undefined` -- [optional] additional salt value used when creating the smart account. Allows for a one-to-many creation from one owner address to many smart account addresses. diff --git a/site/smart-accounts/accounts/light-account.md b/site/smart-accounts/accounts/light-account.md index f56a81890..31fe22ea4 100644 --- a/site/smart-accounts/accounts/light-account.md +++ b/site/smart-accounts/accounts/light-account.md @@ -20,8 +20,6 @@ head: # Light Account -**Secure, optimized, and extendable** - ## Overview Light Account is an [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) smart account. We started with the Ethereum Foundation’s canonical [SimpleAccount](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/samples/SimpleAccount.sol) and added key improvements. It's fully production-ready with a security [audit](https://github.com/alchemyplatform/light-account/blob/main/Quantstamp-Audit.pdf), gas optimizations, and [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) signature support. Additionally, Light Account supports ownership transfer to ensure you and your user don't get locked into a particular Signer. diff --git a/site/smart-accounts/accounts/modular-account.md b/site/smart-accounts/accounts/modular-account.md index 85d2864f2..d969a1ceb 100644 --- a/site/smart-accounts/accounts/modular-account.md +++ b/site/smart-accounts/accounts/modular-account.md @@ -20,12 +20,16 @@ head: # Modular Account +## Introduction + Soon after the first stable version of [ERC-6900](https://eips.ethereum.org/EIPS/eip-6900), we will release an ERC-6900 compatible Modular Account in Account Kit. It will support new use cases like session keys, account recovery, spending limits, and any ERC-6900 plugin you can imagine. The Light Account is forward-compatible with ERC-6900 so you can optionally upgrade it to the Modular Account once released. Read on to learn more about ERC-6900 and modular accounts. :::tip Note -We are working towards the first stable version of [ERC-6900](https://eips.ethereum.org/EIPS/eip-6900) with [the community](https://ethereum-magicians.org/t/erc-6900-modular-smart-contract-accounts-and-plugins/13885). If you're developing a plugin or modular account, we'd love to chat! Join the Modular Accounts [Telegram group](https://t.me/+KfB9WuhKDgk5YzIx) or [say hello](mailto:account-abstraction@alchemy.com) to us! +We are working towards the first stable version of [ERC-6900](https://eips.ethereum.org/EIPS/eip-6900) with [the community](https://ethereum-magicians.org/t/erc-6900-modular-smart-contract-accounts-and-plugins/13885). If you're developing a plugin or modular account, we'd love to chat! + +Please join the waitlist [here](https://docs.google.com/forms/d/1Z3wFRiMoEKoo8FJFrymVEOzrbKQXjSnYhm_hKKDnooE/edit). You can also join the modular accounts [Telegram group](https://t.me/+KfB9WuhKDgk5YzIx) or [email](mailto:account-abstraction@alchemy.com) us! ::: ## Motivation diff --git a/site/snippets/light-account-alchemy-provider.ts b/site/snippets/light-account-alchemy-provider.ts new file mode 100644 index 000000000..f0dc2aa42 --- /dev/null +++ b/site/snippets/light-account-alchemy-provider.ts @@ -0,0 +1,9 @@ +import { createLightAccountAlchemyProvider } from "@alchemy/aa-alchemy"; +import { LocalAccountSigner } from "@alchemy/aa-core"; +import { sepolia } from "viem/chains"; + +export const provider = createLightAccountAlchemyProvider({ + apiKey: "YOUR_API_KEY", + chain: sepolia, + owner: LocalAccountSigner.mnemonicToAccountSigner("OWNER_MNEMONIC"), +}); diff --git a/site/snippets/light-account-provider.ts b/site/snippets/light-account-provider.ts new file mode 100644 index 000000000..9d3b3b9c3 --- /dev/null +++ b/site/snippets/light-account-provider.ts @@ -0,0 +1,9 @@ +import { createLightAccountProvider } from "@alchemy/aa-accounts"; +import { LocalAccountSigner } from "@alchemy/aa-core"; +import { sepolia } from "viem/chains"; + +export const provider = createLightAccountProvider({ + rpcProvider: `${sepolia.rpcUrls.alchemy.http[0]}/${"YOUR_API_KEY"}`, + chain: sepolia, + owner: LocalAccountSigner.mnemonicToAccountSigner("OWNER_MNEMONIC"), +});