Skip to content

Commit

Permalink
feat: 7702 progress
Browse files Browse the repository at this point in the history
  • Loading branch information
adamegyed committed Jan 10, 2025
1 parent edc6066 commit bb48090
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 29 deletions.
2 changes: 0 additions & 2 deletions aa-sdk/core/src/account/smartContractAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
type TypedDataDefinition,
} from "viem";
import { toAccount } from "viem/accounts";
import type { Authorization } from "viem/experimental";
import { createBundlerClient } from "../client/bundlerClient.js";
import type {
EntryPointDef,
Expand Down Expand Up @@ -125,7 +124,6 @@ export type SmartContractAccount<
getFactoryData: () => Promise<Hex>;
getEntryPoint: () => EntryPointDef<TEntryPointVersion>;
getImplementationAddress: () => Promise<NullAddress | Address>;
signAuthorization?: () => Promise<Authorization<number, true> | undefined>;
};
// [!endregion SmartContractAccount]

Expand Down
1 change: 1 addition & 0 deletions aa-sdk/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export {
export { LogLevel, Logger } from "./logger.js";
export { middlewareActions } from "./middleware/actions.js";
export { default7702UserOpSigner } from "./middleware/defaults/7702signer.js";
export { default7702GasEstimator } from "./middleware/defaults/7702gasEstimator.js";
export { defaultFeeEstimator } from "./middleware/defaults/feeEstimator.js";
export { defaultGasEstimator } from "./middleware/defaults/gasEstimator.js";
export { defaultPaymasterAndData } from "./middleware/defaults/paymasterAndData.js";
Expand Down
23 changes: 22 additions & 1 deletion aa-sdk/core/src/middleware/defaults/7702gasEstimator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ import type { UserOperationStruct } from "../../types.js";
import type { ClientMiddlewareFn } from "../types";
import { defaultGasEstimator } from "./gasEstimator.js";

/**
* Asynchronously processes a given struct and parameters, ensuring the account and entry point compatibility before modifying the struct with an authorization contract and estimating gas.
*
* @param {UserOperationStruct} struct The user operation structure to estimate gas over
* @param {*} params The parameters containing an account or a client with an account.
* @throws {AccountNotFoundError} If no account is found in the parameters.
* @throws {Error} If the account's entry point version is not 0.7.0.
* @returns {Promise<UserOperationStruct>} A promise that resolves after estimating gas with the modified struct.
*/
export const default7702GasEstimator: ClientMiddlewareFn = async (
struct,
params
Expand All @@ -12,12 +21,24 @@ export const default7702GasEstimator: ClientMiddlewareFn = async (
throw new AccountNotFoundError();
}

const entryPoint = account.getEntryPoint();
if (entryPoint.version !== "0.7.0") {
throw new Error(
"This middleware is only compatible with EntryPoint v0.7.0"
);
}

// todo: this is currently overloading the meaning of the getImplementationAddress method, replace with a dedicated method or clarify intention in docs
const implementationAddress = await account.getImplementationAddress();

// todo: do we need to omit this from estimation if the account is already 7702 delegated? Not omitting for now.

(struct as UserOperationStruct<"0.7.0">).authorizationContract =
implementationAddress;

return defaultGasEstimator(params.client)(struct, params);
const estimatedUO = await defaultGasEstimator(params.client)(struct, params);

estimatedUO.authorizationContract = undefined; // Strip out authorizationContract after estimation.

return estimatedUO;
};
48 changes: 43 additions & 5 deletions aa-sdk/core/src/middleware/defaults/7702signer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { toHex } from "viem";
import { isSmartAccountWithSigner } from "../../account/smartContractAccount.js";
import { AccountNotFoundError } from "../../errors/account.js";
import { ChainNotFoundError } from "../../errors/client.js";
import type { ClientMiddlewareFn } from "../types";
import { defaultUserOpSigner } from "./userOpSigner.js";

Expand All @@ -18,17 +21,24 @@ export const default7702UserOpSigner: ClientMiddlewareFn = async (
) => {
const uo = await defaultUserOpSigner(struct, params);
const account = params.account ?? params.client.account;
if (!account) {
const { client } = params;

if (!account || !isSmartAccountWithSigner(account)) {
throw new AccountNotFoundError();
}

if (!account.signAuthorization) {
const signer = account.getSigner();

if (!signer.signAuthorization) {
console.log("account does not support signAuthorization");
return uo;
}

const code =
(await params.client.getCode({ address: account.address })) ?? "0x";
if (!client.chain) {
throw new ChainNotFoundError();
}

const code = (await client.getCode({ address: account.address })) ?? "0x";
// TODO: this isn't the cleanest because now the account implementation HAS to know that it needs to return an impl address
// even if the account is not deployed

Expand All @@ -40,8 +50,36 @@ export const default7702UserOpSigner: ClientMiddlewareFn = async (

console.log("signing 7702 authorization");

const accountNonce = await params.client.getTransactionCount({
address: account.address,
});

console.log("account nonce: ", accountNonce);

const {
r,
s,
v,
yParity = v,
} = await signer.signAuthorization({
chainId: client.chain.id,
contractAddress: implAddress,
nonce: accountNonce,
});

if (!yParity) {
throw new Error("incomplete signature!");
}

return {
...uo,
authorizationTuple: await account.signAuthorization(),
authorizationTuple: {
chainId: client.chain.id,
nonce: toHex(accountNonce), // deepHexlify doesn't encode number(0) correctly, it returns "0x"
address: implAddress,
r,
s,
yParity: Number(yParity),
},
};
};
2 changes: 1 addition & 1 deletion aa-sdk/core/src/signer/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,6 @@ export interface SmartAccountSigner<Inner = any> {

signAuthorization?: (
unsignedAuthorization: Authorization<number, false>
) => Promise<Authorization<number, true>> | undefined;
) => Promise<Authorization<number, true>>;
}
// [!endregion SmartAccountSigner]
2 changes: 1 addition & 1 deletion account-kit/signer/src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
*/
signAuthorization: (
unsignedAuthorization: Authorization<number, false>
) => Promise<Authorization<number, true>> | undefined = SignerLogger.profiled(
) => Promise<Authorization<number, true>> = SignerLogger.profiled(
"BaseAlchemySigner.signAuthorization",
async (unsignedAuthorization) => {
const hashedAuthorization = hashAuthorization(unsignedAuthorization);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
toSmartContractAccount,
EntityIdOverrideError,
} from "@aa-sdk/core";
import { type Chain, type Hex, type Transport } from "viem";
import { type Chain, type Hex, type Transport, type Address } from "viem";
import { DEFAULT_OWNER_ENTITY_ID } from "../utils.js";
import { singleSignerMessageSigner } from "../modules/single-signer-validation/signer.js";
import { nativeSMASigner } from "./nativeSMASigner.js";
Expand Down Expand Up @@ -99,10 +99,15 @@ export async function createSMA7702Account(
: singleSignerMessageSigner(signer, chain, _accountAddress, entityId)),
});

const implementation: Address = "0x69007702764179f14F51cdce752f4f775d74E139";

const getImplementationAddress = async () => implementation;

return {
...baseAccount,
...baseFunctions,
getSigner: () => signer,
signerEntity,
getImplementationAddress,
};
}
7 changes: 4 additions & 3 deletions account-kit/smart-contracts/src/ma-v2/client/sma7702.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
type CreateSMA7702AccountParams,
} from "../account/semiModularAccount7702.js";

import { default7702UserOpSigner } from "@aa-sdk/core";
import { default7702UserOpSigner, default7702GasEstimator } from "@aa-sdk/core";

import type { MAV2Account } from "../account/common/modularAccountV2Base.js";

Expand Down Expand Up @@ -68,10 +68,11 @@ export function createSMA7702AccountClient<
export async function createSMA7702AccountClient(
config: CreateSMA7702AccountClientParams
): Promise<SmartAccountClient> {
const smaV2Account = await createSMA7702Account(config);
const sma7702Account = await createSMA7702Account(config);

return createSmartAccountClient({
account: smaV2Account,
account: sma7702Account,
gasEstimator: default7702GasEstimator,
signUserOperation: default7702UserOpSigner,
...config,
});
Expand Down
46 changes: 37 additions & 9 deletions examples/ui-demo/src/components/shared/7702/Debug7702Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@ import { Button } from "../Button";
// } from "@account-kit/react";
import { privateKeyToAccount } from "viem/accounts";
import { createPublicClient } from "viem";
import { createBundlerClientFromExisting } from "@aa-sdk/core";
import {
createBundlerClientFromExisting,
LocalAccountSigner,
} from "@aa-sdk/core";
import { mekong, splitMekongTransport } from "./transportSetup";
import { send7702UO } from "./demoSend7702UO";
import { useSigner } from "@account-kit/react";

export const Debug7702Button = () => {
// const { client, address, isLoadingClient } = useSmartAccountClient({
// type: "LightAccount",
// });

const runFunc = async () => {
// Addr: 0xdF0C33fe84BEBdcbd1029E95295AC4D2A7Ca0f7a
// pkey: 0x18bec901c0253fbb203d3423dada59eb720c68f34935185de43d161b0524404b
const localAccount = privateKeyToAccount(
"0x18bec901c0253fbb203d3423dada59eb720c68f34935185de43d161b0524404b"
);
const signer = useSigner();

const runFunc = async (local: boolean) => {
const publicClient = createPublicClient({
chain: mekong,
transport: splitMekongTransport,
Expand Down Expand Up @@ -50,8 +50,36 @@ export const Debug7702Button = () => {

// console.log("maxPriorityFeePerGas: ", await res);

send7702UO(bundlerClient, splitMekongTransport, localAccount);
// send7702UO(bundlerClient, splitMekongTransport, new LocalAccountSigner(localAccount));

if (local) {
// Addr: 0xdF0C33fe84BEBdcbd1029E95295AC4D2A7Ca0f7a
// pkey: 0x18bec901c0253fbb203d3423dada59eb720c68f34935185de43d161b0524404b
const localAccount = privateKeyToAccount(
"0x18bec901c0253fbb203d3423dada59eb720c68f34935185de43d161b0524404b"
);

send7702UO(
bundlerClient,
splitMekongTransport,
new LocalAccountSigner(localAccount)
);
} else {
if (!signer) {
console.error("No signer found");
return;
}
send7702UO(bundlerClient, splitMekongTransport, signer);
}
};

return <Button onClick={runFunc}>Debug 7702</Button>;
const runLocal = () => runFunc(true);
const runSigner = () => runFunc(false);

return (
<div>
<Button onClick={runLocal}>Debug 7702 local</Button>
<Button onClick={runSigner}>Debug 7702 signer</Button>
</div>
);
};
19 changes: 13 additions & 6 deletions examples/ui-demo/src/components/shared/7702/demoSend7702UO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,22 @@ import {
PrivateKeyAccount,
} from "viem";

import { LocalAccountSigner, type BundlerClient } from "@aa-sdk/core";
import {
LocalAccountSigner,
type SmartAccountSigner,
type BundlerClient,
} from "@aa-sdk/core";

import { alchemyFeeEstimator, type AlchemyTransport } from "@account-kit/infra";

import { createSMA7702AccountClient } from "@account-kit/smart-contracts";
import { createSMA7702AccountClient } from "@account-kit/smart-contracts/experimental";

import { mekong, splitMekongTransport } from "./transportSetup";
import { mekong } from "./transportSetup";

export const send7702UO = async (
client: PublicClient & BundlerClient,
transport: AlchemyTransport,
localAccount: PrivateKeyAccount
signer: SmartAccountSigner
) => {
console.log(
"supported entry points: ",
Expand All @@ -34,16 +38,19 @@ export const send7702UO = async (
const accountClient = await createSMA7702AccountClient({
chain: mekong,
transport,
signer: new LocalAccountSigner(localAccount),
signer,
});

const txnHash = await accountClient.sendUserOperation({
const uoHash = await accountClient.sendUserOperation({
uo: {
target: zeroAddress,
data: "0x",
value: BigInt(0),
},
});

const txnHash = await accountClient.waitForUserOperationTransaction(uoHash);
// const txnHash = await accountClient.waitForUserOperationTransaction({ hash: "0x4115f6006e0418bdaf42d71f124a31be50f473aeacc2915638c97a6a1d3a8750" });

console.log("txnHash: ", txnHash);
};

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit bb48090

Please sign in to comment.