Skip to content

Commit 5a87789

Browse files
committed
feat: add initial smart wallet client actions
1 parent 1e981b1 commit 5a87789

File tree

15 files changed

+276
-35
lines changed

15 files changed

+276
-35
lines changed

account-kit/core/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@
3232
"import": "./dist/esm/environments/web/index.js",
3333
"default": "./dist/esm/environments/web/index.js"
3434
},
35+
"./experimental": {
36+
"types": "./dist/types/experimental/index.d.ts",
37+
"import": "./dist/esm/experimental/index.js",
38+
"default": "./dist/esm/experimental/index.js"
39+
},
3540
"./package.json": "./package.json"
3641
},
3742
"scripts": {
@@ -54,6 +59,7 @@
5459
"@account-kit/react-native-signer": "^4.35.1",
5560
"@account-kit/signer": "^4.35.1",
5661
"@account-kit/smart-contracts": "^4.35.1",
62+
"@account-kit/wallet-client": "^0.1.0-alpha.4",
5763
"@solana/web3.js": "^1.98.0",
5864
"js-cookie": "^3.0.5",
5965
"zod": "^3.22.4",

account-kit/core/src/actions/createAccount.ts

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,27 @@ import {
22
createLightAccount,
33
createMultiOwnerLightAccount,
44
createMultiOwnerModularAccount,
5-
createModularAccountV2,
65
type CreateLightAccountParams,
6+
type CreateModularAccountV2Params,
77
type CreateMultiOwnerLightAccountParams,
88
type CreateMultiOwnerModularAccountParams,
99
type LightAccountVersion,
10-
type CreateModularAccountV2Params,
1110
} from "@account-kit/smart-contracts";
12-
import { custom, type Transport } from "viem";
13-
import { ClientOnlyPropertyError } from "../errors.js";
11+
import type { SmartWalletClient } from "@account-kit/wallet-client";
12+
import { custom, toHex, type Transport } from "viem";
13+
import { ClientOnlyPropertyError, SignerNotConnectedError } from "../errors.js";
14+
import { getSmartWalletClient } from "../experimental/actions/getSmartWalletClient.js";
1415
import { CoreLogger } from "../metrics.js";
1516
import type {
1617
AlchemyAccountsConfig,
1718
AlchemySigner,
1819
SupportedAccountTypes,
1920
SupportedAccounts,
2021
} from "../types.js";
22+
import type { GetAccountParams } from "./getAccount";
2123
import { getBundlerClient } from "./getBundlerClient.js";
2224
import { getSigner } from "./getSigner.js";
2325
import { getSignerStatus } from "./getSignerStatus.js";
24-
import type { GetAccountParams } from "./getAccount";
2526

2627
type OmitSignerTransportChain<T> = Omit<T, "signer" | "transport" | "chain">;
2728

@@ -93,8 +94,9 @@ export async function createAccount<TAccount extends SupportedAccountTypes>(
9394
const signerStatus = getSignerStatus(config);
9495

9596
if (!signerStatus.isConnected || !signer) {
96-
throw new Error("Signer not connected");
97+
throw new SignerNotConnectedError();
9798
}
99+
const smartWalletClient = getSmartWalletClient(config);
98100

99101
const cachedAccount = accounts[chain.id]?.[params.type];
100102
if (cachedAccount.status !== "RECONNECTING" && cachedAccount.account) {
@@ -154,22 +156,23 @@ export async function createAccount<TAccount extends SupportedAccountTypes>(
154156
return account;
155157
});
156158
} else if (isModularV2AccountParams(params)) {
157-
return createModularAccountV2({
158-
...accountConfigs[chain.id]?.[params.type],
159-
...params.accountParams,
160-
signer,
161-
transport: (opts) => transport({ ...opts, retryCount: 0 }),
162-
chain,
163-
}).then((account) => {
164-
CoreLogger.trackEvent({
165-
name: "account_initialized",
166-
data: {
167-
accountType: "ModularAccountV2",
168-
accountVersion: "v2.0.0",
169-
},
159+
// TODO: we can probably do away with some of the if-else logic here and just convert the params to creation hints
160+
// and pass them to the client
161+
return smartWalletClient
162+
.requestAccount({
163+
accountAddress: params.accountParams?.accountAddress,
164+
creationHint: convertAccountParamsToCreationHint(params),
165+
})
166+
.then((account) => {
167+
CoreLogger.trackEvent({
168+
name: "account_initialized",
169+
data: {
170+
accountType: "ModularAccountV2",
171+
accountVersion: "v2.0.0",
172+
},
173+
});
174+
return account as SupportedAccounts;
170175
});
171-
return account;
172-
});
173176
} else {
174177
throw new Error(`Unsupported account type: ${params.type}`);
175178
}
@@ -243,6 +246,27 @@ export async function createAccount<TAccount extends SupportedAccountTypes>(
243246
return accountPromise;
244247
}
245248

249+
function convertAccountParamsToCreationHint<
250+
TAccount extends SupportedAccountTypes,
251+
>(
252+
params: CreateAccountParams<TAccount>,
253+
): NonNullable<
254+
Parameters<SmartWalletClient["requestAccount"]>["0"]
255+
>["creationHint"] {
256+
if (isModularV2AccountParams(params)) {
257+
return params.accountParams?.mode === "7702"
258+
? { accountType: "7702" }
259+
: {
260+
accountType: "sma-b",
261+
...params.accountParams,
262+
// @ts-expect-error salt is defined by TS can't figure that out here
263+
salt: toHex(params.accountParams?.salt ?? 0n),
264+
};
265+
}
266+
267+
throw new Error("account not supported yet");
268+
}
269+
246270
export const isModularV2AccountParams = (
247271
params: CreateAccountParams<SupportedAccountTypes>,
248272
): params is GetAccountParams<"ModularAccountV2"> => {

account-kit/core/src/actions/getSmartAccountClient.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { default7702GasEstimator, default7702UserOpSigner } from "@aa-sdk/core";
12
import {
23
createAlchemySmartAccountClient,
34
type AlchemySmartAccountClient,
@@ -12,28 +13,27 @@ import {
1213
type AccountLoupeActions,
1314
type LightAccount,
1415
type LightAccountClientActions,
16+
type ModularAccountV2,
1517
type MultiOwnerLightAccount,
1618
type MultiOwnerLightAccountClientActions,
1719
type MultiOwnerModularAccount,
1820
type MultiOwnerPluginActions,
1921
type PluginManagerActions,
20-
type ModularAccountV2,
2122
} from "@account-kit/smart-contracts";
2223
import type { Address, Chain } from "viem";
2324
import type {
2425
AlchemyAccountsConfig,
2526
AlchemySigner,
27+
Connection,
2628
SupportedAccount,
2729
SupportedAccounts,
2830
SupportedAccountTypes,
29-
Connection,
3031
} from "../types";
3132
import { createAccount, isModularV2AccountParams } from "./createAccount.js";
3233
import { getAccount, type GetAccountParams } from "./getAccount.js";
3334
import { getAlchemyTransport } from "./getAlchemyTransport.js";
3435
import { getConnection } from "./getConnection.js";
3536
import { getSignerStatus } from "./getSignerStatus.js";
36-
import { default7702GasEstimator, default7702UserOpSigner } from "@aa-sdk/core";
3737

3838
export type GetSmartAccountClientParams<
3939
TChain extends Chain | undefined = Chain | undefined,
@@ -77,6 +77,7 @@ export function getSmartAccountClient<
7777
): GetSmartAccountClientResult<TChain, SupportedAccount<TAccount>>;
7878

7979
/**
80+
* @deprecated Use `getSmartWalletClient` instead.
8081
* Obtains a smart account client based on the provided parameters and configuration. Supports creating any of the SupportAccountTypes in Account Kit.
8182
* If the signer is not connected, or an account is already being intializes, this results in a loading state.
8283
*
@@ -178,6 +179,7 @@ export function getSmartAccountClient(
178179
return clientState;
179180
}
180181

182+
// TODO: this needs to be updated to use the smart wallet client instead
181183
const newState = (() => {
182184
switch (account.source) {
183185
case "LightAccount":

account-kit/core/src/createConfig.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { DEFAULT_SESSION_MS } from "@account-kit/signer";
22
import { createStorage, createConfig as createWagmiConfig } from "@wagmi/core";
33
import { getBundlerClient } from "./actions/getBundlerClient.js";
4+
import { createSigner as createWebSigner } from "./environments/web/createSigner.js";
45
import { CoreLogger } from "./metrics.js";
56
import { createAccountKitStore } from "./store/store.js";
67
import { DEFAULT_STORAGE_KEY } from "./store/types.js";
@@ -9,7 +10,6 @@ import {
910
type Connection,
1011
type CreateConfigProps,
1112
} from "./types.js";
12-
import { createSigner as createWebSigner } from "./environments/web/createSigner.js";
1313

1414
export const DEFAULT_IFRAME_CONTAINER_ID = "alchemy-signer-iframe-container";
1515

@@ -115,6 +115,7 @@ export const createConfig = (
115115

116116
const config: AlchemyAccountsConfig = {
117117
store: store,
118+
accountCreationHint: params.accountCreationHint,
118119
_internal: {
119120
ssr,
120121
createSigner: createSigner ?? createWebSigner, // <-- Default to web signer if not provided

account-kit/core/src/errors.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,13 @@ export class ChainNotFoundError extends BaseError {
5353
});
5454
}
5555
}
56+
57+
export class SignerNotConnectedError extends BaseError {
58+
name: string = "SignerNotConnectedError";
59+
60+
constructor() {
61+
super(
62+
"Signer not connected. Authenticate the user before calling this function",
63+
);
64+
}
65+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Experimental
2+
3+
This experimental namespace is meant to contain actions and other core logic that consumes Wallet APIs. These will replace the usual flow found within this repo
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import {
2+
createSmartWalletClient,
3+
type SmartWalletClient,
4+
} from "@account-kit/wallet-client";
5+
import type { Address, IsUndefined, JsonRpcAccount } from "viem";
6+
import { getAlchemyTransport } from "../../actions/getAlchemyTransport.js";
7+
import { getConnection } from "../../actions/getConnection.js";
8+
import { getSigner } from "../../actions/getSigner.js";
9+
import { getSignerStatus } from "../../actions/getSignerStatus.js";
10+
import { SignerNotConnectedError } from "../../errors.js";
11+
import type { AlchemyAccountsConfig } from "../../types.js";
12+
13+
export type GetSmartWalletClientResult<
14+
TAccount extends JsonRpcAccount<Address> | undefined =
15+
| JsonRpcAccount<`0x${string}`>
16+
| undefined,
17+
> = SmartWalletClient<TAccount>;
18+
19+
export type GetSmartWalletClientParams<
20+
TAccount extends JsonRpcAccount<Address> | undefined =
21+
| JsonRpcAccount<Address>
22+
| undefined,
23+
> = { mode?: "local" | "remote" } & (IsUndefined<TAccount> extends true
24+
? { account?: never }
25+
: { account: Address });
26+
27+
export function getSmartWalletClient<
28+
TAccount extends JsonRpcAccount<Address> | undefined =
29+
| JsonRpcAccount<Address>
30+
| undefined,
31+
>(
32+
config: AlchemyAccountsConfig,
33+
params?: GetSmartWalletClientParams<TAccount>,
34+
): GetSmartWalletClientResult<TAccount>;
35+
36+
export function getSmartWalletClient(
37+
config: AlchemyAccountsConfig,
38+
params?: GetSmartWalletClientParams,
39+
): GetSmartWalletClientResult {
40+
const connection = getConnection(config);
41+
const signerStatus = getSignerStatus(config);
42+
const transport = getAlchemyTransport(config);
43+
const signer = getSigner(config);
44+
45+
if (!signer || !signerStatus.isConnected) {
46+
throw new SignerNotConnectedError();
47+
}
48+
49+
const smartWalletClient =
50+
config.store.getState().smartWalletClients[connection.chain.id];
51+
52+
if (smartWalletClient) {
53+
return smartWalletClient;
54+
}
55+
56+
// TODO: should we still cache the client like we used to?
57+
// honestly that probably caused more problems than it solved
58+
// TBD actually, we might store it in the store so we don't run into issues
59+
// with react
60+
const client = createSmartWalletClient({
61+
transport,
62+
chain: connection.chain,
63+
signer,
64+
account: params?.account,
65+
mode: params?.mode ?? "local",
66+
});
67+
68+
config.store.setState((state) => ({
69+
smartWalletClients: {
70+
...state.smartWalletClients,
71+
[connection.chain.id]: client,
72+
},
73+
}));
74+
75+
return client;
76+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { AlchemyAccountsConfig } from "../../types";
2+
import {
3+
getSmartWalletClient,
4+
type GetSmartWalletClientResult,
5+
} from "./getSmartWalletClient.js";
6+
7+
export function watchSmartWalletClient(config: AlchemyAccountsConfig) {
8+
return (onChange: (client: GetSmartWalletClientResult) => void) => {
9+
return config.store.subscribe(
10+
({ signerStatus, chain }) => ({ signerStatus, chain }),
11+
() => {
12+
onChange(getSmartWalletClient(config));
13+
},
14+
{
15+
equalityFn(a, b) {
16+
return a.signerStatus === b.signerStatus && a.chain === b.chain;
17+
},
18+
},
19+
);
20+
};
21+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export type * from "./actions/getSmartWalletClient.js";
2+
export { getSmartWalletClient } from "./actions/getSmartWalletClient.js";
3+
export type * from "./actions/watchSmartWalletClient.js";
4+
export { watchSmartWalletClient } from "./actions/watchSmartWalletClient.js";

account-kit/core/src/store/store.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
AlchemyWebSigner,
66
type ErrorInfo,
77
} from "@account-kit/signer";
8+
import { Connection as SolanaWeb3Connection } from "@solana/web3.js";
89
import type { Chain } from "viem";
910
import {
1011
createJSONStorage,
@@ -25,7 +26,6 @@ import {
2526
type Store,
2627
type StoreState,
2728
} from "./types.js";
28-
import { Connection as SolanaWeb3Connection } from "@solana/web3.js";
2929

3030
export const STORAGE_VERSION = 14;
3131

@@ -137,6 +137,9 @@ export const createAccountKitStore = (
137137
smartAccountClients: createEmptySmartAccountClientState(
138138
connections.map((c) => c.chain),
139139
),
140+
smartWalletClients: createEmptySmartWalletClientState(
141+
connections.map((c) => c.chain),
142+
),
140143
connections: connectionsMap,
141144
solana: params.solana,
142145
bundlerClient: createAlchemyPublicRpcClient({
@@ -188,6 +191,7 @@ const createInitialStoreState = (
188191
undefined,
189192
),
190193
smartAccountClients: createEmptySmartAccountClientState(chains),
194+
smartWalletClients: createEmptySmartWalletClientState(chains),
191195
};
192196

193197
if ("solana" in params && params.solana) {
@@ -395,6 +399,16 @@ export const createEmptySmartAccountClientState = (chains: Chain[]) => {
395399
);
396400
};
397401

402+
export const createEmptySmartWalletClientState = (chains: Chain[]) => {
403+
return chains.reduce(
404+
(acc, chain) => {
405+
acc[chain.id] = undefined;
406+
407+
return acc;
408+
},
409+
{} as StoreState["smartWalletClients"],
410+
);
411+
};
398412
const deepEquals = (obj1: any, obj2: any) => {
399413
if (typeof obj1 !== typeof obj2) return false;
400414
if (typeof obj1 !== "object") return obj1 === obj2;

account-kit/core/src/store/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
ErrorInfo,
77
User,
88
} from "@account-kit/signer";
9+
import type { SmartWalletClient } from "@account-kit/wallet-client";
910
import type { State as WagmiState } from "@wagmi/core";
1011
import type { Address, Chain } from "viem";
1112
import type { PartialBy } from "viem/chains";
@@ -94,6 +95,9 @@ export type StoreState = {
9495
>;
9596
}>;
9697
};
98+
smartWalletClients: {
99+
[chain: number]: SmartWalletClient | undefined;
100+
};
97101
bundlerClient: ClientWithAlchemyMethods;
98102
// serializable state
99103
// NOTE: in some cases this can be serialized to cookie storage

0 commit comments

Comments
 (0)