Skip to content
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
2 changes: 1 addition & 1 deletion docs-site
Submodule docs-site updated 101 files
4 changes: 4 additions & 0 deletions packages/smart-accounts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,12 @@ export {
lightAccountStaticImplV1_0_2,
lightAccountStaticImplV1_1_0,
lightAccountStaticImplV2_0_0,
lightAccountStaticImplV2_1_0,
lightAccountStaticImplV2_2_0,
lightAccountStaticImpl7702V2_1_0,
multiOwnerLightAccountStaticImplV2_0_0,
} from "./light-account/lightAccountStaticImpl.js";
export type { LightAccountStaticImpl7702V2_1_0 } from "./light-account/lightAccountStaticImpl.js";

// ModularAccountV1
export type * from "./ma-v1/accounts/base.js";
Expand Down
107 changes: 94 additions & 13 deletions packages/smart-accounts/src/light-account/accounts/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import {
type Hex,
type JsonRpcAccount,
type LocalAccount,
type PrivateKeyAccount,
type Transport,
} from "viem";
import { readContract } from "viem/actions";
import { getCode, readContract } from "viem/actions";
import type { ToSmartAccountParameters } from "viem/account-abstraction";
import { LightAccountAbi_v1 } from "../abis/LightAccountAbi_v1.js";
import { LightAccountAbi_v2 } from "../abis/LightAccountAbi_v2.js";
import { LightAccountFactoryAbi_v1 } from "../abis/LightAccountFactoryAbi_v1.js";
Expand All @@ -23,10 +25,15 @@ import {
LightAccountUnsupported1271Factories,
defaultLightAccountVersion,
} from "../utils.js";
import { is7702Delegated } from "../../utils.js";
import { toLightAccountBase, type LightAccountBase } from "./base.js";
import { BaseError, lowerAddress } from "@alchemy/common";
import { getAction } from "viem/utils";
import { LOGGER } from "../../logger.js";
import { InvalidOwnerError } from "../../errors/InvalidOwnerError.js";
import { lightAccountStaticImpl7702V2_1_0 } from "../lightAccountStaticImpl.js";

type LightAccountMode = "default" | "7702";

export type LightAccount<
TLightAccountVersion extends
Expand All @@ -39,15 +46,24 @@ export type LightAccount<
export type ToLightAccountParams<
TLightAccountVersion extends
LightAccountVersion<"LightAccount"> = LightAccountVersion<"LightAccount">,
TMode extends LightAccountMode | undefined = LightAccountMode | undefined,
> = {
client: Client<Transport, Chain, JsonRpcAccount | LocalAccount | undefined>;
owner: JsonRpcAccount | LocalAccount;
salt?: bigint;
accountAddress?: Address;
factory?: Address;
factoryData?: Hex;
version?: TLightAccountVersion;
};
mode?: TMode;
} & (TMode extends "7702"
? {
salt?: never;
factory?: never;
factoryData?: never;
}
: {
salt?: bigint;
factory?: Address;
factoryData?: Hex;
});

/**
* Creates a light account.
Expand All @@ -58,6 +74,7 @@ export type ToLightAccountParams<
export async function toLightAccount<
TLightAccountVersion extends
LightAccountVersion<"LightAccount"> = LightAccountVersion<"LightAccount">,
TMode extends LightAccountMode = LightAccountMode,
>({
client,
owner,
Expand All @@ -66,11 +83,15 @@ export async function toLightAccount<
version = defaultLightAccountVersion() as TLightAccountVersion,
factory = AccountVersionRegistry.LightAccount[version].factoryAddress,
factoryData: factoryData_,
}: ToLightAccountParams<TLightAccountVersion>): Promise<
mode,
}: ToLightAccountParams<TLightAccountVersion, TMode>): Promise<
LightAccount<TLightAccountVersion>
> {
const is7702 = mode === "7702";

LOGGER.debug("toLightAccount:start", {
version,
mode,
hasAccountAddress: !!accountAddress_,
});

Expand All @@ -87,16 +108,42 @@ export async function toLightAccount<

const accountAddress =
accountAddress_ ??
predictLightAccountAddress({
factoryAddress: factory,
salt,
ownerAddress: owner.address,
version,
});
(is7702 && owner.type === "local"
? owner.address
: predictLightAccountAddress({
factoryAddress: factory,
salt,
ownerAddress: owner.address,
version,
}));

LOGGER.debug("toLightAccount:address-resolved", { accountAddress });
LOGGER.debug("toLightAccount:address-resolved", { accountAddress, is7702 });

const getFactoryArgs = async () => {
if (is7702) {
// For 7702 mode, check if the delegation is already set to the correct address.
// We need to do this check here because viem's default isDeployed only checks
// for any code, not whether it's the correct delegation.
const getCodeAction = getAction(client, getCode, "getCode");
const code = await getCodeAction({ address: accountAddress });
const delegationAddress =
lightAccountStaticImpl7702V2_1_0.delegationAddress;
const isCorrectlyDelegated = is7702Delegated(delegationAddress, code);

if (isCorrectlyDelegated) {
return {
factory: undefined,
factoryData: undefined,
};
}

// EIP-7702 uses special factory address for EP 0.8
return {
factory: "0x7702000000000000000000000000000000000000" as Address,
factoryData: "0x" as Hex,
} as const;
}

const factoryData =
factoryData_ ??
encodeFunctionData({
Expand All @@ -111,6 +158,39 @@ export async function toLightAccount<
};
};

let authorization: ToSmartAccountParameters["authorization"];
if (is7702) {
LOGGER.debug("toLightAccount:7702-mode");
if (owner.type !== "local") {
LOGGER.error("toLightAccount:invalid-owner-type", {
ownerType: owner.type,
});
throw new InvalidOwnerError(
`Owner of type ${owner.type} is unsupported for 7702 mode.`,
);
}
if (owner.signAuthorization == null) {
LOGGER.error("toLightAccount:missing-signAuthorization");
throw new InvalidOwnerError(
"Owner must implement `signAuthorization` to be used with 7702 mode.",
);
}

// Check if already correctly delegated - if so, no authorization needed
const getCodeAction = getAction(client, getCode, "getCode");
const code = await getCodeAction({ address: accountAddress });
const delegationAddress =
lightAccountStaticImpl7702V2_1_0.delegationAddress;
const isCorrectlyDelegated = is7702Delegated(delegationAddress, code);

if (!isCorrectlyDelegated) {
authorization = {
account: owner as PrivateKeyAccount,
address: delegationAddress,
};
}
}

const baseAccount = await toLightAccountBase({
client,
owner,
Expand All @@ -119,6 +199,7 @@ export async function toLightAccount<
type: "LightAccount",
version,
getFactoryArgs,
authorization,
});

return {
Expand Down
43 changes: 40 additions & 3 deletions packages/smart-accounts/src/light-account/accounts/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,21 @@ import {
getUserOperationTypedData,
toSmartAccount,
type SmartAccountImplementation,
type ToSmartAccountParameters,
} from "viem/account-abstraction";
import { getStorageAt, signMessage, signTypedData } from "viem/actions";
import {
getCode,
getStorageAt,
signMessage,
signTypedData,
} from "viem/actions";
import type {
EntryPointFromAccountRegistry,
LightAccountType,
LightAccountVersion,
} from "../registry.js";
import { EIP1967_PROXY_IMPL_STORAGE_SLOT } from "../utils.js";
import { is7702Delegated } from "../../utils.js";
import { AccountVersionRegistry, isLightAccountVersion2 } from "../registry.js";
import {
encodeCallsLA as encodeCalls,
Expand Down Expand Up @@ -67,7 +74,7 @@ export type BaseLightAccountImplementation<
prepareSignature: (request: SignatureRequest) => Promise<SignatureRequest>;
formatSignature: (signature: Hex) => Promise<Hex>;
},
false
boolean
>;

export type LightAccountBase<
Expand All @@ -94,6 +101,7 @@ export type ToLightAccountBaseParams<
factory?: Address | undefined;
factoryData?: Hex | undefined;
}>;
authorization?: ToSmartAccountParameters["authorization"];
};

export async function toLightAccountBase<
Expand All @@ -109,6 +117,7 @@ export async function toLightAccountBase<
type,
version,
getFactoryArgs,
authorization,
}: ToLightAccountBaseParams<
TLightAccountType,
TLightAccountVersion,
Expand Down Expand Up @@ -228,10 +237,17 @@ export async function toLightAccountBase<
AccountVersionRegistry[type][version] as StaticSmartAccountImplementation
).entryPoint;

return await toSmartAccount({
// Extract delegation address for 7702 mode
const delegationAddress =
authorization && "address" in authorization
? authorization.address
: undefined;

const account = await toSmartAccount({
getFactoryArgs,
client,
entryPoint,
authorization,

async getAddress() {
return accountAddress;
Expand Down Expand Up @@ -376,4 +392,25 @@ export async function toLightAccountBase<
formatSignature,
},
});

// For 7702 mode, we need to override getFactoryArgs because viem's default
// isDeployed check returns true for any code at the address, but for 7702
// we need to check if it's delegated to the CORRECT address.
if (delegationAddress) {
account.getFactoryArgs = async () => {
const getCodeAction = getAction(client, getCode, "getCode");
const code = await getCodeAction({ address: accountAddress });
const isCorrectlyDelegated = is7702Delegated(delegationAddress, code);

if (isCorrectlyDelegated) {
return { factory: undefined, factoryData: undefined };
}

// Not correctly delegated - call our original getFactoryArgs
// which returns the 0x7702 factory
return getFactoryArgs();
};
}

return account;
}
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,15 @@ export const multiOwnerLightAccountStaticImplV2_0_0: StaticSmartAccountImplement
},
};

const lightAccountBaseV2_1_0 = {
entryPoint: {
abi: entryPoint08Abi,
address: entryPoint08Address,
version: "0.8",
},
accountAbi: LightAccountAbi_v2,
} as const;

export const lightAccountStaticImplV2_1_0: StaticSmartAccountImplementation<
false,
"0.8",
Expand All @@ -182,12 +191,7 @@ export const lightAccountStaticImplV2_1_0: StaticSmartAccountImplementation<
typeof LightAccountAbi_v2,
typeof LightAccountFactoryAbi_v2
> = {
entryPoint: {
abi: entryPoint08Abi,
address: entryPoint08Address,
version: "0.8",
},
accountAbi: LightAccountAbi_v2,
...lightAccountBaseV2_1_0,
accountImplementation: lowerAddress(
"0x2c53D0bD33A60db8881c7b049Df6fd762A1f059C",
),
Expand All @@ -209,6 +213,23 @@ export const lightAccountStaticImplV2_1_0: StaticSmartAccountImplementation<
},
};

export type LightAccountStaticImpl7702V2_1_0 = StaticSmartAccountImplementation<
true,
"0.8",
{},
typeof entryPoint08Abi,
typeof LightAccountAbi_v2,
never
>;

export const lightAccountStaticImpl7702V2_1_0: LightAccountStaticImpl7702V2_1_0 =
{
...lightAccountBaseV2_1_0,
delegationAddress: lowerAddress(
"0x82CfFc0f83A66F016f1273CdD5C43f86E78d2478",
),
};

export const lightAccountStaticImplV2_2_0: StaticSmartAccountImplementation<
false,
"0.9",
Expand Down