Skip to content

feat: add initial smart wallet client actions #1648

New issue

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

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

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions account-kit/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
"import": "./dist/esm/environments/web/index.js",
"default": "./dist/esm/environments/web/index.js"
},
"./experimental": {
"types": "./dist/types/experimental/index.d.ts",
"import": "./dist/esm/experimental/index.js",
"default": "./dist/esm/experimental/index.js"
},
"./package.json": "./package.json"
},
"scripts": {
Expand All @@ -54,6 +59,7 @@
"@account-kit/react-native-signer": "^4.37.0",
"@account-kit/signer": "^4.37.0",
"@account-kit/smart-contracts": "^4.37.0",
"@account-kit/wallet-client": "^0.1.0-alpha.4",
"@solana/web3.js": "^1.98.0",
"js-cookie": "^3.0.5",
"zod": "^3.22.4",
Expand Down
66 changes: 45 additions & 21 deletions account-kit/core/src/actions/createAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,27 @@ import {
createLightAccount,
createMultiOwnerLightAccount,
createMultiOwnerModularAccount,
createModularAccountV2,
type CreateLightAccountParams,
type CreateModularAccountV2Params,
type CreateMultiOwnerLightAccountParams,
type CreateMultiOwnerModularAccountParams,
type LightAccountVersion,
type CreateModularAccountV2Params,
} from "@account-kit/smart-contracts";
import { custom, type Transport } from "viem";
import { ClientOnlyPropertyError } from "../errors.js";
import type { SmartWalletClient } from "@account-kit/wallet-client";
import { custom, toHex, type Transport } from "viem";
import { ClientOnlyPropertyError, SignerNotConnectedError } from "../errors.js";
import { getSmartWalletClient } from "../experimental/actions/getSmartWalletClient.js";
import { CoreLogger } from "../metrics.js";
import type {
AlchemyAccountsConfig,
AlchemySigner,
SupportedAccountTypes,
SupportedAccounts,
} from "../types.js";
import type { GetAccountParams } from "./getAccount";
import { getBundlerClient } from "./getBundlerClient.js";
import { getSigner } from "./getSigner.js";
import { getSignerStatus } from "./getSignerStatus.js";
import type { GetAccountParams } from "./getAccount";

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

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

if (!signerStatus.isConnected || !signer) {
throw new Error("Signer not connected");
throw new SignerNotConnectedError();
}
const smartWalletClient = getSmartWalletClient(config);

const cachedAccount = accounts[chain.id]?.[params.type];
if (cachedAccount.status !== "RECONNECTING" && cachedAccount.account) {
Expand Down Expand Up @@ -154,22 +156,23 @@ export async function createAccount<TAccount extends SupportedAccountTypes>(
return account;
});
} else if (isModularV2AccountParams(params)) {
return createModularAccountV2({
...accountConfigs[chain.id]?.[params.type],
...params.accountParams,
signer,
transport: (opts) => transport({ ...opts, retryCount: 0 }),
chain,
}).then((account) => {
CoreLogger.trackEvent({
name: "account_initialized",
data: {
accountType: "ModularAccountV2",
accountVersion: "v2.0.0",
},
// TODO: we can probably do away with some of the if-else logic here and just convert the params to creation hints
// and pass them to the client
return smartWalletClient
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably the most beneficial place to add support for wallet apis because it means accounts will be index correctly. everything downstream will the touch this flow which is what we want

.requestAccount({
accountAddress: params.accountParams?.accountAddress,
creationHint: convertAccountParamsToCreationHint(params),
})
.then((account) => {
CoreLogger.trackEvent({
name: "account_initialized",
data: {
accountType: "ModularAccountV2",
accountVersion: "v2.0.0",
},
});
return account as SupportedAccounts;
});
return account;
});
} else {
throw new Error(`Unsupported account type: ${params.type}`);
}
Expand Down Expand Up @@ -243,6 +246,27 @@ export async function createAccount<TAccount extends SupportedAccountTypes>(
return accountPromise;
}

function convertAccountParamsToCreationHint<
TAccount extends SupportedAccountTypes,
>(
params: CreateAccountParams<TAccount>,
): NonNullable<
Parameters<SmartWalletClient["requestAccount"]>["0"]
>["creationHint"] {
if (isModularV2AccountParams(params)) {
return params.accountParams?.mode === "7702"
? { accountType: "7702" }
: {
accountType: "sma-b",
...params.accountParams,
// @ts-expect-error salt is defined by TS can't figure that out here
salt: toHex(params.accountParams?.salt ?? 0n),
};
}

throw new Error("account not supported yet");
}

export const isModularV2AccountParams = (
params: CreateAccountParams<SupportedAccountTypes>,
): params is GetAccountParams<"ModularAccountV2"> => {
Expand Down
9 changes: 6 additions & 3 deletions account-kit/core/src/actions/getSmartAccountClient.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { default7702GasEstimator, default7702UserOpSigner } from "@aa-sdk/core";
import {
createAlchemySmartAccountClient,
type AlchemySmartAccountClient,
Expand All @@ -12,28 +13,27 @@ import {
type AccountLoupeActions,
type LightAccount,
type LightAccountClientActions,
type ModularAccountV2,
type MultiOwnerLightAccount,
type MultiOwnerLightAccountClientActions,
type MultiOwnerModularAccount,
type MultiOwnerPluginActions,
type PluginManagerActions,
type ModularAccountV2,
} from "@account-kit/smart-contracts";
import type { Address, Chain } from "viem";
import type {
AlchemyAccountsConfig,
AlchemySigner,
Connection,
SupportedAccount,
SupportedAccounts,
SupportedAccountTypes,
Connection,
} from "../types";
import { createAccount, isModularV2AccountParams } from "./createAccount.js";
import { getAccount, type GetAccountParams } from "./getAccount.js";
import { getAlchemyTransport } from "./getAlchemyTransport.js";
import { getConnection } from "./getConnection.js";
import { getSignerStatus } from "./getSignerStatus.js";
import { default7702GasEstimator, default7702UserOpSigner } from "@aa-sdk/core";

export type GetSmartAccountClientParams<
TChain extends Chain | undefined = Chain | undefined,
Expand Down Expand Up @@ -80,6 +80,8 @@ export function getSmartAccountClient<
* Obtains a smart account client based on the provided parameters and configuration. Supports creating any of the SupportAccountTypes in Account Kit.
* If the signer is not connected, or an account is already being intializes, this results in a loading state.
*
* @deprecated Use `getSmartWalletClient` instead.
*
* @example
* ```ts
* import { getSmartAccountClient } from "@account-kit/core";
Expand Down Expand Up @@ -178,6 +180,7 @@ export function getSmartAccountClient(
return clientState;
}

// TODO: this needs to be updated to use the smart wallet client instead
const newState = (() => {
switch (account.source) {
case "LightAccount":
Expand Down
3 changes: 2 additions & 1 deletion account-kit/core/src/createConfig.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { DEFAULT_SESSION_MS } from "@account-kit/signer";
import { createStorage, createConfig as createWagmiConfig } from "@wagmi/core";
import { getBundlerClient } from "./actions/getBundlerClient.js";
import { createSigner as createWebSigner } from "./environments/web/createSigner.js";
import { CoreLogger } from "./metrics.js";
import { createAccountKitStore } from "./store/store.js";
import { DEFAULT_STORAGE_KEY } from "./store/types.js";
Expand All @@ -9,7 +10,6 @@ import {
type Connection,
type CreateConfigProps,
} from "./types.js";
import { createSigner as createWebSigner } from "./environments/web/createSigner.js";

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

Expand Down Expand Up @@ -115,6 +115,7 @@ export const createConfig = (

const config: AlchemyAccountsConfig = {
store: store,
accountCreationHint: params.accountCreationHint,
_internal: {
ssr,
createSigner: createSigner ?? createWebSigner, // <-- Default to web signer if not provided
Expand Down
10 changes: 10 additions & 0 deletions account-kit/core/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,13 @@ export class ChainNotFoundError extends BaseError {
});
}
}

export class SignerNotConnectedError extends BaseError {
name: string = "SignerNotConnectedError";

constructor() {
super(
"Signer not connected. Authenticate the user before calling this function",
);
}
}
3 changes: 3 additions & 0 deletions account-kit/core/src/experimental/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Experimental

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
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {
createSmartWalletClient,
type SmartWalletClient,
} from "@account-kit/wallet-client";
import type { Address, IsUndefined, JsonRpcAccount } from "viem";
import { getAlchemyTransport } from "../../actions/getAlchemyTransport.js";
import { getConnection } from "../../actions/getConnection.js";
import { getSigner } from "../../actions/getSigner.js";
import { getSignerStatus } from "../../actions/getSignerStatus.js";
import { SignerNotConnectedError } from "../../errors.js";
import type { AlchemyAccountsConfig } from "../../types.js";

export type GetSmartWalletClientResult<
TAccount extends JsonRpcAccount<Address> | undefined =
| JsonRpcAccount<`0x${string}`>
| undefined,
> = SmartWalletClient<TAccount>;

export type GetSmartWalletClientParams<
TAccount extends JsonRpcAccount<Address> | undefined =
| JsonRpcAccount<Address>
| undefined,
> = { mode?: "local" | "remote" } & (IsUndefined<TAccount> extends true
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, we should move the mode into the config rather than here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i do this in a later pr

? { account?: never }
: { account: Address });

export function getSmartWalletClient<
TAccount extends JsonRpcAccount<Address> | undefined =
| JsonRpcAccount<Address>
| undefined,
>(
config: AlchemyAccountsConfig,
params?: GetSmartWalletClientParams<TAccount>,
): GetSmartWalletClientResult<TAccount>;

export function getSmartWalletClient(
config: AlchemyAccountsConfig,
params?: GetSmartWalletClientParams,
): GetSmartWalletClientResult {
const connection = getConnection(config);
const signerStatus = getSignerStatus(config);
const transport = getAlchemyTransport(config);
const signer = getSigner(config);

if (!signer || !signerStatus.isConnected) {
throw new SignerNotConnectedError();
}

const smartWalletClient =
config.store.getState().smartWalletClients[connection.chain.id];

if (
smartWalletClient &&
smartWalletClient.account?.address === params?.account
) {
return smartWalletClient;
}

// TODO: should we still cache the client like we used to?
// honestly that probably caused more problems than it solved
// TBD actually, we might store it in the store so we don't run into issues
// with react
const client = createSmartWalletClient({
transport,
chain: connection.chain,
signer,
account: params?.account,
mode: params?.mode ?? "local",
});

config.store.setState((state) => ({
smartWalletClients: {
...state.smartWalletClients,
[connection.chain.id]: client,
},
}));

return client;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { AlchemyAccountsConfig } from "../../types";
import {
getSmartWalletClient,
type GetSmartWalletClientResult,
} from "./getSmartWalletClient.js";

export function watchSmartWalletClient(config: AlchemyAccountsConfig) {
return (onChange: (client: GetSmartWalletClientResult) => void) => {
return config.store.subscribe(
({ signerStatus, chain }) => ({ signerStatus, chain }),
() => {
onChange(getSmartWalletClient(config));
},
{
equalityFn(a, b) {
return a.signerStatus === b.signerStatus && a.chain === b.chain;
},
},
);
};
}
4 changes: 4 additions & 0 deletions account-kit/core/src/experimental/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type * from "./actions/getSmartWalletClient.js";
export { getSmartWalletClient } from "./actions/getSmartWalletClient.js";
export type * from "./actions/watchSmartWalletClient.js";
export { watchSmartWalletClient } from "./actions/watchSmartWalletClient.js";
6 changes: 4 additions & 2 deletions account-kit/core/src/store/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { getAlchemyTransport } from "../actions/getAlchemyTransport.js";
import { setChain } from "../actions/setChain.js";
import { createConfig } from "../createConfig.js";
import { createDefaultAccountState, STORAGE_VERSION } from "./store.js";

Check warning on line 10 in account-kit/core/src/store/store.test.ts

View workflow job for this annotation

GitHub Actions / Lint

'STORAGE_VERSION' is defined but never used
import { DEFAULT_STORAGE_KEY } from "./types.js";

describe("createConfig tests", () => {
Expand Down Expand Up @@ -515,8 +515,9 @@
"isInitializing": true,
"status": "INITIALIZING",
},
"smartWalletClients": {},
},
"version": ${STORAGE_VERSION},
"version": 14,
}
`);
});
Expand Down Expand Up @@ -584,8 +585,9 @@
"isInitializing": true,
"status": "INITIALIZING",
},
"smartWalletClients": {},
},
"version": ${STORAGE_VERSION},
"version": 14,
}
`);
});
Expand Down
Loading
Loading