diff --git a/.eslintignore b/.eslintignore index 5b84f20786..84abbe65e5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,6 +1,9 @@ **/dist/* -examples/**/* site/.vitepress/cache/**/* .nx/* .github/* -/.nx/cache \ No newline at end of file +/.nx/cache + +/examples/* +!/examples/ui-demo +/examples/ui-demo/.next/* \ No newline at end of file diff --git a/aa-sdk/core/src/client/smartAccountClient.test.ts b/aa-sdk/core/src/client/smartAccountClient.test.ts index 33e6650fd9..b9ac361dce 100644 --- a/aa-sdk/core/src/client/smartAccountClient.test.ts +++ b/aa-sdk/core/src/client/smartAccountClient.test.ts @@ -1,6 +1,6 @@ import { custom, type Transaction } from "viem"; import { polygonMumbai } from "viem/chains"; -import type { SpyInstance } from "vitest"; +import type { MockInstance } from "vitest"; import * as receiptActions from "../actions/bundler/getUserOperationReceipt.js"; import type { UserOperationReceipt } from "../types.js"; import { @@ -110,10 +110,7 @@ describe("SmartAccountClient Tests", async () => { const thenExpectRetriesToBe = async ( expectedRetryMsDelays: number[], expectedMockCalls: number, - getUserOperationReceiptMock: SpyInstance< - any, - Promise - > + getUserOperationReceiptMock: MockInstance ) => { expect(retryMsDelays).toEqual(expectedRetryMsDelays); expect(getUserOperationReceiptMock).toHaveBeenCalledTimes( diff --git a/aa-sdk/core/src/middleware/erc7677middleware.ts b/aa-sdk/core/src/middleware/erc7677middleware.ts index fd253de346..a45716dc8b 100644 --- a/aa-sdk/core/src/middleware/erc7677middleware.ts +++ b/aa-sdk/core/src/middleware/erc7677middleware.ts @@ -22,10 +22,12 @@ import { } from "../utils/index.js"; import type { ClientMiddlewareFn } from "./types"; -export type Erc7677RpcSchema = [ +export type Erc7677RpcSchema< + TContext extends Record = Record +> = [ { Method: "pm_getPaymasterStubData"; - Parameters: [UserOperationRequest, Address, Hex, Record]; + Parameters: [UserOperationRequest, Address, Hex, TContext]; ReturnType: { sponsor?: { name: string; icon?: string }; // Sponsor info paymaster?: Address; // Paymaster address (entrypoint v0.7) @@ -38,7 +40,7 @@ export type Erc7677RpcSchema = [ }, { Method: "pm_getPaymasterData"; - Parameters: [UserOperationRequest, Address, Hex, Record]; + Parameters: [UserOperationRequest, Address, Hex, TContext]; ReturnType: { paymaster?: Address; // Paymaster address (entrypoint v0.7) paymasterData?: Hex; // Paymaster data (entrypoint v0.7) @@ -47,12 +49,10 @@ export type Erc7677RpcSchema = [ } ]; -export type Erc7677Client = Client< - T, - Chain, - undefined, - Erc7677RpcSchema ->; +export type Erc7677Client< + T extends Transport = Transport, + TContext extends Record = Record +> = Client>; export type Erc7677MiddlewareParams< TContext extends Record | undefined = diff --git a/account-kit/core/setupTests.ts b/account-kit/core/setupTests.ts new file mode 100644 index 0000000000..051d7691a0 --- /dev/null +++ b/account-kit/core/setupTests.ts @@ -0,0 +1,3 @@ +beforeEach(() => { + localStorage.clear(); +}); diff --git a/account-kit/core/src/actions/getAlchemyTransport.ts b/account-kit/core/src/actions/getAlchemyTransport.ts new file mode 100644 index 0000000000..35f51fae64 --- /dev/null +++ b/account-kit/core/src/actions/getAlchemyTransport.ts @@ -0,0 +1,14 @@ +import { alchemy, type AlchemyTransport } from "@account-kit/infra"; +import { ChainNotFoundError } from "../errors.js"; +import type { AlchemyAccountsConfig } from "../types"; + +export function getAlchemyTransport( + config: AlchemyAccountsConfig +): AlchemyTransport { + const { chain, connections } = config.store.getState(); + if (!connections.has(chain.id)) { + throw new ChainNotFoundError(chain); + } + + return alchemy(connections.get(chain.id)!.transport); +} diff --git a/account-kit/core/src/actions/getBundlerClient.ts b/account-kit/core/src/actions/getBundlerClient.ts index 95f2ba22b8..8d187095ce 100644 --- a/account-kit/core/src/actions/getBundlerClient.ts +++ b/account-kit/core/src/actions/getBundlerClient.ts @@ -1,4 +1,4 @@ -import type { ClientWithAlchemyMethods } from "@account-kit/infra"; +import { type ClientWithAlchemyMethods } from "@account-kit/infra"; import type { AlchemyAccountsConfig } from "../types"; /** @@ -18,5 +18,7 @@ import type { AlchemyAccountsConfig } from "../types"; export const getBundlerClient = ( config: AlchemyAccountsConfig ): ClientWithAlchemyMethods => { - return config.store.getState().bundlerClient; + const { bundlerClient } = config.store.getState(); + + return bundlerClient; }; diff --git a/account-kit/core/src/actions/getSmartAccountClient.ts b/account-kit/core/src/actions/getSmartAccountClient.ts index a4cb476d69..ed91e1114c 100644 --- a/account-kit/core/src/actions/getSmartAccountClient.ts +++ b/account-kit/core/src/actions/getSmartAccountClient.ts @@ -1,5 +1,5 @@ import { - createAlchemySmartAccountClientFromExisting, + createAlchemySmartAccountClient, type AlchemySmartAccountClient, type AlchemySmartAccountClientConfig, } from "@account-kit/infra"; @@ -19,7 +19,7 @@ import { type MultiOwnerPluginActions, type PluginManagerActions, } from "@account-kit/smart-contracts"; -import type { Address, Chain, Transport } from "viem"; +import type { Address, Chain } from "viem"; import type { AlchemyAccountsConfig, Connection, @@ -29,21 +29,16 @@ import type { } from "../types"; import { createAccount } from "./createAccount.js"; import { getAccount, type GetAccountParams } from "./getAccount.js"; -import { getBundlerClient } from "./getBundlerClient.js"; +import { getAlchemyTransport } from "./getAlchemyTransport.js"; import { getConnection } from "./getConnection.js"; import { getSignerStatus } from "./getSignerStatus.js"; export type GetSmartAccountClientParams< - TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SupportedAccountTypes = SupportedAccountTypes > = Omit< - AlchemySmartAccountClientConfig< - TTransport, - TChain, - SupportedAccount - >, - "rpcUrl" | "chain" | "apiKey" | "jwt" | "account" + AlchemySmartAccountClientConfig>, + "transport" | "account" | "chain" > & GetAccountParams; @@ -60,29 +55,22 @@ export type ClientActions< : never; export type GetSmartAccountClientResult< - TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SupportedAccounts = SupportedAccounts > = { - client?: AlchemySmartAccountClient< - TTransport, - TChain, - TAccount, - ClientActions - >; + client?: AlchemySmartAccountClient>; address?: Address; isLoadingClient: boolean; error?: Error; }; export function getSmartAccountClient< - TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SupportedAccountTypes = SupportedAccountTypes >( - params: GetSmartAccountClientParams, + params: GetSmartAccountClientParams, config: AlchemyAccountsConfig -): GetSmartAccountClientResult>; +): GetSmartAccountClientResult>; /** * Obtains a smart account client based on the provided parameters and configuration. Supports creating any of the SupportAccountTypes in Account Kit. @@ -116,7 +104,7 @@ export function getSmartAccountClient( config ); const signerStatus = getSignerStatus(config); - const bundlerClient = getBundlerClient(config); + const transport = getAlchemyTransport(config); const connection = getConnection(config); const clientState = config.store.getState().smartAccountClients[connection.chain.id]?.[type]; @@ -189,8 +177,9 @@ export function getSmartAccountClient( switch (account.source) { case "LightAccount": return { - client: createAlchemySmartAccountClientFromExisting({ - client: bundlerClient, + client: createAlchemySmartAccountClient({ + transport, + chain: connection.chain, account: account, policyId: connection.policyId, ...clientParams, @@ -200,8 +189,9 @@ export function getSmartAccountClient( }; case "MultiOwnerLightAccount": return { - client: createAlchemySmartAccountClientFromExisting({ - client: bundlerClient, + client: createAlchemySmartAccountClient({ + transport, + chain: connection.chain, account: account, policyId: connection.policyId, ...clientParams, @@ -211,8 +201,9 @@ export function getSmartAccountClient( }; case "MultiOwnerModularAccount": return { - client: createAlchemySmartAccountClientFromExisting({ - client: bundlerClient, + client: createAlchemySmartAccountClient({ + transport, + chain: connection.chain, account: account, policyId: connection.policyId, ...clientParams, diff --git a/account-kit/core/src/actions/setChain.ts b/account-kit/core/src/actions/setChain.ts index b15c422732..ba5f7ad010 100644 --- a/account-kit/core/src/actions/setChain.ts +++ b/account-kit/core/src/actions/setChain.ts @@ -1,4 +1,4 @@ -import { createAlchemyPublicRpcClient } from "@account-kit/infra"; +import { alchemy, createAlchemyPublicRpcClient } from "@account-kit/infra"; import { switchChain } from "@wagmi/core"; import type { Chain } from "viem"; import { ChainNotFoundError } from "../errors.js"; @@ -27,12 +27,13 @@ export async function setChain(config: AlchemyAccountsConfig, chain: Chain) { } await switchChain(config._internal.wagmiConfig, { chainId: chain.id }); + const transport = connection.transport; config.store.setState(() => ({ chain, bundlerClient: createAlchemyPublicRpcClient({ chain, - connectionConfig: connection, + transport: alchemy(transport), }), })); } diff --git a/account-kit/core/src/actions/watchBundlerClient.test.ts b/account-kit/core/src/actions/watchBundlerClient.test.ts new file mode 100644 index 0000000000..1108ea0c91 --- /dev/null +++ b/account-kit/core/src/actions/watchBundlerClient.test.ts @@ -0,0 +1,62 @@ +import { alchemy, arbitrumSepolia, sepolia } from "@account-kit/infra"; +import { AlchemySignerStatus } from "@account-kit/signer"; +import { createConfig } from "../createConfig.js"; +import { + convertSignerStatusToState, + createDefaultAccountState, +} from "../store/store.js"; +import { setChain } from "./setChain.js"; +import { watchBundlerClient } from "./watchBundlerClient.js"; + +describe("watchBundlerClient", () => { + it("should not fire the callback if transport or chain didn't change", () => { + const config = givenConfig(); + const onChange = vi.fn(); + + watchBundlerClient(config)(onChange); + + config.store.setState({ + signerStatus: convertSignerStatusToState( + AlchemySignerStatus.AWAITING_EMAIL_AUTH + ), + }); + + expect(onChange).not.toHaveBeenCalled(); + }); + + it("should fire the callback if chain changed", async () => { + const config = givenConfig(); + const onChange = vi.fn(); + + watchBundlerClient(config)(onChange); + + await setChain(config, arbitrumSepolia); + + expect(onChange).toHaveBeenCalled(); + }); + + it("should not fire if the chain id is the same", async () => { + const config = givenConfig(); + const onChange = vi.fn(); + + watchBundlerClient(config)(onChange); + + await setChain(config, sepolia); + + expect(onChange).not.toHaveBeenCalled(); + }); + + const givenConfig = () => { + const config = createConfig({ + chain: sepolia, + chains: [{ chain: sepolia }, { chain: arbitrumSepolia }], + transport: alchemy({ apiKey: "AN_API_KEY" }), + }); + + config.store.setState({ + accounts: createDefaultAccountState([sepolia, arbitrumSepolia]), + }); + + return config; + }; +}); diff --git a/account-kit/core/src/actions/watchBundlerClient.ts b/account-kit/core/src/actions/watchBundlerClient.ts index 47313c7fc3..00e2265906 100644 --- a/account-kit/core/src/actions/watchBundlerClient.ts +++ b/account-kit/core/src/actions/watchBundlerClient.ts @@ -1,4 +1,4 @@ -import type { ClientWithAlchemyMethods } from "@account-kit/infra"; +import { type ClientWithAlchemyMethods } from "@account-kit/infra"; import type { AlchemyAccountsConfig } from "../types"; /** @@ -21,6 +21,11 @@ export const watchBundlerClient = (onChange: (bundlerClient: ClientWithAlchemyMethods) => void) => { return config.store.subscribe( ({ bundlerClient }) => bundlerClient, - onChange + onChange, + { + equalityFn(a, b) { + return a.chain.id === b.chain.id; + }, + } ); }; diff --git a/account-kit/core/src/actions/watchSmartAccountClient.test.ts b/account-kit/core/src/actions/watchSmartAccountClient.test.ts index 637b6a7093..f83edfab0b 100644 --- a/account-kit/core/src/actions/watchSmartAccountClient.test.ts +++ b/account-kit/core/src/actions/watchSmartAccountClient.test.ts @@ -1,4 +1,4 @@ -import { arbitrumSepolia, sepolia } from "@account-kit/infra"; +import { alchemy, arbitrumSepolia, sepolia } from "@account-kit/infra"; import { AlchemySignerStatus } from "@account-kit/signer"; import { createConfig } from "../createConfig.js"; import { @@ -99,7 +99,7 @@ describe("watchSmartAccountClient", () => { const config = createConfig({ chain: sepolia, chains: [{ chain: sepolia }, { chain: arbitrumSepolia }], - apiKey: "AN_API_KEY", + transport: alchemy({ apiKey: "AN_API_KEY" }), }); config.store.setState({ diff --git a/account-kit/core/src/actions/watchSmartAccountClient.ts b/account-kit/core/src/actions/watchSmartAccountClient.ts index 110bdb058f..61e8a2ba1c 100644 --- a/account-kit/core/src/actions/watchSmartAccountClient.ts +++ b/account-kit/core/src/actions/watchSmartAccountClient.ts @@ -1,4 +1,4 @@ -import type { Chain, Transport } from "viem"; +import type { Chain } from "viem"; import { ClientOnlyPropertyError } from "../errors.js"; import type { AlchemyAccountsConfig, @@ -27,44 +27,41 @@ import { * @template TTransport extends Transport = Transport * @template TChain extends Chain | undefined = Chain | undefined * - * @param {GetSmartAccountClientParams} params the parameters needed to get the smart account client + * @param {GetSmartAccountClientParams} params the parameters needed to get the smart account client * @param {AlchemyAccountsConfig} config the configuration containing the client store and other settings - * @returns {(onChange: (client: GetSmartAccountClientResult>) => void) => (() => void)} a function that accepts a callback to be called when the client changes and returns a function to unsubscribe from the store + * @returns {(onChange: (client: GetSmartAccountClientResult>) => void) => (() => void)} a function that accepts a callback to be called when the client changes and returns a function to unsubscribe from the store */ export function watchSmartAccountClient< TAccount extends SupportedAccountTypes, - TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined >( - params: GetSmartAccountClientParams, + params: GetSmartAccountClientParams, config: AlchemyAccountsConfig ) { return ( onChange: ( - client: GetSmartAccountClientResult< - TTransport, - TChain, - SupportedAccount - > + client: GetSmartAccountClientResult> ) => void ) => { - const accounts = config.store.getState().accounts; - if (!accounts) { + const accounts_ = config.store.getState().accounts; + if (!accounts_) { throw new ClientOnlyPropertyError("account"); } return config.store.subscribe( - ({ signerStatus, accounts, bundlerClient, chain }) => ({ + ({ signerStatus, accounts, chain }) => ({ signerStatus, account: accounts![chain.id][params.type], - bundlerClient, + chain, }), - () => onChange(getSmartAccountClient(params, config)), + () => { + onChange(getSmartAccountClient(params, config)); + }, { equalityFn(a, b) { return ( a.signerStatus === b.signerStatus && a.account === b.account && - a.bundlerClient === b.bundlerClient + a.chain === b.chain ); }, } diff --git a/account-kit/core/src/createConfig.ts b/account-kit/core/src/createConfig.ts index ec6239781b..5215683256 100644 --- a/account-kit/core/src/createConfig.ts +++ b/account-kit/core/src/createConfig.ts @@ -1,6 +1,6 @@ -import { ConnectionConfigSchema } from "@aa-sdk/core"; import { DEFAULT_SESSION_MS } from "@account-kit/signer"; import { createStorage, createConfig as createWagmiConfig } from "@wagmi/core"; +import { getBundlerClient } from "./actions/getBundlerClient.js"; import { createAccountKitStore } from "./store/store.js"; import { DEFAULT_STORAGE_KEY } from "./store/types.js"; import type { @@ -25,7 +25,7 @@ export const DEFAULT_IFRAME_CONTAINER_ID = "alchemy-signer-iframe-container"; * * const config = createConfig({ * chain: sepolia, - * apiKey: "your-api-key", + * transport: alchemy({ apiKey: "your-api-key" }), * }); * ``` * @@ -51,35 +51,31 @@ export const createConfig = ( } = params; const connections: Connection[] = []; - if (connectionConfig.connections != null) { - connectionConfig.connections.forEach(({ chain, ...config }) => { - connections.push({ - ...ConnectionConfigSchema.parse(config), - policyId: config.policyId, - chain, - }); + if (connectionConfig.chains == null) { + connections.push({ + transport: connectionConfig.transport.config, + policyId: connectionConfig.policyId, + chain, }); - } else if (connectionConfig.chains != null) { - connectionConfig.chains.forEach(({ chain, ...config }) => { + } else { + connectionConfig.chains.forEach(({ chain, policyId, transport }) => { connections.push({ - apiKey: connectionConfig.apiKey, - policyId: config.policyId, + transport: transport?.config ?? connectionConfig.transport!.config, chain, + policyId, }); }); - } else { - connections.push({ - ...ConnectionConfigSchema.parse(connectionConfig), - policyId: connectionConfig.policyId, - chain, - }); } + const defaultConnection = connections[0].transport; const store = createAccountKitStore({ connections, chain, client: { - connection: signerConnection ?? connections[0], + connection: + signerConnection ?? + defaultConnection.alchemyConnection ?? + defaultConnection, iframeConfig, rootOrgId, rpId, @@ -98,7 +94,7 @@ export const createConfig = ( const wagmiConfig = createWagmiConfig({ connectors, chains: [chain, ...connections.map((c) => c.chain)], - client: () => config.store.getState().bundlerClient, + client: () => getBundlerClient(config), storage: createStorage({ key: `${DEFAULT_STORAGE_KEY}:wagmi`, storage: storage diff --git a/account-kit/core/src/hydrate.ts b/account-kit/core/src/hydrate.ts index 988e8d075f..674cd1a85c 100644 --- a/account-kit/core/src/hydrate.ts +++ b/account-kit/core/src/hydrate.ts @@ -41,8 +41,7 @@ export function hydrate( : initialState; if (initialAlchemyState && !config.store.persist.hasHydrated()) { - const { accountConfigs, signerStatus, bundlerClient, ...rest } = - initialAlchemyState; + const { accountConfigs, signerStatus, ...rest } = initialAlchemyState; const shouldReconnectAccounts = signerStatus.isConnected || signerStatus.isAuthenticating; diff --git a/account-kit/core/src/index.ts b/account-kit/core/src/index.ts index a26962d15b..c65277b7ca 100644 --- a/account-kit/core/src/index.ts +++ b/account-kit/core/src/index.ts @@ -77,7 +77,6 @@ export { export type * from "@account-kit/infra"; export { createAlchemySmartAccountClient, - createAlchemySmartAccountClientFromExisting, type AlchemySmartAccountClient, type AlchemySmartAccountClientConfig, } from "@account-kit/infra"; diff --git a/account-kit/core/src/store/store.test.ts b/account-kit/core/src/store/store.test.ts new file mode 100644 index 0000000000..b4148948e6 --- /dev/null +++ b/account-kit/core/src/store/store.test.ts @@ -0,0 +1,258 @@ +import { alchemy, arbitrumSepolia, sepolia } from "@account-kit/infra"; +import { getAlchemyTransport } from "../actions/getAlchemyTransport.js"; +import { setChain } from "../actions/setChain.js"; +import { createConfig } from "../createConfig.js"; +import { createDefaultAccountState } from "./store.js"; +import { DEFAULT_STORAGE_KEY } from "./types.js"; + +describe("createConfig tests", () => { + it("should setup the config with the correct transport", async () => { + const config = await givenConfig(); + + expect({ ...getAlchemyTransport(config) }).toMatchInlineSnapshot(` + { + "config": { + "rpcUrl": "/api/sepolia", + }, + "updateHeaders": [Function], + } + `); + }); + + it("should rehydrate the current chain and transport", async () => { + const config = await givenConfig(); + + // update the chain so we can make sure the store is updated + expect(getStorageItem("chain").id).toBe(sepolia.id); + await setChain(config, arbitrumSepolia); + expect(getStorageItem("chain").id).toBe(arbitrumSepolia.id); + + // create a config that is the result of a rehydration + const hydratedConfig = await givenConfig(); + + expect(hydratedConfig.store.getState().chain.id).toBe( + config.store.getState().chain.id + ); + + expect(hydratedConfig.store.getState().bundlerClient.chain.id).toBe( + config.store.getState().bundlerClient.chain.id + ); + + expect(getAlchemyTransport(hydratedConfig).config).toMatchInlineSnapshot(` + { + "rpcUrl": "/api/arbitrumSepolia", + } + `); + }); + + it("should correctly serialize the state to storage", async () => { + await givenConfig(); + + expect(JSON.parse(localStorage.getItem(DEFAULT_STORAGE_KEY) ?? "{}")) + .toMatchInlineSnapshot(` + { + "state": { + "accountConfigs": { + "11155111": {}, + "421614": {}, + }, + "chain": { + "blockExplorers": { + "default": { + "apiUrl": "https://api-sepolia.etherscan.io/api", + "name": "Etherscan", + "url": "https://sepolia.etherscan.io", + }, + }, + "contracts": { + "ensRegistry": { + "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + }, + "ensUniversalResolver": { + "address": "0xc8Af999e38273D658BE1b921b88A9Ddf005769cC", + "blockCreated": 5317080, + }, + "multicall3": { + "address": "0xca11bde05977b3631167028862be2a173976ca11", + "blockCreated": 751532, + }, + }, + "id": 11155111, + "name": "Sepolia", + "nativeCurrency": { + "decimals": 18, + "name": "Sepolia Ether", + "symbol": "ETH", + }, + "rpcUrls": { + "alchemy": { + "http": [ + "https://eth-sepolia.g.alchemy.com/v2", + ], + }, + "default": { + "http": [ + "https://rpc.sepolia.org", + ], + }, + }, + "testnet": true, + }, + "config": { + "client": { + "connection": { + "rpcUrl": "/api/signer", + }, + }, + }, + "connections": { + "__type": "Map", + "value": [ + [ + 11155111, + { + "chain": { + "blockExplorers": { + "default": { + "apiUrl": "https://api-sepolia.etherscan.io/api", + "name": "Etherscan", + "url": "https://sepolia.etherscan.io", + }, + }, + "contracts": { + "ensRegistry": { + "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + }, + "ensUniversalResolver": { + "address": "0xc8Af999e38273D658BE1b921b88A9Ddf005769cC", + "blockCreated": 5317080, + }, + "multicall3": { + "address": "0xca11bde05977b3631167028862be2a173976ca11", + "blockCreated": 751532, + }, + }, + "id": 11155111, + "name": "Sepolia", + "nativeCurrency": { + "decimals": 18, + "name": "Sepolia Ether", + "symbol": "ETH", + }, + "rpcUrls": { + "alchemy": { + "http": [ + "https://eth-sepolia.g.alchemy.com/v2", + ], + }, + "default": { + "http": [ + "https://rpc.sepolia.org", + ], + }, + }, + "testnet": true, + }, + "policyId": "test-policy-id", + "transport": { + "__type": "Transport", + "rpcUrl": "/api/sepolia", + }, + }, + ], + [ + 421614, + { + "chain": { + "blockExplorers": { + "default": { + "apiUrl": "https://api-sepolia.arbiscan.io/api", + "name": "Arbiscan", + "url": "https://sepolia.arbiscan.io", + }, + }, + "contracts": { + "multicall3": { + "address": "0xca11bde05977b3631167028862be2a173976ca11", + "blockCreated": 81930, + }, + }, + "id": 421614, + "name": "Arbitrum Sepolia", + "nativeCurrency": { + "decimals": 18, + "name": "Arbitrum Sepolia Ether", + "symbol": "ETH", + }, + "rpcUrls": { + "alchemy": { + "http": [ + "https://arb-sepolia.g.alchemy.com/v2", + ], + }, + "default": { + "http": [ + "https://sepolia-rollup.arbitrum.io/rpc", + ], + }, + }, + "testnet": true, + }, + "transport": { + "__type": "Transport", + "rpcUrl": "/api/arbitrumSepolia", + }, + }, + ], + ], + }, + "signerStatus": { + "isAuthenticating": false, + "isConnected": false, + "isDisconnected": false, + "isInitializing": true, + "status": "INITIALIZING", + }, + "smartAccountClients": { + "11155111": {}, + "421614": {}, + }, + }, + "version": 8, + } + `); + }); + + const getStorageItem = (name: string) => { + return JSON.parse(localStorage.getItem(DEFAULT_STORAGE_KEY) ?? "{}")[ + "state" + ][name]; + }; + + const givenConfig = async () => { + const config = createConfig({ + chain: sepolia, + chains: [ + { + chain: sepolia, + transport: alchemy({ rpcUrl: "/api/sepolia" }), + policyId: "test-policy-id", + }, + { + chain: arbitrumSepolia, + transport: alchemy({ rpcUrl: "/api/arbitrumSepolia" }), + }, + ], + signerConnection: { rpcUrl: "/api/signer" }, + storage: () => localStorage, + }); + + await config.store.persist.rehydrate(); + + config.store.setState({ + accounts: createDefaultAccountState([sepolia, arbitrumSepolia]), + }); + + return config; + }; +}); diff --git a/account-kit/core/src/store/store.ts b/account-kit/core/src/store/store.ts index 396612386b..2b2d22395d 100644 --- a/account-kit/core/src/store/store.ts +++ b/account-kit/core/src/store/store.ts @@ -1,7 +1,12 @@ import type { NoUndefined } from "@aa-sdk/core"; -import { createAlchemyPublicRpcClient } from "@account-kit/infra"; +import { + alchemy, + createAlchemyPublicRpcClient, + type AlchemyTransportConfig, +} from "@account-kit/infra"; import { AlchemySignerStatus, AlchemyWebSigner } from "@account-kit/signer"; import type { Chain } from "viem"; +import { deepEqual } from "wagmi"; import { createJSONStorage, persist, @@ -10,8 +15,8 @@ import { import { createStore } from "zustand/vanilla"; import { DEFAULT_IFRAME_CONTAINER_ID } from "../createConfig.js"; import type { Connection, SupportedAccountTypes } from "../types.js"; -import { bigintMapReplacer } from "../utils/replacer.js"; -import { bigintMapReviver } from "../utils/reviver.js"; +import { storeReplacer } from "../utils/replacer.js"; +import { storeReviver } from "../utils/reviver.js"; import { DEFAULT_STORAGE_KEY, type AccountState, @@ -40,26 +45,12 @@ export const createAccountKitStore = ( name: DEFAULT_STORAGE_KEY, storage: createJSONStorage(() => storage, { replacer: (key, value) => { - if (key === "bundlerClient") { - const client = value as StoreState["bundlerClient"]; - return { - connection: connections.find( - (x) => x.chain.id === client.chain.id - ), - }; - } - return bigintMapReplacer(key, value); + if (key === "bundlerClient") return undefined; + + return storeReplacer(key, value); }, reviver: (key, value) => { - if (key === "bundlerClient") { - const { connection } = value as { connection: Connection }; - return createAlchemyPublicRpcClient({ - chain: connection.chain, - connectionConfig: connection, - }); - } - - return bigintMapReviver(key, value); + return storeReviver(key, value); }, }), merge: (persisted, current) => { @@ -76,10 +67,19 @@ export const createAccountKitStore = ( if (!persistedConnection) return false; return Array.from(Object.entries(c)).every(([key, value]) => { - return ( - (persistedConnection as Record)[key] === - value - ); + const pConn = persistedConnection as Record; + + if (key === "chain") { + return (value as Chain).id === pConn.chain.id; + } + + if (key === "transport") { + return deepEqual( + value as AlchemyTransportConfig, + pConn.transport + ); + } + return pConn[key] === value; }); }); @@ -94,13 +94,23 @@ export const createAccountKitStore = ( return createInitialStoreState(params); } - // this is the default merge behavior - return { ...current, ...persistedState }; + return { + // this is the default merge behavior + ...current, + ...persistedState, + bundlerClient: createAlchemyPublicRpcClient({ + chain: persistedState.chain, + transport: alchemy( + persistedState.connections.get(persistedState.chain.id)! + .transport + ), + }), + }; }, skipHydration: ssr, partialize: ({ signer, accounts, ...writeableState }) => writeableState, - version: 5, + version: 8, }) : () => createInitialStoreState(params) ) @@ -124,14 +134,13 @@ const createInitialStoreState = ( throw new Error("Chain not found in connections"); } - const bundlerClient = createAlchemyPublicRpcClient({ - chain, - connectionConfig: connectionMap.get(chain.id)!, - }); const chains = connections.map((c) => c.chain); const accountConfigs = createEmptyAccountConfigState(chains); const baseState: StoreState = { - bundlerClient, + bundlerClient: createAlchemyPublicRpcClient({ + chain, + transport: alchemy(connectionMap.get(chain.id)!.transport), + }), chain, connections: connectionMap, accountConfigs, diff --git a/account-kit/core/src/store/types.ts b/account-kit/core/src/store/types.ts index fc8243b83d..03198d76b4 100644 --- a/account-kit/core/src/store/types.ts +++ b/account-kit/core/src/store/types.ts @@ -7,7 +7,7 @@ import type { User, } from "@account-kit/signer"; import type { State as WagmiState } from "@wagmi/core"; -import type { Address, Chain, Transport } from "viem"; +import type { Address, Chain } from "viem"; import type { PartialBy } from "viem/chains"; import type { Mutate, StoreApi } from "zustand/vanilla"; import type { AccountConfig } from "../actions/createAccount"; @@ -58,9 +58,7 @@ export type SignerStatus = { }; export type StoredState = { - alchemy: Omit & { - bundlerClient: { connection: Connection }; - }; + alchemy: Omit; wagmi?: WagmiState; }; @@ -87,16 +85,15 @@ export type StoreState = { smartAccountClients: { [chain: number]: Partial<{ [key in SupportedAccountTypes]: GetSmartAccountClientResult< - Transport, Chain, SupportedAccount >; }>; }; + bundlerClient: ClientWithAlchemyMethods; // serializable state // NOTE: in some cases this can be serialized to cookie storage // be mindful of how big this gets. cookie limit 4KB - bundlerClient: ClientWithAlchemyMethods; config: ClientStoreConfig; accountConfigs: { [chain: number]: Partial<{ diff --git a/account-kit/core/src/types.ts b/account-kit/core/src/types.ts index 4e9001ef76..8a0d77f575 100644 --- a/account-kit/core/src/types.ts +++ b/account-kit/core/src/types.ts @@ -1,4 +1,8 @@ import type { ConnectionConfig } from "@aa-sdk/core"; +import type { + AlchemyTransport, + AlchemyTransportConfig, +} from "@account-kit/infra"; import type { AlchemySignerParams, AlchemySignerWebClient, @@ -49,49 +53,48 @@ export type AlchemyAccountsConfig = { }; // [!region CreateConfigProps] -export type Connection = ConnectionConfig & { +export type Connection = { + transport: AlchemyTransportConfig; chain: Chain; policyId?: string; }; type RpcConnectionConfig = - | (Connection & { - /** - * Optional parameter that allows you to specify a different RPC Url - * or connection to be used specifically by the signer. - * This is useful if you have a different backend proxy for the signer - * than for your Bundler or Node RPC calls. - */ + | { + chain: Chain; + chains: { + chain: Chain; + policyId?: string; + // optional transport override + transport?: AlchemyTransport; + }[]; + // optional global transport to use for all chains + transport: AlchemyTransport; + // When providing multiple chains and no default transport, the signer connection is required signerConnection?: ConnectionConfig; - connections?: never; - chains?: never; - }) + policyId?: never; + } | { - connections: Connection[]; chain: Chain; - /** - * When providing multiple connections, you must specify the signer connection config - * to use since the signer is chain agnostic and has a different RPC url. - */ + chains: { + chain: Chain; + policyId?: string; + transport: AlchemyTransport; + }[]; + transport?: never; + // When providing multiple chains, then the signer connection is required signerConnection: ConnectionConfig; - chains?: never; + policyId?: never; } | { - connections?: never; - apiKey: string; + transport: AlchemyTransport; chain: Chain; - chains: { chain: Chain; policyId?: string }[]; - /** - * Optional parameter that allows you to specify a different RPC Url - * or connection to be used specifically by the signer. - * This is useful if you have a different backend proxy for the signer - * than for your Bundler or Node RPC calls. - */ + policyId?: string; signerConnection?: ConnectionConfig; + chains?: never; }; export type CreateConfigProps = RpcConnectionConfig & { - chain: Chain; sessionConfig?: AlchemySignerParams["sessionConfig"]; /** * Enable this parameter if you are using the config in an SSR setting (eg. NextJS) diff --git a/account-kit/core/src/utils/deserialize.ts b/account-kit/core/src/utils/deserialize.ts index 8dd77febfa..95f5eccd3b 100644 --- a/account-kit/core/src/utils/deserialize.ts +++ b/account-kit/core/src/utils/deserialize.ts @@ -1,4 +1,4 @@ -import { bigintMapReviver } from "./reviver.js"; +import { storeReviver } from "./reviver.js"; /** * JSON parses a string while correctly handling BigInt and Map types. @@ -10,5 +10,5 @@ import { bigintMapReviver } from "./reviver.js"; * @returns {T} the parsed object */ export function deserialize(value: string): type { - return JSON.parse(decodeURIComponent(value), bigintMapReviver); + return JSON.parse(decodeURIComponent(value), storeReviver); } diff --git a/account-kit/core/src/utils/replacer.ts b/account-kit/core/src/utils/replacer.ts index 1185d851ec..8654ca8d39 100644 --- a/account-kit/core/src/utils/replacer.ts +++ b/account-kit/core/src/utils/replacer.ts @@ -1,12 +1,15 @@ /** * JSON stringify replacer that correctly handles BigInt and Map types. * - * @param {string} _key the key in the JSON object + * @param {string} key the key in the JSON object * @param {any} value_ the value to convert if map or bigint * @returns {any} the replaced value */ -export const bigintMapReplacer = (_key: string, value_: any) => { +export const storeReplacer = (key: string, value_: any) => { let value = value_; + if (key === "transport") { + value = { __type: "Transport", ...value }; + } if (typeof value === "bigint") value = { __type: "bigint", value: value_.toString() }; if (value instanceof Map) diff --git a/account-kit/core/src/utils/reviver.ts b/account-kit/core/src/utils/reviver.ts index e0b1d0686c..8b25c60509 100644 --- a/account-kit/core/src/utils/reviver.ts +++ b/account-kit/core/src/utils/reviver.ts @@ -6,8 +6,12 @@ * @param {any} value_ the value of the key being revived * @returns {any} the revived value */ -export const bigintMapReviver = (_key: string, value_: any) => { +export const storeReviver = (_key: string, value_: any) => { let value = value_; + if (value?.__type === "Transport") { + const { __type, ...config } = value; + value = config; + } if (value?.__type === "bigint") value = BigInt(value.value); if (value?.__type === "Map") value = new Map(value.value); return value; diff --git a/account-kit/core/vitest.config.ts b/account-kit/core/vitest.config.ts index 9c69f985a7..a4cf8b6031 100644 --- a/account-kit/core/vitest.config.ts +++ b/account-kit/core/vitest.config.ts @@ -1,3 +1,4 @@ +import { join } from "node:path"; import { defineProject, mergeConfig } from "vitest/config"; import { sharedConfig } from "../../.vitest/vitest.shared"; @@ -7,6 +8,11 @@ export default mergeConfig( defineProject({ test: { name: "account-kit/core", + environment: "jsdom", + setupFiles: [ + ...(sharedConfig.test?.setupFiles ?? []), + join(__dirname, "setupTests.ts"), + ], }, }) ); diff --git a/account-kit/infra/src/__snapshots__/alchemyTransport.test.ts.snap b/account-kit/infra/src/__snapshots__/alchemyTransport.test.ts.snap new file mode 100644 index 0000000000..3149dc08a7 --- /dev/null +++ b/account-kit/infra/src/__snapshots__/alchemyTransport.test.ts.snap @@ -0,0 +1,93 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Alchemy Transport Tests > should correctly create a split transport 1`] = ` +[ + { + "fallback": [Function], + "overrides": [ + { + "methods": [ + "eth_sendUserOperation", + "eth_estimateUserOperationGas", + "eth_getUserOperationReceipt", + "eth_getUserOperationByHash", + "eth_supportedEntryPoints", + "rundler_maxPriorityFeePerGas", + "pm_getPaymasterData", + "pm_getPaymasterStubData", + ], + "transport": [Function], + }, + ], + }, +] +`; + +exports[`Alchemy Transport Tests > should correctly create a split transport 2`] = ` +[ + { + "fallback": [Function], + "overrides": [ + { + "methods": [ + "eth_sendUserOperation", + "eth_estimateUserOperationGas", + "eth_getUserOperationReceipt", + "eth_getUserOperationByHash", + "eth_supportedEntryPoints", + "rundler_maxPriorityFeePerGas", + "pm_getPaymasterData", + "pm_getPaymasterStubData", + ], + "transport": [Function], + }, + ], + }, +] +`; + +exports[`Alchemy Transport Tests > should correctly create a split transport 3`] = ` +[ + { + "fallback": [Function], + "overrides": [ + { + "methods": [ + "eth_sendUserOperation", + "eth_estimateUserOperationGas", + "eth_getUserOperationReceipt", + "eth_getUserOperationByHash", + "eth_supportedEntryPoints", + "rundler_maxPriorityFeePerGas", + "pm_getPaymasterData", + "pm_getPaymasterStubData", + ], + "transport": [Function], + }, + ], + }, +] +`; + +exports[`Alchemy Transport Tests > should correctly create a split transport 4`] = ` +[ + { + "fallback": [Function], + "overrides": [ + { + "methods": [ + "eth_sendUserOperation", + "eth_estimateUserOperationGas", + "eth_getUserOperationReceipt", + "eth_getUserOperationByHash", + "eth_supportedEntryPoints", + "rundler_maxPriorityFeePerGas", + "pm_getPaymasterData", + "pm_getPaymasterStubData", + ], + "transport": [Function], + }, + ], + }, +] +`; diff --git a/account-kit/infra/src/alchemyTransport.test.ts b/account-kit/infra/src/alchemyTransport.test.ts new file mode 100644 index 0000000000..abc5a8687c --- /dev/null +++ b/account-kit/infra/src/alchemyTransport.test.ts @@ -0,0 +1,49 @@ +import * as AACoreModule from "@aa-sdk/core"; +import { avalanche } from "viem/chains"; +import { alchemy } from "./alchemyTransport.js"; +import { sepolia } from "./chains.js"; + +describe("Alchemy Transport Tests", () => { + it.each([ + { rpcUrl: "/api" }, + { jwt: "test" }, + { apiKey: "key" }, + { rpcUrl: "/api", jwt: "jwt" }, + ])("should successfully create a non-split transport", (args) => { + expect(() => + alchemy({ + ...args, + }) + ).not.toThrowError(); + }); + + it.each([ + { rpcUrl: "/api" }, + { jwt: "test" }, + { apiKey: "key" }, + { rpcUrl: "/api", jwt: "jwt" }, + ])("should correctly create a split transport", (args) => { + const splitSpy = vi.spyOn(AACoreModule, "split"); + alchemy({ + alchemyConnection: args, + nodeRpcUrl: "/test", + })({ chain: sepolia }); + + expect(splitSpy.mock.calls.length).toBe(1); + expect(splitSpy.mock.calls[0]).toMatchSnapshot(); + }); + + it("should correctly do runtime validation when chain is not supported by Alchemy", () => { + expect(() => alchemy({ rpcUrl: "/test" })({ chain: avalanche })) + .toThrowErrorMatchingInlineSnapshot(` + [ZodError: [ + { + "code": "custom", + "message": "chain must include an alchemy rpc url. See \`createAlchemyChain\` or import a chain from \`@account-kit/infra\`.", + "fatal": true, + "path": [] + } + ]] + `); + }); +}); diff --git a/account-kit/infra/src/alchemyTransport.ts b/account-kit/infra/src/alchemyTransport.ts new file mode 100644 index 0000000000..cf3cf94d67 --- /dev/null +++ b/account-kit/infra/src/alchemyTransport.ts @@ -0,0 +1,214 @@ +import { + ChainNotFoundError, + ConnectionConfigSchema, + split, + type ConnectionConfig, + type NoUndefined, +} from "@aa-sdk/core"; +import { + createTransport, + http, + type EIP1193RequestFn, + type HttpTransportConfig, + type PublicRpcSchema, + type Transport, + type TransportConfig, +} from "viem"; +import type { AlchemyRpcSchema } from "./client/types.js"; +import { AlchemyChainSchema } from "./schema.js"; +import { VERSION } from "./version.js"; + +type Never = T extends object + ? { + [K in keyof T]?: never; + } + : never; + +type SplitTransportConfig = { + alchemyConnection: ConnectionConfig; + nodeRpcUrl: string; +}; + +const alchemyMethods = [ + "eth_sendUserOperation", + "eth_estimateUserOperationGas", + "eth_getUserOperationReceipt", + "eth_getUserOperationByHash", + "eth_supportedEntryPoints", + "rundler_maxPriorityFeePerGas", + "pm_getPaymasterData", + "pm_getPaymasterStubData", +]; + +export type AlchemyTransportConfig = ( + | (ConnectionConfig & Never) + | (SplitTransportConfig & Never) +) & { + /** The max number of times to retry. */ + retryCount?: TransportConfig["retryCount"] | undefined; + /** The base delay (in ms) between retries. */ + retryDelay?: TransportConfig["retryDelay"] | undefined; + fetchOptions?: NoUndefined; +}; + +type AlchemyTransportBase = Transport< + "alchemy", + { + alchemyRpcUrl: string; + fetchOptions?: AlchemyTransportConfig["fetchOptions"]; + }, + EIP1193RequestFn<[...PublicRpcSchema, ...AlchemyRpcSchema]> +>; + +export type AlchemyTransport = AlchemyTransportBase & { + updateHeaders(newHeaders: HeadersInit): void; + config: AlchemyTransportConfig; +}; + +/** + * 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. + * If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl. + * + * @example + * ### Basic Example + * If the chain you're using is supported for both Bundler and Node RPCs, then you can do the following: + * ```ts + * import { alchemy } from "@account-kit/infra"; + * + * const transport = alchemy({ + * // NOTE: you can also pass in an rpcUrl or jwt here or rpcUrl and jwt + * apiKey: "your-api-key", + * }); + * ``` + * + * ### AA Only Chains + * For AA-only chains, you need to specify the alchemyConnection and nodeRpcUrl since Alchemy only + * handles the Bundler and Paymaster RPCs for these chains. + * ```ts + * import { alchemy } from "@account-kit/infra"; + * + * const transport = alchemy({ + * alchemyConnection: { + * apiKey: "your-api-key", + * }, + * nodeRpcUrl: "https://zora.rpc.url", + * }); + * ``` + * + * @param {AlchemyTransportConfig} config The configuration object for the Alchemy transport. + * @param {number} config.retryDelay Optional The delay between retries, in milliseconds. + * @param {number} config.retryCount Optional The number of retry attempts. + * @param {string} [config.alchemyConnection] Optional Alchemy connection configuration (if this is passed in, nodeRpcUrl is required). + * @param {string} [config.fetchOptions] Optional fetch options for HTTP requests. + * @param {string} [config.nodeRpcUrl] Optional RPC URL for node (if this is passed in, alchemyConnection is required). + * @param {string} [config.rpcUrl] Optional RPC URL. + * @param {string} [config.apiKey] Optional API key for Alchemy. + * @param {string} [config.jwt] Optional JSON Web Token for authorization. + * @returns {AlchemyTransport} The configured Alchemy transport object. + */ +export function alchemy(config: AlchemyTransportConfig): AlchemyTransport { + const { retryDelay, retryCount } = config; + // we create a copy here in case we create a split transport down below + // we don't want to add alchemy headers to 3rd party nodes + const fetchOptions = { ...config.fetchOptions }; + + const connectionConfig = ConnectionConfigSchema.parse( + config.alchemyConnection ?? config + ); + + const headersAsObject = convertHeadersToObject(fetchOptions.headers); + + // TODO: we probably should just log these headers during telemetry logging instead of doing this mutable header stuff + fetchOptions.headers = { + ...headersAsObject, + "Alchemy-AA-Sdk-Version": VERSION, + }; + + if (connectionConfig.jwt != null) { + fetchOptions.headers = { + ...fetchOptions.headers, + Authorization: `Bearer ${connectionConfig.jwt}`, + }; + } + + const transport: AlchemyTransportBase = (opts) => { + const { chain: chain_ } = opts; + if (!chain_) { + throw new ChainNotFoundError(); + } + const chain = AlchemyChainSchema.parse(chain_); + + const rpcUrl = + connectionConfig.rpcUrl == null + ? `${chain.rpcUrls.alchemy.http[0]}/${connectionConfig.apiKey ?? ""}` + : connectionConfig.rpcUrl; + + const innerTransport = (() => { + if (config.alchemyConnection && config.nodeRpcUrl) { + return split({ + overrides: [ + { + methods: alchemyMethods, + transport: http(rpcUrl, { fetchOptions }), + }, + ], + fallback: http(config.nodeRpcUrl, { + fetchOptions: config.fetchOptions, + }), + }); + } + + return http(rpcUrl, { fetchOptions }); + })(); + + return createTransport( + { + key: "alchemy", + name: "Alchemy Transport", + request: innerTransport(opts).request, + retryCount: retryCount ?? opts?.retryCount, + retryDelay, + type: "alchemy", + }, + { alchemyRpcUrl: rpcUrl, fetchOptions } + ); + }; + + return Object.assign(transport, { + updateHeaders(newHeaders_: HeadersInit) { + const newHeaders = convertHeadersToObject(newHeaders_); + + fetchOptions.headers = { + ...fetchOptions.headers, + ...newHeaders, + }; + }, + config, + }); +} + +const convertHeadersToObject = ( + headers?: HeadersInit +): Record => { + if (!headers) { + return {}; + } + + if (headers instanceof Headers) { + const headersObject = {} as Record; + headers.forEach((value, key) => { + headersObject[key] = value; + }); + return headersObject; + } + + if (Array.isArray(headers)) { + return headers.reduce((acc, header) => { + acc[header[0]] = header[1]; + return acc; + }, {} as Record); + } + + return headers; +}; diff --git a/account-kit/infra/src/client/decorators/alchemyEnhancedApis.ts b/account-kit/infra/src/client/decorators/alchemyEnhancedApis.ts index f307d02fe1..b7dd677e0f 100644 --- a/account-kit/infra/src/client/decorators/alchemyEnhancedApis.ts +++ b/account-kit/infra/src/client/decorators/alchemyEnhancedApis.ts @@ -1,6 +1,6 @@ import type { SmartContractAccount } from "@aa-sdk/core"; import type { Alchemy } from "alchemy-sdk"; -import type { Chain, HttpTransport, Transport } from "viem"; +import type { Chain } from "viem"; import { AlchemySdkClientSchema } from "../../schema.js"; import type { AlchemySmartAccountClient } from "../smartAccountClient.js"; @@ -34,30 +34,23 @@ export type AlchemyEnhancedApis = { export function alchemyEnhancedApiActions( alchemy: Alchemy ): < - TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SmartContractAccount | undefined = | SmartContractAccount | undefined >( - client: AlchemySmartAccountClient + client: AlchemySmartAccountClient ) => AlchemyEnhancedApis { return (client) => { const alchemySdk = AlchemySdkClientSchema.parse(alchemy); - if (client.transport.type === "http") { - const { url } = client.transport as ReturnType["config"] & - ReturnType["value"]; - - if ( - client.transport.type === "http" && - alchemy.config.url && - alchemy.config.url !== url - ) { - throw new Error( - "Alchemy SDK client JSON-RPC URL must match AlchemyProvider JSON-RPC URL" - ); - } + if ( + alchemy.config.url && + alchemy.config.url !== client.transport.alchemyRpcUrl + ) { + throw new Error( + "Alchemy SDK client JSON-RPC URL must match AlchemyProvider JSON-RPC URL" + ); } return { diff --git a/account-kit/infra/src/client/internal/smartAccountClientFromRpc.ts b/account-kit/infra/src/client/internal/smartAccountClientFromRpc.ts deleted file mode 100644 index 1dba5d96d3..0000000000 --- a/account-kit/infra/src/client/internal/smartAccountClientFromRpc.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { - createSmartAccountClientFromExisting, - getDefaultUserOperationFeeOptions, - isSmartAccountWithSigner, - type SmartContractAccount, - type SmartContractAccountWithSigner, - type UserOperationContext, -} from "@aa-sdk/core"; -import type { Chain, CustomTransport, Transport } from "viem"; -import { alchemyFeeEstimator } from "../../middleware/feeEstimator.js"; -import { alchemyGasManagerMiddleware } from "../../middleware/gasManager.js"; -import { alchemyUserOperationSimulator } from "../../middleware/userOperationSimulator.js"; -import { alchemyActions } from "../decorators/smartAccount.js"; -import type { - AlchemySmartAccountClient, - AlchemySmartAccountClientConfig, -} from "../smartAccountClient.js"; -import type { ClientWithAlchemyMethods } from "../types.js"; - -export type CreateAlchemySmartAccountClientFromRpcClientParams< - TAccount extends SmartContractAccount | undefined = - | SmartContractAccount - | undefined, - TContext extends UserOperationContext | undefined = - | UserOperationContext - | undefined -> = Omit< - AlchemySmartAccountClientConfig, - "rpcUrl" | "chain" | "apiKey" | "jwt" -> & { client: ClientWithAlchemyMethods }; - -export function getSignerTypeHeader< - TAccount extends SmartContractAccountWithSigner ->(account: TAccount) { - return { "Alchemy-Aa-Sdk-Signer": account.getSigner().signerType }; -} - -/** - * Helper method meant to be used internally to create an alchemy smart account client - * from an existing Alchemy Rpc Client - * - * @param {CreateAlchemySmartAccountClientFromRpcClientParams} args configuration for the client - * @returns {AlchemySmartAccountClient} a smart account client configured to use Alchemy's RPC - */ -export function createAlchemySmartAccountClientFromRpcClient< - TChain extends Chain | undefined = Chain | undefined, - TAccount extends SmartContractAccount | undefined = - | SmartContractAccount - | undefined, - TContext extends UserOperationContext | undefined = - | UserOperationContext - | undefined ->( - args: CreateAlchemySmartAccountClientFromRpcClientParams -): AlchemySmartAccountClient< - CustomTransport, - TChain, - TAccount, - Record, - TContext ->; - -/** - * Creates an AlchemySmartAccountClient using the provided RPC client configuration, including options, account, simulation settings, gas management, fee estimation, middleware and user operation signing. - * - * @example - * ```ts - * import { createAlchemySmartAccountClientFromRpcClient, createAlchemyPublicRpcClient } from "@account-kit/infra"; - * - * const client = createAlchemyPublicRpcClient(...); - * const scaClient = createAlchemySmartAccountClientFromRpcClient({ - * client, - * ... - * }); - * ``` - * - * @param {CreateAlchemySmartAccountClientFromRpcClientParams} args The configuration object containing all required parameters - * @returns {AlchemySmartAccountClient} An instance of AlchemySmartAccountClient - */ -export function createAlchemySmartAccountClientFromRpcClient( - args: CreateAlchemySmartAccountClientFromRpcClientParams -): AlchemySmartAccountClient { - const { - opts, - account, - useSimulation, - policyId, - feeEstimator, - gasEstimator, - customMiddleware, - signUserOperation, - client, - } = args; - const feeOptions = - opts?.feeOptions ?? getDefaultUserOperationFeeOptions(client.chain); - - const scaClient = createSmartAccountClientFromExisting({ - account, - client, - type: "AlchemySmartAccountClient", - opts: { - ...opts, - feeOptions, - }, - customMiddleware: async (struct, args) => { - if (isSmartAccountWithSigner(args.account)) { - client.updateHeaders(getSignerTypeHeader(args.account)); - } - return customMiddleware ? customMiddleware(struct, args) : struct; - }, - feeEstimator: feeEstimator ?? alchemyFeeEstimator(client), - userOperationSimulator: useSimulation - ? alchemyUserOperationSimulator(client) - : undefined, - gasEstimator, - ...(policyId && alchemyGasManagerMiddleware(policyId)), - signUserOperation, - }).extend(alchemyActions); - - if (account && isSmartAccountWithSigner(account)) { - client.updateHeaders(getSignerTypeHeader(account)); - } - - return scaClient; -} diff --git a/account-kit/infra/src/client/isAlchemySmartAccountClient.ts b/account-kit/infra/src/client/isAlchemySmartAccountClient.ts index 71b39083d3..94cc65ff98 100644 --- a/account-kit/infra/src/client/isAlchemySmartAccountClient.ts +++ b/account-kit/infra/src/client/isAlchemySmartAccountClient.ts @@ -1,4 +1,4 @@ -import { isSmartAccountClient, type SmartContractAccount } from "@aa-sdk/core"; +import { type SmartContractAccount } from "@aa-sdk/core"; import type { Chain, Client, Transport } from "viem"; import type { AlchemySmartAccountClient } from "./smartAccountClient"; @@ -18,17 +18,12 @@ import type { AlchemySmartAccountClient } from "./smartAccountClient"; * @returns {boolean} `true` if the client is an Alchemy Smart Account Client, otherwise `false` */ export function isAlchemySmartAccountClient< - TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SmartContractAccount | undefined = | SmartContractAccount | undefined >( - client: Client -): client is AlchemySmartAccountClient { - // TODO: the goal of this check is to make sure that the client supports certain RPC methods - // we should probably do this by checking the client's transport and configured URL, since alchemy - // clients have to be RPC clients. this is difficult to do though because the transport might - // point to a proxy url :/ - return isSmartAccountClient(client); + client: Client +): client is AlchemySmartAccountClient { + return client.transport.type === "alchemy"; } diff --git a/account-kit/infra/src/client/rpcClient.ts b/account-kit/infra/src/client/rpcClient.ts index 0e10294fd3..bffd1f5b33 100644 --- a/account-kit/infra/src/client/rpcClient.ts +++ b/account-kit/infra/src/client/rpcClient.ts @@ -1,11 +1,6 @@ -import { - createBundlerClient, - type ConnectionConfig, - type NoUndefined, -} from "@aa-sdk/core"; -import { http, type Chain, type HttpTransportConfig } from "viem"; -import { AlchemyChainSchema } from "../schema.js"; -import { VERSION } from "../version.js"; +import { createBundlerClient } from "@aa-sdk/core"; +import type { Chain } from "viem"; +import type { AlchemyTransport } from "../alchemyTransport.js"; import type { ClientWithAlchemyMethods } from "./types.js"; /** @@ -13,14 +8,14 @@ import type { ClientWithAlchemyMethods } from "./types.js"; * * @example * ```ts - * import { createAlchemyPublicRpcClient } from "@account-kit/infra"; + * import { createAlchemyPublicRpcClient, alchemy } from "@account-kit/infra"; * import { sepolia } from "@account-kit/infra"; * * const client = createAlchemyPublicRpcClient({ + * transport: alchemy({ + * apiKey: "ALCHEMY_API_KEY" + * }), * chain: sepolia, - * connectionConfig: { - * apiKey: "your-api-key", - * } * }); * ``` * @@ -31,42 +26,14 @@ import type { ClientWithAlchemyMethods } from "./types.js"; * @returns {ClientWithAlchemyMethods} A client object tailored with Alchemy methods and capabilities to interact with the blockchain */ export const createAlchemyPublicRpcClient = ({ - chain: chain_, - connectionConfig, - fetchOptions = {}, + transport, + chain, }: { - connectionConfig: ConnectionConfig; - chain: Chain; - fetchOptions?: NoUndefined; + transport: AlchemyTransport; + chain: Chain | undefined; }): ClientWithAlchemyMethods => { - const chain = AlchemyChainSchema.parse(chain_); - - const rpcUrl = - connectionConfig.rpcUrl == null - ? `${chain.rpcUrls.alchemy.http[0]}/${connectionConfig.apiKey ?? ""}` - : connectionConfig.rpcUrl; - - fetchOptions.headers = { - ...fetchOptions.headers, - "Alchemy-AA-Sdk-Version": VERSION, - }; - - if (connectionConfig.jwt != null) { - fetchOptions.headers = { - ...fetchOptions.headers, - Authorization: `Bearer ${connectionConfig.jwt}`, - }; - } - return createBundlerClient({ - chain: chain, - transport: http(rpcUrl, { fetchOptions }), - }).extend(() => ({ - updateHeaders(newHeaders: HeadersInit) { - fetchOptions.headers = { - ...fetchOptions.headers, - ...newHeaders, - }; - }, - })); + chain, + transport, + }); }; diff --git a/account-kit/infra/src/client/smartAccountClient.test.ts b/account-kit/infra/src/client/smartAccountClient.test.ts index 9be7fd612f..d6a0389137 100644 --- a/account-kit/infra/src/client/smartAccountClient.test.ts +++ b/account-kit/infra/src/client/smartAccountClient.test.ts @@ -6,6 +6,7 @@ import { import { createLightAccount } from "@account-kit/smart-contracts"; import { http, zeroAddress } from "viem"; import { generatePrivateKey } from "viem/accounts"; +import { alchemy } from "../alchemyTransport.js"; import { sepolia } from "../chains.js"; import { createAlchemySmartAccountClient } from "./smartAccountClient.js"; @@ -173,9 +174,11 @@ describe("AlchemySmartAccountClient tests", () => { customMiddleware?: ClientMiddlewareFn; } = {}) => { return createAlchemySmartAccountClient({ - rpcUrl: "https://localhost:3000", - account, + transport: alchemy({ + rpcUrl: "https://localhost:3000", + }), chain: sepolia, + account, customMiddleware, }); }; diff --git a/account-kit/infra/src/client/smartAccountClient.ts b/account-kit/infra/src/client/smartAccountClient.ts index d50e82973c..420eff3cab 100644 --- a/account-kit/infra/src/client/smartAccountClient.ts +++ b/account-kit/infra/src/client/smartAccountClient.ts @@ -1,24 +1,36 @@ import { + ChainNotFoundError, + createSmartAccountClient, + isSmartAccountWithSigner, type Prettify, type SmartAccountClient, type SmartAccountClientActions, type SmartAccountClientConfig, type SmartAccountClientRpcSchema, type SmartContractAccount, + type SmartContractAccountWithSigner, type UserOperationContext, } from "@aa-sdk/core"; -import { type Chain, type Transport } from "viem"; +import { type Chain } from "viem"; +import type { AlchemyTransport } from "../alchemyTransport.js"; import { getDefaultUserOperationFeeOptions } from "../defaults.js"; -import { AlchemyProviderConfigSchema } from "../schema.js"; -import type { AlchemyProviderConfig } from "../type.js"; -import type { AlchemySmartAccountClientActions } from "./decorators/smartAccount.js"; -import { createAlchemySmartAccountClientFromRpcClient } from "./internal/smartAccountClientFromRpc.js"; -import { createAlchemyPublicRpcClient } from "./rpcClient.js"; +import { alchemyFeeEstimator } from "../middleware/feeEstimator.js"; +import { alchemyGasManagerMiddleware } from "../middleware/gasManager.js"; +import { alchemyUserOperationSimulator } from "../middleware/userOperationSimulator.js"; +import { + alchemyActions, + type AlchemySmartAccountClientActions, +} from "./decorators/smartAccount.js"; import type { AlchemyRpcSchema } from "./types.js"; +export function getSignerTypeHeader< + TAccount extends SmartContractAccountWithSigner +>(account: TAccount) { + return { "Alchemy-Aa-Sdk-Signer": account.getSigner().signerType }; +} + // #region AlchemySmartAccountClientConfig export type AlchemySmartAccountClientConfig< - transport extends Transport = Transport, chain extends Chain | undefined = Chain | undefined, account extends SmartContractAccount | undefined = | SmartContractAccount @@ -30,11 +42,16 @@ export type AlchemySmartAccountClientConfig< account?: account; useSimulation?: boolean; policyId?: string; -} & AlchemyProviderConfig & - Pick< - SmartAccountClientConfig, - "customMiddleware" | "feeEstimator" | "gasEstimator" | "signUserOperation" - >; +} & Pick< + SmartAccountClientConfig, + | "customMiddleware" + | "feeEstimator" + | "gasEstimator" + | "signUserOperation" + | "transport" + | "chain" + | "opts" +>; // #endregion AlchemySmartAccountClientConfig export type BaseAlchemyActions< @@ -49,7 +66,6 @@ export type BaseAlchemyActions< AlchemySmartAccountClientActions; export type AlchemySmartAccountClient_Base< - transport extends Transport = Transport, chain extends Chain | undefined = Chain | undefined, account extends SmartContractAccount | undefined = | SmartContractAccount @@ -60,7 +76,7 @@ export type AlchemySmartAccountClient_Base< | undefined > = Prettify< SmartAccountClient< - transport, + AlchemyTransport, chain, account, actions & BaseAlchemyActions, @@ -70,7 +86,6 @@ export type AlchemySmartAccountClient_Base< >; export type AlchemySmartAccountClient< - transport extends Transport = Transport, chain extends Chain | undefined = Chain | undefined, account extends SmartContractAccount | undefined = | SmartContractAccount @@ -79,12 +94,9 @@ export type AlchemySmartAccountClient< context extends UserOperationContext | undefined = | UserOperationContext | undefined -> = Prettify< - AlchemySmartAccountClient_Base ->; +> = Prettify>; export function createAlchemySmartAccountClient< - TTransport extends Transport = Transport, TChain extends Chain = Chain, TAccount extends SmartContractAccount | undefined = | SmartContractAccount @@ -92,39 +104,21 @@ export function createAlchemySmartAccountClient< TContext extends UserOperationContext | undefined = | UserOperationContext | undefined ->({ - account, - policyId, - useSimulation, - feeEstimator, - customMiddleware, - gasEstimator, - signUserOperation, - ...config_ -}: AlchemySmartAccountClientConfig< - TTransport, - TChain, - TAccount, - TContext ->): AlchemySmartAccountClient< - TTransport, - TChain, - TAccount, - Record, - TContext ->; +>( + params: AlchemySmartAccountClientConfig +): AlchemySmartAccountClient, TContext>; /** * Creates an Alchemy smart account client using the provided configuration options, including account details, gas manager configuration, and custom middleware. * * @example * ```ts - * import { createAlchemySmartAccountClient } from "@account-kit/infra"; + * import { createAlchemySmartAccountClient, alchemy } from "@account-kit/infra"; * import { sepolia } from "@account-kit/infra/chain"; * * const client = createAlchemySmartAccountClient({ * chain: sepolia, - * apiKey: "your-api-key", + * transport: alchemy({ apiKey: "your-api-key" }), * }); * ``` * @@ -139,31 +133,44 @@ export function createAlchemySmartAccountClient({ customMiddleware, gasEstimator, signUserOperation, - ...config_ + transport, + chain, + opts, }: AlchemySmartAccountClientConfig): AlchemySmartAccountClient { - const config = AlchemyProviderConfigSchema.parse(config_); - const { chain, opts, ...connectionConfig } = config; - - const client = createAlchemyPublicRpcClient({ - chain, - connectionConfig, - }); + if (!chain) { + throw new ChainNotFoundError(); + } const feeOptions = opts?.feeOptions ?? getDefaultUserOperationFeeOptions(chain); - return createAlchemySmartAccountClientFromRpcClient({ - client, + const scaClient = createSmartAccountClient({ account, + transport, + chain, + type: "AlchemySmartAccountClient", opts: { ...opts, feeOptions, }, - policyId, - useSimulation, - feeEstimator, - customMiddleware, + customMiddleware: async (struct, args) => { + if (isSmartAccountWithSigner(args.account)) { + transport.updateHeaders(getSignerTypeHeader(args.account)); + } + return customMiddleware ? customMiddleware(struct, args) : struct; + }, + feeEstimator: feeEstimator ?? alchemyFeeEstimator(transport), + userOperationSimulator: useSimulation + ? alchemyUserOperationSimulator(transport) + : undefined, gasEstimator, + ...(policyId && alchemyGasManagerMiddleware(policyId)), signUserOperation, - }); + }).extend(alchemyActions); + + if (account && isSmartAccountWithSigner(account)) { + transport.updateHeaders(getSignerTypeHeader(account)); + } + + return scaClient; } diff --git a/account-kit/infra/src/client/types.ts b/account-kit/infra/src/client/types.ts index 1b73202f57..e597df4465 100644 --- a/account-kit/infra/src/client/types.ts +++ b/account-kit/infra/src/client/types.ts @@ -1,9 +1,13 @@ -import { type BundlerClient, type UserOperationRequest } from "@aa-sdk/core"; -import type { HttpTransport } from "viem"; +import { + type BundlerClient, + type Erc7677RpcSchema, + type UserOperationRequest, +} from "@aa-sdk/core"; import type { SimulateUserOperationAssetChangesRequest, SimulateUserOperationAssetChangesResponse, } from "../actions/types"; +import type { AlchemyTransport } from "../alchemyTransport"; export type AlchemyRpcSchema = [ { @@ -15,11 +19,12 @@ export type AlchemyRpcSchema = [ Method: "rundler_maxPriorityFeePerGas"; Parameters: []; ReturnType: UserOperationRequest["maxPriorityFeePerGas"]; - } + }, + ...Erc7677RpcSchema<{ policyId: string }> ]; -export type ClientWithAlchemyMethods = BundlerClient & { - request: BundlerClient["request"] & +export type ClientWithAlchemyMethods = BundlerClient & { + request: BundlerClient["request"] & { request(args: { method: "alchemy_simulateUserOperationAssetChanges"; @@ -31,6 +36,4 @@ export type ClientWithAlchemyMethods = BundlerClient & { params: []; }): Promise; }["request"]; -} & { - updateHeaders: (headers: HeadersInit) => void; }; diff --git a/account-kit/infra/src/index.ts b/account-kit/infra/src/index.ts index dbef1efce7..4aae2cd140 100644 --- a/account-kit/infra/src/index.ts +++ b/account-kit/infra/src/index.ts @@ -1,6 +1,8 @@ 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 type * from "./chains.js"; export { arbitrum, @@ -34,7 +36,6 @@ export type * from "./client/decorators/alchemyEnhancedApis.js"; export { alchemyEnhancedApiActions } from "./client/decorators/alchemyEnhancedApis.js"; export type * from "./client/decorators/smartAccount.js"; export { alchemyActions } from "./client/decorators/smartAccount.js"; -export { createAlchemySmartAccountClientFromRpcClient as createAlchemySmartAccountClientFromExisting } from "./client/internal/smartAccountClientFromRpc.js"; export { isAlchemySmartAccountClient } from "./client/isAlchemySmartAccountClient.js"; export type * from "./client/rpcClient.js"; export { createAlchemyPublicRpcClient } from "./client/rpcClient.js"; @@ -48,5 +49,3 @@ export type * from "./middleware/gasManager.js"; export { alchemyGasManagerMiddleware } from "./middleware/gasManager.js"; export { alchemyUserOperationSimulator } from "./middleware/userOperationSimulator.js"; export type * from "./schema.js"; -export { AlchemyProviderConfigSchema } from "./schema.js"; -export type { AlchemyProviderConfig } from "./type.js"; diff --git a/account-kit/infra/src/middleware/feeEstimator.ts b/account-kit/infra/src/middleware/feeEstimator.ts index 38c311ff40..915b967b63 100644 --- a/account-kit/infra/src/middleware/feeEstimator.ts +++ b/account-kit/infra/src/middleware/feeEstimator.ts @@ -1,6 +1,6 @@ import type { ClientMiddlewareFn } from "@aa-sdk/core"; import { applyUserOpOverrideOrFeeOption } from "@aa-sdk/core"; -import type { ClientWithAlchemyMethods } from "../client/types"; +import type { AlchemyTransport } from "../alchemyTransport"; /** * Function that estimates the transaction fees using Alchemy methods for a given client. @@ -8,28 +8,33 @@ import type { ClientWithAlchemyMethods } from "../client/types"; * * @example * ```ts - * import { alchemyFeeEstimator, createAlchemyPublicRpcClient } from "@account-kit/infra"; + * import { alchemyFeeEstimator, alchemy } from "@account-kit/infra"; * import { createSmartAccountClient } from "@aa-sdk/core"; * - * const bundlerClient = createAlchemyPublicRpcClient(...); + * const alchemyTransport = alchemy({ + * chain: sepolia, + * apiKey: "your-api-key" + * }); + * * const client = createSmartAccountClient({ - * feeEstimator: alchemyFeeEstimator(bundlerClient), + * feeEstimator: alchemyFeeEstimator(alchemyTransport), * ...otherParams * }); * ``` * - * @param {ClientWithAlchemyMethods} client The client with Alchemy methods + * @param {AlchemyTransport} transport An alchemy transport for making Alchemy specific RPC calls * @returns {ClientMiddlewareFn} A middleware function that takes a transaction structure and fee options, and returns the augmented structure with estimated fees */ -export const alchemyFeeEstimator: ( - client: C +export const alchemyFeeEstimator: ( + transport: AlchemyTransport ) => ClientMiddlewareFn = - (client) => - async (struct, { overrides, feeOptions }) => { + (transport) => + async (struct, { overrides, feeOptions, client }) => { + const transport_ = transport({ chain: client.chain }); let [block, maxPriorityFeePerGasEstimate] = await Promise.all([ client.getBlock({ blockTag: "latest" }), // it is a fair assumption that if someone is using this Alchemy Middleware, then they are using Alchemy RPC - client.request({ + transport_.request({ method: "rundler_maxPriorityFeePerGas", params: [], }), diff --git a/account-kit/infra/src/middleware/gasManager.ts b/account-kit/infra/src/middleware/gasManager.ts index 282f7f3229..8236665b2a 100644 --- a/account-kit/infra/src/middleware/gasManager.ts +++ b/account-kit/infra/src/middleware/gasManager.ts @@ -6,15 +6,14 @@ import { erc7677Middleware } from "@aa-sdk/core"; * * @example * ```ts - * - * import { sepolia } from "@account-kit/infra"; + * import { sepolia, alchemyErc7677Middleware } from "@account-kit/infra"; * import { http } from "viem"; * * const client = createSmartAccountClient({ - * http("rpc-url"), - * sepolia, - * alchemyErc7677Middleware("policyId") - * ); + * transport: http("rpc-url"), + * chain: sepolia, + * ...alchemyErc7677Middleware("policyId") + * }); * ``` * * @param {string} policyId the policyId for Alchemy's gas manager diff --git a/account-kit/infra/src/middleware/userOperationSimulator.ts b/account-kit/infra/src/middleware/userOperationSimulator.ts index b0cbf234b1..22ec0a4c57 100644 --- a/account-kit/infra/src/middleware/userOperationSimulator.ts +++ b/account-kit/infra/src/middleware/userOperationSimulator.ts @@ -4,35 +4,38 @@ import { type ClientMiddlewareFn, type UserOperationContext, } from "@aa-sdk/core"; -import type { ClientWithAlchemyMethods } from "../client/types"; +import type { AlchemyTransport } from "../alchemyTransport"; /** * A middleware function to be used during simulation of user operations which leverages Alchemy's RPC uo simulation method. * * @example * ```ts - * import { alchemyUserOperationSimulator, createAlchemyPublicRpcClient } from "@account-kit/infra"; + * import { alchemyUserOperationSimulator, alchemy, sepolia } from "@account-kit/infra"; * import { createSmartAccountClient } from "@aa-sdk/core"; * - * const bundlerClient = createAlchemyPublicRpcClient(...); + * const alchemyTransport = alchemy({ + * chain: sepolia, + * apiKey: "your-api-key" + * }); + * * const client = createSmartAccountClient({ - * userOperationSimulator: alchemyUserOperationSimulator(bundlerClient), + * chain: sepolia, + * userOperationSimulator: alchemyUserOperationSimulator(alchemyTransport), * ...otherParams * }); * ``` * - * @template C The client object with Alchemy methods - * @param {C} client The client object with Alchemy methods + * @param {AlchemyTransport} transport An Alchemy Transport that can be used for making RPC calls to alchemy * @returns {ClientMiddlewareFn} A middleware function to simulate and process user operations */ export function alchemyUserOperationSimulator< - C extends ClientWithAlchemyMethods, TContext extends UserOperationContext | undefined = | UserOperationContext | undefined ->(client: C): ClientMiddlewareFn { - return async (struct, { account }) => { - const uoSimResult = await client.request({ +>(transport: AlchemyTransport): ClientMiddlewareFn { + return async (struct, { account, client }) => { + const uoSimResult = await transport({ chain: client.chain }).request({ method: "alchemy_simulateUserOperationAssetChanges", params: [ deepHexlify(await resolveProperties(struct)), diff --git a/account-kit/infra/src/schema.ts b/account-kit/infra/src/schema.ts index b2d2003f05..b202cebeb5 100644 --- a/account-kit/infra/src/schema.ts +++ b/account-kit/infra/src/schema.ts @@ -1,8 +1,4 @@ -import { - ChainSchema, - ConnectionConfigSchema, - SmartAccountClientOptsSchema, -} from "@aa-sdk/core"; +import { ChainSchema } from "@aa-sdk/core"; import { Alchemy } from "alchemy-sdk"; import type { Chain } from "viem"; import z from "zod"; @@ -13,13 +9,4 @@ export const AlchemyChainSchema = z.custom((chain) => { return chain_.rpcUrls.alchemy != null; }, "chain must include an alchemy rpc url. See `createAlchemyChain` or import a chain from `@account-kit/infra`."); -export const AlchemyProviderConfigSchema = ConnectionConfigSchema.and( - z.object({ - chain: AlchemyChainSchema, - opts: SmartAccountClientOptsSchema.optional().default( - SmartAccountClientOptsSchema.parse({}) - ), - }) -); - export const AlchemySdkClientSchema = z.instanceof(Alchemy); diff --git a/account-kit/infra/src/type.ts b/account-kit/infra/src/type.ts deleted file mode 100644 index d21c0ee41c..0000000000 --- a/account-kit/infra/src/type.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { z } from "zod"; -import type { AlchemyProviderConfigSchema } from "./schema.js"; - -export type AlchemyProviderConfig = z.input; diff --git a/account-kit/react/src/hooks/useClientActions.ts b/account-kit/react/src/hooks/useClientActions.ts index 70e703a207..a634f58b31 100644 --- a/account-kit/react/src/hooks/useClientActions.ts +++ b/account-kit/react/src/hooks/useClientActions.ts @@ -14,11 +14,7 @@ export type UseClientActionsProps< [x: string]: (...args: any[]) => unknown; } > = { - client?: UseSmartAccountClientResult< - TTransport, - TChain, - SupportedAccounts - >["client"]; + client?: UseSmartAccountClientResult["client"]; actions: (client: Client) => TActions; }; diff --git a/account-kit/react/src/hooks/useSmartAccountClient.ts b/account-kit/react/src/hooks/useSmartAccountClient.ts index 309e951cb5..7d02e0a421 100644 --- a/account-kit/react/src/hooks/useSmartAccountClient.ts +++ b/account-kit/react/src/hooks/useSmartAccountClient.ts @@ -12,29 +12,26 @@ import { watchSmartAccountClient, } from "@account-kit/core"; import { useMemo, useSyncExternalStore } from "react"; -import type { Chain, Transport } from "viem"; +import type { Chain } from "viem"; import { useAccount as wagmi_useAccount } from "wagmi"; import { useAlchemyAccountContext } from "../context.js"; export type UseSmartAccountClientProps< - TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SupportedAccountTypes = SupportedAccountTypes -> = GetSmartAccountClientParams; +> = GetSmartAccountClientParams; export type UseSmartAccountClientResult< - TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SupportedAccounts = SupportedAccounts -> = GetSmartAccountClientResult; +> = GetSmartAccountClientResult; export function useSmartAccountClient< - TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SupportedAccountTypes = SupportedAccountTypes >( - args: UseSmartAccountClientProps -): UseSmartAccountClientResult>; + args: UseSmartAccountClientProps +): UseSmartAccountClientResult>; /** * Uses the provided smart account client parameters to create or retrieve an existing smart account client, handling different types of accounts including LightAccount, MultiOwnerLightAccount, and MultiOwnerModularAccount. diff --git a/account-kit/smart-contracts/src/light-account/clients/alchemyClient.test.ts b/account-kit/smart-contracts/src/light-account/clients/alchemyClient.test.ts index f46730c930..22d6247169 100644 --- a/account-kit/smart-contracts/src/light-account/clients/alchemyClient.test.ts +++ b/account-kit/smart-contracts/src/light-account/clients/alchemyClient.test.ts @@ -5,6 +5,7 @@ import { type SmartAccountSigner, } from "@aa-sdk/core"; import { + alchemy, alchemyEnhancedApiActions, createAlchemySmartAccountClient, polygonMumbai, @@ -33,6 +34,7 @@ describe("Light Account Client Tests", () => { }, ` { + "alchemyRpcUrl": "https://polygon-mumbai.g.alchemy.com/v2/", "fetchOptions": { "headers": { "Alchemy-AA-Sdk-Version": Any, @@ -40,14 +42,13 @@ describe("Light Account Client Tests", () => { "Authorization": "Bearer test", }, }, - "key": "http", - "name": "HTTP JSON-RPC", + "key": "alchemy", + "name": "Alchemy Transport", "request": [Function], "retryCount": 3, "retryDelay": 150, - "timeout": 10000, - "type": "http", - "url": "https://polygon-mumbai.g.alchemy.com/v2/", + "timeout": undefined, + "type": "alchemy", } ` ); @@ -80,7 +81,9 @@ describe("Light Account Client Tests", () => { ])("should successfully create a provider", (args) => { expect(() => createAlchemySmartAccountClient({ - ...args, + transport: alchemy({ + ...args, + }), chain, }) ).not.toThrowError(); @@ -89,7 +92,7 @@ describe("Light Account Client Tests", () => { it("should correctly do runtime validation when connection config is invalid", () => { expect(() => createAlchemySmartAccountClient({ - rpcUrl: 1 as unknown as string, + transport: alchemy({ rpcUrl: 1 as unknown as string }), chain, }) ).toThrowErrorMatchingSnapshot(); @@ -104,9 +107,7 @@ describe("Light Account Client Tests", () => { "code": "custom", "message": "chain must include an alchemy rpc url. See \`createAlchemyChain\` or import a chain from \`@account-kit/infra\`.", "fatal": true, - "path": [ - "chain" - ] + "path": [] } ]] `); @@ -136,9 +137,11 @@ describe("Light Account Client Tests", () => { chain: Chain; }) => createLightAccountAlchemyClient({ - jwt: "test", - signer, + transport: alchemy({ + jwt: "test", + }), chain, + signer, accountAddress: "0x86f3B0211764971Ad0Fc8C8898d31f5d792faD84", }); }); diff --git a/account-kit/smart-contracts/src/light-account/clients/alchemyClient.ts b/account-kit/smart-contracts/src/light-account/clients/alchemyClient.ts index b4db13cee0..12b7b8a19f 100644 --- a/account-kit/smart-contracts/src/light-account/clients/alchemyClient.ts +++ b/account-kit/smart-contracts/src/light-account/clients/alchemyClient.ts @@ -1,8 +1,6 @@ import type { HttpTransport, SmartAccountSigner } from "@aa-sdk/core"; import { - AlchemyProviderConfigSchema, - createAlchemyPublicRpcClient, - createAlchemySmartAccountClientFromExisting, + createAlchemySmartAccountClient, type AlchemySmartAccountClient, type AlchemySmartAccountClientConfig, } from "@account-kit/infra"; @@ -13,16 +11,13 @@ import { type LightAccount, type LightAccountClientActions, } from "@account-kit/smart-contracts"; -import { custom, type Chain, type CustomTransport, type Transport } from "viem"; +import { type Chain } from "viem"; export type AlchemyLightAccountClientConfig< TSigner extends SmartAccountSigner = SmartAccountSigner -> = Omit< - CreateLightAccountParams, - "transport" | "chain" -> & +> = Omit, "transport"> & Omit< - AlchemySmartAccountClientConfig>, + AlchemySmartAccountClientConfig>, "account" >; @@ -32,7 +27,6 @@ export async function createLightAccountAlchemyClient< params: AlchemyLightAccountClientConfig ): Promise< AlchemySmartAccountClient< - CustomTransport, Chain | undefined, LightAccount, LightAccountClientActions @@ -45,12 +39,12 @@ export async function createLightAccountAlchemyClient< * @example * ```ts * import { createLightAccountAlchemyClient } from "@account-kit/smart-contracts"; - * import { sepolia } from "@account-kit/infra"; + * import { sepolia, alchemy } from "@account-kit/infra"; * import { LocalAccountSigner } from "@aa-sdk/core"; * import { generatePrivateKey } from "viem" * * const lightAccountClient = await createLightAccountAlchemyClient({ - * apiKey: "your-api-key", + * transport: alchemy({ apiKey: "your-api-key" }), * chain: sepolia, * signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()) * }); @@ -59,25 +53,22 @@ export async function createLightAccountAlchemyClient< * @param {AlchemyLightAccountClientConfig} config The configuration for setting up the Alchemy Light Account Client * @returns {Promise} A promise that resolves to an `AlchemySmartAccountClient` object containing the created client */ -export async function createLightAccountAlchemyClient( - config: AlchemyLightAccountClientConfig -): Promise { - const { chain, opts, ...connectionConfig } = - AlchemyProviderConfigSchema.parse(config); - - const client = createAlchemyPublicRpcClient({ - chain, - connectionConfig, - }); - +export async function createLightAccountAlchemyClient({ + opts, + transport, + chain, + ...config +}: AlchemyLightAccountClientConfig): Promise { const account = await createLightAccount({ - transport: custom(client), ...config, + transport, + chain, }); - return createAlchemySmartAccountClientFromExisting({ + return createAlchemySmartAccountClient({ ...config, - client, + transport, + chain, account, opts, }).extend(lightAccountClientActions); diff --git a/account-kit/smart-contracts/src/light-account/clients/multiOwnerAlchemyClient.test.ts b/account-kit/smart-contracts/src/light-account/clients/multiOwnerAlchemyClient.test.ts index a80c7cddb4..4faff104ab 100644 --- a/account-kit/smart-contracts/src/light-account/clients/multiOwnerAlchemyClient.test.ts +++ b/account-kit/smart-contracts/src/light-account/clients/multiOwnerAlchemyClient.test.ts @@ -5,6 +5,7 @@ import { type SmartAccountSigner, } from "@aa-sdk/core"; import { + alchemy, alchemyEnhancedApiActions, arbitrumSepolia, createAlchemySmartAccountClient, @@ -33,6 +34,7 @@ describe("MultiOwnerLightAccount Client Tests", () => { }, ` { + "alchemyRpcUrl": "https://arb-sepolia.g.alchemy.com/v2/", "fetchOptions": { "headers": { "Alchemy-AA-Sdk-Version": Any, @@ -40,14 +42,13 @@ describe("MultiOwnerLightAccount Client Tests", () => { "Authorization": "Bearer test", }, }, - "key": "http", - "name": "HTTP JSON-RPC", + "key": "alchemy", + "name": "Alchemy Transport", "request": [Function], "retryCount": 3, "retryDelay": 150, - "timeout": 10000, - "type": "http", - "url": "https://arb-sepolia.g.alchemy.com/v2/", + "timeout": undefined, + "type": "alchemy", } ` ); @@ -80,7 +81,9 @@ describe("MultiOwnerLightAccount Client Tests", () => { ])("should successfully create a provider", (args) => { expect(() => createAlchemySmartAccountClient({ - ...args, + transport: alchemy({ + ...args, + }), chain, }) ).not.toThrowError(); @@ -89,7 +92,9 @@ describe("MultiOwnerLightAccount Client Tests", () => { it("should correctly do runtime validation when connection config is invalid", () => { expect(() => createAlchemySmartAccountClient({ - rpcUrl: 1 as unknown as string, + transport: alchemy({ + rpcUrl: 1 as unknown as string, + }), chain, }) ).toThrowErrorMatchingSnapshot(); @@ -104,15 +109,13 @@ describe("MultiOwnerLightAccount Client Tests", () => { "code": "custom", "message": "chain must include an alchemy rpc url. See \`createAlchemyChain\` or import a chain from \`@account-kit/infra\`.", "fatal": true, - "path": [ - "chain" - ] + "path": [] } ]] `); }); - it("should hanve enhanced api properties on the provider", async () => { + it("should have enhanced api properties on the provider", async () => { const alchemy = new Alchemy({ network: Network.MATIC_MUMBAI, apiKey: "test", @@ -136,9 +139,11 @@ describe("MultiOwnerLightAccount Client Tests", () => { chain: Chain; }) => createMultiOwnerLightAccountAlchemyClient({ - jwt: "test", - signer, + transport: alchemy({ + jwt: "test", + }), chain, + signer, accountAddress: "0x86f3B0211764971Ad0Fc8C8898d31f5d792faD84", }); }); diff --git a/account-kit/smart-contracts/src/light-account/clients/multiOwnerAlchemyClient.ts b/account-kit/smart-contracts/src/light-account/clients/multiOwnerAlchemyClient.ts index 058b9922af..86c11ec7f0 100644 --- a/account-kit/smart-contracts/src/light-account/clients/multiOwnerAlchemyClient.ts +++ b/account-kit/smart-contracts/src/light-account/clients/multiOwnerAlchemyClient.ts @@ -1,8 +1,6 @@ import type { HttpTransport, SmartAccountSigner } from "@aa-sdk/core"; import { - AlchemyProviderConfigSchema, - createAlchemyPublicRpcClient, - createAlchemySmartAccountClientFromExisting, + createAlchemySmartAccountClient, type AlchemySmartAccountClient, type AlchemySmartAccountClientConfig, } from "@account-kit/infra"; @@ -13,20 +11,16 @@ import { type MultiOwnerLightAccount, type MultiOwnerLightAccountClientActions, } from "@account-kit/smart-contracts"; -import { custom, type Chain, type CustomTransport, type Transport } from "viem"; +import { type Chain } from "viem"; export type AlchemyMultiOwnerLightAccountClientConfig< TSigner extends SmartAccountSigner = SmartAccountSigner > = Omit< CreateMultiOwnerLightAccountParams, - "transport" | "chain" | "type" + "transport" | "type" > & Omit< - AlchemySmartAccountClientConfig< - Transport, - Chain, - MultiOwnerLightAccount - >, + AlchemySmartAccountClientConfig>, "account" >; @@ -36,7 +30,6 @@ export async function createMultiOwnerLightAccountAlchemyClient< params: AlchemyMultiOwnerLightAccountClientConfig ): Promise< AlchemySmartAccountClient< - CustomTransport, Chain | undefined, MultiOwnerLightAccount, MultiOwnerLightAccountClientActions @@ -49,13 +42,15 @@ export async function createMultiOwnerLightAccountAlchemyClient< * @example * ```ts * import { createMultiOwnerLightAccountAlchemyClient } from "@account-kit/smart-contracts"; - * import { sepolia } from "@account-kit/infra"; + * import { sepolia, alchemy } from "@account-kit/infra"; * import { LocalAccountSigner } from "@aa-sdk/core"; * import { generatePrivateKey } from "viem" * * const lightAccountClient = await createMultiOwnerLightAccountAlchemyClient({ - * apiKey: "your-api-key", - * chain: sepolia, + * transport: alchemy({ + * apiKey: "your-api-key", + * }), + * chain: sepolia * signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()) * }); * ``` @@ -63,25 +58,22 @@ export async function createMultiOwnerLightAccountAlchemyClient< * @param {AlchemyMultiOwnerLightAccountClientConfig} config The configuration for creating the Alchemy client * @returns {Promise} A promise that resolves to an `AlchemySmartAccountClient` object containing the created account information and methods */ -export async function createMultiOwnerLightAccountAlchemyClient( - config: AlchemyMultiOwnerLightAccountClientConfig -): Promise { - const { chain, opts, ...connectionConfig } = - AlchemyProviderConfigSchema.parse(config); - - const client = createAlchemyPublicRpcClient({ - chain, - connectionConfig, - }); - +export async function createMultiOwnerLightAccountAlchemyClient({ + opts, + transport, + chain, + ...config +}: AlchemyMultiOwnerLightAccountClientConfig): Promise { const account = await createMultiOwnerLightAccount({ - transport: custom(client), ...config, + transport, + chain, }); - return createAlchemySmartAccountClientFromExisting({ + return createAlchemySmartAccountClient({ ...config, - client, + transport, + chain, account, opts, }).extend(multiOwnerLightAccountClientActions); diff --git a/account-kit/smart-contracts/src/msca/client/alchemyClient.ts b/account-kit/smart-contracts/src/msca/client/alchemyClient.ts index 6adca7cd34..ef47b8b6a7 100644 --- a/account-kit/smart-contracts/src/msca/client/alchemyClient.ts +++ b/account-kit/smart-contracts/src/msca/client/alchemyClient.ts @@ -1,8 +1,6 @@ import type { SmartAccountSigner } from "@aa-sdk/core"; import { - AlchemyProviderConfigSchema, - createAlchemyPublicRpcClient, - createAlchemySmartAccountClientFromExisting, + createAlchemySmartAccountClient, type AlchemySmartAccountClient, type AlchemySmartAccountClientConfig, } from "@account-kit/infra"; @@ -18,22 +16,16 @@ import { type MultiOwnerPluginActions, type PluginManagerActions, } from "@account-kit/smart-contracts"; -import { - custom, - type Chain, - type CustomTransport, - type HttpTransport, - type Transport, -} from "viem"; +import { type Chain, type HttpTransport } from "viem"; export type AlchemyModularAccountClientConfig< TSigner extends SmartAccountSigner = SmartAccountSigner > = Omit< CreateMultiOwnerModularAccountParams, - "transport" | "chain" + "transport" > & Omit< - AlchemySmartAccountClientConfig>, + AlchemySmartAccountClientConfig>, "account" >; @@ -43,7 +35,6 @@ export function createModularAccountAlchemyClient< params: AlchemyModularAccountClientConfig ): Promise< AlchemySmartAccountClient< - CustomTransport, Chain | undefined, MultiOwnerModularAccount, MultiOwnerPluginActions> & @@ -58,12 +49,12 @@ export function createModularAccountAlchemyClient< * @example * ```ts * import { createModularAccountAlchemyClient } from "@account-kit/smart-contracts"; - * import { sepolia } from "@account-kit/infra"; + * import { sepolia, alchemy } from "@account-kit/infra"; * import { LocalAccountSigner } from "@aa-sdk/core"; * import { generatePrivateKey } from "viem" * * const alchemyAccountClient = await createModularAccountAlchemyClient({ - * apiKey: "your-api-key", + * transport: alchemy({ apiKey: "your-api-key" }), * chain: sepolia, * signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()) * }); @@ -75,22 +66,16 @@ export function createModularAccountAlchemyClient< export async function createModularAccountAlchemyClient( config: AlchemyModularAccountClientConfig ): Promise { - const { chain, opts, ...connectionConfig } = - AlchemyProviderConfigSchema.parse(config); - - const client = createAlchemyPublicRpcClient({ - chain, - connectionConfig, - }); + const { transport, chain, opts } = config; const account = await createMultiOwnerModularAccount({ - transport: custom(client), ...config, + transport, + chain, }); - return createAlchemySmartAccountClientFromExisting({ + return createAlchemySmartAccountClient({ ...config, - client, account, opts, }) diff --git a/account-kit/smart-contracts/src/msca/client/multiSigAlchemyClient.ts b/account-kit/smart-contracts/src/msca/client/multiSigAlchemyClient.ts index 1b9f4bd8ca..96c3cfb465 100644 --- a/account-kit/smart-contracts/src/msca/client/multiSigAlchemyClient.ts +++ b/account-kit/smart-contracts/src/msca/client/multiSigAlchemyClient.ts @@ -3,9 +3,7 @@ import { type SmartAccountSigner, } from "@aa-sdk/core"; import { - AlchemyProviderConfigSchema, - createAlchemyPublicRpcClient, - createAlchemySmartAccountClientFromExisting, + createAlchemySmartAccountClient, type AlchemySmartAccountClient, type AlchemySmartAccountClientConfig, } from "@account-kit/infra"; @@ -23,13 +21,7 @@ import { type MultisigUserOperationContext, type PluginManagerActions, } from "@account-kit/smart-contracts"; -import { - custom, - type Chain, - type CustomTransport, - type HttpTransport, - type Transport, -} from "viem"; +import { type Chain, type HttpTransport } from "viem"; // todo: this file seems somewhat duplicated with ./modularAccountClient.ts, but that file has some multi-owner specific fields. Is there a way to refactor these two to de-dupe? @@ -37,11 +29,10 @@ export type AlchemyMultisigAccountClientConfig< TSigner extends SmartAccountSigner = SmartAccountSigner > = Omit< CreateMultisigModularAccountParams, - "transport" | "chain" + "transport" > & Omit< AlchemySmartAccountClientConfig< - Transport, Chain, LightAccount, MultisigUserOperationContext @@ -55,7 +46,6 @@ export function createMultisigAccountAlchemyClient< params: AlchemyMultisigAccountClientConfig ): Promise< AlchemySmartAccountClient< - CustomTransport, Chain | undefined, MultisigModularAccount, MultisigPluginActions> & @@ -76,7 +66,7 @@ export function createMultisigAccountAlchemyClient< * import { generatePrivateKey } from "viem" * * const alchemyAccountClient = await createMultisigAccountAlchemyClient({ - * apiKey: "your-api-key", + * transport: alchemy({ apiKey: "your-api-key" }), * chain: sepolia, * signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()), * owners: [...], // other owners on the account @@ -91,7 +81,6 @@ export async function createMultisigAccountAlchemyClient( config: AlchemyMultisigAccountClientConfig ): Promise< AlchemySmartAccountClient< - Transport, Chain | undefined, MultisigModularAccount, MultisigPluginActions> & @@ -100,26 +89,16 @@ export async function createMultisigAccountAlchemyClient( MultisigUserOperationContext > > { - const { chain, opts, ...connectionConfig } = - AlchemyProviderConfigSchema.parse(config); - - const client = createAlchemyPublicRpcClient({ - chain, - connectionConfig, - }); + const { transport, opts, chain } = config; const account = await createMultisigModularAccount({ - transport: custom(client), ...config, + transport, + chain, }); - return createAlchemySmartAccountClientFromExisting< - Chain | undefined, - MultisigModularAccount, - MultisigUserOperationContext - >({ + return createAlchemySmartAccountClient({ ...config, - client, account, opts, signUserOperation: multisigSignatureMiddleware, diff --git a/examples/ui-demo/components.json b/examples/ui-demo/components.json index 8c574b77ef..7559f63f10 100644 --- a/examples/ui-demo/components.json +++ b/examples/ui-demo/components.json @@ -14,4 +14,4 @@ "components": "@/components", "utils": "@/lib/utils" } -} \ No newline at end of file +} diff --git a/examples/ui-demo/next.config.mjs b/examples/ui-demo/next.config.mjs index b7790b93b9..926cc28305 100644 --- a/examples/ui-demo/next.config.mjs +++ b/examples/ui-demo/next.config.mjs @@ -1,16 +1,16 @@ await import("./env.mjs"); const nextConfig = { - images: { - remotePatterns: [ - { - protocol: 'https', - hostname: 'static.alchemyapi.io', - port: '', - pathname: '/assets/accountkit/**', - }, - ], - } + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "static.alchemyapi.io", + port: "", + pathname: "/assets/accountkit/**", + }, + ], + }, }; export default nextConfig; diff --git a/examples/ui-demo/src/app/providers.tsx b/examples/ui-demo/src/app/providers.tsx index 5fb4d328e8..8bff379afd 100644 --- a/examples/ui-demo/src/app/providers.tsx +++ b/examples/ui-demo/src/app/providers.tsx @@ -1,7 +1,7 @@ "use client"; import { AuthCardHeader } from "@/components/shared/AuthCardHeader"; -import { arbitrumSepolia } from "@account-kit/infra"; +import { alchemy, arbitrumSepolia } from "@account-kit/infra"; import { AlchemyAccountProvider, createConfig } from "@account-kit/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { PropsWithChildren, Suspense } from "react"; @@ -11,10 +11,10 @@ const queryClient = new QueryClient(); const alchemyConfig = createConfig( { - rpcUrl: "/api/rpc", + transport: alchemy({ rpcUrl: "/api/rpc" }), chain: arbitrumSepolia, ssr: true, - policyId: process.env.NEXT_PUBLIC_PAYMASTER_POLICY_ID + policyId: process.env.NEXT_PUBLIC_PAYMASTER_POLICY_ID, }, { illustrationStyle: DEFAULT_CONFIG.ui.illustrationStyle, diff --git a/examples/ui-demo/src/components/shared/MintCard.tsx b/examples/ui-demo/src/components/shared/MintCard.tsx index 67c6daba92..a16918a2cd 100644 --- a/examples/ui-demo/src/components/shared/MintCard.tsx +++ b/examples/ui-demo/src/components/shared/MintCard.tsx @@ -96,7 +96,6 @@ export const MintCard = () => { }); console.log("uri", uri); return uri; - }, enabled: !!client && !!client?.readContract, }); diff --git a/examples/ui-demo/src/utils/hooks/useDebounceEffect.ts b/examples/ui-demo/src/utils/hooks/useDebounceEffect.ts index 1b174afca4..f627fe95aa 100644 --- a/examples/ui-demo/src/utils/hooks/useDebounceEffect.ts +++ b/examples/ui-demo/src/utils/hooks/useDebounceEffect.ts @@ -9,15 +9,15 @@ import { useEffect, useRef } from "react"; const useDebounceEffect = (fnc: () => void, deps: any[], delay: number) => { - const ref = useRef(); + const ref = useRef(); - useEffect(() => { - clearTimeout(ref.current); - ref.current = setTimeout(() => { - fnc(); - clearTimeout(ref.current); - }, delay); - }, [fnc, deps, delay]); + useEffect(() => { + clearTimeout(ref.current); + ref.current = setTimeout(() => { + fnc(); + clearTimeout(ref.current); + }, delay); + }, [fnc, deps, delay]); }; export { useDebounceEffect }; diff --git a/examples/ui-demo/src/utils/truncate-address.ts b/examples/ui-demo/src/utils/truncate-address.ts index 30c4a36071..e83e8b888c 100644 --- a/examples/ui-demo/src/utils/truncate-address.ts +++ b/examples/ui-demo/src/utils/truncate-address.ts @@ -2,9 +2,9 @@ const truncateRegex = /^(0x[a-zA-Z0-9]{4})[a-zA-Z0-9]+([a-zA-Z0-9]{5})$/; const truncateAddress = (address: string) => { - const match = address.match(truncateRegex); - if (!match) return address; - return `${match[1]}…${match[2]}`; + const match = address.match(truncateRegex); + if (!match) return address; + return `${match[1]}…${match[2]}`; }; export default truncateAddress; diff --git a/package.json b/package.json index da88abe5ae..b5a5577fe3 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "eslint-plugin-mdx": "^3.1.5", "husky": "^8.0.0", "is-ci": "^3.0.1", + "jsdom": "^25.0.1", "lerna": "^8.0.2", "lint-staged": "^13.2.2", "node-fetch": "^3.3.1", diff --git a/site/pages/concepts/smart-account-client.mdx b/site/pages/concepts/smart-account-client.mdx index 6bd8b2185f..166fc02c10 100644 --- a/site/pages/concepts/smart-account-client.mdx +++ b/site/pages/concepts/smart-account-client.mdx @@ -28,21 +28,25 @@ When creating a Smart Account Client, you have two options: Let's see an example: ```ts twoslash -import { createAlchemySmartAccountClient, sepolia } from "@account-kit/infra"; +import { + createAlchemySmartAccountClient, + sepolia, + alchemy, +} from "@account-kit/infra"; import { createLightAccount } from "@account-kit/smart-contracts"; import { LocalAccountSigner } from "@aa-sdk/core"; import { http } from "viem"; import { generatePrivateKey } from "viem/accounts"; // with account hoisting - +const transport = alchemy({ apiKey: "your-api-key" }); const hoistedClient = createAlchemySmartAccountClient({ - apiKey: "your-api-key", + transport, chain: sepolia, account: await createLightAccount({ signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()), chain: sepolia, - transport: http(sepolia.rpcUrls.alchemy.http[0]), + transport, }), }); @@ -50,14 +54,14 @@ const signature = await hoistedClient.signMessage({ message: "Hello world! " }); // without account hoisting const nonHoistedClient = createAlchemySmartAccountClient({ - apiKey: "your-api-key", + transport, chain: sepolia, }); const lightAccount = await createLightAccount({ signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()), chain: sepolia, - transport: http(sepolia.rpcUrls.alchemy.http[0]), + transport, }); const signature2 = await nonHoistedClient.signMessage({ diff --git a/site/pages/core/multi-chain-apps.mdx b/site/pages/core/multi-chain-apps.mdx index bbddb2858f..57cd648113 100644 --- a/site/pages/core/multi-chain-apps.mdx +++ b/site/pages/core/multi-chain-apps.mdx @@ -13,10 +13,11 @@ In order to support multiple chains in your app, the first thing you need to do ```ts twoslash import { createConfig } from "@account-kit/core"; -import { sepolia, mainnet } from "@account-kit/infra"; +import { sepolia, mainnet, alchemy } from "@account-kit/infra"; export const config = createConfig({ - apiKey: "ALCHEMY_API_KEY", + // use this transport for all chains + transport: alchemy({ apiKey: "ALCHEMY_API_KEY" }), // this is the default chain chain: sepolia, chains: [ @@ -27,6 +28,8 @@ export const config = createConfig({ }, { chain: sepolia, + // optional: override the default transport for this chain + transport: alchemy({ apiKey: "OTHER_API_KEY" }), // optional: sponsor gas for this chain policyId: "SEPOLIA_GAS_MANAGER_POLICY_ID", }, @@ -55,10 +58,10 @@ await setChain(config, mainnet); ```ts twoslash [config.ts] filename="config.ts" import { createConfig } from "@account-kit/core"; -import { sepolia, mainnet } from "@account-kit/infra"; +import { sepolia, mainnet, alchemy } from "@account-kit/infra"; export const config = createConfig({ - apiKey: "ALCHEMY_API_KEY", + transport: alchemy({ apiKey: "ALCHEMY_API_KEY" }), // this is the default chain chain: sepolia, chains: [ diff --git a/site/pages/core/sponsor-gas.mdx b/site/pages/core/sponsor-gas.mdx index 84e04526bd..3894e54fc6 100644 --- a/site/pages/core/sponsor-gas.mdx +++ b/site/pages/core/sponsor-gas.mdx @@ -29,10 +29,10 @@ Remember to replace `ALCHEMY_API_KEY` with your Alchemy API key. If you don't ha ```ts twoslash import { createConfig } from "@account-kit/core"; -import { sepolia } from "@account-kit/infra"; +import { sepolia, alchemy } from "@account-kit/infra"; export const config = createConfig({ - apiKey: "ALCHEMY_API_KEY", + transport: alchemy({ apiKey: "ALCHEMY_API_KEY" }), chain: sepolia, policyId: "GAS_MANAGER_POLICY_ID", // [!code ++] }); diff --git a/site/pages/core/ssr.mdx b/site/pages/core/ssr.mdx index 589c4d3107..5fa1879960 100644 --- a/site/pages/core/ssr.mdx +++ b/site/pages/core/ssr.mdx @@ -13,10 +13,10 @@ To enable this setting, you can set `ssr: true` when creating a config. ```ts twoslash import { createConfig } from "@account-kit/core"; -import { sepolia } from "@account-kit/infra"; +import { sepolia, alchemy } from "@account-kit/infra"; export const config = createConfig({ - apiKey: "YOUR_API_KEY", + transport: alchemy({ apiKey: "ALCHEMY_API_KEY" }), chain: sepolia, ssr: true, // [!code ++] }); @@ -59,10 +59,10 @@ import { createConfig, cookieStorage, // [!code ++] } from "@account-kit/core"; -import { sepolia } from "@account-kit/infra"; +import { sepolia, alchemy } from "@account-kit/infra"; export const config = createConfig({ - apiKey: "YOUR_API_KEY", + transport: alchemy({ apiKey: "ALCHEMY_API_KEY" }), chain: sepolia, ssr: true, // [!code ++] storage: cookieStorage, // [!code ++] @@ -90,10 +90,10 @@ if (typeof window !== "undefined") { ```ts twoslash [config.ts] filename="config.ts" import { createConfig, cookieStorage } from "@account-kit/core"; -import { sepolia } from "@account-kit/infra"; +import { sepolia, alchemy } from "@account-kit/infra"; export const config = createConfig({ - apiKey: "YOUR_API_KEY", + transport: alchemy({ apiKey: "ALCHEMY_API_KEY" }), chain: sepolia, ssr: true, storage: cookieStorage, diff --git a/site/pages/infra/sponsor-gas.mdx b/site/pages/infra/sponsor-gas.mdx index 5a998da55e..a517ecf84b 100644 --- a/site/pages/infra/sponsor-gas.mdx +++ b/site/pages/infra/sponsor-gas.mdx @@ -81,6 +81,7 @@ import { sepolia, alchemyFeeEstimator, createAlchemyPublicRpcClient, + alchemy, } from "@account-kit/infra"; import { createLightAccount } from "@account-kit/smart-contracts"; // You can replace this with any signer you'd like @@ -90,11 +91,13 @@ import { http, custom } from "viem"; import { generatePrivateKey } from "viem/accounts"; // 1. create an alchemy rpc client +const alchemyTransport = alchemy({ + apiKey: "API_KEY", +}); + const alchemyRpcClient = createAlchemyPublicRpcClient({ chain: sepolia, - connectionConfig: { - apiKey: "API_KEY", - }, + transport: alchemyTransport, }); // 2. create a split transport to route traffic between the paymaster and the bundler @@ -105,7 +108,7 @@ const transport = split({ transport: http("PAYMASTER_URL"), }, ], - fallback: custom(alchemyRpcClient), + fallback: alchemyTransport, }); // 3. create smart account client @@ -114,7 +117,7 @@ export const client = createSmartAccountClient({ chain: sepolia, account: await createLightAccount({ chain: sepolia, - transport: http(`${sepolia.rpcUrls.alchemy.http[0]}/YOUR_API_KEY`), + transport: alchemyTransport, signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()), }), // this is required to get correct fee estimates when using our Bundler RPC @@ -148,6 +151,10 @@ import { http, custom } from "viem"; import { generatePrivateKey } from "viem/accounts"; // 1. create an alchemy rpc client +const alchemyTransport = alchemy({ + apiKey: "API_KEY", +}); + const alchemyRpcClient = createAlchemyPublicRpcClient({ chain: sepolia, connectionConfig: { @@ -164,7 +171,7 @@ const transport = split({ transport: http("https://api.stackup.sh/v1/paymaster/STACKUP_API_KEY"), }, ], - fallback: custom(alchemyRpcClient), + fallback: alchemyTransport, }); // 3. create smart account client @@ -173,7 +180,7 @@ export const client = createSmartAccountClient({ chain: sepolia, account: await createLightAccount({ chain: sepolia, - transport: custom(alchemyRpcClient), + transport: alchemyTransport, signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()), }), // Bypasses alchemy gas estimation and instead uses Stackup for gas estimation diff --git a/site/pages/migration-guide.mdx b/site/pages/migration-guide.mdx index e9d2fddf49..52e2ed27cf 100644 --- a/site/pages/migration-guide.mdx +++ b/site/pages/migration-guide.mdx @@ -32,6 +32,20 @@ We still support all Signers in the SDK! use the guide above to integrate with any signer of your choice. ::: +### Alchemy Transport + +The new `Transport` type: `AlchemyTransport` has been added. This impacts how Alchemy clients, middleware, and configs are created. + +For Smart Account Clients, the `create*AlchemyClient` methods have been updated to take a new `transport` property (of type `AlchemyTrasnport`) instead of `rpcUrl` or `apiKey` directly. + +The `alchemyFeeEstimator` and `alchemyUserOperationSimulator` middleware methods now no longer take in a `ClientWithAlchemyMethods` (returned by `createAlchemyPublicRpcClient`) and instead take in an `AlchemyTransport` as well. + +The `AlchemyTransport` is a type of `viem` transport that makes it easier to configure your communication with Alchemy RPC. It supports splitting traffic between Alchemy's AA infra and other Node providers (if needed). The `AlchemyTransport` has been added to `@account-kit/infra` and is exported as `alchemy`. + +Creating a config with `@account-kit/core` or `@account-kit/react` has been updated to accept an `AlchemyTransport` and the parameters of `createConfig` have been simplified as a result. You will now need to replace your `rpcUrl` or `apiKey` params with `transport: alchemy({...})`. + +For more detailed examples, see the relevant Quickstart guides, depending on which package you are using. If you don't know where to start, checkout the [React Quickstart](/react/quickstart). + ### Hooks: `useSendTransaction` and `useSendTransactions` removed These methods have been removed since `useSendUserOperation` can be used to send transactions and user operations. If you were using `useSendTransaction` or `useSendTransactions`, you can replace them with diff --git a/site/pages/reference/account-kit/core/functions/createConfig.mdx b/site/pages/reference/account-kit/core/functions/createConfig.mdx index 7ebf8c7fad..f8e1c11395 100644 --- a/site/pages/reference/account-kit/core/functions/createConfig.mdx +++ b/site/pages/reference/account-kit/core/functions/createConfig.mdx @@ -27,7 +27,7 @@ import { sepolia } from "@account-kit/infra"; const config = createConfig({ chain: sepolia, - apiKey: "your-api-key", + transport: alchemy({ apiKey: "your-api-key" }), }); ``` diff --git a/site/pages/reference/account-kit/core/functions/watchSmartAccountClient.mdx b/site/pages/reference/account-kit/core/functions/watchSmartAccountClient.mdx index c700ec935e..defaed2f06 100644 --- a/site/pages/reference/account-kit/core/functions/watchSmartAccountClient.mdx +++ b/site/pages/reference/account-kit/core/functions/watchSmartAccountClient.mdx @@ -28,7 +28,7 @@ watchSmartAccountClient({ type: "LightAccount" }, config)(console.log); ### params -`GetSmartAccountClientParams` +`GetSmartAccountClientParams` the parameters needed to get the smart account client ### config @@ -38,5 +38,5 @@ the configuration containing the client store and other settings ## Returns -`(onChange: (client: GetSmartAccountClientResult>) => void) => (() => void)` +`(onChange: (client: GetSmartAccountClientResult>) => void) => (() => void)` a function that accepts a callback to be called when the client changes and returns a function to unsubscribe from the store diff --git a/site/pages/reference/account-kit/infra/functions/alchemy.mdx b/site/pages/reference/account-kit/infra/functions/alchemy.mdx new file mode 100644 index 0000000000..5d4bd9acb3 --- /dev/null +++ b/site/pages/reference/account-kit/infra/functions/alchemy.mdx @@ -0,0 +1,101 @@ +--- +# This file is autogenerated + +title: alchemy +description: Overview of the alchemy method +--- + +# 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. +If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl. + +## Import + +```ts +import { alchemy } from "@account-kit/infra"; +``` + +## Usage + +### Basic Example + +If the chain you're using is supported for both Bundler and Node RPCs, then you can do the following: + +```ts +import { alchemy } from "@account-kit/infra"; + +const transport = alchemy({ + // NOTE: you can also pass in an rpcUrl or jwt here or rpcUrl and jwt + apiKey: "your-api-key", +}); +``` + +### AA Only Chains + +For AA-only chains, you need to specify the alchemyConnection and nodeRpcUrl since Alchemy only +handles the Bundler and Paymaster RPCs for these chains. + +```ts +import { alchemy } from "@account-kit/infra"; + +const transport = alchemy({ + alchemyConnection: { + apiKey: "your-api-key", + }, + nodeRpcUrl: "https://zora.rpc.url", +}); +``` + +## Parameters + +### config + +`AlchemyTransportConfig` +The configuration object for the Alchemy transport. + +### config.retryDelay + +`number` +Optional The delay between retries, in milliseconds. + +### config.retryCount + +`number` +Optional The number of retry attempts. + +### config.alchemyConnection + +`string` +Optional Alchemy connection configuration (if this is passed in, nodeRpcUrl is required). + +### config.fetchOptions + +`string` +Optional fetch options for HTTP requests. + +### config.nodeRpcUrl + +`string` +Optional RPC URL for node (if this is passed in, alchemyConnection is required). + +### config.rpcUrl + +`string` +Optional RPC URL. + +### config.apiKey + +`string` +Optional API key for Alchemy. + +### config.jwt + +`string` +Optional JSON Web Token for authorization. + +## Returns + +`AlchemyTransport` +The configured Alchemy transport object. diff --git a/site/pages/reference/account-kit/infra/functions/alchemyFeeEstimator.mdx b/site/pages/reference/account-kit/infra/functions/alchemyFeeEstimator.mdx index 467f9f7909..f7d73c8f13 100644 --- a/site/pages/reference/account-kit/infra/functions/alchemyFeeEstimator.mdx +++ b/site/pages/reference/account-kit/infra/functions/alchemyFeeEstimator.mdx @@ -19,22 +19,26 @@ import { alchemyFeeEstimator } from "@account-kit/infra"; ## Usage ```ts -import { alchemyFeeEstimator, createAlchemyPublicRpcClient } from "@account-kit/infra"; +import { alchemyFeeEstimator, alchemy } from "@account-kit/infra"; import { createSmartAccountClient } from "@aa-sdk/core"; -const bundlerClient = createAlchemyPublicRpcClient(...); +const alchemyTransport = alchemy({ + chain: sepolia, + apiKey: "your-api-key", +}); + const client = createSmartAccountClient({ -feeEstimator: alchemyFeeEstimator(bundlerClient), -...otherParams + feeEstimator: alchemyFeeEstimator(alchemyTransport), + ...otherParams, }); ``` ## Parameters -### client +### transport -`ClientWithAlchemyMethods` -The client with Alchemy methods +`AlchemyTransport` +An alchemy transport for making Alchemy specific RPC calls ## Returns diff --git a/site/pages/reference/account-kit/infra/functions/alchemyGasManagerMiddleware.mdx b/site/pages/reference/account-kit/infra/functions/alchemyGasManagerMiddleware.mdx index 9d1bc1c6ec..7fbd8327b1 100644 --- a/site/pages/reference/account-kit/infra/functions/alchemyGasManagerMiddleware.mdx +++ b/site/pages/reference/account-kit/infra/functions/alchemyGasManagerMiddleware.mdx @@ -18,15 +18,14 @@ import { alchemyGasManagerMiddleware } from "@account-kit/infra"; ## Usage ```ts - -import { sepolia } from "@account-kit/infra"; +import { sepolia, alchemyErc7677Middleware } from "@account-kit/infra"; import { http } from "viem"; const client = createSmartAccountClient({ - http("rpc-url"), - sepolia, - alchemyErc7677Middleware("policyId") -); + transport: http("rpc-url"), + chain: sepolia, + ...alchemyErc7677Middleware("policyId"), +}); ``` ## Parameters diff --git a/site/pages/reference/account-kit/infra/functions/alchemyUserOperationSimulator.mdx b/site/pages/reference/account-kit/infra/functions/alchemyUserOperationSimulator.mdx index 53e4049c16..8abf7a7ae9 100644 --- a/site/pages/reference/account-kit/infra/functions/alchemyUserOperationSimulator.mdx +++ b/site/pages/reference/account-kit/infra/functions/alchemyUserOperationSimulator.mdx @@ -18,22 +18,31 @@ import { alchemyUserOperationSimulator } from "@account-kit/infra"; ## Usage ```ts -import { alchemyUserOperationSimulator, createAlchemyPublicRpcClient } from "@account-kit/infra"; +import { + alchemyUserOperationSimulator, + alchemy, + sepolia, +} from "@account-kit/infra"; import { createSmartAccountClient } from "@aa-sdk/core"; -const bundlerClient = createAlchemyPublicRpcClient(...); +const alchemyTransport = alchemy({ + chain: sepolia, + apiKey: "your-api-key", +}); + const client = createSmartAccountClient({ -userOperationSimulator: alchemyUserOperationSimulator(bundlerClient), -...otherParams + chain: sepolia, + userOperationSimulator: alchemyUserOperationSimulator(alchemyTransport), + ...otherParams, }); ``` ## Parameters -### client +### transport -`C` -The client object with Alchemy methods +`AlchemyTransport` +An Alchemy Transport that can be used for making RPC calls to alchemy ## Returns diff --git a/site/pages/reference/account-kit/infra/functions/createAlchemyPublicRpcClient.mdx b/site/pages/reference/account-kit/infra/functions/createAlchemyPublicRpcClient.mdx index 1d807fc7c5..a3bbe250f1 100644 --- a/site/pages/reference/account-kit/infra/functions/createAlchemyPublicRpcClient.mdx +++ b/site/pages/reference/account-kit/infra/functions/createAlchemyPublicRpcClient.mdx @@ -18,14 +18,14 @@ import { createAlchemyPublicRpcClient } from "@account-kit/infra"; ## Usage ```ts -import { createAlchemyPublicRpcClient } from "@account-kit/infra"; +import { createAlchemyPublicRpcClient, alchemy } from "@account-kit/infra"; import { sepolia } from "@account-kit/infra"; const client = createAlchemyPublicRpcClient({ + transport: alchemy({ + apiKey: "ALCHEMY_API_KEY", + }), chain: sepolia, - connectionConfig: { - apiKey: "your-api-key", - }, }); ``` diff --git a/site/pages/reference/account-kit/infra/functions/createAlchemySmartAccountClient.mdx b/site/pages/reference/account-kit/infra/functions/createAlchemySmartAccountClient.mdx index 7c36616b60..1c6531ad8e 100644 --- a/site/pages/reference/account-kit/infra/functions/createAlchemySmartAccountClient.mdx +++ b/site/pages/reference/account-kit/infra/functions/createAlchemySmartAccountClient.mdx @@ -18,12 +18,12 @@ import { createAlchemySmartAccountClient } from "@account-kit/infra"; ## Usage ```ts -import { createAlchemySmartAccountClient } from "@account-kit/infra"; +import { createAlchemySmartAccountClient, alchemy } from "@account-kit/infra"; import { sepolia } from "@account-kit/infra/chain"; const client = createAlchemySmartAccountClient({ chain: sepolia, - apiKey: "your-api-key", + transport: alchemy({ apiKey: "your-api-key" }), }); ``` diff --git a/site/pages/reference/account-kit/smart-contracts/functions/createLightAccountAlchemyClient.mdx b/site/pages/reference/account-kit/smart-contracts/functions/createLightAccountAlchemyClient.mdx index 7bd1878409..bc9494804d 100644 --- a/site/pages/reference/account-kit/smart-contracts/functions/createLightAccountAlchemyClient.mdx +++ b/site/pages/reference/account-kit/smart-contracts/functions/createLightAccountAlchemyClient.mdx @@ -19,12 +19,12 @@ import { createLightAccountAlchemyClient } from "@account-kit/smart-contracts"; ```ts import { createLightAccountAlchemyClient } from "@account-kit/smart-contracts"; -import { sepolia } from "@account-kit/infra"; +import { sepolia, alchemy } from "@account-kit/infra"; import { LocalAccountSigner } from "@aa-sdk/core"; import { generatePrivateKey } from "viem"; const lightAccountClient = await createLightAccountAlchemyClient({ - apiKey: "your-api-key", + transport: alchemy({ apiKey: "your-api-key" }), chain: sepolia, signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()), }); diff --git a/site/pages/reference/account-kit/smart-contracts/functions/createModularAccountAlchemyClient.mdx b/site/pages/reference/account-kit/smart-contracts/functions/createModularAccountAlchemyClient.mdx index 15402d6779..2cd77a6f73 100644 --- a/site/pages/reference/account-kit/smart-contracts/functions/createModularAccountAlchemyClient.mdx +++ b/site/pages/reference/account-kit/smart-contracts/functions/createModularAccountAlchemyClient.mdx @@ -19,12 +19,12 @@ import { createModularAccountAlchemyClient } from "@account-kit/smart-contracts" ```ts import { createModularAccountAlchemyClient } from "@account-kit/smart-contracts"; -import { sepolia } from "@account-kit/infra"; +import { sepolia, alchemy } from "@account-kit/infra"; import { LocalAccountSigner } from "@aa-sdk/core"; import { generatePrivateKey } from "viem"; const alchemyAccountClient = await createModularAccountAlchemyClient({ - apiKey: "your-api-key", + transport: alchemy({ apiKey: "your-api-key" }), chain: sepolia, signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()), }); diff --git a/site/pages/reference/account-kit/smart-contracts/functions/createMultiOwnerLightAccountAlchemyClient.mdx b/site/pages/reference/account-kit/smart-contracts/functions/createMultiOwnerLightAccountAlchemyClient.mdx index ace6f80321..b52980aed4 100644 --- a/site/pages/reference/account-kit/smart-contracts/functions/createMultiOwnerLightAccountAlchemyClient.mdx +++ b/site/pages/reference/account-kit/smart-contracts/functions/createMultiOwnerLightAccountAlchemyClient.mdx @@ -19,14 +19,16 @@ import { createMultiOwnerLightAccountAlchemyClient } from "@account-kit/smart-co ```ts import { createMultiOwnerLightAccountAlchemyClient } from "@account-kit/smart-contracts"; -import { sepolia } from "@account-kit/infra"; +import { sepolia, alchemy } from "@account-kit/infra"; import { LocalAccountSigner } from "@aa-sdk/core"; -import { generatePrivateKey } from "viem"; +import { generatePrivateKey } from "viem" const lightAccountClient = await createMultiOwnerLightAccountAlchemyClient({ - apiKey: "your-api-key", - chain: sepolia, - signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()), +transport: alchemy({ +apiKey: "your-api-key", +}), +chain: sepolia +signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()) }); ``` diff --git a/site/pages/reference/account-kit/smart-contracts/functions/createMultisigAccountAlchemyClient.mdx b/site/pages/reference/account-kit/smart-contracts/functions/createMultisigAccountAlchemyClient.mdx index 14c0da6284..577a384023 100644 --- a/site/pages/reference/account-kit/smart-contracts/functions/createMultisigAccountAlchemyClient.mdx +++ b/site/pages/reference/account-kit/smart-contracts/functions/createMultisigAccountAlchemyClient.mdx @@ -24,7 +24,7 @@ import { LocalAccountSigner } from "@aa-sdk/core"; import { generatePrivateKey } from "viem" const alchemyAccountClient = await createMultisigAccountAlchemyClient({ -apiKey: "your-api-key", +transport: alchemy({ apiKey: "your-api-key" }), chain: sepolia, signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()), owners: [...], // other owners on the account diff --git a/site/pages/third-party/bundlers.mdx b/site/pages/third-party/bundlers.mdx index b6ea734da6..5017f86264 100644 --- a/site/pages/third-party/bundlers.mdx +++ b/site/pages/third-party/bundlers.mdx @@ -34,6 +34,22 @@ with your provider on what the correct logic is. It might be the case that you want to use a different RPC provider for your bundler traffic and your node traffic. This is a common use case, and you can do this by leveraging the [`split`](/reference/aa-sdk/core/functions/split) transport and passing it to your `createSmartAccountClient` call. For example: +### Using Alchemy Bundler and Gas Manager with 3rd Party Node RPCs + +If you want to split your node traffic from Alchemy's Bundler traffic, you can do this with the `alchemyTransport` + +```ts twoslash +import { alchemy } from "@account-kit/infra"; + +const alchemyTransport = alchemy({ + alchemyConnection: { apiKey: "your-api-key" }, + nodeRpcUrl: "YOUR_NODE_RPC_URL", +}); +// now use this transport in a client +``` + +### Using two different 3rd Party Bundler and Node RPCs + ```ts twoslash import { split } from "@aa-sdk/core"; import { createPublicClient, http } from "viem"; diff --git a/site/pages/third-party/chains.mdx b/site/pages/third-party/chains.mdx index 63261346f2..43c1f9345b 100644 --- a/site/pages/third-party/chains.mdx +++ b/site/pages/third-party/chains.mdx @@ -10,52 +10,22 @@ with a `split` transport to route your traffic accordingly. ## AA only chains example -```ts twoslash -import { createLightAccountClient } from "@account-kit/smart-contracts"; -import { - createAlchemyPublicRpcClient, - zora, - alchemyFeeEstimator, - type ClientWithAlchemyMethods, - alchemyGasManagerMiddleware, -} from "@account-kit/infra"; -import { split, LocalAccountSigner, createBundlerClient } from "@aa-sdk/core"; -import { http, custom } from "viem"; - -const alchemyMethods = [ - "eth_sendUserOperation", - "eth_estimateUserOperationGas", - "eth_getUserOperationReceipt", - "eth_getUserOperationByHash", - "eth_supportedEntryPoints", - "rundler_maxPriorityFeePerGas", - "pm_getPaymasterData", - "pm_getPaymasterStubData", -]; +For AA only chains, the `alchemyTransport` allows you to specify both the Alchemy Connection object as well as pass in a Node RPC URL, allowing you to split traffic between Alchemy's Bundler and Paymaster RPC and your Node RPC provider. -const transport = split({ - overrides: [ - { - methods: alchemyMethods, - transport: http("ALCHEMY_RPC_URL"), +```ts twoslash +import { zora, alchemy } from "@account-kit/infra"; +import { createLightAccountAlchemyClient } from "@account-kit/smart-contracts"; +import { LocalAccountSigner } from "@aa-sdk/core"; + +const smartAccountClient = createLightAccountAlchemyClient({ + transport: alchemy({ + alchemyConnection: { + apiKey: "ALCHEMY_API_KEY", }, - ], - fallback: http("ZORA_NODE_RPC_URL"), -}); - -const alchemyBundlerClient = createBundlerClient({ - // zora is an example of an aa only chain - chain: zora, - transport, -}) as unknown as ClientWithAlchemyMethods; - -const smartAccountClient = createLightAccountClient({ - transport, + nodeRpcUrl: "ZORA_NODE_RPC_URL", + }), chain: zora, - feeEstimator: alchemyFeeEstimator(alchemyBundlerClient), signer: LocalAccountSigner.generatePrivateKeySigner(), - // if you want to sponsor gas add this - ...alchemyGasManagerMiddleware("YOUR_POLICY_ID"), }); ``` diff --git a/site/pages/third-party/paymasters.mdx b/site/pages/third-party/paymasters.mdx index 2a648dd390..aa89d1e9d3 100644 --- a/site/pages/third-party/paymasters.mdx +++ b/site/pages/third-party/paymasters.mdx @@ -101,6 +101,7 @@ import { createMultiOwnerModularAccountClient } from "@account-kit/smart-contrac import { alchemyFeeEstimator, createAlchemyPublicRpcClient, + alchemy, } from "@account-kit/infra"; import { deepHexlify, @@ -117,16 +118,15 @@ const stackupClient = createClient({ transport: http("https://api.stackup.sh/v1/paymaster/STACKUP_API_KEY"), }); -const alchemyRpcClient = createAlchemyPublicRpcClient({ +const alchemyTransport = alchemy({ // TODO: Replace with your Alchemy API key (https://dashboard.alchemypreview.com/apps) - connectionConfig: { rpcUrl: "ALCHEMY_RPC_URL" }, - chain, + apiKey: "ALCHEMY_API_KEY", }); const alchemyClient = await createMultiOwnerModularAccountClient({ chain, signer, - transport: http("ALCHEMY_RPC_URL"), + transport: alchemyTransport, // Bypasses alchemy gas estimation and instead uses Stackup for gas estimation gasEstimator: async (userOp) => ({ ...userOp, @@ -135,7 +135,7 @@ const alchemyClient = await createMultiOwnerModularAccountClient({ verificationGasLimit: "0x0", }), // Uses alchemy fee estimation to comply with bundler - feeEstimator: alchemyFeeEstimator(alchemyRpcClient), + feeEstimator: alchemyFeeEstimator(alchemyTransport), paymasterAndData: async (userop, opts) => { const pmResponse: any = await stackupClient.request({ // @ts-ignore diff --git a/site/pages/third-party/smart-contracts.mdx b/site/pages/third-party/smart-contracts.mdx index fe228cefcf..3a19a5d2de 100644 --- a/site/pages/third-party/smart-contracts.mdx +++ b/site/pages/third-party/smart-contracts.mdx @@ -61,7 +61,7 @@ To use your account, you will need to pass it into a `SmartAccountClient`. :::code-group ```ts twoslash [example.ts] -import { createAlchemySmartAccountClient } from "@account-kit/infra"; +import { createAlchemySmartAccountClient, alchemy } from "@account-kit/infra"; import { sepolia } from "viem/chains"; import { myAccount } from "./my-account"; @@ -69,7 +69,9 @@ const client = createAlchemySmartAccountClient({ // created above account: myAccount, chain: sepolia, - transport: http("RPC_URL"), + transport: alchemy({ + apiKey: "YOUR_API_KEY", + }), }); ``` diff --git a/site/shared/core/config.ts b/site/shared/core/config.ts index 84e9668546..8c73a8b8fc 100644 --- a/site/shared/core/config.ts +++ b/site/shared/core/config.ts @@ -1,8 +1,8 @@ import { createConfig } from "@account-kit/core"; -import { sepolia } from "@account-kit/infra"; +import { alchemy, sepolia } from "@account-kit/infra"; export const config = createConfig({ - apiKey: "YOUR_API_KEY", + transport: alchemy({ apiKey: "YOUR_API_KEY" }), chain: sepolia, // optional if you want to sponsor gas policyId: "YOUR_POLICY_ID", diff --git a/site/shared/core/ssr-config.ts b/site/shared/core/ssr-config.ts index 08d9932840..b819469a24 100644 --- a/site/shared/core/ssr-config.ts +++ b/site/shared/core/ssr-config.ts @@ -1,8 +1,8 @@ import { createConfig } from "@account-kit/core"; -import { sepolia } from "@account-kit/infra"; +import { alchemy, sepolia } from "@account-kit/infra"; export const config = createConfig({ - apiKey: "YOUR_API_KEY", + transport: alchemy({ apiKey: "YOUR_API_KEY" }), chain: sepolia, ssr: true, }); diff --git a/site/shared/infra/client.ts b/site/shared/infra/client.ts index 1e3a1402e4..0a06bb69b1 100644 --- a/site/shared/infra/client.ts +++ b/site/shared/infra/client.ts @@ -1,18 +1,25 @@ -import { createAlchemySmartAccountClient, sepolia } from "@account-kit/infra"; +import { + alchemy, + createAlchemySmartAccountClient, + sepolia, +} from "@account-kit/infra"; import { createLightAccount } from "@account-kit/smart-contracts"; // You can replace this with any signer you'd like // We're using a LocalAccountSigner to generate a local key to sign with import { LocalAccountSigner } from "@aa-sdk/core"; -import { http } from "viem"; import { generatePrivateKey } from "viem/accounts"; -export const client = createAlchemySmartAccountClient({ +const alchemyTransport = alchemy({ apiKey: "YOUR_API_KEY", +}); + +export const client = createAlchemySmartAccountClient({ + transport: alchemyTransport, policyId: "YOUR_POLICY_ID", chain: sepolia, account: await createLightAccount({ chain: sepolia, - transport: http(`${sepolia.rpcUrls.alchemy.http[0]}/YOUR_API_KEY`), + transport: alchemyTransport, signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()), }), }); diff --git a/site/shared/smart-contracts/light-account-client.ts b/site/shared/smart-contracts/light-account-client.ts index acc379a4c8..927b01d37b 100644 --- a/site/shared/smart-contracts/light-account-client.ts +++ b/site/shared/smart-contracts/light-account-client.ts @@ -1,10 +1,10 @@ import { LocalAccountSigner } from "@aa-sdk/core"; -import { sepolia } from "@account-kit/infra"; +import { alchemy, sepolia } from "@account-kit/infra"; import { createLightAccountAlchemyClient } from "@account-kit/smart-contracts"; import { generatePrivateKey } from "viem/accounts"; export const lightAccountClient = await createLightAccountAlchemyClient({ signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()), chain: sepolia, - apiKey: "YOUR_API_KEY", + transport: alchemy({ apiKey: "YOUR_API_KEY" }), }); diff --git a/site/shared/smart-contracts/modular-account-client.ts b/site/shared/smart-contracts/modular-account-client.ts index 3a1aafbe74..f62907ca4c 100644 --- a/site/shared/smart-contracts/modular-account-client.ts +++ b/site/shared/smart-contracts/modular-account-client.ts @@ -1,5 +1,5 @@ import { LocalAccountSigner } from "@aa-sdk/core"; -import { sepolia } from "@account-kit/infra"; +import { alchemy, sepolia } from "@account-kit/infra"; import { createModularAccountAlchemyClient } from "@account-kit/smart-contracts"; import { generatePrivateKey } from "viem/accounts"; @@ -8,5 +8,5 @@ export const chain = sepolia; export const modularAccountClient = await createModularAccountAlchemyClient({ signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()), chain, - apiKey: "YOUR_API_KEY", + transport: alchemy({ apiKey: "YOUR_API_KEY" }), }); diff --git a/site/shared/smart-contracts/multi-owner-light-account-client.ts b/site/shared/smart-contracts/multi-owner-light-account-client.ts index caf4f60542..93d118e7b1 100644 --- a/site/shared/smart-contracts/multi-owner-light-account-client.ts +++ b/site/shared/smart-contracts/multi-owner-light-account-client.ts @@ -1,5 +1,5 @@ import { LocalAccountSigner } from "@aa-sdk/core"; -import { sepolia } from "@account-kit/infra"; +import { alchemy, sepolia } from "@account-kit/infra"; import { createMultiOwnerLightAccountAlchemyClient } from "@account-kit/smart-contracts"; import { generatePrivateKey } from "viem/accounts"; @@ -7,5 +7,7 @@ export const multiOwnerLightAccountClient = await createMultiOwnerLightAccountAlchemyClient({ signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()), chain: sepolia, - apiKey: "YOUR_API_KEY", + transport: alchemy({ + apiKey: "YOUR_API_KEY", + }), }); diff --git a/site/shared/smart-contracts/multisig-client.ts b/site/shared/smart-contracts/multisig-client.ts index a4db91aa8c..377adc8780 100644 --- a/site/shared/smart-contracts/multisig-client.ts +++ b/site/shared/smart-contracts/multisig-client.ts @@ -1,5 +1,5 @@ import { LocalAccountSigner } from "@aa-sdk/core"; -import { sepolia } from "@account-kit/infra"; +import { alchemy, sepolia } from "@account-kit/infra"; import { createMultisigAccountAlchemyClient } from "@account-kit/smart-contracts"; const MODULAR_MULTISIG_ACCOUNT_OWNER_MNEMONIC = "YOUR MNEMONIC"; @@ -29,5 +29,7 @@ export const multisigAccountClient = await createMultisigAccountAlchemyClient({ signer: signers[0], owners, threshold, - apiKey: "YOUR_API_KEY", + transport: alchemy({ + apiKey: "YOUR_API_KEY", + }), }); diff --git a/site/shared/smart-contracts/session-keys/base-client.ts b/site/shared/smart-contracts/session-keys/base-client.ts index ebbc4c3e25..b3e14e9e91 100644 --- a/site/shared/smart-contracts/session-keys/base-client.ts +++ b/site/shared/smart-contracts/session-keys/base-client.ts @@ -1,5 +1,5 @@ import { LocalAccountSigner } from "@aa-sdk/core"; -import { sepolia } from "@account-kit/infra"; +import { alchemy, sepolia } from "@account-kit/infra"; import { createModularAccountAlchemyClient, sessionKeyPluginActions, @@ -9,6 +9,6 @@ export const client = ( await createModularAccountAlchemyClient({ chain: sepolia, signer: LocalAccountSigner.mnemonicToAccountSigner("MNEMONIC"), - apiKey: "ALCHEMY_API_KEY", + transport: alchemy({ apiKey: "ALCHEMY_API_KEY" }), }) ).extend(sessionKeyPluginActions); diff --git a/site/shared/smart-contracts/session-keys/full-example.ts b/site/shared/smart-contracts/session-keys/full-example.ts index ecb9d1cd7b..eb1706258b 100644 --- a/site/shared/smart-contracts/session-keys/full-example.ts +++ b/site/shared/smart-contracts/session-keys/full-example.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { LocalAccountSigner } from "@aa-sdk/core"; -import { sepolia } from "@account-kit/infra"; +import { alchemy, sepolia } from "@account-kit/infra"; import { SessionKeyAccessListType, SessionKeyPermissionsBuilder, @@ -12,13 +12,15 @@ import { import { zeroHash } from "viem"; const chain = sepolia; +const transport = alchemy({ apiKey: "YOUR_API_KEY" }); + // this is the signer to connect with the account, later we will create a new client using a session key signe const signer = LocalAccountSigner.mnemonicToAccountSigner("MNEMONIC"); const sessionKeySigner = new SessionKeySigner(); const client = ( await createModularAccountAlchemyClient({ chain, - apiKey: "ALCHEMY_API_KEY", + transport, signer, }) ).extend(sessionKeyPluginActions); @@ -63,7 +65,7 @@ const sessionKeyClient = ( await createModularAccountAlchemyClient({ chain, signer: sessionKeySigner, - apiKey: "ALCHEMY_API_KEY", + transport, // this is important because it tells the client to use our previously deployed account accountAddress: client.getAddress(), }) diff --git a/site/shared/smart-contracts/session-keys/supported-permissions.ts b/site/shared/smart-contracts/session-keys/supported-permissions.ts index 4908e496fc..07bbeac4e4 100644 --- a/site/shared/smart-contracts/session-keys/supported-permissions.ts +++ b/site/shared/smart-contracts/session-keys/supported-permissions.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { LocalAccountSigner } from "@aa-sdk/core"; -import { sepolia } from "@account-kit/infra"; +import { alchemy, sepolia } from "@account-kit/infra"; import { createModularAccountAlchemyClient, SessionKeyAccessListType, @@ -11,11 +11,12 @@ import { } from "@account-kit/smart-contracts"; import { keccak256, zeroHash } from "viem"; +const transport = alchemy({ apiKey: "ALCHEMY_API_KEY" }); export const client = ( await createModularAccountAlchemyClient({ chain: sepolia, signer: LocalAccountSigner.mnemonicToAccountSigner("MNEMONIC"), - apiKey: "ALCHEMY_API_KEY", + transport, }) ).extend(sessionKeyPluginActions); diff --git a/site/sidebar/reference/account-kit/infra.ts b/site/sidebar/reference/account-kit/infra.ts index 010ef9cda0..25da7e35fd 100644 --- a/site/sidebar/reference/account-kit/infra.ts +++ b/site/sidebar/reference/account-kit/infra.ts @@ -14,6 +14,10 @@ export const accountKitInfraReferenceSidebar: SidebarItem[] = [ { text: "Functions", items: [ + { + text: "alchemy", + link: "/reference/account-kit/infra/functions/alchemy", + }, { text: "alchemyActions", link: "/reference/account-kit/infra/functions/alchemyActions", diff --git a/site/sidebar/reference/account-kit/signer.ts b/site/sidebar/reference/account-kit/signer.ts index 8d436db53f..722e371cd4 100644 --- a/site/sidebar/reference/account-kit/signer.ts +++ b/site/sidebar/reference/account-kit/signer.ts @@ -18,8 +18,8 @@ export const accountKitSignerReferenceSidebar: SidebarItem[] = [ text: "AlchemySignerWebClient", items: [ { - text: "completeEmailAuth", - link: "/reference/account-kit/signer/classes/AlchemySignerWebClient/completeEmailAuth", + text: "completeAuthWithBundle", + link: "/reference/account-kit/signer/classes/AlchemySignerWebClient/completeAuthWithBundle", }, { text: "constructor", @@ -45,6 +45,14 @@ export const accountKitSignerReferenceSidebar: SidebarItem[] = [ text: "lookupUserWithPasskey", link: "/reference/account-kit/signer/classes/AlchemySignerWebClient/lookupUserWithPasskey", }, + { + text: "oauthWithPopup", + link: "/reference/account-kit/signer/classes/AlchemySignerWebClient/oauthWithPopup", + }, + { + text: "oauthWithRedirect", + link: "/reference/account-kit/signer/classes/AlchemySignerWebClient/oauthWithRedirect", + }, ], }, { @@ -95,6 +103,10 @@ export const accountKitSignerReferenceSidebar: SidebarItem[] = [ text: "on", link: "/reference/account-kit/signer/classes/BaseAlchemySigner/on", }, + { + text: "preparePopupOauth", + link: "/reference/account-kit/signer/classes/BaseAlchemySigner/preparePopupOauth", + }, { text: "signMessage", link: "/reference/account-kit/signer/classes/BaseAlchemySigner/signMessage", @@ -128,6 +140,10 @@ export const accountKitSignerReferenceSidebar: SidebarItem[] = [ text: "getUser", link: "/reference/account-kit/signer/classes/BaseSignerClient/getUser", }, + { + text: "initOauth", + link: "/reference/account-kit/signer/classes/BaseSignerClient/initOauth", + }, { text: "lookupUserByEmail", link: "/reference/account-kit/signer/classes/BaseSignerClient/lookupUserByEmail", @@ -154,6 +170,15 @@ export const accountKitSignerReferenceSidebar: SidebarItem[] = [ }, ], }, + { + text: "OauthCancelledError", + items: [ + { + text: "constructor", + link: "/reference/account-kit/signer/classes/OauthCancelledError/constructor", + }, + ], + }, ], }, { diff --git a/site/sidebar/signer.ts b/site/sidebar/signer.ts index 0d7cd2462f..9452e8c18b 100644 --- a/site/sidebar/signer.ts +++ b/site/sidebar/signer.ts @@ -7,29 +7,29 @@ export const signerSidebar: SidebarItem[] = [ { text: "Using Alchemy Signer", items: [ - { - text: "Authentication", - items: [ - { - text: "Email magic-link", - link: "/signer/authentication/email-magic-link", - }, - { - text: "Passkey signup", - link: "/signer/authentication/passkey-signup", - }, - { - text: "Passkey login", - link: "/signer/authentication/passkey-login", - }, - { text: "Add passkey", link: "/signer/authentication/add-passkey" }, - ], - }, { text: "User sessions", link: "/signer/user-sessions" }, { text: "Export private key", link: "/signer/export-private-key" }, { text: "As an EOA", link: "/signer/as-an-eoa" }, ], }, + { + text: "Authentication methods", + items: [ + { + text: "Email magic-link", + link: "/signer/authentication/email-magic-link", + }, + { + text: "Passkey signup", + link: "/signer/authentication/passkey-signup", + }, + { + text: "Passkey login", + link: "/signer/authentication/passkey-login", + }, + { text: "Add passkey", link: "/signer/authentication/add-passkey" }, + ], + }, { text: "Third-party signers", items: [{ text: "Custom signer", link: "/signer/custom-signer" }], diff --git a/yarn.lock b/yarn.lock index 84a89cf5ce..4fbf00cf1b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10622,6 +10622,13 @@ cssesc@^3.0.0: resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +cssstyle@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-4.1.0.tgz#161faee382af1bafadb6d3867a92a19bcb4aea70" + integrity sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA== + dependencies: + rrweb-cssom "^0.7.1" + csstype@^3.0.2, csstype@^3.0.7: version "3.1.3" resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz" @@ -10658,6 +10665,14 @@ data-uri-to-buffer@^4.0.0: resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz" integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== +data-urls@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-5.0.0.tgz#2f76906bce1824429ffecb6920f45a0b30f00dde" + integrity sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg== + dependencies: + whatwg-mimetype "^4.0.0" + whatwg-url "^14.0.0" + date-fns@^2.29.3: version "2.30.0" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" @@ -10718,6 +10733,11 @@ decamelize@^1.1.0, decamelize@^1.2.0: resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== +decimal.js@^10.4.3: + version "10.4.3" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" + integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== + decode-formdata@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/decode-formdata/-/decode-formdata-0.4.0.tgz#485715a37417868435a92d2144b39646fe654aae" @@ -11286,6 +11306,11 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== +entities@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + env-ci@^5.0.0: version "5.5.0" resolved "https://registry.npmjs.org/env-ci/-/env-ci-5.5.0.tgz" @@ -13765,6 +13790,13 @@ hosted-git-info@^7.0.0: dependencies: lru-cache "^10.0.1" +html-encoding-sniffer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz#696df529a7cfd82446369dc5193e590a3735b448" + integrity sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ== + dependencies: + whatwg-encoding "^3.1.1" + html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" @@ -13839,6 +13871,14 @@ http-proxy-agent@^7.0.0: agent-base "^7.1.0" debug "^4.3.4" +http-proxy-agent@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" + integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== + dependencies: + agent-base "^7.1.0" + debug "^4.3.4" + http-proxy@^1.18.1: version "1.18.1" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" @@ -13869,6 +13909,14 @@ https-proxy-agent@^7.0.1: agent-base "^7.0.2" debug "4" +https-proxy-agent@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz#9e8b5013873299e11fab6fd548405da2d6c602b2" + integrity sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw== + dependencies: + agent-base "^7.0.2" + debug "4" + human-signals@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz" @@ -13934,7 +13982,7 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.6.2: +iconv-lite@0.6.3, iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== @@ -14421,6 +14469,11 @@ is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + is-reference@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-3.0.2.tgz#154747a01f45cd962404ee89d43837af2cba247c" @@ -15295,6 +15348,33 @@ jsdoc-type-pratt-parser@~4.0.0: resolved "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz" integrity sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ== +jsdom@^25.0.1: + version "25.0.1" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-25.0.1.tgz#536ec685c288fc8a5773a65f82d8b44badcc73ef" + integrity sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw== + dependencies: + cssstyle "^4.1.0" + data-urls "^5.0.0" + decimal.js "^10.4.3" + form-data "^4.0.0" + html-encoding-sniffer "^4.0.0" + http-proxy-agent "^7.0.2" + https-proxy-agent "^7.0.5" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.12" + parse5 "^7.1.2" + rrweb-cssom "^0.7.1" + saxes "^6.0.0" + symbol-tree "^3.2.4" + tough-cookie "^5.0.0" + w3c-xmlserializer "^5.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^3.1.1" + whatwg-mimetype "^4.0.0" + whatwg-url "^14.0.0" + ws "^8.18.0" + xml-name-validator "^5.0.0" + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" @@ -17731,6 +17811,11 @@ nth-check@^2.0.0: dependencies: boolbase "^1.0.0" +nwsapi@^2.2.12: + version "2.2.12" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.12.tgz#fb6af5c0ec35b27b4581eb3bbad34ec9e5c696f8" + integrity sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w== + nx@17.3.0, "nx@>=17.1.2 < 18", nx@^17.3.0: version "17.3.0" resolved "https://registry.npmjs.org/nx/-/nx-17.3.0.tgz" @@ -18376,6 +18461,13 @@ parse-url@^8.1.0: dependencies: parse-path "^7.0.0" +parse5@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" + integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + dependencies: + entities "^4.4.0" + parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -19016,7 +19108,7 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -punycode@^2.1.0, punycode@^2.1.1: +punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.1: version "2.3.1" resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== @@ -19887,6 +19979,11 @@ rollup@^4.13.0: "@rollup/rollup-win32-x64-msvc" "4.17.1" fsevents "~2.3.2" +rrweb-cssom@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz#c73451a484b86dd7cfb1e0b2898df4b703183e4b" + integrity sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg== + run-async@^2.4.0: version "2.4.1" resolved "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz" @@ -19959,6 +20056,13 @@ safe-stable-stringify@^2.1.0: resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +saxes@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" + integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== + dependencies: + xmlchars "^2.2.0" + scheduler@^0.23.0: version "0.23.0" resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz" @@ -20883,6 +20987,11 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + synchronous-promise@^2.0.15: version "2.0.17" resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.17.tgz#38901319632f946c982152586f2caf8ddc25c032" @@ -21218,6 +21327,18 @@ tinyspy@^3.0.0: resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.0.tgz#cb61644f2713cd84dee184863f4642e06ddf0585" integrity sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA== +tldts-core@^6.1.48: + version "6.1.48" + resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.48.tgz#efa7dc689b9757d1d4326b787cd992f10a16b2fb" + integrity sha512-3gD9iKn/n2UuFH1uilBviK9gvTNT6iYwdqrj1Vr5mh8FuelvpRNaYVH4pNYqUgOGU4aAdL9X35eLuuj0gRsx+A== + +tldts@^6.1.32: + version "6.1.48" + resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.48.tgz#bfef97f407fe73f1a88db8e0f6905378e9a348c0" + integrity sha512-SPbnh1zaSzi/OsmHb1vrPNnYuwJbdWjwo5TbBYYMlTtH3/1DSb41t8bcSxkwDmmbG2q6VLPVvQc7Yf23T+1EEw== + dependencies: + tldts-core "^6.1.48" + tmp@^0.0.33: version "0.0.33" resolved "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz" @@ -21274,6 +21395,20 @@ tough-cookie@^4.1.4: universalify "^0.2.0" url-parse "^1.5.3" +tough-cookie@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-5.0.0.tgz#6b6518e2b5c070cf742d872ee0f4f92d69eac1af" + integrity sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q== + dependencies: + tldts "^6.1.32" + +tr46@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-5.0.0.tgz#3b46d583613ec7283020d79019f1335723801cec" + integrity sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g== + dependencies: + punycode "^2.3.1" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" @@ -22334,6 +22469,13 @@ vocs@^1.0.0-alpha.55: unist-util-visit "^5.0.0" vite "^5.3.3" +w3c-xmlserializer@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz#f925ba26855158594d907313cedd1476c5967f6c" + integrity sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA== + dependencies: + xml-name-validator "^5.0.0" + wagmi@2.12.7, wagmi@^2.12.7: version "2.12.7" resolved "https://registry.yarnpkg.com/wagmi/-/wagmi-2.12.7.tgz#7c50271cd6a3edb7acc5f8cbaee096a0aac0e4ab" @@ -22433,6 +22575,11 @@ webidl-conversions@^3.0.0: resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + webpack-virtual-modules@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz#057faa9065c8acf48f24cb57ac0e77739ab9a7e8" @@ -22450,6 +22597,26 @@ websocket@^1.0.34: utf-8-validate "^5.0.2" yaeti "^0.0.6" +whatwg-encoding@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz#d0f4ef769905d426e1688f3e34381a99b60b76e5" + integrity sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ== + dependencies: + iconv-lite "0.6.3" + +whatwg-mimetype@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz#bc1bf94a985dc50388d54a9258ac405c3ca2fc0a" + integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== + +whatwg-url@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-14.0.0.tgz#00baaa7fd198744910c4b1ef68378f2200e4ceb6" + integrity sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw== + dependencies: + tr46 "^5.0.0" + webidl-conversions "^7.0.0" + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" @@ -22666,7 +22833,7 @@ ws@^7.5.1: resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== -ws@^8.2.3: +ws@^8.18.0, ws@^8.2.3: version "8.18.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== @@ -22690,11 +22857,21 @@ xdg-portable@^7.0.0: dependencies: os-paths "^4.0.1" +xml-name-validator@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz#82be9b957f7afdacf961e5980f1bf227c0bf7673" + integrity sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg== + xml@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw== +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + xmlhttprequest-ssl@~2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz"