Skip to content

Commit

Permalink
feat: add a convenience method to make creating an alchemy provider e…
Browse files Browse the repository at this point in the history
…asier (#206)
  • Loading branch information
moldy530 committed Dec 1, 2023
1 parent bebe702 commit 0fb8911
Show file tree
Hide file tree
Showing 31 changed files with 380 additions and 159 deletions.
1 change: 0 additions & 1 deletion packages/accounts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
"test:run-e2e": "vitest run --config vitest.config.e2e.ts"
},
"devDependencies": {
"@alchemy/aa-alchemy": "^1.2.0",
"@alchemy/aa-core": "^1.2.0",
"typescript": "^5.0.4",
"typescript-template": "*",
Expand Down
6 changes: 6 additions & 0 deletions packages/accounts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@ export type { KernelBaseValidatorParams } from "./kernel-zerodev/validator/base.

//light-account exports
export { LightSmartContractAccount } from "./light-account/account.js";
export { createLightAccountProvider } from "./light-account/provider.js";
export {
LightAccountFactoryConfigSchema,
LightAccountProviderConfigSchema,
} from "./light-account/schema.js";
export type { LightAccountProviderConfig } from "./light-account/types.js";
export { getDefaultLightAccountFactoryAddress } from "./light-account/utils.js";
25 changes: 6 additions & 19 deletions packages/accounts/src/light-account/__tests__/account.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import {
LocalAccountSigner,
SmartAccountProvider,
type BatchUserOperationCallData,
type SmartAccountSigner,
} from "@alchemy/aa-core";
import { polygonMumbai, type Chain } from "viem/chains";
import { describe, it } from "vitest";
import { LightSmartContractAccount } from "../account.js";
import { getDefaultLightAccountFactoryAddress } from "../utils.js";
import { createLightAccountProvider } from "../provider.js";

const chain = polygonMumbai;

Expand Down Expand Up @@ -82,22 +81,10 @@ const givenConnectedProvider = ({
}: {
owner: SmartAccountSigner;
chain: Chain;
}) => {
return new SmartAccountProvider({
rpcProvider: `${chain.rpcUrls.alchemy.http[0]}/${"test"}`,
}) =>
createLightAccountProvider({
owner,
chain,
}).connect((provider) => {
const account = new LightSmartContractAccount({
chain,
owner,
factoryAddress: getDefaultLightAccountFactoryAddress(chain),
rpcClient: provider,
});

account.getAddress = vi.fn(
async () => "0xb856DBD4fA1A79a46D426f537455e7d3E79ab7c4"
);

return account;
rpcProvider: `${chain.rpcUrls.alchemy.http[0]}/${"test"}`,
accountAddress: "0xb856DBD4fA1A79a46D426f537455e7d3E79ab7c4",
});
};
65 changes: 32 additions & 33 deletions packages/accounts/src/light-account/e2e-tests/light-account.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { AlchemyProvider } from "@alchemy/aa-alchemy";
import {
LocalAccountSigner,
Logger,
Expand All @@ -8,6 +7,7 @@ import {
} from "@alchemy/aa-core";
import {
isAddress,
toHex,
type Address,
type Chain,
type Hash,
Expand All @@ -16,13 +16,12 @@ import {
import { generatePrivateKey } from "viem/accounts";
import { sepolia } from "viem/chains";
import {
getDefaultLightAccountFactoryAddress,
createLightAccountProvider,
LightSmartContractAccount,
} from "../../index.js";
import {
API_KEY,
LIGHT_ACCOUNT_OWNER_MNEMONIC,
PAYMASTER_POLICY_ID,
UNDEPLOYED_OWNER_MNEMONIC,
} from "./constants.js";

Expand Down Expand Up @@ -155,6 +154,11 @@ describe("Light Account Tests", () => {
});

it("should transfer ownership successfully", async () => {
const provider = givenConnectedProvider({
owner,
chain,
});

// create a throwaway address
const throwawayOwner = LocalAccountSigner.privateKeyToAccountSigner(
generatePrivateKey()
Expand All @@ -164,24 +168,32 @@ describe("Light Account Tests", () => {
chain,
});

const oldOwner = await throwawayOwner.getAddress();

// fund the throwaway address
await provider.sendTransaction({
from: await provider.getAddress(),
to: await throwawayProvider.getAddress(),
data: "0x",
value: toHex(1000000000000000n),
});

// create new owner and transfer ownership
const newThrowawayOwner = LocalAccountSigner.privateKeyToAccountSigner(
generatePrivateKey()
);
const result = await LightSmartContractAccount.transferOwnership(
await LightSmartContractAccount.transferOwnership(
throwawayProvider,
newThrowawayOwner
newThrowawayOwner,
true
);

const txnHash = throwawayProvider.waitForUserOperationTransaction(result);
await expect(txnHash).resolves.not.toThrowError();
const newOwnerViaProvider =
await throwawayProvider.account.getOwnerAddress();
const newOwner = await newThrowawayOwner.getAddress();

expect(await throwawayProvider.account.getOwnerAddress()).not.toBe(
await throwawayOwner.getAddress()
);
expect(await throwawayProvider.account.getOwnerAddress()).toBe(
await newThrowawayOwner.getAddress()
);
expect(newOwnerViaProvider).not.toBe(oldOwner);
expect(newOwnerViaProvider).toBe(newOwner);
}, 100000);
});

Expand All @@ -196,29 +208,16 @@ const givenConnectedProvider = ({
accountAddress?: Address;
feeOptions?: UserOperationFeeOptions;
}) => {
const provider = new AlchemyProvider({
apiKey: API_KEY!,
const provider = createLightAccountProvider({
rpcProvider: `${chain.rpcUrls.alchemy.http[0]}/${API_KEY!}`,
chain,
owner,
accountAddress,
opts: {
txMaxRetries: 10,
feeOptions: {
maxFeePerGas: { percentage: 100 },
maxPriorityFeePerGas: { percentage: 100 },
...feeOptions,
},
feeOptions,
txMaxRetries: 100,
},
}).connect(
(rpcClient) =>
new LightSmartContractAccount({
chain,
owner,
factoryAddress: getDefaultLightAccountFactoryAddress(chain),
rpcClient,
accountAddress,
})
);
provider.withAlchemyGasManager({
policyId: PAYMASTER_POLICY_ID,
});

return provider;
};
31 changes: 31 additions & 0 deletions packages/accounts/src/light-account/provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { SmartAccountProvider } from "@alchemy/aa-core";
import { LightSmartContractAccount } from "./account.js";
import { LightAccountProviderConfigSchema } from "./schema.js";
import type { LightAccountProviderConfig } from "./types.js";
import { getDefaultLightAccountFactoryAddress } from "./utils.js";

export const createLightAccountProvider = (
config_: LightAccountProviderConfig
): SmartAccountProvider & { account: LightSmartContractAccount } => {
const config = LightAccountProviderConfigSchema.parse(config_);

return new SmartAccountProvider({
rpcProvider: config.rpcProvider,
chain: config.chain,
entryPointAddress: config.entryPointAddress,
opts: config.opts,
}).connect(
(rpcClient) =>
new LightSmartContractAccount({
rpcClient,
initCode: config.initCode,
owner: config.owner,
chain: config.chain,
entryPointAddress: config.entryPointAddress,
factoryAddress:
config.factoryAddress ??
getDefaultLightAccountFactoryAddress(config.chain),
accountAddress: config.accountAddress,
})
);
};
25 changes: 25 additions & 0 deletions packages/accounts/src/light-account/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {
SignerSchema,
createSmartAccountProviderConfigSchema,
} from "@alchemy/aa-core";
import { Address } from "abitype/zod";
import { isHex } from "viem";
import { z } from "zod";

export const LightAccountFactoryConfigSchema = z.object({
owner: SignerSchema,
accountAddress: Address.optional().describe(
"Optional override for the account address."
),
initCode: z
.string()
.refine(isHex, "initCode must be a valid hex.")
.optional()
.describe("Optional override for the account init code."),
factoryAddress: Address.optional().describe(
"Optional override for the factory address which deploys the smart account."
),
});

export const LightAccountProviderConfigSchema =
createSmartAccountProviderConfigSchema().and(LightAccountFactoryConfigSchema);
6 changes: 6 additions & 0 deletions packages/accounts/src/light-account/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { z } from "zod";
import { LightAccountProviderConfigSchema } from "./schema.js";

export type LightAccountProviderConfig = z.input<
typeof LightAccountProviderConfigSchema
>;
3 changes: 2 additions & 1 deletion packages/alchemy/e2e-tests/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const API_KEY = process.env.API_KEY!;
export const OWNER_MNEMONIC = process.env.OWNER_MNEMONIC!;
export const PAYMASTER_POLICY_ID = process.env.PAYMASTER_POLICY_ID!;
export const LIGHT_ACCOUNT_OWNER_MNEMONIC =
process.env.LIGHT_ACCOUNT_OWNER_MNEMONIC!;
Original file line number Diff line number Diff line change
@@ -1,47 +1,39 @@
import type { UserOperationOverrides } from "@alchemy/aa-core";
import {
LogLevel,
Logger,
SimpleSmartContractAccount,
getDefaultSimpleAccountFactoryAddress,
type SmartAccountSigner,
type UserOperationFeeOptions,
} from "@alchemy/aa-core";
import { Alchemy, Network } from "alchemy-sdk";
import {
toHex,
type Address,
type Chain,
type HDAccount,
type Hash,
} from "viem";
import { toHex, type Address, type Chain, type Hash } from "viem";
import { mnemonicToAccount } from "viem/accounts";
import { sepolia } from "viem/chains";
import { AlchemyProvider } from "../src/provider.js";
import { API_KEY, OWNER_MNEMONIC, PAYMASTER_POLICY_ID } from "./constants.js";
import { createLightAccountAlchemyProvider } from "../src/index.js";
import {
API_KEY,
LIGHT_ACCOUNT_OWNER_MNEMONIC,
PAYMASTER_POLICY_ID,
} from "./constants.js";

const chain = sepolia;
const network = Network.ETH_SEPOLIA;

Logger.setLogLevel(LogLevel.DEBUG);

describe("Simple Account Tests", () => {
const ownerAccount = mnemonicToAccount(OWNER_MNEMONIC);
const owner: SmartAccountSigner<HDAccount> = {
describe("Light Account Tests", () => {
const ownerAccount = mnemonicToAccount(LIGHT_ACCOUNT_OWNER_MNEMONIC);
const owner: SmartAccountSigner = {
inner: ownerAccount,
signMessage: async (msg) =>
ownerAccount.signMessage({
message: { raw: toHex(msg) },
}),
signTypedData: async () => "0xHash",
getAddress: async () => ownerAccount.address,
signerType: "aa-sdk-tests",
inner: ownerAccount,
};

it("should successfully get counterfactual address", async () => {
const provider = givenConnectedProvider({ owner, chain });
expect(await provider.getAddress()).toMatchInlineSnapshot(
`"0xb856DBD4fA1A79a46D426f537455e7d3E79ab7c4"`
`"0x1a3a89cd46f124EF40848966c2D7074a575dbC27"`
);
});

Expand Down Expand Up @@ -222,8 +214,8 @@ describe("Simple Account Tests", () => {

const address = await provider.getAddress();
const balances = await provider.core.getTokenBalances(address);
expect(balances.tokenBalances.length).toMatchInlineSnapshot(`4`);
}, 100000);
expect(balances.tokenBalances.length).toMatchInlineSnapshot("1");
}, 50000);

it("should get owned nfts for the smart account", async () => {
const alchemy = new Alchemy({
Expand Down Expand Up @@ -257,29 +249,8 @@ describe("Simple Account Tests", () => {
value: 1n,
});

expect(simulatedAssetChanges.changes.length).toEqual(2);
expect(
simulatedAssetChanges.changes.map((change) => ({
assertType: change.assetType,
changeType: change.changeType,
from: change.from.toLowerCase(),
to: change.to.toLowerCase(),
}))
).toEqual([
{
assertType: "NATIVE",
changeType: "TRANSFER",
from: (await provider.getAddress()).toLowerCase(),
to: provider.getEntryPointAddress().toLowerCase(),
},
{
assertType: "NATIVE",
changeType: "TRANSFER",
from: (await provider.getAddress()).toLowerCase(),
to: provider.getEntryPointAddress().toLowerCase(),
},
]);
}, 100000);
expect(simulatedAssetChanges.changes.length).toMatchInlineSnapshot("2");
}, 50000);

it("should simulate as part of middleware stack when added to provider", async () => {
const provider = givenConnectedProvider({
Expand Down Expand Up @@ -310,9 +281,10 @@ const givenConnectedProvider = ({
accountAddress?: Address;
feeOptions?: UserOperationFeeOptions;
}) =>
new AlchemyProvider({
createLightAccountAlchemyProvider({
apiKey: API_KEY!,
chain,
owner,
opts: {
txMaxRetries: 10,
feeOptions: {
Expand All @@ -321,13 +293,5 @@ const givenConnectedProvider = ({
maxPriorityFeePerGas: { percentage: 50 },
},
},
}).connect(
(provider) =>
new SimpleSmartContractAccount({
chain,
owner,
factoryAddress: getDefaultSimpleAccountFactoryAddress(chain),
rpcClient: provider,
accountAddress,
})
);
accountAddress,
});
2 changes: 2 additions & 0 deletions packages/alchemy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"test:run-e2e": "vitest run --config vitest.config.e2e.ts"
},
"devDependencies": {
"@alchemy/aa-accounts": "^1.2.0",
"@alchemy/aa-core": "^1.2.0",
"typescript": "^5.0.4",
"typescript-template": "*",
Expand All @@ -62,6 +63,7 @@
"homepage": "https://github.com/alchemyplatform/aa-sdk#readme",
"gitHead": "ee46e8bb857de3b631044fa70714ea706d9e317d",
"optionalDependencies": {
"@alchemy/aa-accounts": "^1.2.0",
"alchemy-sdk": "^3.0.0"
}
}
Loading

0 comments on commit 0fb8911

Please sign in to comment.