From ab156762d5beb6a23705c786a96d7f92dde864b6 Mon Sep 17 00:00:00 2001 From: Dan Nolan Date: Thu, 15 Feb 2024 10:37:37 -0500 Subject: [PATCH 1/4] docs: session key permissions fix (#460) --- site/snippets/session-keys/full-example.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/site/snippets/session-keys/full-example.ts b/site/snippets/session-keys/full-example.ts index c36b6adea6..71d65b23b2 100644 --- a/site/snippets/session-keys/full-example.ts +++ b/site/snippets/session-keys/full-example.ts @@ -1,4 +1,5 @@ import { + SessionKeyAccessListType, SessionKeyPermissionsBuilder, SessionKeyPlugin, SessionKeySigner, @@ -32,6 +33,8 @@ if (!isPluginInstalled) { .setNativeTokenSpendLimit({ spendLimit: 1000000n, }) + // this will allow the session key plugin to interact with all addresses + .setContractAccessControlType(SessionKeyAccessListType.ALLOW_ALL_ACCESS) .setTimeRange({ validFrom: Date.now() / 1000, // valid for 1 hour From 6fd817f340e7f65916da62be88516941f84bd328 Mon Sep 17 00:00:00 2001 From: Dan Nolan Date: Thu, 15 Feb 2024 10:41:38 -0500 Subject: [PATCH 2/4] docs: update address calculation, session key, and guides (#451) * docs: update address calculation, session key, and guides - fix signer typos - add address calculation information to LA and MA - update session key tag to use keccak algorithm - improve MA getting started guide for better flow * Update site/smart-accounts/light-account/index.md Co-authored-by: Michael Moldoveanu * Update site/smart-accounts/light-account/index.md Co-authored-by: Dennis Won * Update site/snippets/session-keys/add-session-key.ts Co-authored-by: Dennis Won * docs: fix aa core definition * docs: address modular account getting started PR comment Co-authored-by: Dennis Won --------- Co-authored-by: Dennis Won Co-authored-by: Michael Moldoveanu --- .../aa-accounts/light-account/index.md | 2 + .../aa-signers/capsule/authenticate.md | 4 +- site/smart-accounts/light-account/index.md | 6 +++ .../modular-account/getting-started.md | 48 +++++++++++++++++-- site/snippets/session-keys/add-session-key.ts | 4 +- 5 files changed, 57 insertions(+), 7 deletions(-) diff --git a/site/packages/aa-accounts/light-account/index.md b/site/packages/aa-accounts/light-account/index.md index e0609dce7d..6181646c25 100644 --- a/site/packages/aa-accounts/light-account/index.md +++ b/site/packages/aa-accounts/light-account/index.md @@ -95,6 +95,8 @@ A Promise containing a new `LightAccount`. - `initCode: Hex` -- [optional] the initCode for deploying the smart account with which the client will connect. +- `salt: bigint` -- [optional] a value that is added to the address calculation to allow for multiple accounts for the same owner. The default value supplied is `0n`. To see this calculation used in the smart contract, check out [the LightAccountFactory](https://github.com/alchemyplatform/light-account/blob/main/src/LightAccountFactory.sol#L30). + - `accountAddress: Address` -- [optional] a smart account address override that this object will manage instead of generating its own. - `version: LightAccountVersion` -- [optional] the LightAccount contract version. Default: [v1.1.0](https://github.com/alchemyplatform/light-account/releases/tag/v1.1.0) diff --git a/site/packages/aa-signers/capsule/authenticate.md b/site/packages/aa-signers/capsule/authenticate.md index eec130163d..b33d7db4c9 100644 --- a/site/packages/aa-signers/capsule/authenticate.md +++ b/site/packages/aa-signers/capsule/authenticate.md @@ -54,8 +54,8 @@ A Promise containing the `CapsuleUserInfo`, an `Record` where Wa - `signer: string` -- Capsule Signer information. -- `address: string` -- [optional] EOA address of the Capusle Signer. +- `address: string` -- [optional] EOA address of the Capsule Signer. -- `publicKey: string` -- [optional] Public Key of the Capusle Signer. +- `publicKey: string` -- [optional] Public Key of the Capsule Signer. - `scheme: WalletScheme` -- [optional] either `CGGMP` or `DKLS`. diff --git a/site/smart-accounts/light-account/index.md b/site/smart-accounts/light-account/index.md index 9fc47e3a89..9a4a436f46 100644 --- a/site/smart-accounts/light-account/index.md +++ b/site/smart-accounts/light-account/index.md @@ -35,6 +35,12 @@ The code snippet below demonstrates how to use Light Account with Account Kit. I ::: code-group +::: tip Address calculation +For the Light Account, the address of the smart account will be calculated as a combination of [the owner and the salt](https://github.com/alchemyplatform/light-account/blob/main/src/LightAccountFactory.sol#L24-L33). You will get the same smart account address each time you supply the same `owner`. You can also optionally supply `salt` if you want a different address for the same owner (the default salt is `0n`). + +If you already have an account with a new or different owner (transferred ownership), you can supply the `accountAddress` to connect with your account with a new owner. In that case, the `owner` is not used for address calculation, but still used for signing the operation. +::: + ## [Deployment addresses](https://github.com/alchemyplatform/light-account/blob/v1.1.0/Deployments.md) The following tables list the deployed factory and account implementation contract addresses for `LightAccount` on different chains: diff --git a/site/smart-accounts/modular-account/getting-started.md b/site/smart-accounts/modular-account/getting-started.md index 1a452e0951..a657343cee 100644 --- a/site/smart-accounts/modular-account/getting-started.md +++ b/site/smart-accounts/modular-account/getting-started.md @@ -20,24 +20,62 @@ head: # Getting started with Modular Account -Getting started with Modular Account is really simple, especially if you are using `@alchemy/aa-alchemy`. +It's easy to get started with Modular Account! We'll show you two different ways using `@alchemy/aa-alchemy` and `@alchemy/aa-core`. + +::: tip Choosing your package +The [`aa-core`](/packages/aa-core/) package is not opinionated about your RPC provider. As a result, creating a client requires more configuration. If you don't need this extra flexibility, [`aa-alchemy`](/packages/aa-alchemy/) is much easier to jump in with, as you'll see below. +::: ## With `@alchemy/aa-alchemy` -When using `@alchemy/aa-alchemy` it is really easy to get started simply do the following: +### Install packages + +::: code-group + +```bash [npm] +npm i @alchemy/aa-alchemy @alchemy/aa-core +``` + +```bash [yarn] +yarn add @alchemy/aa-alchemy @alchemy/aa-core +``` + +::: + +### Create a client + +Then you can simply do the following: <<< @/snippets/aa-alchemy/connected-client.ts +::: tip Address calculation +For the Modular Account, the address of the smart account will be calculated as a combination of [several variables](https://github.com/alchemyplatform/modular-account/blob/74fe1bfa056bbd41c933990fca0598c8cc3e90e8/src/factory/MultiOwnerModularAccountFactory.sol#L66-L71). You will get the same smart account address each time you supply the same `owner` or `owners`. You can also optionally supply `salt` if you want a different address for the same owner(s) (the default salt is `0n`). + +If you already have an account with a new or different owner (transferred ownership), you can supply the `accountAddress` to connect with your account with a new owner. In that case, the `owner` is not used for address calculation, but still used for signing the operation. +::: + +That's it! You've configured your client. + +Next, if you want to replace that `owner` with a smart account signer, check out [choosing a signer](/signers/choosing-a-signer). Or, if you're ready to get onchain, go to [send user operations](/using-smart-accounts/send-user-operations). + ## With `@alchemy/aa-core` ### Install packages If you are using `@alchemy/aa-core` you'll want to also add `@alchemy/aa-accounts` to get the Smart Account factory for Modular Account. -```bash -yarn add @alchemy/aa-core @alchemy/aa-accounts +::: code-group + +```bash [npm] +npm i @alchemy/aa-core @alchemy/aa-accounts viem +``` + +```bash [yarn] +yarn add @alchemy/aa-core @alchemy/aa-accounts viem ``` +::: + ### Create a client Then you'll need to create a `SmartAccountClient` @@ -73,3 +111,5 @@ const decoratedClient = smartAccountClient <<< @/snippets/aa-core/smartAccountClient.ts ::: + +Next, if you want to replace that `owner` with a smart account signer, check out [choosing a signer](/signers/choosing-a-signer). Or, if you're ready to get onchain, go to [send user operations](/using-smart-accounts/send-user-operations). diff --git a/site/snippets/session-keys/add-session-key.ts b/site/snippets/session-keys/add-session-key.ts index 72d9b1bfb5..dbc324c667 100644 --- a/site/snippets/session-keys/add-session-key.ts +++ b/site/snippets/session-keys/add-session-key.ts @@ -1,8 +1,10 @@ import { SessionKeyPermissionsBuilder } from "@alchemy/aa-accounts"; import { client } from "./base-client.js"; +import { keccak256 } from "viem"; const result = await client.addSessionKey({ key: "0xSessionKeyAddress", - tag: "0xkeytag", + // tag is an identifier for the emitted SessionKeyAdded event + tag: keccak256(new TextEncoder().encode("session-key-tag")), permissions: new SessionKeyPermissionsBuilder().encode(), }); From 10a1b85daea7ff9adcb130aa65c32b83fc850e59 Mon Sep 17 00:00:00 2001 From: Rohan Thomare <3498866+rthomare@users.noreply.github.com> Date: Thu, 15 Feb 2024 15:05:43 -0800 Subject: [PATCH 3/4] feat!: breaking change of percent to multiplier for feeOverrides + dropAndReplace (#461) --- .../e2e-tests/light-account.e2e.test.ts | 4 +- .../e2e-tests/light-account.e2e.test.ts | 10 ++--- packages/alchemy/src/defaults.ts | 6 +-- packages/alchemy/src/middleware/gasManager.ts | 28 +++++++------- .../core/e2e-tests/simple-account.e2e.test.ts | 8 ++-- .../dropAndReplaceUserOperation.ts | 6 +-- packages/core/src/client/schema.ts | 4 +- packages/core/src/index.ts | 4 +- packages/core/src/types.ts | 14 +++---- .../core/src/utils/__tests__/bigint.test.ts | 29 ++++++++++++++ packages/core/src/utils/bigint.ts | 38 +++++++++++-------- packages/core/src/utils/defaults.ts | 2 +- packages/core/src/utils/index.ts | 16 +------- packages/core/src/utils/schema.ts | 20 ++++++++-- packages/core/src/utils/userop.ts | 18 ++++----- site/migration-guide.md | 22 +++++++++++ .../aa-alchemy/smart-account-client/index.md | 6 +-- .../types/userOperationFeeOptions.md | 2 +- .../types/userOperationFeeOptionsField.md | 8 ++-- .../types/userOperationOverrides.md | 12 +++--- .../aa-ethers/provider-adapter/constructor.md | 6 +-- site/resources/types.md | 4 +- 22 files changed, 160 insertions(+), 107 deletions(-) create mode 100644 packages/core/src/utils/__tests__/bigint.test.ts diff --git a/packages/accounts/src/light-account/e2e-tests/light-account.e2e.test.ts b/packages/accounts/src/light-account/e2e-tests/light-account.e2e.test.ts index c5cd30765a..218943b892 100644 --- a/packages/accounts/src/light-account/e2e-tests/light-account.e2e.test.ts +++ b/packages/accounts/src/light-account/e2e-tests/light-account.e2e.test.ts @@ -287,8 +287,8 @@ const givenConnectedProvider = async ({ opts: { feeOptions: { ...feeOptions, - maxFeePerGas: { percentage: 50 }, - maxPriorityFeePerGas: { percentage: 50 }, + maxFeePerGas: { multiplier: 1.5 }, + maxPriorityFeePerGas: { multiplier: 1.5 }, }, txMaxRetries: 100, }, diff --git a/packages/alchemy/e2e-tests/light-account.e2e.test.ts b/packages/alchemy/e2e-tests/light-account.e2e.test.ts index be2f9055c3..fffffed604 100644 --- a/packages/alchemy/e2e-tests/light-account.e2e.test.ts +++ b/packages/alchemy/e2e-tests/light-account.e2e.test.ts @@ -144,9 +144,9 @@ describe("Light Account Tests", () => { }, opts: { feeOptions: { - maxFeePerGas: { percentage: 50 }, - maxPriorityFeePerGas: { percentage: 50 }, - preVerificationGas: { percentage: 50 }, + maxFeePerGas: { multiplier: 1.5 }, + maxPriorityFeePerGas: { multiplier: 1.5 }, + preVerificationGas: { multiplier: 1.5 }, }, }, }); @@ -320,8 +320,8 @@ const givenConnectedProvider = async ({ txMaxRetries: 10, ...opts, feeOptions: { - maxFeePerGas: { percentage: 50 }, - maxPriorityFeePerGas: { percentage: 50 }, + maxFeePerGas: { multiplier: 1.5 }, + maxPriorityFeePerGas: { multiplier: 1.5 }, ...opts?.feeOptions, }, }, diff --git a/packages/alchemy/src/defaults.ts b/packages/alchemy/src/defaults.ts index e75c842181..c7d934b221 100644 --- a/packages/alchemy/src/defaults.ts +++ b/packages/alchemy/src/defaults.ts @@ -13,8 +13,8 @@ export const getDefaultUserOperationFeeOptions = ( chain: Chain ): UserOperationFeeOptions => { const feeOptions: UserOperationFeeOptions = { - maxFeePerGas: { percentage: 50 }, - maxPriorityFeePerGas: { percentage: 5 }, + maxFeePerGas: { multiplier: 1.5 }, + maxPriorityFeePerGas: { multiplier: 1.05 }, }; if ( @@ -27,7 +27,7 @@ export const getDefaultUserOperationFeeOptions = ( optimismSepolia.id, ]).has(chain.id) ) { - feeOptions.preVerificationGas = { percentage: 5 }; + feeOptions.preVerificationGas = { multiplier: 1.05 }; } return feeOptions; diff --git a/packages/alchemy/src/middleware/gasManager.ts b/packages/alchemy/src/middleware/gasManager.ts index 6ce251a55b..d7f4e850b9 100644 --- a/packages/alchemy/src/middleware/gasManager.ts +++ b/packages/alchemy/src/middleware/gasManager.ts @@ -7,10 +7,10 @@ import { defaultGasEstimator, filterUndefined, isBigNumberish, - isPercentage, + isMultiplier, resolveProperties, type Hex, - type Percentage, + type Multiplier, type UserOperationFeeOptions, type UserOperationRequest, } from "@alchemy/aa-core"; @@ -19,11 +19,11 @@ import type { ClientWithAlchemyMethods } from "../client/types"; import { alchemyFeeEstimator } from "./feeEstimator.js"; export type RequestGasAndPaymasterAndDataOverrides = Partial<{ - maxFeePerGas: UserOperationRequest["maxFeePerGas"] | Percentage; - maxPriorityFeePerGas: UserOperationRequest["maxFeePerGas"] | Percentage; - callGasLimit: UserOperationRequest["maxFeePerGas"] | Percentage; - preVerificationGas: UserOperationRequest["maxFeePerGas"] | Percentage; - verificationGasLimit: UserOperationRequest["maxFeePerGas"] | Percentage; + maxFeePerGas: UserOperationRequest["maxFeePerGas"] | Multiplier; + maxPriorityFeePerGas: UserOperationRequest["maxFeePerGas"] | Multiplier; + callGasLimit: UserOperationRequest["maxFeePerGas"] | Multiplier; + preVerificationGas: UserOperationRequest["maxFeePerGas"] | Multiplier; + verificationGasLimit: UserOperationRequest["maxFeePerGas"] | Multiplier; }>; export interface AlchemyGasManagerConfig { @@ -138,26 +138,24 @@ const requestGasAndPaymasterData: ( const overrideField = ( field: keyof UserOperationFeeOptions - ): Hex | Percentage | undefined => { + ): Hex | Multiplier | undefined => { if (overrides?.[field] != null) { // one-off absolute override if (isBigNumberish(overrides[field])) { return deepHexlify(overrides[field]); } - // one-off percentage overrides + // one-off multiplier overrides else { return { - percentage: - 100 + Number((overrides[field] as Percentage).percentage), + multiplier: Number((overrides[field] as Multiplier).multiplier), }; } } - // provider level fee options with percentage - if (isPercentage(feeOptions?.[field])) { + // provider level fee options with multiplier + if (isMultiplier(feeOptions?.[field])) { return { - percentage: - 100 + Number((feeOptions![field] as Percentage).percentage), + multiplier: Number((feeOptions![field] as Multiplier).multiplier), }; } diff --git a/packages/core/e2e-tests/simple-account.e2e.test.ts b/packages/core/e2e-tests/simple-account.e2e.test.ts index ee486ff193..e4c0eb5043 100644 --- a/packages/core/e2e-tests/simple-account.e2e.test.ts +++ b/packages/core/e2e-tests/simple-account.e2e.test.ts @@ -76,7 +76,7 @@ describe("Simple Account Tests", () => { expect(isAddress(address)).toBe(true); }); - it("should correctly handle percentage overrides for buildUserOperation", async () => { + it("should correctly handle multiplier overrides for buildUserOperation", async () => { const signer = await givenConnectedProvider({ owner, chain, @@ -95,7 +95,7 @@ describe("Simple Account Tests", () => { owner, chain, feeOptions: { - preVerificationGas: { percentage: 100 }, + preVerificationGas: { multiplier: 2 }, }, }); @@ -147,12 +147,12 @@ describe("Simple Account Tests", () => { expect(struct.preVerificationGas).toBe(100_000_000n); }, 60000); - it("should correctly handle percentage overrides for sendUserOperation", async () => { + it("should correctly handle multiplier overrides for sendUserOperation", async () => { const signer = await givenConnectedProvider({ owner, chain, feeOptions: { - preVerificationGas: { percentage: 100 }, + preVerificationGas: { multiplier: 2 }, }, }); diff --git a/packages/core/src/actions/smartAccount/dropAndReplaceUserOperation.ts b/packages/core/src/actions/smartAccount/dropAndReplaceUserOperation.ts index 32573e456b..3a6300ee44 100644 --- a/packages/core/src/actions/smartAccount/dropAndReplaceUserOperation.ts +++ b/packages/core/src/actions/smartAccount/dropAndReplaceUserOperation.ts @@ -5,7 +5,7 @@ import type { SendUserOperationResult } from "../../client/types"; import { AccountNotFoundError } from "../../errors/account.js"; import { IncompatibleClientError } from "../../errors/client.js"; import type { UserOperationOverrides, UserOperationStruct } from "../../types"; -import { bigIntMax, bigIntPercent } from "../../utils/index.js"; +import { bigIntMax, bigIntMultiply } from "../../utils/index.js"; import { _runMiddlewareStack } from "./internal/runMiddlewareStack.js"; import { _sendUserOperation } from "./internal/sendUserOperation.js"; import type { DropAndReplaceUserOperationParameters } from "./types"; @@ -54,11 +54,11 @@ export const dropAndReplaceUserOperation: < const _overrides: UserOperationOverrides = { maxFeePerGas: bigIntMax( BigInt(maxFeePerGas ?? 0n), - bigIntPercent(uoToDrop.maxFeePerGas, 110n) + bigIntMultiply(uoToDrop.maxFeePerGas, 1.1) ), maxPriorityFeePerGas: bigIntMax( BigInt(maxPriorityFeePerGas ?? 0n), - bigIntPercent(uoToDrop.maxPriorityFeePerGas, 110n) + bigIntMultiply(uoToDrop.maxPriorityFeePerGas, 1.1) ), }; diff --git a/packages/core/src/client/schema.ts b/packages/core/src/client/schema.ts index 6d09314b00..add73d46ca 100644 --- a/packages/core/src/client/schema.ts +++ b/packages/core/src/client/schema.ts @@ -1,7 +1,7 @@ import type { Transport } from "viem"; import { z } from "zod"; -import { BigNumberishRangeSchema, PercentageSchema } from "../utils/index.js"; +import { BigNumberishRangeSchema, MultiplierSchema } from "../utils/index.js"; import type { BundlerClient } from "./bundlerClient.js"; export const createPublicErc4337ClientSchema = < @@ -42,7 +42,7 @@ export const ConnectionConfigSchema = z.union([ ]); export const UserOperationFeeOptionsFieldSchema = - BigNumberishRangeSchema.merge(PercentageSchema).partial(); + BigNumberishRangeSchema.merge(MultiplierSchema).partial(); export const UserOperationFeeOptionsSchema = z .object({ diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index fa6d85bfb1..99e8bbf344 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -119,7 +119,7 @@ export { applyUserOpOverrideOrFeeOption, asyncPipe, bigIntMax, - bigIntPercent, + bigIntMultiply, deepHexlify, defineReadOnly, filterUndefined, @@ -129,6 +129,6 @@ export { getDefaultUserOperationFeeOptions, getUserOperationHash, isBigNumberish, - isPercentage, + isMultiplier, resolveProperties, } from "./utils/index.js"; diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 7e0f553383..86d3d5997e 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -8,7 +8,7 @@ import type { BigNumberishRangeSchema, BigNumberishSchema, HexSchema, - PercentageSchema, + MultiplierSchema, } from "./utils"; export type Hex = z.input; @@ -17,7 +17,7 @@ export type EmptyHex = `0x`; // based on @account-abstraction/common export type PromiseOrValue = T | Promise; export type BytesLike = Uint8Array | Hex; -export type Percentage = z.input; +export type Multiplier = z.input; export type BigNumberish = z.input; export type BigNumberishRange = z.input; @@ -44,15 +44,15 @@ export type UserOperationFeeOptions = z.input< >; export type UserOperationOverrides = Partial<{ - callGasLimit: UserOperationStruct["callGasLimit"] | Percentage; - maxFeePerGas: UserOperationStruct["maxFeePerGas"] | Percentage; + callGasLimit: UserOperationStruct["callGasLimit"] | Multiplier; + maxFeePerGas: UserOperationStruct["maxFeePerGas"] | Multiplier; maxPriorityFeePerGas: | UserOperationStruct["maxPriorityFeePerGas"] - | Percentage; - preVerificationGas: UserOperationStruct["preVerificationGas"] | Percentage; + | Multiplier; + preVerificationGas: UserOperationStruct["preVerificationGas"] | Multiplier; verificationGasLimit: | UserOperationStruct["verificationGasLimit"] - | Percentage; + | Multiplier; paymasterAndData: UserOperationStruct["paymasterAndData"]; }>; diff --git a/packages/core/src/utils/__tests__/bigint.test.ts b/packages/core/src/utils/__tests__/bigint.test.ts new file mode 100644 index 0000000000..c35b2a6c0f --- /dev/null +++ b/packages/core/src/utils/__tests__/bigint.test.ts @@ -0,0 +1,29 @@ +import { RoundingMode, bigIntMultiply } from "../bigint.js"; + +describe("BigInt utility", () => { + describe("multiplication operations", () => { + it("should multiply a big int correctly with an int", async () => { + expect(bigIntMultiply(10n, 2, RoundingMode.ROUND_UP)).toEqual(20n); + }); + + it("should multiply a big int correctly with a float", async () => { + expect(bigIntMultiply(10n, 0.5, RoundingMode.ROUND_UP)).toEqual(5n); + }); + + it("should fail to multiply a big int and a float with too much precision", async () => { + expect(() => + bigIntMultiply(10n, 0.53958491842948789, RoundingMode.ROUND_UP) + ).toThrowError( + "bigIntMultiply requires a multiplier validated number as the second argument" + ); + }); + + it("should multiply a big int correctly when rouding up", async () => { + expect(bigIntMultiply(10n, 0.53, RoundingMode.ROUND_UP)).toEqual(6n); + }); + + it("should multiply a big int correctly when rouding down", async () => { + expect(bigIntMultiply(10n, 0.53, RoundingMode.ROUND_DOWN)).toEqual(5n); + }); + }); +}); diff --git a/packages/core/src/utils/bigint.ts b/packages/core/src/utils/bigint.ts index 80147e7adc..856be4a607 100644 --- a/packages/core/src/utils/bigint.ts +++ b/packages/core/src/utils/bigint.ts @@ -1,5 +1,6 @@ import { keccak256, toHex } from "viem"; -import type { BigNumberish } from "../types"; +import type { BigNumberish, Multiplier } from "../types"; +import { isMultiplier } from "./schema.js"; /** * Returns the max bigint in a list of bigints @@ -67,29 +68,34 @@ export enum RoundingMode { } /** - * Useful if you want to increment a bigint by N% or decrement by N% + * Given a bigint and a number (which can be a float), returns the bigint value. + * Note: this function has loss and will round down to the nearest integer. * - * example: - * ``` - * const tenPercentIncrease = bigIntPercent(100n, 110n); - * const tenPercentDecrease = bigIntPercent(100n, 90n); - * ``` - * - * @param base -- the base bigint that we want to apply a percent to - * @param percent -- the percent to apply to the base + * @param a -- the bigint value to multiply + * @param b -- the number to multiply by * @param roundingMode -- the rounding mode to use when calculating the percent. defaults to ROUND_UP - * @returns the base multiplied by the percent and divided by 100 + * @returns -- the bigint value of the multiplication with the number rounded by the rounding mode */ -export const bigIntPercent = ( +export const bigIntMultiply = ( base: BigNumberish, - percent: bigint, + multiplier: Multiplier["multiplier"], roundingMode: RoundingMode = RoundingMode.ROUND_UP ) => { - if (roundingMode === RoundingMode.ROUND_UP) { - return (BigInt(base) * percent + 99n) / 100n; + if (!isMultiplier({ multiplier })) { + throw new Error( + "bigIntMultiply requires a multiplier validated number as the second argument" + ); } - return (BigInt(base) * percent) / 100n; + // Get decimal places of b. Max decimal places is defined by the MultiplerSchema. + const decimalPlaces = multiplier.toString().split(".")[1]?.length ?? 0; + const val = + roundingMode === RoundingMode.ROUND_UP + ? BigInt(base) * BigInt(multiplier * 10 ** decimalPlaces) + + BigInt(10 ** decimalPlaces - 1) + : BigInt(base) * BigInt(multiplier * 10 ** decimalPlaces); + + return val / BigInt(10 ** decimalPlaces); }; /** diff --git a/packages/core/src/utils/defaults.ts b/packages/core/src/utils/defaults.ts index 47a4941c48..decee969c9 100644 --- a/packages/core/src/utils/defaults.ts +++ b/packages/core/src/utils/defaults.ts @@ -94,7 +94,7 @@ export const getDefaultUserOperationFeeOptions = ( return { maxPriorityFeePerGas: { min: minPriorityFeePerBidDefaults.get(chain.id) ?? 100_000_000n, - percentage: 33, + multiplier: 1.33, }, }; }; diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 682f7683b5..295d3e2775 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -2,13 +2,7 @@ import type { Address, Chain, Hash, Hex } from "viem"; import { encodeAbiParameters, hexToBigInt, keccak256, toHex } from "viem"; import * as chains from "viem/chains"; import * as alchemyChains from "../chains/index.js"; -import type { - BigNumberish, - Percentage, - PromiseOrValue, - UserOperationRequest, -} from "../types.js"; -import { BigNumberishSchema, PercentageSchema } from "./schema.js"; +import type { PromiseOrValue, UserOperationRequest } from "../types.js"; export const AlchemyChainMap = new Map( Object.values(alchemyChains).map((c) => [c.id, c]) @@ -167,14 +161,6 @@ export function defineReadOnly( }); } -export function isBigNumberish(x: any): x is BigNumberish { - return x != null && BigNumberishSchema.safeParse(x).success; -} - -export function isPercentage(x: any): x is Percentage { - return x != null && PercentageSchema.safeParse(x).success; -} - export function filterUndefined( obj: Record ): Record { diff --git a/packages/core/src/utils/schema.ts b/packages/core/src/utils/schema.ts index 21f09692ae..df9b8f5f52 100644 --- a/packages/core/src/utils/schema.ts +++ b/packages/core/src/utils/schema.ts @@ -1,5 +1,6 @@ import { isHex, type Chain } from "viem"; import { z } from "zod"; +import type { BigNumberish, Multiplier } from "../types"; export const ChainSchema = z.custom( (chain) => @@ -22,11 +23,24 @@ export const BigNumberishRangeSchema = z }) .strict(); -export const PercentageSchema = z +export const MultiplierSchema = z .object({ /** - * Percent value between 1 and 1000 inclusive + * Multiplier value with max precision of 4 decmial places */ - percentage: z.number().min(1).max(1000), + multiplier: z.number().refine( + (n) => { + return (n.toString().split(".")[1]?.length ?? 0) <= 4; + }, + { message: "Max precision is 4 decimal places" } + ), }) .strict(); + +export function isBigNumberish(x: any): x is BigNumberish { + return x != null && BigNumberishSchema.safeParse(x).success; +} + +export function isMultiplier(x: any): x is Multiplier { + return x != null && MultiplierSchema.safeParse(x).success; +} diff --git a/packages/core/src/utils/userop.ts b/packages/core/src/utils/userop.ts index 34bbe729dc..b8ea302af4 100644 --- a/packages/core/src/utils/userop.ts +++ b/packages/core/src/utils/userop.ts @@ -1,11 +1,11 @@ import type { BigNumberish, - Percentage, + Multiplier, UserOperationFeeOptionsField, UserOperationRequest, UserOperationStruct, } from "../types"; -import { bigIntClamp, bigIntPercent } from "./bigint.js"; +import { bigIntClamp, bigIntMultiply } from "./bigint.js"; import { isBigNumberish } from "./index.js"; /** @@ -29,7 +29,7 @@ export function isValidRequest( export function applyUserOpOverride( value: BigNumberish | undefined, - override?: BigNumberish | Percentage + override?: BigNumberish | Multiplier ): BigNumberish | undefined { if (override == null) { return value; @@ -39,11 +39,9 @@ export function applyUserOpOverride( return override; } - // percentage override + // multiplier override else { - return value != null - ? bigIntPercent(value, BigInt(100 + override.percentage)) - : value; + return value != null ? bigIntMultiply(value, override.multiplier) : value; } } @@ -56,8 +54,8 @@ export function applyUserOpFeeOption( } return value != null ? bigIntClamp( - feeOption.percentage - ? bigIntPercent(value, BigInt(100 + feeOption.percentage)) + feeOption.multiplier + ? bigIntMultiply(value, feeOption.multiplier) : value, feeOption.min, feeOption.max @@ -67,7 +65,7 @@ export function applyUserOpFeeOption( export function applyUserOpOverrideOrFeeOption( value: BigNumberish | undefined, - override?: BigNumberish | Percentage, + override?: BigNumberish | Multiplier, feeOption?: UserOperationFeeOptionsField ): BigNumberish { return value != null && override != null diff --git a/site/migration-guide.md b/site/migration-guide.md index bf69fff834..dd47efdced 100644 --- a/site/migration-guide.md +++ b/site/migration-guide.md @@ -268,3 +268,25 @@ The `getPublicErc4337Client` method has been renamed to `getBundlerClient` to ma ### Ethers: Updated Signer Adapter constructor The `AccountSigner` now takes in a `SmartContractAccount` as a param in its constructor. + +### Core: Transition from ~~`Percent`~~ to `Multiplier` api and types + +The `Percent` type and `PercentSchema` have been removed in favor of the `Multiplier` type and `MultiplierSchema`. + +Going forward when using the feeOptions, you can specify the `Multiplier` type instead of a `Percent`. The `Multiplier` type is a number that represents direct multipliaction of the estimation. For example, `0.1` is 10% of the estimated value and `1` is 100% of the estimated value. + +```ts +createModularAccountAlchemyClient({ + ... + opts: { + ... + // The maxFeePerGas and maxPriorityFeePerGas estimated values will now be multipled by 1.5 + feeOptions: { + // This was previously { percent: 50n } + maxFeePerGas: { multiplier: 1.5 }, + // This was previously { percent: 25n } + maxPriorityFeePerGas: { multiplier: 1.25 }, + }, + }, + }); +``` diff --git a/site/packages/aa-alchemy/smart-account-client/index.md b/site/packages/aa-alchemy/smart-account-client/index.md index 66091a75d0..77af8708f8 100644 --- a/site/packages/aa-alchemy/smart-account-client/index.md +++ b/site/packages/aa-alchemy/smart-account-client/index.md @@ -37,9 +37,9 @@ export const client = createAlchemySmartAccountClient({ txRetryMultiplier: 1.5, minPriorityFeePerBid: 100_000_000n, feeOpts: { - baseFeeBufferPercent: 50n, - maxPriorityFeeBufferPercent: 5n, - preVerificationGasBufferPercent: 5n, + baseFeeBufferMultiplier: 1.5n, + maxPriorityFeeBufferMultiplier: 1.05, + preVerificationGasBufferMultiplier: 1.05, }, }, // will simulate user operations before sending them to ensure they don't revert diff --git a/site/packages/aa-core/smart-account-client/types/userOperationFeeOptions.md b/site/packages/aa-core/smart-account-client/types/userOperationFeeOptions.md index a8e4b0c40c..7d471b57c6 100644 --- a/site/packages/aa-core/smart-account-client/types/userOperationFeeOptions.md +++ b/site/packages/aa-core/smart-account-client/types/userOperationFeeOptions.md @@ -42,7 +42,7 @@ const rpcTransport = http("https://polygon-mumbai.g.alchemy.com/v2/demo"); const userOperationFeeOptions: UserOperationFeeOptions = { maxPriorityFeePerGas: { min: 100_000_000n, - percentage: 50, + multiplier: 1.5, }, }; diff --git a/site/packages/aa-core/smart-account-client/types/userOperationFeeOptionsField.md b/site/packages/aa-core/smart-account-client/types/userOperationFeeOptionsField.md index 45a28b37c4..7b9bb449c0 100644 --- a/site/packages/aa-core/smart-account-client/types/userOperationFeeOptionsField.md +++ b/site/packages/aa-core/smart-account-client/types/userOperationFeeOptionsField.md @@ -14,7 +14,7 @@ head: # UserOperationFeeOptionsField -Merged type of [`BigNumberishRange`](/resources/types.md#bignumberishrange) with [`Percentage`](/resources/types.md#percentage) type that can be used as [`UserOperationFeeOptions`](./userOperationFeeOptions.md) fields for the [`SmartAccountClient`](/packages/aa-core/smart-account-client/index.md) to use during the gas fee calculation middlewares when constructing the user operation to send. +Merged type of [`BigNumberishRange`](/resources/types.md#bignumberishrange) with [`Multiplier`](/resources/types.md#multiplier) type that can be used as [`UserOperationFeeOptions`](./userOperationFeeOptions.md) fields for the [`SmartAccountClient`](/packages/aa-core/smart-account-client/index.md) to use during the gas fee calculation middlewares when constructing the user operation to send. For example, if the below example `UserOperationFeeOptionsField` is set as the fee option for the `maxPriorityFeePerGas` field of [`UserOperationFeeOptions`](./userOperationFeeOptions.md), then the [`SmartAccountClient`](/packages/aa-core/smart-account-client/index.md) will apply 50% buffer to the estimated `maxPriorityFeePerGas`, then set the `maxPriorityFeePerGas` on the user operation as the larger value between the buffered `maxPriorityFeePerGas` fee and the min `maxPriorityFeePerGas` which is `100_000_000n` here. @@ -23,10 +23,10 @@ For example, if the below example `UserOperationFeeOptionsField` is set as the f * { * min?: BigNumberish * max?: BigNumberish - * percentage?: number + * multiplier?: number * } */ -type UserOperationFeeOptionsFieldSchema = BigNumberishRange & Percentage; +type UserOperationFeeOptionsFieldSchema = BigNumberishRange & Multiplier; ``` ## Usage @@ -38,7 +38,7 @@ import { type UserOperationFeeOptionsField } from "@alchemy/aa-core"; const userOperationFeeOptionsField: UserOperationFeeOptionsField = { min: 100_000_000n, - percentage: 50, + multiplier: 1.5, }; ``` diff --git a/site/packages/aa-core/smart-account-client/types/userOperationOverrides.md b/site/packages/aa-core/smart-account-client/types/userOperationOverrides.md index 5ab4981877..3710ec647f 100644 --- a/site/packages/aa-core/smart-account-client/types/userOperationOverrides.md +++ b/site/packages/aa-core/smart-account-client/types/userOperationOverrides.md @@ -20,7 +20,7 @@ Contains override values to be applied on the user operation request to be const These override values are available from each middleware of the `SmartAccountClient`. For example, the default middlewares such as `gasEstimator` or `feeEstimator` apply the overrides values to the estimated values if the override values are provided. -Other than the `paymasterAndData` field, the override fields could be either the absolute value or the percentage value. In the default middlewares, if the override value is an absolute value, it simply overrides the estimated value. If the override value is a percentage value, the estimated value is _bumped_ with the indicated percentage value. For example, if the override value is `{ percentage: 10 }` for the `maxPriorityFeePerGas` field, then 10% bump is applied to the estimated `maxPriorityFeePerGas` of the user operation. +Other than the `paymasterAndData` field, the override fields could be either the absolute value or the multiplier value. In the default middlewares, if the override value is an absolute value, it simply overrides the estimated value. If the override value is a multiplier value, the estimated value is _bumped_ with the indicated multiplier value. For example, if the override value is `{ multiplier: 1.1 }` for the `maxPriorityFeePerGas` field, then a 1.1 multiplier, or a 10% increase, is applied to the estimated `maxPriorityFeePerGas` of the user operation. The `paymasterAndData` only allows an absolute value override, and if the override value is provided, then the paymaster middleware is bypassed entirely. Refer to our guide [How to handle User Operations that are not eligible for gas sponsorship](/using-smart-accounts/sponsoring-gas/checking-eligibility.md) on the example of using the `paymasterAndData` override to bypass the paymaster middleware to fallback to the user paying the gas fee instead of the gas being subsidized by the paymaster. @@ -32,15 +32,15 @@ Note that if you are using your own middleware, for example a custom `feeEstimat type BytesLike = Uint8Array | Hex; export type UserOperationOverrides = Partial<{ - callGasLimit: UserOperationStruct["callGasLimit"] | Percentage; - maxFeePerGas: UserOperationStruct["maxFeePerGas"] | Percentage; + callGasLimit: UserOperationStruct["callGasLimit"] | Multiplier; + maxFeePerGas: UserOperationStruct["maxFeePerGas"] | Multiplier; maxPriorityFeePerGas: | UserOperationStruct["maxPriorityFeePerGas"] - | Percentage; - preVerificationGas: UserOperationStruct["preVerificationGas"] | Percentage; + | Multiplier; + preVerificationGas: UserOperationStruct["preVerificationGas"] | Multiplier; verificationGasLimit: | UserOperationStruct["verificationGasLimit"] - | Percentage; + | Multiplier; paymasterAndData: UserOperationStruct["paymasterAndData"]; }>; ``` diff --git a/site/packages/aa-ethers/provider-adapter/constructor.md b/site/packages/aa-ethers/provider-adapter/constructor.md index 13818af67b..2fa0822d2b 100644 --- a/site/packages/aa-ethers/provider-adapter/constructor.md +++ b/site/packages/aa-ethers/provider-adapter/constructor.md @@ -44,9 +44,9 @@ export const accountProvider = createAlchemySmartAccountClient({ txRetryMultiplier: 1.5, minPriorityFeePerBid: 100_000_000n, feeOpts: { - baseFeeBufferPercent: 50n, - maxPriorityFeeBufferPercent: 5n, - preVerificationGasBufferPercent: 5n, + baseFeeBufferMultiplier: 1.5, + maxPriorityFeeBufferMultiplier: 1.5, + preVerificationGasBufferMultiplier: 1.5, }, }, }); diff --git a/site/resources/types.md b/site/resources/types.md index 95d5857814..13034a6b07 100644 --- a/site/resources/types.md +++ b/site/resources/types.md @@ -146,8 +146,8 @@ An object type that may contain optional `min` and `max` fields, each of which a [See Type ↗️](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/core/src/types.ts#L23) -## `Percentage` +## `Multiplier` -An object type with a required `percentage` field, which is a `number` that must be within the range of 1 to 1000. +An object type with a required `multipler` field, which is a `number` that must be within the range of 1 to 1000. [See Type ↗️](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/core/src/types.ts#L20) From 7e48e1385ce11b49112ced040db5dbc8014243b4 Mon Sep 17 00:00:00 2001 From: avarobinson <45052879+avarobinson@users.noreply.github.com> Date: Thu, 15 Feb 2024 15:40:39 -0800 Subject: [PATCH 4/4] docs: faq upgrade from simple to light account instructions (#449) * docs: feat: update session key abi * docs: faq upgrade from simple to light account instructions * fix: add uups upgrade to and call link to the faq doc --------- Co-authored-by: Dennis Won --- site/resources/faqs.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/site/resources/faqs.md b/site/resources/faqs.md index 74babe3ba5..e2ea52829b 100644 --- a/site/resources/faqs.md +++ b/site/resources/faqs.md @@ -51,6 +51,23 @@ It is unlikely you will need to frequently update the Light Account contract its Yes! The optional index value (salt) on Light Account enables the ability to have multiple accounts for the same owner address. This value defaults to 0. You can set it when you create [light account](/packages/aa-accounts/light-account/). ::: +### How can I upgrade from Simple Account to Light Account? + +::: details Answer +[Simple Account's](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/samples/SimpleAccount.sol) support [`upgradeToAndCall`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/4e7e6e54daedf091d91f2f2df024cbb8f253e2ef/contracts/proxy/utils/UUPSUpgradeable.sol#L86) implemented by openzeppelin’s [UUPSUpgradeable](https://docs.openzeppelin.com/contracts/4.x/api/proxy#UUPSUpgradeable) contract. This allows you to upgrade from Simple Account to Light Account without changing the smart contract account address. Using `upgradeToAndCall` will update the underlying implementation contract on the account while the account address and assets will stay the same. + +You can call `upgradeToAndCall` on the Simple Account with these params: + +- `newImplementation`: Latest LightAccount implementation address (found [here](https://github.com/alchemyplatform/light-account/blob/main/Deployments.md#lightaccount) - make sure to use the implementation address, not the factory address) + - For example LightAccount v1.1.0 - 0xae8c656ad28F2B59a196AB61815C16A0AE1c3cba +- `data`: encoded version of the `initialize` function with `anOwner` parameter set to the owner/signer on the account, usually the same owner as what the account used as Simple Account. + - In solidity (foundry) you can use abi.encodeCall and in viem you can use [encodeFunctionData](https://github.com/alchemyplatform/light-account/blob/main/Deployments.md#lightaccount) + +It is very important that the `initialize` step is encoded correctly to ensure the account does not get in to a risky state where someone else could call initialize on it and reassign and a signer. You can call `owner()` on the account after upgrade to make sure it is assigned correctly. + +This can be called on the existing smart contract account by sending a user operation that calls `execute` (or `executeBatch`) and have that call `upgradeToAndCall` (the same way you would make the account send calls to other addresses). +::: + ## Submitting `UserOperation`s ### How does the speed of `UserOperation`s compare to normal transactions?