diff --git a/.env.example b/.env.example
new file mode 100644
index 000000000..a293b6aaf
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,5 @@
+E2E_PRIVATE_KEY_ONE=
+E2E_PRIVATE_KEY_TWO=
+E2E_BICO_PAYMASTER_KEY_MUMBAI=
+E2E_BICO_PAYMASTER_KEY_BASE=
+BICONOMY_SDK_DEBUG=true
\ No newline at end of file
diff --git a/.eslintrc.js b/.eslintrc.js
index 78d998dfd..eb97a48dc 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -23,9 +23,11 @@ module.exports = {
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
"security/detect-object-injection": "warn",
"security/detect-unsafe-regex": "error",
- "import/extensions": "off",
+ "import/extensions": "error", // Now we need to specify extensions for imports for esm builds
"security/detect-object-injection": "off", // turning off Injection Sink rule
"@typescript-eslint/no-throw-literal": "off", // temp deactivated needs to be removed once fixed
+ "@typescript-eslint/ban-ts-ignore": "off",
+ "@typescript-eslint/ban-ts-comment": "off",
},
settings: {},
overrides: [
diff --git a/.github/workflows/check_branch_name.yml b/.github/workflows/check_branch_name.yml
index 9c4cf9a67..39422432d 100644
--- a/.github/workflows/check_branch_name.yml
+++ b/.github/workflows/check_branch_name.yml
@@ -18,4 +18,4 @@ jobs:
if [[ ! $BRANCH_NAME =~ ^(features/|fixes/|releases/) ]]; then
echo "error: Branch names should follow GitFlow naming convention (features/, fixes/, releases/)."
exit 1
- fi
\ No newline at end of file
+ fi
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 000000000..5b5dd6522
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,27 @@
+name: Build and Deploy Documentation
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - develop
+ - v4
+
+permissions:
+ contents: write
+jobs:
+ build-docs-and-deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout 🛎️
+ uses: actions/checkout@v3
+
+ - name: Install and Build
+ run: |
+ yarn
+ yarn build
+ yarn --cwd ./packages/account docs
+
+ - name: Deploy 🚀
+ uses: JamesIves/github-pages-deploy-action@v4
+ with:
+ folder: ./packages/account/docs
diff --git a/.github/workflows/pull_request_approved.yml b/.github/workflows/pull_request_approved.yml
new file mode 100644
index 000000000..d4f142659
--- /dev/null
+++ b/.github/workflows/pull_request_approved.yml
@@ -0,0 +1,33 @@
+name: E2E Test workflow
+on:
+ pull_request_review:
+ types: [submitted]
+ workflow_dispatch:
+jobs:
+ e2e_test:
+ if: github.event.review.state == 'approved'
+ name: E2E tests
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ node-version: [18.x]
+
+ steps:
+ - name: Checkout
+ uses: "actions/checkout@main"
+
+ - name: Set Node.js
+ uses: actions/setup-node@v1
+ with:
+ node-version: ${{ matrix.node-version }}
+
+ - name: Install dependencies
+ run: yarn install --frozen-lockfile && yarn build
+
+ - name: Run tests
+ env:
+ E2E_PRIVATE_KEY_ONE: ${{ secrets.E2E_PRIVATE_KEY_ONE }}
+ E2E_PRIVATE_KEY_TWO: ${{ secrets.E2E_PRIVATE_KEY_TWO }}
+ E2E_BICO_PAYMASTER_KEY_MUMBAI: ${{ secrets.E2E_BICO_PAYMASTER_KEY_MUMBAI }}
+ E2E_BICO_PAYMASTER_KEY_BASE: ${{ secrets.E2E_BICO_PAYMASTER_KEY_BASE }}
+ run: yarn test:e2e
diff --git a/.github/workflows/push_check.yml b/.github/workflows/push_check.yml
index 6cfca05c1..35705a430 100644
--- a/.github/workflows/push_check.yml
+++ b/.github/workflows/push_check.yml
@@ -1,5 +1,6 @@
name: Test workflow
-on: push
+on: [push, workflow_dispatch]
+
jobs:
lint:
name: Lint sources
diff --git a/.gitignore b/.gitignore
index 3ac2e3158..cc1d5d78c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,7 +3,6 @@ logs
*.log
yarn-debug.log*
yarn-error.log*
-yarn.lock
lockfiles
# Diagnostic reports (https://nodejs.org/api/report.html)
@@ -47,7 +46,6 @@ node_modules/
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
-yarn.lock
# macOS
.DS_Store
@@ -58,20 +56,23 @@ artifacts
deployments
# lockfiles
-packages/core-types/package-lock.json
packages/account/package-lock.json
-packages/common/package-lock.json
+packages/modules/package-lock.json
packages/bundler/package-lock.json
-packages/node-client/package-lock.json
packages/paymaster/package-lock.json
packages/particle-auth/package-lock.json
-packages/web3-auth/package-lock.json
-packages/web3-auth-native/package-lock.json
packages/transak/package-lock.json
package-lock.json
-yarn.lock
+
+#ignore sessionStorageData files
+packages/modules/tests/utils/sessionStorageData/*
+#except sessionStorageData folder
+!packages/modules/tests/utils/sessionStorageData/.gitkeep
# Typechain
typechain
openapi/
+
+# docs
+packages/account/docs/*
\ No newline at end of file
diff --git a/.nvmrc b/.nvmrc
index c9d82507f..b1215e876 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-v14.17.0
\ No newline at end of file
+v18.16.0
\ No newline at end of file
diff --git a/README.md b/README.md
index 096b1e7f2..00c720607 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,31 @@
-# Biconomy SDK: Your Gateway to ERC4337 Account Abstraction & Smart Accounts 🛠️
+# Biconomy SDK
-![Biconomy SDK](https://img.shields.io/badge/Biconomy-SDK-blue.svg) ![TypeScript](https://img.shields.io/badge/-TypeScript-blue) ![Test Coverage](https://img.shields.io/badge/Coverage-45%25-yellow.svg)
+![Biconomy SDK](https://img.shields.io/badge/Biconomy-SDK-blue.svg)
+![TypeScript](https://img.shields.io/badge/-TypeScript-blue)
+![Test Coverage](https://img.shields.io/badge/Coverage-79.82%25-green.svg)
-
+## 👋 Introduction
-## Introduction
+The Biconomy SDK is your all-in-one toolkit for building decentralized applications (dApps) with **ERC4337 Account Abstraction** and **Smart Accounts**. It is designed for seamless user experiences and offers non-custodial solutions for user onboarding, sending transactions (userOps), gas sponsorship and much more.
-The Biconomy SDK is your all-in-one toolkit for building decentralized applications (dApps) with **ERC4337 Account Abstraction** and **Smart Accounts**. This SDK is designed for seamless user experiences and offers non-custodial solutions for user onboarding, transaction management, and gas abstraction.
+## 🛠️ Quickstart
+
+```typescript
+import { createSmartAccountClient } from "@biconomy/account";
+
+const smartAccount = await createSmartAccountClient({
+ signer: viemWalletOrEthersSigner,
+ bundlerUrl: "", // From dashboard.biconomy.io
+ biconomyPaymasterApiKey: "", // From dashboard.biconomy.io
+});
+
+const { wait } = await smartAccount.sendTransaction({ to: "0x...", value: 1 });
-
+const {
+ receipt: { transactionHash },
+ userOpHash,
+} = await wait();
+```
## 🌟 Features
@@ -16,75 +33,101 @@ The Biconomy SDK is your all-in-one toolkit for building decentralized applicati
- **Smart Accounts**: Enhance user experience with modular smart accounts.
- **Paymaster Service**: Enable third-party gas sponsorship.
- **Bundler Infrastructure**: Ensure efficient and reliable transaction bundling.
-- **Backend Node**: Manage chain configurations and gas estimations.
-
-## 📦 Packages
-
-### Account
-
-Unlock the full potential of **ERC4337 Account Abstraction** with methods that simplify the creation and dispatch of UserOperations, streamlining dApp development and management.
-
-```javascript
-import { ECDSAOwnershipValidationModule, DEFAULT_ECDSA_OWNERSHIP_MODULE } from "@biconomy/modules";
-import { IBundler, Bundler } from '@biconomy/bundler'
-import { DEFAULT_ENTRYPOINT_ADDRESS } from "@biconomy/account"
-import { providers } from 'ethers'
-import { ChainId } from "@biconomy/core-types"
+For a step-by-step guide on integrating **ERC4337 Account Abstraction** and **Smart Accounts** into your dApp using the Biconomy SDK, refer to the [official documentation](https://docs.biconomy.io/docs/overview). You can also start with Quick start [here](https://docs.biconomy.io/quickstart).
+## 📚 Resources
-const module = await ECDSAOwnershipValidationModule.create({
- signer: wallet,
- moduleAddress: DEFAULT_ECDSA_OWNERSHIP_MODULE
- })
+- [Biconomy Documentation](https://docs.biconomy.io/)
+- [Biconomy Dashboard](https://dashboard.biconomy.io)
+- [TSDoc](https://bcnmy.github.io/biconomy-client-sdk)
-const biconomySmartAccount = await BiconomySmartAccountV2.create({
- chainId: ChainId.POLYGON_MUMBAI,
- bundler: bundler,
- paymaster: paymaster,
- entryPointAddress: DEFAULT_ENTRYPOINT_ADDRESS,
- defaultValidationModule: module,
- activeValidationModule: module
-})
+## ⚙️ installation
-console.log("address: ", await biconomySmartAccount.getAccountAddress());
+```bash
+npm i @biconomy/account
```
-### Bundler
+## 💼 Example Usages
-Leverage standardized bundler infrastructure for efficient operation of account abstraction across EVM networks.
+### [Initialise the smartAccount](https://bcnmy.github.io/biconomy-client-sdk/functions/createSmartAccountClient.html)
-```javascript
+| Key | Description |
+| -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
+| [signer](https://bcnmy.github.io/biconomy-client-sdk/packages/account/docs/interfaces/SmartAccountSigner.html) | This signer will be used for signing userOps for any transactions you build. Will accept ethers.JsonRpcSigner as well as a viemWallet |
+| [biconomyPaymasterApiKey](https://dashboard.biconomy.io) | You can pass in a biconomyPaymasterApiKey necessary for sponsoring transactions (retrieved from the biconomy dashboard) |
+| [bundlerUrl](https://dashboard.biconomy.io) | You can pass in a bundlerUrl (retrieved from the biconomy dashboard) for sending transactions |
-import { IBundler, Bundler } from '@biconomy/bundler'
+```typescript
+import { createSmartAccountClient } from "@biconomy/account";
+import { createWalletClient, http, createPublicClient } from "viem";
+import { privateKeyToAccount, generatePrivateKey } from "viem/accounts";
+import { mainnet as chain } from "viem/chains";
+const account = privateKeyToAccount(generatePrivateKey());
+const signer = createWalletClient({ account, chain, transport: http() });
-const bundler: IBundler = new Bundler({
- bundlerUrl: 'https://bundler.biconomy.io/api/v2/80001/',
- // Please go to https://dashboard.biconomy.io and generate bundler url
- chainId: ChainId.POLYGON_MUMBAI,
- entryPointAddress: DEFAULT_ENTRYPOINT_ADDRESS,
- })
+const smartAccount = await createSmartAccountClient({
+ signer,
+ bundlerUrl,
+ biconomyPaymasterApiKey,
+});
```
-### Paymaster
+### [Send some ETH, have gas sponsored](https://bcnmy.github.io/biconomy-client-sdk/classes/BiconomySmartAccountV2.html#sendTransaction)
+
+| Key | Description |
+| --------------------------------------------------------------------------------- | -------------------------------------------------------------- |
+| [oneOrManyTx](https://bcnmy.github.io/biconomy-client-sdk/types/Transaction.html) | Submit multiple or one transactions |
+| [userOpReceipt](https://bcnmy.github.io/biconomy-client-sdk/types/UserOpReceipt) | Returned information about your tx, receipts, userOpHashes etc |
-Acting as third-party intermediaries, Paymasters have the capability to sponsor gas fees for an account, provided specific predefined conditions are met. Additionally, they can accept gas payments in ERC20 tokens from users' smart accounts, with the Paymaster managing the conversion to native tokens for gas payment.
+```typescript
+const oneOrManyTx = { to: "0x...", value: 1 };
-```javascript
-const paymaster: IPaymaster = new BiconomyPaymaster({
- paymasterUrl: '' // From Biconomy Dashboard
+const { wait } = await smartAccount.sendTransaction(oneOrManyTx, {
+ mode: PaymasterMode.SPONSORED,
});
+
+const {
+ receipt: { transactionHash },
+ userOpHash,
+} = await wait();
```
-## 🛠️ Quickstart
+### [Mint two NFTs, pay gas with token](https://bcnmy.github.io/biconomy-client-sdk/classes/BiconomySmartAccountV2.html#getTokenFees)
-For a step-by-step guide on integrating **ERC4337 Account Abstraction** and **Smart Accounts** into your dApp using the Biconomy SDK, refer to the [official documentation](https://docs.biconomy.io/docs/overview). You can also start with Quick explore here https://docs.biconomy.io/docs/category/quick-explore
+| Key | Description |
+| -------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
+| [buildUseropDto](https://bcnmy.github.io/biconomy-client-sdk/types/BuildUserOpOptions.html) | Options for building a userOp |
+| [paymasterServiceData](https://bcnmy.github.io/biconomy-client-sdk/types/PaymasterUserOperationDto.html) | PaymasterOptions set in the buildUseropDto |
-## 📚 Resources
+```typescript
+import { encodeFunctionData, parseAbi } from "viem";
+
+const encodedCall = encodeFunctionData({
+ abi: parseAbi(["function safeMint(address to) public"]),
+ functionName: "safeMint",
+ args: ["0x..."],
+});
-- [Biconomy Documentation](https://docs.biconomy.io/docs/overview)
-- [Biconomy Dashboard](https://dashboard.biconomy.io/)
+const tx = {
+ to: nftAddress,
+ data: encodedCall,
+};
+const oneOrManyTx = [tx, tx]; // Mint twice
+const paymasterServiceData = {
+ mode: PaymasterMode.ERC20,
+ preferredToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
+};
+const buildUseropDto = { paymasterServiceData };
+
+const { wait } = await smartAccount.sendTransaction(oneOrManyTx, buildUseropDto);
+
+const {
+ receipt: { transactionHash },
+ userOpHash,
+} = await wait();
+```
## 🤝 Contributing
diff --git a/jest.config.e2e.ts b/jest.config.e2e.ts
new file mode 100644
index 000000000..2fff1ca6d
--- /dev/null
+++ b/jest.config.e2e.ts
@@ -0,0 +1,6 @@
+import config from "./jest.config";
+const e2eConfig = { ...config };
+e2eConfig.testMatch = ["**/*.e2e.spec.ts"];
+e2eConfig.setupFilesAfterEnv = ["/tests/setup-e2e-tests.ts"];
+
+export default e2eConfig;
diff --git a/jest.config.ts b/jest.config.ts
index a968e1995..7cc8b4123 100644
--- a/jest.config.ts
+++ b/jest.config.ts
@@ -78,7 +78,11 @@ const config: Config = {
moduleFileExtensions: ["js", "mjs", "cjs", "jsx", "ts", "tsx", "json", "node"],
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
- // moduleNameMapper: {},
+ moduleNameMapper: {
+ '^(?!bn\.js$|hash\.js$)(.+)\\.js$': '$1'
+ },
+
+ extensionsToTreatAsEsm: ['.ts'],
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
@@ -123,7 +127,7 @@ const config: Config = {
// setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
- // setupFilesAfterEnv: [],
+ setupFilesAfterEnv: ["/tests/setup-unit-tests.ts"],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
@@ -141,7 +145,7 @@ const config: Config = {
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
- testMatch: ["**/*.spec.ts"],
+ testMatch: ["**/*.spec.ts", "!**/*.e2e.spec.ts"],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
@@ -159,7 +163,7 @@ const config: Config = {
// A map from regular expressions to paths to transformers
transform: {
- "^.+\\.tsx?$": "ts-jest",
+ "^.+\\.tsx?$": ["ts-jest", { useESM: true }],
},
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
@@ -179,6 +183,10 @@ const config: Config = {
// Whether to use watchman for file crawling
// watchman: true,
+
+ globals: {
+ testDataPerChain: [],
+ }
};
-export default config;
+export default config;
\ No newline at end of file
diff --git a/linkAll.sh b/linkAll.sh
index 6e9f511b6..3597ec1c0 100755
--- a/linkAll.sh
+++ b/linkAll.sh
@@ -1,2 +1,3 @@
!/bin/sh
-for dir in ./packages/*; do (cd "$dir" && yarn link); done
\ No newline at end of file
+for dir in ./packages/*; do (cd "$dir" && yarn link); done
+cd ./packages/account && yarn link '@biconomy/bundler' '@biconomy/modules' '@biconomy/paymaster' '@biconomy/common'
\ No newline at end of file
diff --git a/package.json b/package.json
index 65f030a37..0939192fb 100644
--- a/package.json
+++ b/package.json
@@ -26,10 +26,10 @@
"author": "Biconomy (https://biconomy.io)",
"private": true,
"scripts": {
- "dev": "yarn rebuild && yarn install && yarn build && yarn link:all",
+ "dev": "yarn rebuild && yarn install && lerna run build && yarn link:all",
"rebuild": "./rebuild.sh",
"link:all": "./linkAll.sh",
- "build": "lerna run build",
+ "build": "yarn rebuild && yarn install && lerna run build",
"clean": "lerna clean && lerna run unbuild",
"format": "lerna run format --npm-client=yarn",
"prettier": "npx prettier --write .",
@@ -40,6 +40,7 @@
"start:ganache": "ganache -m 'direct buyer cliff train rice spirit census refuse glare expire innocent quote'",
"test:ci": "FORCE_COLOR=1 lerna run test:ci --stream --npm-client=yarn",
"test:coverage": "concurrently -k --success first 'yarn start:ganache' 'yarn jest --runInBand --coverage'",
+ "test:e2e": "yarn test:run --config=jest.config.e2e.ts",
"diff": "lerna diff",
"release": "lerna version patch --no-git-tag-version --no-push --conventional-commits --yes"
},
@@ -56,13 +57,14 @@
},
"dependencies": {
"node-gyp": "^9.4.0",
- "typescript": "^5.2.2"
+ "typescript": "^5.3.3"
},
"devDependencies": {
"@types/debug": "^4.1.9",
"@types/jest": "^29.5.4",
"@typescript-eslint/eslint-plugin": "^6.7.0",
"@typescript-eslint/parser": "^6.6.0",
+ "concurrently": "^8.2.2",
"eslint": "^8.48.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-airbnb-typescript": "17.1.0",
@@ -70,7 +72,7 @@
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-security": "^1.7.1",
- "ganache": "^7.9.1",
+ "ganache": "^7.9.2",
"hardhat": "^2.17.3",
"jest": "^29.7.0",
"lerna": "^7.2.0",
diff --git a/packages/account/.esbuild.js b/packages/account/.esbuild.js
new file mode 100644
index 000000000..ca355e346
--- /dev/null
+++ b/packages/account/.esbuild.js
@@ -0,0 +1,58 @@
+const esbuildPluginTsc = require("esbuild-plugin-tsc");
+const esbuild = require("esbuild");
+const { dependencies, peerDependencies = {} } = require("./package.json");
+const { Generator } = require("npm-dts");
+
+const COMMON_SETTINGS = {
+ entryPoints: ["src/index.ts"],
+ minify: true,
+ bundle: true,
+ plugins: [esbuildPluginTsc({ force: true })],
+};
+
+const ESM_SETTINGS = {
+ ...COMMON_SETTINGS,
+ sourcemap: true,
+ outfile: "dist/esm/index.js",
+ platform: "browser",
+ target: "esnext",
+ format: "esm",
+ mainFields: ["browser", "module", "main"],
+};
+const buildForESM = async () => await esbuild.build(ESM_SETTINGS);
+
+const CJS_SETTINGS = {
+ ...COMMON_SETTINGS,
+ format: "cjs",
+ sourcemap: false,
+ outfile: "dist/cjs/index.js",
+ platform: "node",
+};
+
+const watchForCJS = async () => {
+ let ctx = await esbuild.context(CJS_SETTINGS);
+ await ctx.watch();
+ await ctx.serve({ servedir: "dist/src" });
+ console.log("watching...");
+};
+
+const buildForCJS = async () => await esbuild.build(CJS_SETTINGS);
+const buildForTYP = async () => await new Generator({ entry: "src/index.ts", output: "dist/types/index.d.ts" }).generate();
+
+(async () => {
+ const buildType = process.argv.slice(2)[0];
+ const shouldWatch = process.argv.slice(3)[0] === "--watch";
+ if (!buildType) {
+ console.log("No build type provided");
+ process.exit(1);
+ }
+ console.log(`Building for ${buildType}`);
+ if (buildType === "ESM") {
+ await buildForESM();
+ } else if (buildType === "CJS") {
+ console.log("watching? " + shouldWatch);
+ await (shouldWatch ? watchForCJS : buildForCJS)();
+ } else if (buildType === "TYP") {
+ await buildForTYP();
+ }
+})();
diff --git a/packages/account/CHANGELOG.md b/packages/account/CHANGELOG.md
index f2f70fe1f..abff0df13 100644
--- a/packages/account/CHANGELOG.md
+++ b/packages/account/CHANGELOG.md
@@ -3,6 +3,40 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## 4.0.0 (2023-02-06)
+
+### Features
+
+- Export bundler / paymaster instances + helpers from master package ([1d1f9d](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/1d1f9dafddf11bde0e1a75383bc935b22448bedd))
+- Export modules aliases from master package ([d6205c](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/d6205c4d76ab846ecdc10843c65e0277f3ceab00))
+- Added sendTransaction abstraction for buildUserOp + sendUserOp ([335c6e](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/335c6e7bfc5ca1ad240e7cbfd678d905c7f16812))
+- Reduced bundle size ([765a3e3](https://github.com/bcnmy/biconomy-client-sdk/commit/765a3e337fb9ad8f1f8dc92b5edcb1ed0940f94d))
+- Added bundler abstraction during SmartAccount init ([591bbb4](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/591bbb4e37774b16cbe801d583d31b3a14608bc1))
+- Added e2e tests that speak with prod bundler / paymasters ([4b4a53a](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/4b4a53aabdf9e22485599872332b3d63e8ddd87a))
+- Added support for simultaneous ethers + viem signers ([8e4b2c8](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/8e4b2c86b871130befbf3b733cf503d24f7226a5))
+- E2E tests for multichain modules ([ecc86e2](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/ecc86e2c7146046a981c3b6fd4bb29e4828b278b))
+- E2E tests for session validation modules ([4ad7ea7](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/4ad7ea7f8eb6a28854dcce83834b2b7ff9ad3287))
+- Added [TSDoc](https://bcnmy.github.io/biconomy-client-sdk) ([638dae](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/638daee0ed6924f67c5151a2d0e5a02d32e4bf23))
+- Make txs more typesafe and default with valueOrData ([b1e5b5e](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/b1e5b5e02ab3a7fb99faa1d45b55e3cbe8d6bc93))
+- Added createSmartAccountClient alias ([232472](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/232472c788bed0619cf6295ade076b6ec3a9e0f8))
+- Improve dx of using paymster to build userOps ([bb54888](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/bb548884e76a94a20329e49b18994503de9e3888))
+- Add ethers v6 signer support ([9727fd](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/9727fd51e47d62904399d17d48f5c9d6b9e591e5))
+- Improved dx of using gas payments with erc20 ([741806](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/741806da68457eba262e1a49be77fcc24360ba36))
+
+### Chores
+
+- Removed SmartAccount Base class in favour of Alchemy's ([be82732](https://github.com/bcnmy/biconomy-client-sdk/commit/be827327fafa858b1551ade0c8389293034cacbb))
+- Migrate to viem v2 ([8ce04a](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/8ce04a56f6dcdfd1f44d9534f43e3c6eb8b3885d))
+- Remove common + core-types dependencies ([673514](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/6735141fbd21a855aadf69011bc06c69e20f811b))
+- Reincluded simulation flags ([25d2be](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/25d2bee339afd9d8c143fe6dad1898e28034be17))
+
+### Bug Fixes
+
+- Make silently failing paymaster calls throw an error instead ([693bf0](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/693bf08591427c03e317d64d0491e23b1c96ea30))
+- Added string as a supported Transaction value type ([b905dc](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/b905dcf3f7849396573fc8b51f808cc68061ee11))
+- Removed skipBundlerGasEstimation option ([b905dc](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/b905dcf3f7849396573fc8b51f808cc68061ee11))
+- Ingest rpcUrl from SupportedSigners (ethers + viem) ([f56b4d](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/f56b4da08f47af577c01a641b81a3ef9e354cf97))
+
## 3.1.3 (2023-12-28)
VERSION Bump Only.
@@ -11,54 +45,44 @@ VERSION Bump Only.
### Features
-* Make entryPointAddress optional in config([cf35c4a](https://github.com/bcnmy/biconomy-client-sdk/pull/336/commits/cf35c4a8266d27648035d8c9d63f1b9157553128))
+- Make entryPointAddress optional in config([cf35c4a](https://github.com/bcnmy/biconomy-client-sdk/pull/336/commits/cf35c4a8266d27648035d8c9d63f1b9157553128))
### Bug Fixes
-* use undefined in place of ! + check on limits returned by paymaster and throw ([0376901](https://github.com/bcnmy/biconomy-client-sdk/commit/0376901b7aec8c268a6a3c654d147335974d78f3))
-* change receipt status type from boolean to string to be compatible with bundler response. ([317f986](https://github.com/bcnmy/biconomy-client-sdk/pull/342/commits/317f986b7e8f08d3ccf1e68f12a0696f1116de6b))
+- use undefined in place of ! + check on limits returned by paymaster and throw ([0376901](https://github.com/bcnmy/biconomy-client-sdk/commit/0376901b7aec8c268a6a3c654d147335974d78f3))
+- change receipt status type from boolean to string to be compatible with bundler response. ([317f986](https://github.com/bcnmy/biconomy-client-sdk/pull/342/commits/317f986b7e8f08d3ccf1e68f12a0696f1116de6b))
## 3.1.1 (2023-11-09)
-
### Bug Fixes
-* optimistic implementation for getNonce() and cache for isAccountDeployed ([5b1d4bf](https://github.com/bcnmy/biconomy-client-sdk/commit/5b1d4bfd7b5062d05bbb97286b833d879cd972b0))
-
+- optimistic implementation for getNonce() and cache for isAccountDeployed ([5b1d4bf](https://github.com/bcnmy/biconomy-client-sdk/commit/5b1d4bfd7b5062d05bbb97286b833d879cd972b0))
### Reverts
-* update paymaster check in estimateUserOpGas ([2eb0237](https://github.com/bcnmy/biconomy-client-sdk/commit/2eb0237b37425da3558801bbe9d0ce5d6fd696c9))
-
-
-
-
+- update paymaster check in estimateUserOpGas ([2eb0237](https://github.com/bcnmy/biconomy-client-sdk/commit/2eb0237b37425da3558801bbe9d0ce5d6fd696c9))
## 3.1.0 (2023-09-20)
+
Modular Account Abstraction is here. Contains BiconomySmartAccountV2 - an API for modular smart account.
### Bug Fixes
-* add 10sec timeout limit for a test ([5d12fe7](https://github.com/bcnmy/biconomy-client-sdk/commit/5d12fe7d4b32e5c4628b971d22f6fc9cfcc6b414))
-* avoid sending populated values of gas prices when estimating from bundler ([c58c9fc](https://github.com/bcnmy/biconomy-client-sdk/commit/c58c9fc29ee83978e1a90305e839002431db2b7b))
-* BiconomySmartAccountV2 API Specs ([69a580e](https://github.com/bcnmy/biconomy-client-sdk/commit/69a580ea9e309141b500274aa95e20e24365b522))
-* build errors ([9fb0475](https://github.com/bcnmy/biconomy-client-sdk/commit/9fb047534935b0600bd08a4de7e68fd91a8a089a))
-* comments [#296](https://github.com/bcnmy/biconomy-client-sdk/issues/296) ([55b7376](https://github.com/bcnmy/biconomy-client-sdk/commit/55b7376336886226967b5bec5f11ba3ab750c5b6))
-* estimation without bundler ([5e49473](https://github.com/bcnmy/biconomy-client-sdk/commit/5e49473e7745c2e87e241731ef8ca1f65ee90388))
-* gitInitCode cache issue ([4df3502](https://github.com/bcnmy/biconomy-client-sdk/commit/4df3502204e3c6c0c6faa90ba2c8aa0d6e826e48))
-* lint warning and errors ([2135498](https://github.com/bcnmy/biconomy-client-sdk/commit/2135498896beb54d25add820c1521ffa22d5db7c))
-* unshift error for batch ([4d090e8](https://github.com/bcnmy/biconomy-client-sdk/commit/4d090e8fbc7e7bcc03805d8dd28c738d5c95dae7))
-
+- add 10sec timeout limit for a test ([5d12fe7](https://github.com/bcnmy/biconomy-client-sdk/commit/5d12fe7d4b32e5c4628b971d22f6fc9cfcc6b414))
+- avoid sending populated values of gas prices when estimating from bundler ([c58c9fc](https://github.com/bcnmy/biconomy-client-sdk/commit/c58c9fc29ee83978e1a90305e839002431db2b7b))
+- BiconomySmartAccountV2 API Specs ([69a580e](https://github.com/bcnmy/biconomy-client-sdk/commit/69a580ea9e309141b500274aa95e20e24365b522))
+- build errors ([9fb0475](https://github.com/bcnmy/biconomy-client-sdk/commit/9fb047534935b0600bd08a4de7e68fd91a8a089a))
+- comments [#296](https://github.com/bcnmy/biconomy-client-sdk/issues/296) ([55b7376](https://github.com/bcnmy/biconomy-client-sdk/commit/55b7376336886226967b5bec5f11ba3ab750c5b6))
+- estimation without bundler ([5e49473](https://github.com/bcnmy/biconomy-client-sdk/commit/5e49473e7745c2e87e241731ef8ca1f65ee90388))
+- gitInitCode cache issue ([4df3502](https://github.com/bcnmy/biconomy-client-sdk/commit/4df3502204e3c6c0c6faa90ba2c8aa0d6e826e48))
+- lint warning and errors ([2135498](https://github.com/bcnmy/biconomy-client-sdk/commit/2135498896beb54d25add820c1521ffa22d5db7c))
+- unshift error for batch ([4d090e8](https://github.com/bcnmy/biconomy-client-sdk/commit/4d090e8fbc7e7bcc03805d8dd28c738d5c95dae7))
### Features
-* get fee quote or data method in biconomy paymaster ([47748a6](https://github.com/bcnmy/biconomy-client-sdk/commit/47748a6384c2b74e1d9be4d570554098e1ac02e7))
-* update responses to support calculateGasLimits flag + update interfaces ([55bbd38](https://github.com/bcnmy/biconomy-client-sdk/commit/55bbd38b4ef8acaf8da1d52e36846557b134aba4))
-* using hybrid paymaster interface ([5fc56a7](https://github.com/bcnmy/biconomy-client-sdk/commit/5fc56a7db2de4a3f4bb87cd4d75584e79010b206))
-
-
-
-
+- get fee quote or data method in biconomy paymaster ([47748a6](https://github.com/bcnmy/biconomy-client-sdk/commit/47748a6384c2b74e1d9be4d570554098e1ac02e7))
+- update responses to support calculateGasLimits flag + update interfaces ([55bbd38](https://github.com/bcnmy/biconomy-client-sdk/commit/55bbd38b4ef8acaf8da1d52e36846557b134aba4))
+- using hybrid paymaster interface ([5fc56a7](https://github.com/bcnmy/biconomy-client-sdk/commit/5fc56a7db2de4a3f4bb87cd4d75584e79010b206))
## 3.0.0 (2023-08-28)
@@ -66,40 +90,27 @@ VERSION Bump Only.
Modular SDK - consists stable version of below updates done in Alphas.
-
-
## 3.1.1-alpha.0 (2023-08-02)
-
### Bug Fixes
VERSION Bump Only.
-
-
-
-
# 3.1.0-alpha.0 (2023-07-24)
-
### Bug Fixes
-* avoid sending populated values of gas prices when estimating from bundler ([c58c9fc](https://github.com/bcnmy/biconomy-client-sdk/commit/c58c9fc29ee83978e1a90305e839002431db2b7b))
-
-
-
+- avoid sending populated values of gas prices when estimating from bundler ([c58c9fc](https://github.com/bcnmy/biconomy-client-sdk/commit/c58c9fc29ee83978e1a90305e839002431db2b7b))
## 3.0.0-alpha.0 (2023-07-12)
-
### Bug Fixes
-* estimation without bundler ([5e49473](https://github.com/bcnmy/biconomy-client-sdk/commit/5e49473e7745c2e87e241731ef8ca1f65ee90388))
-* unshift error for batch ([4d090e8](https://github.com/bcnmy/biconomy-client-sdk/commit/4d090e8fbc7e7bcc03805d8dd28c738d5c95dae7))
-
+- estimation without bundler ([5e49473](https://github.com/bcnmy/biconomy-client-sdk/commit/5e49473e7745c2e87e241731ef8ca1f65ee90388))
+- unshift error for batch ([4d090e8](https://github.com/bcnmy/biconomy-client-sdk/commit/4d090e8fbc7e7bcc03805d8dd28c738d5c95dae7))
### Features
-* get fee quote or data method in biconomy paymaster ([47748a6](https://github.com/bcnmy/biconomy-client-sdk/commit/47748a6384c2b74e1d9be4d570554098e1ac02e7))
-* update responses to support calculateGasLimits flag + update interfaces ([55bbd38](https://github.com/bcnmy/biconomy-client-sdk/commit/55bbd38b4ef8acaf8da1d52e36846557b134aba4))
-* using hybrid paymaster interface ([5fc56a7](https://github.com/bcnmy/biconomy-client-sdk/commit/5fc56a7db2de4a3f4bb87cd4d75584e79010b206))
+- get fee quote or data method in biconomy paymaster ([47748a6](https://github.com/bcnmy/biconomy-client-sdk/commit/47748a6384c2b74e1d9be4d570554098e1ac02e7))
+- update responses to support calculateGasLimits flag + update interfaces ([55bbd38](https://github.com/bcnmy/biconomy-client-sdk/commit/55bbd38b4ef8acaf8da1d52e36846557b134aba4))
+- using hybrid paymaster interface ([5fc56a7](https://github.com/bcnmy/biconomy-client-sdk/commit/5fc56a7db2de4a3f4bb87cd4d75584e79010b206))
diff --git a/packages/account/Readme.md b/packages/account/Readme.md
index fb8fc3959..0aa74954b 100644
--- a/packages/account/Readme.md
+++ b/packages/account/Readme.md
@@ -1,85 +1,138 @@
-# installation
+# Biconomy SDK
-Using `npm` package manager
+![Biconomy SDK](https://img.shields.io/badge/Biconomy-SDK-blue.svg)
+![TypeScript](https://img.shields.io/badge/-TypeScript-blue)
+![Test Coverage](https://img.shields.io/badge/Coverage-79.82%25-green.svg)
-```bash
-npm i @biconomy/account
-```
+## 👋 Introduction
-OR
+The Biconomy SDK is your all-in-one toolkit for building decentralized applications (dApps) with **ERC4337 Account Abstraction** and **Smart Accounts**. It is designed for seamless user experiences and offers non-custodial solutions for user onboarding, sending transactions (userOps), gas sponsorship and much more.
-Using `yarn` package manager
+## ⚙️ installation
```bash
-yarn add @biconomy/account
+npm i @biconomy/account
```
-### Account
+## 🛠️ Quickstart
-Integrating and deploying Smart Accounts, building and sending user operations is a key offering of any toolkit designed for ERC4337. This package seamlessly integrates the essential features associated with ERC-4337 and simplifies the development of your Dapp's account and transaction rails with added usability features.
+```typescript
+import { createSmartAccountClient } from "@biconomy/account";
-The account package achieves this by providing a comprehensive set of methods that enable developers to effortlessly create UserOperations. Combined with the sophisticated, developer friendly and scalable infrastructure of Biconomy, it ensures efficient and reliable transmission of these operations across multiple EVM chains.
+const smartAccount = await createSmartAccountClient({
+ signer: viemWalletOrEthersSigner,
+ bundlerUrl: "", // From dashboard.biconomy.io
+ biconomyPaymasterApiKey: "", // From dashboard.biconomy.io
+});
-## Smart Account instance configuration
+const { wait } = await smartAccount.sendTransaction({ to: "0x...", value: 1 });
-#### BiconomySmartAccount (V1 Smart Account)
+const {
+ receipt: { transactionHash },
+ userOpHash,
+} = await wait();
+```
-| Key | Description |
-| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| signer | This signer will be used for signing userOps for any transactions you build. You can supply your your EOA wallet signer |
-| chainId | This represents the network your smart wallet transactions will be conducted on. Take a look following Link for supported chain id's |
-| rpcUrl | This represents the EVM node RPC URL you'll interact with, adjustable according to your needs. We recommend to use some private node url for efficient userOp building |
-| paymaster | you can pass same paymaster instance that you have build in previous step. Alternatively, you can skip this if you are not interested in sponsoring transaction using paymaster |
-| | Note: if you don't pass the paymaster instance, your smart account will need funds to pay for transaction fees. |
-| bundler | You can pass same bundler instance that you have build in previous step. Alternatively, you can skip this if you are only interested in building userOP |
+## 🌟 Features
-## Example Usage
+- **ERC4337 Account Abstraction**: Simplify user operations and gas payments.
+- **Smart Accounts**: Enhance user experience with modular smart accounts.
+- **Paymaster Service**: Enable third-party gas sponsorship.
+- **Bundler Infrastructure**: Ensure efficient and reliable transaction bundling.
-```typescript
-// This is how you create BiconomySmartAccount instance in your dapp's
+For a step-by-step guide on integrating **ERC4337 Account Abstraction** and **Smart Accounts** into your dApp using the Biconomy SDK, refer to the [official documentation](https://docs.biconomy.io/docs/overview). You can also start with Quick start [here](https://docs.biconomy.io/quickstart).
-import { BiconomySmartAccount, BiconomySmartAccountConfig } from "@biconomy/account";
+## 📚 Resources
-// Note that paymaster and bundler are optional. You can choose to create new instances of this later and make account API use
-const biconomySmartAccountConfig: BiconomySmartAccountConfig = {
- signer: wallet.getSigner(),
- chainId: ChainId.POLYGON_MAINNET,
- rpcUrl: "",
- // paymaster: paymaster, // check the README.md section of Paymaster package
- // bundler: bundler, // check the README.md section of Bundler package
-};
+- [Biconomy Documentation](https://docs.biconomy.io/)
+- [Biconomy Dashboard](https://dashboard.biconomy.io)
+- [TSDoc](https://bcnmy.github.io/biconomy-client-sdk)
-const biconomyAccount = new BiconomySmartAccount(biconomySmartAccountConfig);
-const biconomySmartAccount = await biconomyAccount.init();
+## 💼 Example Usages
-// native token transfer
-// you can create any sort of transaction following same structure
-const transaction = {
- to: "0x85B51B068bF0fefFEFD817882a14f6F5BDF7fF2E",
- data: "0x",
- value: ethers.utils.parseEther("0.1"),
-};
+### [Initialise the smartAccount](https://bcnmy.github.io/biconomy-client-sdk/functions/createSmartAccountClient.html)
-// building partialUserOp
-const partialUserOp = await biconomySmartAccount.buildUserOp([transaction]);
+| Key | Description |
+| -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
+| [signer](https://bcnmy.github.io/biconomy-client-sdk/packages/account/docs/interfaces/SmartAccountSigner.html) | This signer will be used for signing userOps for any transactions you build. Will accept ethers.JsonRpcSigner as well as a viemWallet |
+| [biconomyPaymasterApiKey](https://dashboard.biconomy.io) | You can pass in a biconomyPaymasterApiKey necessary for sponsoring transactions (retrieved from the biconomy dashboard) |
+| [bundlerUrl](https://dashboard.biconomy.io) | You can pass in a bundlerUrl (retrieved from the biconomy dashboard) for sending transactions |
-// using the paymaster package one can populate paymasterAndData to partial userOp. by default it is '0x'
+```typescript
+import { createSmartAccountClient } from "@biconomy/account";
+import { createWalletClient, http, createPublicClient } from "viem";
+import { privateKeyToAccount, generatePrivateKey } from "viem/accounts";
+import { mainnet as chain } from "viem/chains";
+
+const account = privateKeyToAccount(generatePrivateKey());
+const signer = createWalletClient({ account, chain, transport: http() });
+
+const smartAccount = await createSmartAccountClient({
+ signer,
+ bundlerUrl,
+ biconomyPaymasterApiKey,
+});
```
+### [Send some ETH, have gas sponsored](https://bcnmy.github.io/biconomy-client-sdk/classes/BiconomySmartAccountV2.html#sendTransaction)
+
+| Key | Description |
+| --------------------------------------------------------------------------------- | -------------------------------------------------------------- |
+| [oneOrManyTx](https://bcnmy.github.io/biconomy-client-sdk/types/Transaction.html) | Submit multiple or one transactions |
+| [userOpReceipt](https://bcnmy.github.io/biconomy-client-sdk/types/UserOpReceipt) | Returned information about your tx, receipts, userOpHashes etc |
+
```typescript
-const userOpResponse = await smartAccount.sendUserOp(partialUserOp);
-const transactionDetails = await userOpResponse.wait();
-console.log("transaction details below");
-console.log(transactionDetails);
+const oneOrManyTx = { to: "0x...", value: 1 };
+
+const { wait } = await smartAccount.sendTransaction(oneOrManyTx, {
+ mode: PaymasterMode.SPONSORED,
+});
+
+const {
+ receipt: { transactionHash },
+ userOpHash,
+} = await wait();
```
-Finally we send the userOp and save the value to a variable named userOpResponse and get the transactionDetails after calling `typescript userOpResponse.wait()`
+### [Mint two NFTs, pay gas with token](https://bcnmy.github.io/biconomy-client-sdk/classes/BiconomySmartAccountV2.html#getTokenFees)
+
+| Key | Description |
+| -------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
+| [buildUseropDto](https://bcnmy.github.io/biconomy-client-sdk/types/BuildUserOpOptions.html) | Options for building a userOp |
+| [paymasterServiceData](https://bcnmy.github.io/biconomy-client-sdk/types/PaymasterUserOperationDto.html) | PaymasterOptions set in the buildUseropDto |
```typescript
-const transactionDetails = await userOpResponse.wait();
-console.log("transaction details below");
-console.log(transactionDetails);
+import { encodeFunctionData, parseAbi } from "viem";
+
+const encodedCall = encodeFunctionData({
+ abi: parseAbi(["function safeMint(address to) public"]),
+ functionName: "safeMint",
+ args: ["0x..."],
+});
+
+const tx = {
+ to: nftAddress,
+ data: encodedCall,
+};
+const oneOrManyTx = [tx, tx]; // Mint twice
+const paymasterServiceData = {
+ mode: PaymasterMode.ERC20,
+ preferredToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
+};
+const buildUseropDto = { paymasterServiceData };
+
+const { wait } = await smartAccount.sendTransaction(oneOrManyTx, buildUseropDto);
+
+const {
+ receipt: { transactionHash },
+ userOpHash,
+} = await wait();
```
-#### BiconomySmartAccount (V2 Smart Account aka Modular Smart Account)
+## 🤝 Contributing
+
+Community contributions are welcome! For guidelines on contributing, please read our [contribution guidelines](./CONTRIBUTING.md).
+
+## 📜 License
+This project is licensed under the MIT License. See the [LICENSE.md](./LICENSE.md) file for details.
diff --git a/packages/account/package.json b/packages/account/package.json
index 1dfa29482..07844719d 100644
--- a/packages/account/package.json
+++ b/packages/account/package.json
@@ -1,9 +1,19 @@
{
"name": "@biconomy/account",
- "version": "3.1.3",
+ "version": "4.0.0",
"description": "This package provides apis for ERC-4337 based smart account implementations",
- "main": "./dist/src/index.js",
- "typings": "./dist/src/index.d.ts",
+ "main": "./dist/cjs/index.js",
+ "module": "./dist/esm/index.js",
+ "types": "./dist/types/index.d.ts",
+ "typings": "./dist/types/index.d.ts",
+ "exports": {
+ ".": {
+ "import": "./dist/esm/index.js",
+ "types": "./dist/types/index.d.ts",
+ "default": "./dist/cjs/index.js"
+ },
+ "./package.json": "./package.json"
+ },
"keywords": [
"Ethereum",
"Smart Account",
@@ -14,17 +24,28 @@
"SDK"
],
"scripts": {
+ "docs": "typedoc",
"unbuild": "rimraf dist *.tsbuildinfo",
- "build": "rimraf dist && tsc",
+ "build:watch": "yarn build:tsc --watch",
+ "dist:minify": "esbuild ./dist/esm/**/*.js ./dist/esm/*.js --minify --outdir=./dist/esm --bundle=false --allow-overwrite",
+ "build": "yarn unbuild && yarn build:tsc",
+ "build:esbuild": "yarn build:esbuild:cjs && yarn build:esbuild:esm && yarn build:typ",
+ "build:tsc:cjs": "tsc --project tsconfig.build.json --module commonjs --outDir ./dist/cjs --removeComments --verbatimModuleSyntax false && echo > ./dist/cjs/package.json '{\"type\":\"commonjs\"}'",
+ "build:tsc:esm": "tsc --project tsconfig.build.json --module esnext --outDir ./dist/esm --removeComments && echo > ./dist/esm/package.json '{\"type\":\"module\"}'",
+ "build:esbuild:cjs": "node .esbuild.js CJS && echo > ./dist/cjs/package.json '{\"type\":\"commonjs\"}'",
+ "build:esbuild:esm": "node .esbuild.js ESM && echo > ./dist/esm/package.json '{\"type\":\"module\"}'",
+ "build:typ": "tsc --project tsconfig.build.json --module esnext --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap",
+ "build:tsc": "yarn build:tsc:cjs && yarn build:tsc:esm && yarn build:typ && yarn dist:minify",
"test:concurrently": "concurrently -k --success first 'yarn start:ganache' 'yarn test'",
"test:cov": "jest --coverage",
"test": "jest tests/**/*.spec.ts --runInBand",
"test:run": "yarn test:concurrently",
"start:ganache": "ganache -m 'direct buyer cliff train rice spirit census refuse glare expire innocent quote'",
"format": "prettier --write \"{src,tests}/**/*.ts\"",
- "lint": "tslint -p tsconfig.json"
+ "lint": "tslint -p tsconfig.json",
+ "docs:deploy": "yarn docs && gh-pages -d docs"
},
- "author": "talhamalik883 ",
+ "author": "Biconomy",
"license": "MIT",
"files": [
"dist/*",
@@ -34,24 +55,21 @@
"access": "public"
},
"devDependencies": {
+ "@types/node": "^20.11.10",
+ "esbuild": "^0.19.11",
+ "esbuild-plugin-tsc": "^0.4.0",
+ "gh-pages": "^6.1.1",
"nock": "^13.2.9",
- "viem": "^1.19.11"
+ "npm-dts": "^1.3.12",
+ "typedoc": "^0.25.7",
+ "lru-cache": "^10.0.1"
},
"dependencies": {
- "@account-abstraction/contracts": "^0.6.0",
- "@account-abstraction/utils": "^0.4.0",
- "@alchemy/aa-core": "^1.2.2",
- "@biconomy-devx/account-contracts-v2": "npm:@biconomy-devx/account-contracts-v2@^1.0.0",
- "@biconomy/bundler": "^3.1.3",
- "@biconomy/common": "^3.1.3",
- "@biconomy/core-types": "^3.1.3",
- "@biconomy/modules": "^3.1.3",
- "@biconomy/node-client": "^3.1.3",
- "@biconomy/paymaster": "^3.1.3",
- "@ethersproject/logger": "^5.7.0",
- "@ethersproject/providers": "^5.7.2",
- "ethers": "^5.7.0",
- "loglevel": "^1.8.1",
- "lru-cache": "^10.0.1"
+ "@alchemy/aa-core": "3.0.0-alpha.4",
+ "@biconomy/bundler": "4.0.0",
+ "@biconomy/common": "4.0.0",
+ "@biconomy/modules": "4.0.0",
+ "@biconomy/paymaster": "4.0.0",
+ "viem": "^2.7.3"
}
}
diff --git a/packages/account/src/BaseSmartAccount.ts b/packages/account/src/BaseSmartAccount.ts
deleted file mode 100644
index 90bb0ecff..000000000
--- a/packages/account/src/BaseSmartAccount.ts
+++ /dev/null
@@ -1,481 +0,0 @@
-import { JsonRpcProvider, Provider } from "@ethersproject/providers";
-import { BigNumber, BigNumberish, BytesLike, ethers, Bytes } from "ethers";
-import { IBaseSmartAccount } from "./interfaces/IBaseSmartAccount";
-import { defaultAbiCoder, keccak256 } from "ethers/lib/utils";
-import { UserOperation, ChainId } from "@biconomy/core-types";
-import { calcPreVerificationGas, DefaultGasLimits } from "./utils/Preverificaiton";
-import { NotPromise, packUserOp, Logger, RPC_PROVIDER_URLS, isNullOrUndefined } from "@biconomy/common";
-import { Bundler, IBundler, UserOpResponse } from "@biconomy/bundler";
-import { IPaymaster, PaymasterAndDataResponse } from "@biconomy/paymaster";
-import { SendUserOpParams } from "@biconomy/modules";
-import { SponsorUserOperationDto, BiconomyPaymaster, PaymasterMode, IHybridPaymaster } from "@biconomy/paymaster";
-import { BaseSmartAccountConfig, EstimateUserOpGasParams, TransactionDetailsForUserOp } from "./utils/Types";
-import { GasOverheads } from "./utils/Preverificaiton";
-import { EntryPoint, EntryPoint__factory } from "@account-abstraction/contracts";
-import { DEFAULT_ENTRYPOINT_ADDRESS, DefaultGasLimit } from "./utils/Constants";
-import { LRUCache } from "lru-cache";
-
-type UserOperationKey = keyof UserOperation;
-
-export abstract class BaseSmartAccount implements IBaseSmartAccount {
- bundler?: IBundler; // httpRpcClient
-
- paymaster?: IPaymaster; // paymasterAPI
-
- overheads?: Partial;
-
- entryPointAddress!: string;
-
- accountAddress?: string;
-
- // owner?: Signer // owner is not mandatory for some account implementations
- index: number;
-
- chainId?: ChainId;
-
- provider: Provider;
-
- // entryPoint connected to "zero" address. allowed to make static calls (e.g. to getSenderAddress)
- private readonly entryPoint!: EntryPoint;
-
- private isContractDeployedCache = new LRUCache({
- max: 500,
- });
-
- constructor(_smartAccountConfig: BaseSmartAccountConfig) {
- this.index = _smartAccountConfig.index ?? 0;
- this.overheads = _smartAccountConfig.overheads;
- this.entryPointAddress = _smartAccountConfig.entryPointAddress ?? DEFAULT_ENTRYPOINT_ADDRESS;
- this.accountAddress = _smartAccountConfig.accountAddress;
-
- this.chainId = _smartAccountConfig.chainId;
-
- if (_smartAccountConfig.bundlerUrl) {
- this.bundler = new Bundler({
- bundlerUrl: _smartAccountConfig.bundlerUrl,
- chainId: _smartAccountConfig.chainId,
- });
- } else {
- this.bundler = _smartAccountConfig.bundler;
- }
-
- if (_smartAccountConfig.paymaster) {
- this.paymaster = _smartAccountConfig.paymaster;
- }
-
- this.provider = _smartAccountConfig.provider ?? new JsonRpcProvider(RPC_PROVIDER_URLS[this.chainId]);
-
- // Create an instance of the EntryPoint contract using the provided address and provider (facory "connect" contract address)
- // Then, set the transaction's sender ("from" address) to the zero address (AddressZero). (contract "connect" from address)
- this.entryPoint = EntryPoint__factory.connect(this.entryPointAddress, this.provider).connect(ethers.constants.AddressZero);
- }
-
- async init(): Promise {
- if (this.entryPointAddress === DEFAULT_ENTRYPOINT_ADDRESS) return this;
- if ((await this.provider.getCode(this.entryPointAddress)) === "0x") {
- throw new Error(`EntryPoint not deployed at ${this.entryPointAddress} at chainId ${this.chainId}}`);
- }
- return this;
- }
-
- setEntryPointAddress(entryPointAddress: string): void {
- this.entryPointAddress = entryPointAddress;
- }
-
- validateUserOp(userOp: Partial, requiredFields: UserOperationKey[]): boolean {
- for (const field of requiredFields) {
- if (isNullOrUndefined(userOp[field])) {
- throw new Error(`${String(field)} is missing in the UserOp`);
- }
- }
- return true;
- }
-
- isProviderDefined(): boolean {
- if (!this.provider) throw new Error("Provider is undefined");
-
- return true;
- }
-
- /**
- * return the value to put into the "initCode" field, if the contract is not yet deployed.
- * this value holds the "factory" address, followed by this account's information
- */
- abstract getAccountInitCode(): Promise;
-
- /**
- * return current account's nonce.
- */
- abstract getNonce(): Promise;
-
- /**
- * encode the call from entryPoint through our account to the target contract.
- * @param to
- * @param value
- * @param data
- */
- abstract encodeExecute(_to: string, _value: BigNumberish, _data: BytesLike): Promise;
-
- /**
- * encode the batch call from entryPoint through our account to the target contract.
- * @param to
- * @param value
- * @param data
- */
- abstract encodeExecuteBatch(_to: Array, _value: Array, _data: Array): Promise;
-
- /**
- * sign a userOp's hash (userOpHash).
- * @param userOpHash
- */
- abstract signUserOpHash(_userOpHash: string): Promise;
-
- abstract signMessage(_message: Bytes | string): Promise;
-
- /**
- * get dummy signature for userOp
- */
- abstract getDummySignature(): Promise;
-
- /**
- * Sign the filled userOp.
- * @param userOp the UserOperation to sign (with signature field ignored)
- */
- async signUserOp(userOp: Partial): Promise {
- const requiredFields: UserOperationKey[] = [
- "sender",
- "nonce",
- "initCode",
- "callData",
- "callGasLimit",
- "verificationGasLimit",
- "preVerificationGas",
- "maxFeePerGas",
- "maxPriorityFeePerGas",
- "paymasterAndData",
- ];
- this.validateUserOp(userOp, requiredFields);
- const userOpHash = await this.getUserOpHash(userOp);
- let signature = await this.signUserOpHash(userOpHash);
-
- // Some signers do not return signed data with 0x prefix. make sure the v value is 27/28 instead of 0/1
- // Also split sig and add +27 to v is v is only 0/1. then stitch it back
-
- // Note: Should only be applied for ECDSA k1 signatures
- const potentiallyIncorrectV = parseInt(signature.slice(-2), 16);
- if (![27, 28].includes(potentiallyIncorrectV)) {
- const correctV = potentiallyIncorrectV + 27;
- signature = signature.slice(0, -2) + correctV.toString(16);
- }
- if (signature.slice(0, 2) !== "0x") {
- signature = "0x" + signature;
- }
-
- userOp.signature = signature;
- return userOp as UserOperation;
- }
-
- /**
- *
- * @param userOp
- * @description This function call will take 'unsignedUserOp' as an input, sign it with the owner key, and send it to the bundler.
- * @returns Promise
- */
- async sendUserOp(userOp: Partial): Promise {
- Logger.log("userOp received in base account ", userOp);
- delete userOp.signature;
- const userOperation = await this.signUserOp(userOp);
- const bundlerResponse = await this.sendSignedUserOp(userOperation);
- return bundlerResponse;
- }
-
- /**
- *
- * @param userOp
- * @description This function call will take 'signedUserOp' as input and send it to the bundler
- * @returns
- */
- async sendSignedUserOp(userOp: UserOperation, params?: SendUserOpParams): Promise {
- const requiredFields: UserOperationKey[] = [
- "sender",
- "nonce",
- "initCode",
- "callData",
- "callGasLimit",
- "verificationGasLimit",
- "preVerificationGas",
- "maxFeePerGas",
- "maxPriorityFeePerGas",
- "paymasterAndData",
- "signature",
- ];
- this.validateUserOp(userOp, requiredFields);
- Logger.log("userOp validated");
- if (!this.bundler) throw new Error("Bundler is not provided");
- Logger.log("userOp being sent to the bundler", userOp);
- const bundlerResponse = await this.bundler.sendUserOp(userOp, params?.simulationType);
- return bundlerResponse;
- }
-
- async calculateUserOpGasValues(userOp: Partial): Promise> {
- if (!this.provider) throw new Error("Provider is not present for making rpc calls");
- let feeData = null;
-
- if (
- userOp.maxFeePerGas === undefined ||
- userOp.maxFeePerGas === null ||
- userOp.maxPriorityFeePerGas === undefined ||
- userOp.maxPriorityFeePerGas === null
- ) {
- feeData = await this.provider.getFeeData();
- }
-
- if (userOp.maxFeePerGas === undefined || userOp.maxFeePerGas === null) {
- userOp.maxFeePerGas = feeData?.maxFeePerGas ?? feeData?.gasPrice ?? (await this.provider.getGasPrice());
- }
-
- if (userOp.maxPriorityFeePerGas === undefined || userOp.maxPriorityFeePerGas === null) {
- userOp.maxPriorityFeePerGas = feeData?.maxPriorityFeePerGas ?? feeData?.gasPrice ?? (await this.provider.getGasPrice());
- }
- if (userOp.initCode) userOp.verificationGasLimit = userOp.verificationGasLimit ?? (await this.getVerificationGasLimit(userOp.initCode));
- userOp.callGasLimit =
- userOp.callGasLimit ??
- (await this.provider.estimateGas({
- from: this.entryPointAddress,
- to: userOp.sender,
- data: userOp.callData,
- }));
- userOp.preVerificationGas = userOp.preVerificationGas ?? (await this.getPreVerificationGas(userOp));
- return userOp;
- }
-
- async estimateUserOpGas(params: EstimateUserOpGasParams): Promise> {
- let userOp = params.userOp;
- const { overrides, skipBundlerGasEstimation, paymasterServiceData } = params;
- const requiredFields: UserOperationKey[] = ["sender", "nonce", "initCode", "callData"];
- this.validateUserOp(userOp, requiredFields);
-
- let finalUserOp = userOp;
- const skipBundlerCall = skipBundlerGasEstimation ?? true;
- // Override gas values in userOp if provided in overrides params
- if (overrides) {
- userOp = { ...userOp, ...overrides };
- }
-
- Logger.log("userOp in estimation", userOp);
-
- if (skipBundlerCall) {
- if (this.paymaster && this.paymaster instanceof BiconomyPaymaster) {
- if (isNullOrUndefined(userOp.maxFeePerGas) || isNullOrUndefined(userOp.maxPriorityFeePerGas)) {
- throw new Error("maxFeePerGas and maxPriorityFeePerGas are required for skipBundlerCall mode");
- }
- if (paymasterServiceData?.mode === PaymasterMode.SPONSORED) {
- // Making call to paymaster to get gas estimations for userOp
- const { callGasLimit, verificationGasLimit, preVerificationGas, paymasterAndData } = await (
- this.paymaster as IHybridPaymaster
- ).getPaymasterAndData(userOp, paymasterServiceData);
- if (paymasterAndData === "0x" && (callGasLimit === undefined || verificationGasLimit === undefined || preVerificationGas === undefined)) {
- throw new Error("Since you intend to use sponsorship paymaster, please check and make sure policies are set on the dashboard");
- }
- finalUserOp.verificationGasLimit = verificationGasLimit ?? userOp.verificationGasLimit;
- finalUserOp.callGasLimit = callGasLimit ?? userOp.callGasLimit;
- finalUserOp.preVerificationGas = preVerificationGas ?? userOp.preVerificationGas;
- finalUserOp.paymasterAndData = paymasterAndData ?? userOp.paymasterAndData;
- } else {
- // use dummy values for gas limits as fee quote call will ignore this later.
- finalUserOp.callGasLimit = DefaultGasLimit.callGasLimit;
- finalUserOp.verificationGasLimit = DefaultGasLimit.verificationGasLimit;
- finalUserOp.preVerificationGas = DefaultGasLimit.preVerificationGas;
- }
- } else {
- {
- Logger.warn("Skipped paymaster call. If you are using paymasterAndData, generate data externally");
- finalUserOp = await this.calculateUserOpGasValues(userOp);
- finalUserOp.paymasterAndData = "0x";
- }
- }
- } else {
- if (!this.bundler) throw new Error("Bundler is not provided");
- delete userOp.maxFeePerGas;
- delete userOp.maxPriorityFeePerGas;
- // Making call to bundler to get gas estimations for userOp
- const { callGasLimit, verificationGasLimit, preVerificationGas, maxFeePerGas, maxPriorityFeePerGas } =
- await this.bundler.estimateUserOpGas(userOp);
- // if neither user sent gas fee nor the bundler, estimate gas from provider
- if (
- isNullOrUndefined(userOp.maxFeePerGas) &&
- isNullOrUndefined(userOp.maxPriorityFeePerGas) &&
- (isNullOrUndefined(maxFeePerGas) || isNullOrUndefined(maxPriorityFeePerGas))
- ) {
- const feeData = await this.provider.getFeeData();
- finalUserOp.maxFeePerGas = feeData.maxFeePerGas ?? feeData.gasPrice ?? (await this.provider.getGasPrice());
- finalUserOp.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? feeData.gasPrice ?? (await this.provider.getGasPrice());
- } else {
- finalUserOp.maxFeePerGas = maxFeePerGas ?? userOp.maxFeePerGas;
- finalUserOp.maxPriorityFeePerGas = maxPriorityFeePerGas ?? userOp.maxPriorityFeePerGas;
- }
- finalUserOp.verificationGasLimit = verificationGasLimit ?? userOp.verificationGasLimit;
- finalUserOp.callGasLimit = callGasLimit ?? userOp.callGasLimit;
- finalUserOp.preVerificationGas = preVerificationGas ?? userOp.preVerificationGas;
- finalUserOp.paymasterAndData = "0x";
- }
- return finalUserOp;
- }
-
- // Would only be used if paymaster is attached
- async getPaymasterAndData(userOp: Partial): Promise {
- if (this.paymaster) {
- const paymasterAndDataResponse: PaymasterAndDataResponse = await this.paymaster.getPaymasterAndData(userOp);
- return paymasterAndDataResponse.paymasterAndData;
- }
- return "0x";
- }
-
- async isAccountDeployed(address: string): Promise {
- if (this.isContractDeployedCache.get(address)) {
- return true;
- }
-
- this.isProviderDefined();
- let isDeployed = false;
- const contractCode = await this.provider.getCode(address);
- if (contractCode.length > 2) {
- this.isContractDeployedCache.set(address, true);
- isDeployed = true;
- } else {
- isDeployed = false;
- }
- return isDeployed;
- }
-
- /**
- * calculate the account address even before it is deployed
- */
- async getCounterFactualAddress(): Promise {
- const initCode = this.getAccountInitCode();
- // use entryPoint to query account address (factory can provide a helper method to do the same, but
- // this method attempts to be generic
- try {
- await this.entryPoint.callStatic.getSenderAddress(initCode);
- } catch (e: any) {
- if (e.errorArgs == null) {
- throw e;
- }
- return e.errorArgs.sender;
- }
- throw new Error("must handle revert");
- }
-
- /**
- * return initCode value to into the UserOp.
- * (either deployment code, or empty hex if contract already deployed)
- */
- async getInitCode(): Promise {
- if (await this.isAccountDeployed(await this.getAccountAddress())) {
- return "0x";
- }
- return this.getAccountInitCode();
- }
-
- async getPreVerificationGas(userOp: Partial): Promise {
- return calcPreVerificationGas(userOp);
- }
-
- async getVerificationGasLimit(initCode: BytesLike): Promise {
- // Verification gas should be max(initGas(wallet deployment) + validateUserOp + validatePaymasterUserOp , postOp)
-
- const initGas = await this.estimateCreationGas(initCode as string);
- const validateUserOpGas = BigNumber.from(DefaultGasLimits.validatePaymasterUserOpGas + DefaultGasLimits.validateUserOpGas);
- const postOpGas = BigNumber.from(DefaultGasLimits.postOpGas);
-
- let verificationGasLimit = BigNumber.from(validateUserOpGas).add(initGas);
-
- if (BigNumber.from(postOpGas).gt(verificationGasLimit)) {
- verificationGasLimit = postOpGas;
- }
- return verificationGasLimit;
- }
-
- /**
- * return the account's address.
- * this value is valid even before deploying the contract.
- */
- async getAccountAddress(): Promise {
- if (this.accountAddress == null) {
- // means it needs deployment
- this.accountAddress = await this.getCounterFactualAddress();
- }
- return this.accountAddress;
- }
-
- async estimateCreationGas(initCode?: string): Promise {
- if (initCode == null || initCode === "0x") return 0;
- const deployerAddress = initCode.substring(0, 42);
- const deployerCallData = "0x" + initCode.substring(42);
- return this.provider.estimateGas({ to: deployerAddress, data: deployerCallData });
- }
-
- /**
- * get the transaction that has this userOpHash mined, or null if not found
- * @param userOpHash returned by sendUserOpToBundler (or by getUserOpHash..)
- * @param timeout stop waiting after this timeout
- * @param interval time to wait between polls.
- * @return the transactionHash this userOp was mined, or null if not found.
- */
- async getUserOpReceipt(userOpHash: string, timeout = 30000, interval = 5000): Promise {
- const endtime = Date.now() + timeout;
- while (Date.now() < endtime) {
- const events = await this.entryPoint.queryFilter(this.entryPoint.filters.UserOperationEvent(userOpHash));
- if (events.length > 0) {
- return events[0].transactionHash;
- }
- await new Promise((resolve) => setTimeout(resolve, interval));
- }
- return null;
- }
-
- async getUserOpHash(userOp: Partial): Promise {
- const userOpHash = keccak256(packUserOp(userOp, true));
- const enc = defaultAbiCoder.encode(["bytes32", "address", "uint256"], [userOpHash, this.entryPoint.address, this.chainId]);
- return keccak256(enc);
- }
-
- /**
- * ABI-encode a user operation. used for calldata cost estimation
- */
- packUserOp(userOp: NotPromise): string {
- return packUserOp(userOp, false);
- }
-
- async encodeUserOpCallDataAndGasLimit(detailsForUserOp: TransactionDetailsForUserOp): Promise<{ callData: string; callGasLimit: BigNumber }> {
- function parseNumber(a: any): BigNumber | null {
- if (a == null || a === "") return null;
- return BigNumber.from(a.toString());
- }
-
- const value = parseNumber(detailsForUserOp.value) ?? BigNumber.from(0);
- const callData = await this.encodeExecute(detailsForUserOp.target, value, detailsForUserOp.data);
-
- const callGasLimit =
- parseNumber(detailsForUserOp.gasLimit) ??
- (await this.provider.estimateGas({
- from: this.entryPointAddress,
- to: this.getAccountAddress(),
- data: callData,
- }));
-
- return {
- callData,
- callGasLimit,
- };
- }
-
- /**
- * helper method: create and sign a user operation.
- * @param info transaction details for the userOp
- */
- async createSignedUserOp(info: TransactionDetailsForUserOp): Promise {
- Logger.log("createSignedUserOp called with info", info);
- throw new Error("Not implemented. Please use buildUserOp/buildUserOperation in account implementation");
- }
-}
diff --git a/packages/account/src/BiconomySmartAccount.ts b/packages/account/src/BiconomySmartAccount.ts
deleted file mode 100644
index 3307856b7..000000000
--- a/packages/account/src/BiconomySmartAccount.ts
+++ /dev/null
@@ -1,544 +0,0 @@
-import { JsonRpcProvider } from "@ethersproject/providers";
-import { ethers, BigNumberish, BytesLike, BigNumber } from "ethers";
-import { SmartAccount } from "./SmartAccount";
-import {
- Logger,
- NODE_CLIENT_URL,
- RPC_PROVIDER_URLS,
- SmartAccountFactory_v100,
- SmartAccount_v200,
- getEntryPointContract,
- getSAFactoryContract,
- getSAProxyContract,
- isNullOrUndefined,
-} from "@biconomy/common";
-import { BiconomySmartAccountConfig, Overrides, BiconomyTokenPaymasterRequest, InitilizationData } from "./utils/Types";
-import { UserOperation, Transaction, SmartAccountType } from "@biconomy/core-types";
-import NodeClient from "@biconomy/node-client";
-import INodeClient from "@biconomy/node-client";
-import { IHybridPaymaster, BiconomyPaymaster, SponsorUserOperationDto } from "@biconomy/paymaster";
-import { DEFAULT_ECDSA_OWNERSHIP_MODULE, ECDSAOwnershipValidationModule } from "@biconomy/modules";
-import { IBiconomySmartAccount } from "./interfaces/IBiconomySmartAccount";
-import {
- ISmartAccount,
- SupportedChainsResponse,
- BalancesResponse,
- BalancesDto,
- UsdBalanceResponse,
- SmartAccountByOwnerDto,
- SmartAccountsResponse,
- SCWTransactionResponse,
-} from "@biconomy/node-client";
-import {
- ENTRYPOINT_ADDRESSES,
- BICONOMY_FACTORY_ADDRESSES,
- BICONOMY_IMPLEMENTATION_ADDRESSES,
- DEFAULT_ENTRYPOINT_ADDRESS,
- DEFAULT_BICONOMY_IMPLEMENTATION_ADDRESS,
- BICONOMY_IMPLEMENTATION_ADDRESSES_BY_VERSION,
-} from "./utils/Constants";
-import { Signer } from "ethers";
-
-export class BiconomySmartAccount extends SmartAccount implements IBiconomySmartAccount {
- private factory!: SmartAccountFactory_v100;
-
- private nodeClient: INodeClient;
-
- private accountIndex!: number;
-
- private address!: string;
-
- private smartAccountInfo!: ISmartAccount;
-
- private _isInitialised!: boolean;
-
- constructor(readonly biconomySmartAccountConfig: BiconomySmartAccountConfig) {
- const { signer, rpcUrl, entryPointAddress, bundler, paymaster, chainId, nodeClientUrl } = biconomySmartAccountConfig;
-
- const _entryPointAddress = entryPointAddress ?? DEFAULT_ENTRYPOINT_ADDRESS;
- super({
- bundler,
- entryPointAddress: _entryPointAddress,
- });
- const _rpcUrl = rpcUrl ?? RPC_PROVIDER_URLS[chainId];
-
- if (!_rpcUrl) {
- throw new Error(
- `Chain Id ${chainId} is not supported. Please refer to the following link for supported chains list https://docs.biconomy.io/build-with-biconomy-sdk/gasless-transactions#supported-chains`,
- );
- }
- this.provider = new JsonRpcProvider(_rpcUrl);
- this.nodeClient = new NodeClient({ txServiceUrl: nodeClientUrl ?? NODE_CLIENT_URL });
- this.signer = signer;
-
- if (paymaster) {
- this.paymaster = paymaster;
- }
- if (bundler) this.bundler = bundler;
- }
-
- /**
- * @description This function will initialise BiconomyAccount class state
- * @returns Promise
- */
- async init(initilizationData?: InitilizationData): Promise {
- try {
- let _accountIndex, signerAddress;
- if (initilizationData) {
- _accountIndex = initilizationData.accountIndex;
- signerAddress = initilizationData.signerAddress;
- }
-
- if (!_accountIndex) _accountIndex = 0;
- this.isProviderDefined();
- this.isSignerDefined();
-
- if (signerAddress) {
- this.owner = signerAddress;
- } else {
- this.owner = await this.signer.getAddress();
- }
- this.chainId = await this.provider.getNetwork().then((net) => net.chainId);
- await this.initializeAccountAtIndex(_accountIndex);
- this._isInitialised = true;
- } catch (error) {
- Logger.error(`Failed to call init: ${error}`);
- throw error;
- }
-
- return this;
- }
-
- async attachSigner(_signer: Signer): Promise {
- try {
- this.signer = _signer;
- this.owner = await this.signer.getAddress();
- } catch (error) {
- throw new Error(`Failed to get signer address`);
- }
- }
-
- private isInitialized(): boolean {
- if (!this._isInitialised)
- throw new Error(
- "BiconomySmartAccount is not initialized. Please call init() on BiconomySmartAccount instance before interacting with any other function",
- );
- return true;
- }
-
- private setProxyContractState(): void {
- if (!BICONOMY_IMPLEMENTATION_ADDRESSES[this.smartAccountInfo.implementationAddress])
- throw new Error(
- "Could not find attached implementation address against your smart account. Please raise an issue on https://github.com/bcnmy/biconomy-client-sdk for further investigation.",
- );
- const proxyInstanceDto = {
- smartAccountType: SmartAccountType.BICONOMY,
- version: BICONOMY_IMPLEMENTATION_ADDRESSES[this.smartAccountInfo.implementationAddress],
- contractAddress: this.address,
- provider: this.provider,
- };
- this.proxy = getSAProxyContract(proxyInstanceDto);
- }
-
- private setEntryPointContractState(): void {
- const _entryPointAddress = this.smartAccountInfo.entryPointAddress;
- this.setEntryPointAddress(_entryPointAddress);
- if (!ENTRYPOINT_ADDRESSES[_entryPointAddress])
- throw new Error(
- "Could not find attached entrypoint address against your smart account. Please raise an issue on https://github.com/bcnmy/biconomy-client-sdk for further investigation.",
- );
- const entryPointInstanceDto = {
- smartAccountType: SmartAccountType.BICONOMY,
- version: ENTRYPOINT_ADDRESSES[_entryPointAddress],
- contractAddress: _entryPointAddress,
- provider: this.provider,
- };
- this.entryPoint = getEntryPointContract(entryPointInstanceDto);
- }
-
- private setFactoryContractState(): void {
- const _factoryAddress = this.smartAccountInfo.factoryAddress;
- if (!BICONOMY_FACTORY_ADDRESSES[_factoryAddress])
- throw new Error(
- "Could not find attached factory address against your smart account. Please raise an issue on https://github.com/bcnmy/biconomy-client-sdk for further investigation.",
- );
- const factoryInstanceDto = {
- smartAccountType: SmartAccountType.BICONOMY,
- version: BICONOMY_FACTORY_ADDRESSES[_factoryAddress],
- contractAddress: _factoryAddress,
- provider: this.provider,
- };
- this.factory = getSAFactoryContract(factoryInstanceDto) as SmartAccountFactory_v100;
- }
-
- private async setContractsState(): Promise {
- this.setProxyContractState();
- this.setEntryPointContractState();
- this.setFactoryContractState();
- }
-
- async initializeAccountAtIndex(accountIndex: number): Promise {
- this.accountIndex = accountIndex;
- this.address = await this.getSmartAccountAddress(accountIndex);
- await this.setContractsState();
- await this.setInitCode(this.accountIndex);
- }
-
- async getSmartAccountAddress(accountIndex = 0): Promise {
- try {
- this.isSignerDefined();
- let smartAccountsList: ISmartAccount[] = (
- await this.getSmartAccountsByOwner({
- chainId: this.chainId,
- owner: this.owner,
- index: accountIndex,
- })
- ).data;
- if (!smartAccountsList)
- throw new Error(
- "Failed to get smart account address. Please raise an issue on https://github.com/bcnmy/biconomy-client-sdk for further investigation.",
- );
- smartAccountsList = smartAccountsList.filter((smartAccount: ISmartAccount) => {
- return accountIndex === smartAccount.index;
- });
- if (smartAccountsList.length === 0)
- throw new Error(
- "Failed to get smart account address. Please raise an issue on https://github.com/bcnmy/biconomy-client-sdk for further investigation.",
- );
- this.smartAccountInfo = smartAccountsList[0];
- return this.smartAccountInfo.smartAccountAddress;
- } catch (error) {
- Logger.error(`Failed to get smart account address: ${error}`);
- throw error;
- }
- }
-
- private async setInitCode(accountIndex = 0): Promise {
- this.initCode = ethers.utils.hexConcat([
- this.factory.address,
- this.factory.interface.encodeFunctionData("deployCounterFactualAccount", [this.owner, ethers.BigNumber.from(accountIndex)]),
- ]);
- return this.initCode;
- }
-
- /**
- * @description an overrided function to showcase overriding example
- * @returns
- */
- nonce(): Promise {
- this.isProxyDefined();
- return this.proxy.nonce();
- }
-
- /**
- *
- * @param to { target } address of transaction
- * @param value represents amount of native tokens
- * @param data represent data associated with transaction
- * @returns
- */
- getExecuteCallData(to: string, value: BigNumberish, data: BytesLike): string {
- this.isInitialized();
- this.isProxyDefined();
- const executeCallData = this.proxy.interface.encodeFunctionData("executeCall", [to, value, data]);
- return executeCallData;
- }
-
- /**
- *
- * @param to { target } array of addresses in transaction
- * @param value represents array of amount of native tokens associated with each transaction
- * @param data represent array of data associated with each transaction
- * @returns
- */
- getExecuteBatchCallData(to: Array, value: Array, data: Array): string {
- this.isInitialized();
- this.isProxyDefined();
- const executeBatchCallData = this.proxy.interface.encodeFunctionData("executeBatchCall", [to, value, data]);
- return executeBatchCallData;
- }
-
- getDummySignature(): string {
- return "0x73c3ac716c487ca34bb858247b5ccf1dc354fbaabdd089af3b2ac8e78ba85a4959a2d76250325bd67c11771c31fccda87c33ceec17cc0de912690521bb95ffcb1b";
- }
-
- getDummyPaymasterData(): string {
- return "0x";
- }
-
- private async getNonce(): Promise {
- let nonce = BigNumber.from(0);
- try {
- nonce = await this.nonce();
- } catch (error) {
- // Not throwing this error as nonce would be 0 if this.nonce() throw exception, which is expected flow for undeployed account
- Logger.log("Error while getting nonce for the account. This is expected for undeployed accounts set nonce to 0");
- }
- return nonce;
- }
-
- private async getGasFeeValues(
- overrides: Overrides | undefined,
- skipBundlerGasEstimation: boolean | undefined,
- ): Promise<{ maxFeePerGas?: BigNumberish | undefined; maxPriorityFeePerGas?: BigNumberish | undefined }> {
- const gasFeeValues = {
- maxFeePerGas: overrides?.maxFeePerGas,
- maxPriorityFeePerGas: overrides?.maxPriorityFeePerGas,
- };
- try {
- if (this.bundler && !gasFeeValues.maxFeePerGas && !gasFeeValues.maxPriorityFeePerGas && (skipBundlerGasEstimation ?? true)) {
- const gasFeeEstimation = await this.bundler.getGasFeeValues();
- gasFeeValues.maxFeePerGas = gasFeeEstimation.maxFeePerGas;
- gasFeeValues.maxPriorityFeePerGas = gasFeeEstimation.maxPriorityFeePerGas;
- }
- return gasFeeValues;
- } catch (error: any) {
- Logger.error("Error while getting gasFeeValues from bundler. Provided bundler might not have getGasFeeValues endpoint", error);
- return gasFeeValues;
- }
- }
-
- async buildUserOp(
- transactions: Transaction[],
- overrides?: Overrides,
- skipBundlerGasEstimation?: boolean,
- paymasterServiceData?: SponsorUserOperationDto,
- ): Promise> {
- this.isInitialized();
- const to = transactions.map((element: Transaction) => element.to);
- const data = transactions.map((element: Transaction) => element.data ?? "0x");
- const value = transactions.map((element: Transaction) => element.value ?? BigNumber.from("0"));
- this.isProxyDefined();
-
- const [nonce, gasFeeValues] = await Promise.all([this.getNonce(), this.getGasFeeValues(overrides, skipBundlerGasEstimation)]);
-
- let callData = "";
- if (transactions.length === 1) {
- callData = this.getExecuteCallData(to[0], value[0], data[0]);
- } else {
- callData = this.getExecuteBatchCallData(to, value, data);
- }
-
- let isDeployed = true;
-
- if (nonce.eq(0)) {
- isDeployed = await this.isAccountDeployed(this.address);
- }
-
- let userOp: Partial = {
- sender: this.address,
- nonce,
- initCode: !isDeployed ? this.initCode : "0x",
- callData: callData,
- maxFeePerGas: gasFeeValues.maxFeePerGas || undefined,
- maxPriorityFeePerGas: gasFeeValues.maxPriorityFeePerGas || undefined,
- signature: this.getDummySignature(),
- };
-
- // Note: Can change the default behaviour of calling estimations using bundler/local
- userOp = await this.estimateUserOpGas({ userOp, overrides, skipBundlerGasEstimation, paymasterServiceData });
- userOp.paymasterAndData = userOp.paymasterAndData ?? "0x";
- Logger.log("UserOp after estimation ", userOp);
-
- return userOp;
- }
-
- private validateUserOpAndRequest(userOp: Partial, tokenPaymasterRequest: BiconomyTokenPaymasterRequest): void {
- if (isNullOrUndefined(userOp.callData)) {
- throw new Error("Userop callData cannot be undefined");
- }
-
- const feeTokenAddress = tokenPaymasterRequest?.feeQuote?.tokenAddress;
- Logger.log("requested fee token is ", feeTokenAddress);
-
- if (!feeTokenAddress || feeTokenAddress == ethers.constants.AddressZero) {
- throw new Error("Invalid or missing token address. Token address must be part of the feeQuote in tokenPaymasterRequest");
- }
-
- const spender = tokenPaymasterRequest?.spender;
- Logger.log("fee token approval to be checked and added for spender: ", spender);
-
- if (!spender || spender == ethers.constants.AddressZero) {
- throw new Error("Invalid or missing spender address. Sepnder address must be part of tokenPaymasterRequest");
- }
- }
-
- /**
- *
- * @param userOp partial user operation without signature and paymasterAndData
- * @param tokenPaymasterRequest This dto provides information about fee quote. Fee quote is received from earlier request getFeeQuotesOrData() to the Biconomy paymaster.
- * maxFee and token decimals from the quote, along with the spender is required to append approval transaction.
- * @notice This method should be called when gas is paid in ERC20 token using TokenPaymaster
- * @description Optional method to update the userOp.calldata with batched transaction which approves the paymaster spender with necessary amount(if required)
- * @returns updated userOp with new callData, callGasLimit
- */
- async buildTokenPaymasterUserOp(
- userOp: Partial,
- tokenPaymasterRequest: BiconomyTokenPaymasterRequest,
- ): Promise> {
- this.validateUserOpAndRequest(userOp, tokenPaymasterRequest);
- try {
- let batchTo: Array = [];
- let batchValue: Array = [];
- let batchData: Array = [];
-
- let newCallData = userOp.callData;
- Logger.log("received information about fee token address and quote ", tokenPaymasterRequest);
-
- if (this.paymaster && this.paymaster instanceof BiconomyPaymaster) {
- // Make a call to paymaster.buildTokenApprovalTransaction() with necessary details
-
- // Review: might request this form of an array of Transaction
- const approvalRequest: Transaction = await (this.paymaster as IHybridPaymaster).buildTokenApprovalTransaction(
- tokenPaymasterRequest,
- this.provider,
- );
- Logger.log("approvalRequest is for erc20 token ", approvalRequest.to);
-
- if (approvalRequest.data == "0x" || approvalRequest.to == ethers.constants.AddressZero) {
- return userOp;
- }
-
- if (isNullOrUndefined(userOp.callData)) {
- throw new Error("Userop callData cannot be undefined");
- }
-
- const decodedDataSmartWallet = this.proxy.interface.parseTransaction({
- data: userOp.callData!.toString(),
- });
- if (!decodedDataSmartWallet) {
- throw new Error("Could not parse call data of smart wallet for userOp");
- }
-
- const smartWalletExecFunctionName = decodedDataSmartWallet.name;
-
- if (smartWalletExecFunctionName === "executeCall") {
- Logger.log("originally an executeCall for Biconomy Account");
- const methodArgsSmartWalletExecuteCall = decodedDataSmartWallet.args;
- const toOriginal = methodArgsSmartWalletExecuteCall[0];
- const valueOriginal = methodArgsSmartWalletExecuteCall[1];
- const dataOriginal = methodArgsSmartWalletExecuteCall[2];
-
- batchTo.push(toOriginal);
- batchValue.push(valueOriginal);
- batchData.push(dataOriginal);
- } else if (smartWalletExecFunctionName === "executeBatchCall") {
- Logger.log("originally an executeBatchCall for Biconomy Account");
- const methodArgsSmartWalletExecuteCall = decodedDataSmartWallet.args;
- batchTo = methodArgsSmartWalletExecuteCall[0];
- batchValue = methodArgsSmartWalletExecuteCall[1];
- batchData = methodArgsSmartWalletExecuteCall[2];
- }
-
- if (approvalRequest.to && approvalRequest.data && approvalRequest.value) {
- batchTo = [approvalRequest.to, ...batchTo];
- batchValue = [approvalRequest.value, ...batchValue];
- batchData = [approvalRequest.data, ...batchData];
-
- newCallData = this.getExecuteBatchCallData(batchTo, batchValue, batchData);
- }
- const finalUserOp: Partial = {
- ...userOp,
- callData: newCallData,
- };
-
- return finalUserOp;
- }
- } catch (error) {
- Logger.log("Failed to update userOp. sending back original op");
- Logger.error("Failed to update callData with error", error);
- return userOp;
- }
- return userOp;
- }
-
- async getAllTokenBalances(balancesDto: BalancesDto): Promise {
- return this.nodeClient.getAllTokenBalances(balancesDto);
- }
-
- async getTotalBalanceInUsd(balancesDto: BalancesDto): Promise {
- return this.nodeClient.getTotalBalanceInUsd(balancesDto);
- }
-
- async getSmartAccountsByOwner(smartAccountByOwnerDto: SmartAccountByOwnerDto): Promise {
- return this.nodeClient.getSmartAccountsByOwner(smartAccountByOwnerDto);
- }
-
- async getTransactionsByAddress(chainId: number, address: string): Promise {
- return this.nodeClient.getTransactionByAddress(chainId, address);
- }
-
- async getTransactionByHash(txHash: string): Promise {
- return this.nodeClient.getTransactionByHash(txHash);
- }
-
- async getAllSupportedChains(): Promise {
- return this.nodeClient.getAllSupportedChains();
- }
-
- async getUpdateImplementationData(newImplementationAddress?: string): Promise {
- // V2 address or latest implementation if possible to jump from V1 -> Vn without upgrading to V2
- // If needed we can fetch this from backend config
-
- Logger.log("Recommended implementation address to upgrade to", BICONOMY_IMPLEMENTATION_ADDRESSES_BY_VERSION.V2_0_0);
-
- const latestImplementationAddress = newImplementationAddress ?? DEFAULT_BICONOMY_IMPLEMENTATION_ADDRESS; // BICONOMY_IMPLEMENTATION_ADDRESSES_BY_VERSION.V2_0_0 // 2.0
- Logger.log("Requested implementation address to upgrade to", latestImplementationAddress);
-
- // by querying the proxy contract
- // TODO: add isDeployed checks before reading below
- const currentImplementationAddress = await this.proxy.getImplementation();
- Logger.log("Current implementation address for this Smart Account", currentImplementationAddress);
-
- if (ethers.utils.getAddress(currentImplementationAddress) !== ethers.utils.getAddress(latestImplementationAddress)) {
- const impInterface = new ethers.utils.Interface(["function updateImplementation(address _implementation)"]);
- const encodedData = impInterface.encodeFunctionData("updateImplementation", [latestImplementationAddress]);
- return { to: this.address, value: BigNumber.from(0), data: encodedData };
- } else {
- return { to: this.address, value: 0, data: "0x" };
- }
- }
-
- async getModuleSetupData(ecdsaModuleAddress?: string): Promise {
- try {
- const moduleAddress = ecdsaModuleAddress ?? DEFAULT_ECDSA_OWNERSHIP_MODULE;
- const ecdsaModule = await ECDSAOwnershipValidationModule.create({
- signer: this.signer,
- moduleAddress: moduleAddress,
- });
-
- // initForSmartAccount
- const ecdsaOwnershipSetupData = await ecdsaModule.getInitData();
-
- const proxyInstanceDto = {
- smartAccountType: SmartAccountType.BICONOMY,
- version: "V2_0_0", // Review
- contractAddress: this.address,
- provider: this.provider,
- };
- const accountV2: SmartAccount_v200 = getSAProxyContract(proxyInstanceDto) as SmartAccount_v200;
-
- const data = accountV2.interface.encodeFunctionData("setupAndEnableModule", [moduleAddress, ecdsaOwnershipSetupData]);
-
- return { to: this.address, value: 0, data: data };
- } catch (error) {
- Logger.error("Failed to get module setup data", error);
- // Could throw error
- return { to: this.address, value: 0, data: "0x" };
- }
- }
-
- // Once this userOp is sent (batch: a. updateImplementation and b. setupModule on upgraded proxy)
- // Afterwards you can start using BiconomySmartAccountV2
- async updateImplementationUserOp(newImplementationAddress?: string, ecdsaModuleAddress?: string): Promise> {
- const tx1 = await this.getUpdateImplementationData(newImplementationAddress);
- const tx2 = await this.getModuleSetupData(ecdsaModuleAddress);
-
- if (tx1.data !== "0x" && tx2.data !== "0x") {
- const partialUserOp: Partial = await this.buildUserOp([tx1, tx2]);
- return partialUserOp;
- } else {
- throw new Error("Not eligible for upgrade");
- }
- }
-}
diff --git a/packages/account/src/BiconomySmartAccountV2.ts b/packages/account/src/BiconomySmartAccountV2.ts
index fe9ce9e39..e5a7bfb48 100644
--- a/packages/account/src/BiconomySmartAccountV2.ts
+++ b/packages/account/src/BiconomySmartAccountV2.ts
@@ -1,71 +1,91 @@
-import { JsonRpcProvider } from "@ethersproject/providers";
-import { ethers, BigNumberish, BytesLike, BigNumber } from "ethers";
-import { BaseSmartAccount } from "./BaseSmartAccount";
-import { Bytes, getCreate2Address, hexConcat, keccak256, solidityKeccak256 } from "ethers/lib/utils";
import {
- Logger,
- NODE_CLIENT_URL,
- SmartAccount_v200,
- SmartAccountFactory_v200,
- SmartAccount_v200__factory,
- SmartAccountFactory_v200__factory,
- AddressResolver,
- AddressResolver__factory,
- isNullOrUndefined,
-} from "@biconomy/common";
+ Hex,
+ keccak256,
+ encodePacked,
+ getCreate2Address,
+ encodeAbiParameters,
+ parseAbiParameters,
+ toHex,
+ toBytes,
+ encodeFunctionData,
+ PublicClient,
+ createPublicClient,
+ http,
+ concatHex,
+ GetContractReturnType,
+ getContract,
+ decodeFunctionData,
+} from "viem";
+import {
+ BaseSmartContractAccount,
+ getChain,
+ type BigNumberish,
+ type UserOperationStruct,
+ BatchUserOperationCallData,
+ SmartAccountSigner,
+} from "@alchemy/aa-core";
+import { isNullOrUndefined, packUserOp } from "./utils/Utils.js";
+import { BaseValidationModule, ModuleInfo, SendUserOpParams, createECDSAOwnershipValidationModule } from "@biconomy/modules";
+import {
+ IHybridPaymaster,
+ IPaymaster,
+ Paymaster,
+ PaymasterMode,
+ SponsorUserOperationDto,
+ Bundler,
+ IBundler,
+ UserOpResponse,
+ extractChainIdFromBundlerUrl,
+ convertSigner,
+} from "./index.js";
import {
BiconomyTokenPaymasterRequest,
BiconomySmartAccountV2Config,
CounterFactualAddressParam,
BuildUserOpOptions,
- Overrides,
NonceOptions,
- SmartAccountInfo,
+ Transaction,
QueryParamsForAddressResolver,
-} from "./utils/Types";
-import { BaseValidationModule, ECDSAOwnershipValidationModule, ModuleInfo, SendUserOpParams } from "@biconomy/modules";
-import { UserOperation, Transaction } from "@biconomy/core-types";
-import NodeClient from "@biconomy/node-client";
-import INodeClient from "@biconomy/node-client";
-import { IHybridPaymaster, BiconomyPaymaster, SponsorUserOperationDto } from "@biconomy/paymaster";
-import {
- SupportedChainsResponse,
- BalancesResponse,
- BalancesDto,
- UsdBalanceResponse,
- SmartAccountByOwnerDto,
- SmartAccountsResponse,
- SCWTransactionResponse,
-} from "@biconomy/node-client";
-import { UserOpResponse } from "@biconomy/bundler";
+ BiconomySmartAccountV2ConfigConstructorProps,
+ PaymasterUserOperationDto,
+ SimulationType,
+} from "./utils/Types.js";
import {
ADDRESS_RESOLVER_ADDRESS,
BICONOMY_IMPLEMENTATION_ADDRESSES_BY_VERSION,
DEFAULT_BICONOMY_FACTORY_ADDRESS,
DEFAULT_FALLBACK_HANDLER_ADDRESS,
PROXY_CREATION_CODE,
-} from "./utils/Constants";
-import log from "loglevel";
+ ADDRESS_ZERO,
+ DEFAULT_ENTRYPOINT_ADDRESS,
+ ERROR_MESSAGES,
+} from "./utils/Constants.js";
+import { BiconomyFactoryAbi } from "./abi/Factory.js";
+import { BiconomyAccountAbi } from "./abi/SmartAccount.js";
+import { AccountResolverAbi } from "./abi/AccountResolver.js";
+import { Logger } from "@biconomy/common";
+import { FeeQuotesOrDataDto, FeeQuotesOrDataResponse } from "@biconomy/paymaster";
+
+type UserOperationKey = keyof UserOperationStruct;
+
+export class BiconomySmartAccountV2 extends BaseSmartContractAccount {
+ private SENTINEL_MODULE = "0x0000000000000000000000000000000000000001";
-type UserOperationKey = keyof UserOperation;
-export class BiconomySmartAccountV2 extends BaseSmartAccount {
- private nodeClient!: INodeClient;
+ private index: number;
- private SENTINEL_MODULE = "0x0000000000000000000000000000000000000001";
+ private chainId: number;
- factoryAddress?: string;
+ private provider: PublicClient;
- /**
- * our account contract.
- * should support the "execFromEntryPoint" and "nonce" methods
- */
- accountContract?: SmartAccount_v200;
+ paymaster?: IPaymaster;
+
+ bundler?: IBundler;
- factory?: SmartAccountFactory_v200;
+ private accountContract?: GetContractReturnType;
- private defaultFallbackHandlerAddress!: string;
+ private defaultFallbackHandlerAddress: Hex;
- private implementationAddress!: string;
+ private implementationAddress: Hex;
private scanForUpgradedAccountsFromV1!: boolean;
@@ -77,134 +97,168 @@ export class BiconomySmartAccountV2 extends BaseSmartAccount {
// Deployed Smart Account can have more than one module enabled. When sending a transaction activeValidationModule is used to prepare and validate userOp signature.
activeValidationModule!: BaseValidationModule;
- private constructor(readonly biconomySmartAccountConfig: BiconomySmartAccountV2Config) {
- super(biconomySmartAccountConfig);
- }
+ private constructor(readonly biconomySmartAccountConfig: BiconomySmartAccountV2ConfigConstructorProps) {
+ super({
+ ...biconomySmartAccountConfig,
+ chain: getChain(biconomySmartAccountConfig.chainId),
+ rpcClient: biconomySmartAccountConfig.rpcUrl || getChain(biconomySmartAccountConfig.chainId).rpcUrls.default.http[0],
+ entryPointAddress: (biconomySmartAccountConfig.entryPointAddress as Hex) ?? DEFAULT_ENTRYPOINT_ADDRESS,
+ accountAddress: (biconomySmartAccountConfig.accountAddress as Hex) ?? undefined,
+ factoryAddress: biconomySmartAccountConfig.factoryAddress ?? DEFAULT_BICONOMY_FACTORY_ADDRESS,
+ });
- /**
- * Creates a new instance of BiconomySmartAccountV2.
- *
- * This method will create a BiconomySmartAccountV2 instance but will not deploy the Smart Account.
- *
- * Deployment of the Smart Account will be donewith the first user operation.
- *
- * @param biconomySmartAccountConfig - Configuration for initializing the BiconomySmartAccountV2 instance.
- * @returns A promise that resolves to a new instance of BiconomySmartAccountV2.
- * @throws An error if something is wrong with the smart account instance creation.
- */
- public static async create(biconomySmartAccountConfig: BiconomySmartAccountV2Config): Promise {
- const instance = new BiconomySmartAccountV2(biconomySmartAccountConfig);
- instance.factoryAddress = biconomySmartAccountConfig.factoryAddress ?? DEFAULT_BICONOMY_FACTORY_ADDRESS; // This would be fetched from V2
+ this.defaultValidationModule = biconomySmartAccountConfig.defaultValidationModule;
+ this.activeValidationModule = biconomySmartAccountConfig.activeValidationModule;
+
+ this.index = biconomySmartAccountConfig.index ?? 0;
+ this.chainId = biconomySmartAccountConfig.chainId;
+ this.bundler = biconomySmartAccountConfig.bundler;
+ this.implementationAddress = biconomySmartAccountConfig.implementationAddress ?? (BICONOMY_IMPLEMENTATION_ADDRESSES_BY_VERSION.V2_0_0 as Hex);
if (biconomySmartAccountConfig.biconomyPaymasterApiKey) {
- instance.paymaster = new BiconomyPaymaster({
+ this.paymaster = new Paymaster({
paymasterUrl: `https://paymaster.biconomy.io/api/v1/${biconomySmartAccountConfig.chainId}/${biconomySmartAccountConfig.biconomyPaymasterApiKey}`,
});
+ } else {
+ this.paymaster = biconomySmartAccountConfig.paymaster;
}
+ this.bundler = biconomySmartAccountConfig.bundler;
+
const defaultFallbackHandlerAddress =
- instance.factoryAddress === DEFAULT_BICONOMY_FACTORY_ADDRESS
- ? DEFAULT_FALLBACK_HANDLER_ADDRESS
- : biconomySmartAccountConfig.defaultFallbackHandler;
+ this.factoryAddress === DEFAULT_BICONOMY_FACTORY_ADDRESS ? DEFAULT_FALLBACK_HANDLER_ADDRESS : biconomySmartAccountConfig.defaultFallbackHandler;
if (!defaultFallbackHandlerAddress) {
throw new Error("Default Fallback Handler address is not provided");
}
- instance.accountAddress = biconomySmartAccountConfig.senderAddress ?? undefined;
- instance.defaultFallbackHandlerAddress = defaultFallbackHandlerAddress;
-
- instance.implementationAddress = biconomySmartAccountConfig.implementationAddress ?? BICONOMY_IMPLEMENTATION_ADDRESSES_BY_VERSION.V2_0_0;
-
- // Note: if no module is provided, we will use ECDSA_OWNERSHIP as default
- if (biconomySmartAccountConfig.defaultValidationModule) {
- instance.defaultValidationModule = biconomySmartAccountConfig.defaultValidationModule;
- } else {
- instance.defaultValidationModule = await ECDSAOwnershipValidationModule.create({
- signer: biconomySmartAccountConfig.signer!,
- });
- }
+ this.defaultFallbackHandlerAddress = defaultFallbackHandlerAddress;
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- instance.activeValidationModule = biconomySmartAccountConfig.activeValidationModule ?? instance.defaultValidationModule;
+ // Added bang operator to avoid null check as the constructor have these params as optional
+ this.defaultValidationModule = biconomySmartAccountConfig.defaultValidationModule!;
+ this.activeValidationModule = biconomySmartAccountConfig.activeValidationModule!;
- const { rpcUrl, nodeClientUrl } = biconomySmartAccountConfig;
-
- if (rpcUrl) {
- instance.provider = new JsonRpcProvider(rpcUrl);
- }
-
- instance.nodeClient = new NodeClient({ txServiceUrl: nodeClientUrl ?? NODE_CLIENT_URL });
-
- instance.scanForUpgradedAccountsFromV1 = biconomySmartAccountConfig.scanForUpgradedAccountsFromV1 ?? false;
-
- instance.maxIndexForScan = biconomySmartAccountConfig.maxIndexForScan ?? 10;
-
- await instance.init();
+ this.provider = createPublicClient({
+ chain: getChain(biconomySmartAccountConfig.chainId),
+ transport: http(biconomySmartAccountConfig.rpcUrl || getChain(biconomySmartAccountConfig.chainId).rpcUrls.default.http[0]),
+ });
- return instance;
+ this.scanForUpgradedAccountsFromV1 = biconomySmartAccountConfig.scanForUpgradedAccountsFromV1 ?? false;
+ this.maxIndexForScan = biconomySmartAccountConfig.maxIndexForScan ?? 10;
}
- async _getAccountContract(): Promise {
- if (this.accountContract == null) {
- this.accountContract = SmartAccount_v200__factory.connect(await this.getAccountAddress(), this.provider);
+ /**
+ * Creates a new instance of BiconomySmartAccountV2
+ *
+ * This method will create a BiconomySmartAccountV2 instance but will not deploy the Smart Account
+ * Deployment of the Smart Account will be donewith the first user operation.
+ *
+ * - Docs: https://docs.biconomy.io/Account/integration#integration-1
+ *
+ * @param biconomySmartAccountConfig - Configuration for initializing the BiconomySmartAccountV2 instance.
+ * @returns A promise that resolves to a new instance of BiconomySmartAccountV2.
+ * @throws An error if something is wrong with the smart account instance creation.
+ *
+ * @example
+ * import { createClient } from "viem"
+ * import { createSmartAccountClient, BiconomySmartAccountV2 } from "@biconomy/account"
+ * import { createWalletClient, http } from "viem";
+ * import { polygonMumbai } from "viem/chains";
+ *
+ * const signer = createWalletClient({
+ * account,
+ * chain: polygonMumbai,
+ * transport: http(),
+ * });
+ *
+ * const bundlerUrl = "" // Retrieve bundler url from dasboard
+ *
+ * const smartAccountFromStaticCreate = await BiconomySmartAccountV2.create({ signer, bundlerUrl });
+ *
+ * // Is the same as...
+ *
+ * const smartAccount = await createSmartAccountClient({ signer, bundlerUrl });
+ *
+ */
+ public static async create(biconomySmartAccountConfig: BiconomySmartAccountV2Config): Promise {
+ let chainId = biconomySmartAccountConfig.chainId;
+ let resolvedSmartAccountSigner!: SmartAccountSigner;
+ let rpcUrl = biconomySmartAccountConfig.rpcUrl;
+
+ // Signer needs to be initialised here before defaultValidationModule is set
+ if (biconomySmartAccountConfig.signer) {
+ const signerResult = await convertSigner(biconomySmartAccountConfig.signer, !!chainId);
+ if (!chainId && !!signerResult.chainId) {
+ chainId = signerResult.chainId;
+ }
+ if (!rpcUrl && !!signerResult.rpcUrl) {
+ rpcUrl = signerResult.rpcUrl;
+ }
+ resolvedSmartAccountSigner = signerResult.signer;
}
- return this.accountContract;
- }
-
- isActiveValidationModuleDefined(): boolean {
- if (!this.activeValidationModule) throw new Error("Must provide an instance of active validation module.");
- return true;
- }
-
- isDefaultValidationModuleDefined(): boolean {
- if (!this.defaultValidationModule) throw new Error("Must provide an instance of default validation module.");
- return true;
- }
-
- setActiveValidationModule(validationModule: BaseValidationModule): BiconomySmartAccountV2 {
- if (validationModule instanceof BaseValidationModule) {
- this.activeValidationModule = validationModule;
+ if (!chainId) {
+ // Get it from bundler
+ if (biconomySmartAccountConfig.bundlerUrl) {
+ chainId = extractChainIdFromBundlerUrl(biconomySmartAccountConfig.bundlerUrl);
+ } else if (biconomySmartAccountConfig.bundler) {
+ const bundlerUrlFromBundler = biconomySmartAccountConfig.bundler.getBundlerUrl();
+ chainId = extractChainIdFromBundlerUrl(bundlerUrlFromBundler);
+ }
}
- return this;
- }
+ if (!chainId) {
+ throw new Error("chainId required");
+ }
+ const bundler: IBundler = biconomySmartAccountConfig.bundler ?? new Bundler({ bundlerUrl: biconomySmartAccountConfig.bundlerUrl!, chainId });
+ let defaultValidationModule = biconomySmartAccountConfig.defaultValidationModule;
- setDefaultValidationModule(validationModule: BaseValidationModule): BiconomySmartAccountV2 {
- if (validationModule instanceof BaseValidationModule) {
- this.defaultValidationModule = validationModule;
- this.accountAddress = undefined;
+ // Note: If no module is provided, we will use ECDSA_OWNERSHIP as default
+ if (!defaultValidationModule) {
+ const newModule = await createECDSAOwnershipValidationModule({ signer: resolvedSmartAccountSigner! });
+ defaultValidationModule = newModule;
}
- return this;
+ const activeValidationModule = biconomySmartAccountConfig?.activeValidationModule ?? defaultValidationModule;
+ if (!resolvedSmartAccountSigner) {
+ resolvedSmartAccountSigner = await activeValidationModule.getSigner();
+ }
+ if (!resolvedSmartAccountSigner) {
+ throw new Error("signer required");
+ }
+ const config: BiconomySmartAccountV2ConfigConstructorProps = {
+ ...biconomySmartAccountConfig,
+ defaultValidationModule,
+ activeValidationModule,
+ chainId,
+ bundler,
+ signer: resolvedSmartAccountSigner,
+ rpcUrl,
+ };
+
+ return new BiconomySmartAccountV2(config);
}
- // Could call it nonce space
- async getNonce(nonceKey?: number): Promise {
- const nonceSpace = nonceKey ?? 0;
- try {
- const accountContract = await this._getAccountContract();
- const nonce = await accountContract.nonce(nonceSpace);
- return nonce;
- } catch (e) {
- log.debug("Failed to get nonce from deployed account. Returning 0 as nonce");
- return BigNumber.from(0);
+ // Calls the getCounterFactualAddress
+ async getAddress(params?: CounterFactualAddressParam): Promise {
+ if (this.accountAddress == null) {
+ // means it needs deployment
+ this.accountAddress = await this.getCounterFactualAddress(params);
}
+ return this.accountAddress;
}
- /**
- * return the account's address.
- * this value is valid even before deploying the contract.
- */
- async getAccountAddress(params?: CounterFactualAddressParam): Promise {
+ // Calls the getCounterFactualAddress
+ async getAccountAddress(params?: CounterFactualAddressParam): Promise<`0x${string}`> {
if (this.accountAddress == null || this.accountAddress == undefined) {
+ // means it needs deployment
this.accountAddress = await this.getCounterFactualAddress(params);
}
return this.accountAddress;
}
/**
- * calculate the account address even before it is deployed
+ * Return the account's address. This value is valid even before deploying the contract.
*/
- async getCounterFactualAddress(params?: CounterFactualAddressParam): Promise {
+ async getCounterFactualAddress(params?: CounterFactualAddressParam): Promise {
const validationModule = params?.validationModule ?? this.defaultValidationModule;
const index = params?.index ?? this.index;
+
const maxIndexForScan = params?.maxIndexForScan ?? this.maxIndexForScan;
// Review: default behavior
const scanForUpgradedAccountsFromV1 = params?.scanForUpgradedAccountsFromV1 ?? this.scanForUpgradedAccountsFromV1;
@@ -212,9 +266,9 @@ export class BiconomySmartAccountV2 extends BaseSmartAccount {
// if it's intended to detect V1 upgraded accounts
if (scanForUpgradedAccountsFromV1) {
const eoaSigner = await validationModule.getSigner();
- const eoaAddress = await eoaSigner.getAddress();
- const moduleAddress = validationModule.getAddress();
- const moduleSetupData = await validationModule.getInitData();
+ const eoaAddress = (await eoaSigner.getAddress()) as Hex;
+ const moduleAddress = validationModule.getAddress() as Hex;
+ const moduleSetupData = (await validationModule.getInitData()) as Hex;
const queryParams = {
eoaAddress,
index,
@@ -223,36 +277,35 @@ export class BiconomySmartAccountV2 extends BaseSmartAccount {
maxIndexForScan,
};
const accountAddress = await this.getV1AccountsUpgradedToV2(queryParams);
- Logger.log("account address from V1 ", accountAddress);
- if (accountAddress !== ethers.constants.AddressZero) {
+ if (accountAddress !== ADDRESS_ZERO) {
return accountAddress;
}
}
+
const counterFactualAddressV2 = await this.getCounterFactualAddressV2({ validationModule, index });
return counterFactualAddressV2;
}
- private async getCounterFactualAddressV2(params?: CounterFactualAddressParam): Promise {
- if (this.factory == null) {
- if (this.factoryAddress != null && this.factoryAddress !== "") {
- this.factory = SmartAccountFactory_v200__factory.connect(this.factoryAddress, this.provider);
- } else {
- throw new Error("no factory to get initCode");
- }
- }
-
+ private async getCounterFactualAddressV2(params?: CounterFactualAddressParam): Promise {
const validationModule = params?.validationModule ?? this.defaultValidationModule;
const index = params?.index ?? this.index;
try {
- const initCalldata = SmartAccount_v200__factory.createInterface().encodeFunctionData("init", [
- this.defaultFallbackHandlerAddress,
- validationModule.getAddress(),
- await validationModule.getInitData(),
- ]);
- const proxyCreationCodeHash = solidityKeccak256(["bytes", "uint256"], [PROXY_CREATION_CODE, this.implementationAddress]);
- const salt = solidityKeccak256(["bytes32", "uint256"], [keccak256(initCalldata), index]);
- const counterFactualAddress = getCreate2Address(this.factory.address, salt, proxyCreationCodeHash);
+ const initCalldata = encodeFunctionData({
+ abi: BiconomyAccountAbi,
+ functionName: "init",
+ args: [this.defaultFallbackHandlerAddress, validationModule.getAddress() as Hex, (await validationModule.getInitData()) as Hex],
+ });
+
+ const proxyCreationCodeHash = keccak256(encodePacked(["bytes", "uint256"], [PROXY_CREATION_CODE, BigInt(this.implementationAddress)]));
+
+ const salt = keccak256(encodePacked(["bytes32", "uint256"], [keccak256(initCalldata), BigInt(index)]));
+
+ const counterFactualAddress = getCreate2Address({
+ from: this.factoryAddress,
+ salt: salt,
+ bytecodeHash: proxyCreationCodeHash,
+ });
return counterFactualAddress;
} catch (e) {
@@ -260,60 +313,93 @@ export class BiconomySmartAccountV2 extends BaseSmartAccount {
}
}
- async getV1AccountsUpgradedToV2(params: QueryParamsForAddressResolver): Promise {
- Logger.log("index to filter ", params.index);
+ async _getAccountContract(): Promise> {
+ if (this.accountContract == null) {
+ this.accountContract = getContract({
+ address: await this.getAddress(),
+ abi: BiconomyAccountAbi,
+ client: this.provider as PublicClient,
+ });
+ }
+ return this.accountContract;
+ }
+
+ isActiveValidationModuleDefined(): boolean {
+ if (!this.activeValidationModule) throw new Error("Must provide an instance of active validation module.");
+ return true;
+ }
+
+ isDefaultValidationModuleDefined(): boolean {
+ if (!this.defaultValidationModule) throw new Error("Must provide an instance of default validation module.");
+ return true;
+ }
+
+ setActiveValidationModule(validationModule: BaseValidationModule): BiconomySmartAccountV2 {
+ if (validationModule instanceof BaseValidationModule) {
+ this.activeValidationModule = validationModule;
+ }
+ return this;
+ }
+
+ setDefaultValidationModule(validationModule: BaseValidationModule): BiconomySmartAccountV2 {
+ if (validationModule instanceof BaseValidationModule) {
+ this.defaultValidationModule = validationModule;
+ }
+ return this;
+ }
+
+ async getV1AccountsUpgradedToV2(params: QueryParamsForAddressResolver): Promise {
const maxIndexForScan = params.maxIndexForScan ?? this.maxIndexForScan;
- const addressResolver: AddressResolver = AddressResolver__factory.connect(ADDRESS_RESOLVER_ADDRESS, this.provider);
+
+ const addressResolver = getContract({
+ address: ADDRESS_RESOLVER_ADDRESS,
+ abi: AccountResolverAbi,
+ client: {
+ public: this.provider as PublicClient,
+ },
+ });
// Note: depending on moduleAddress and moduleSetupData passed call this. otherwise could call resolveAddresses()
if (params.moduleAddress && params.moduleSetupData) {
- const result: SmartAccountInfo[] = await addressResolver.resolveAddressesFlexibleForV2(
+ const result = await addressResolver.read.resolveAddressesFlexibleForV2([
params.eoaAddress,
maxIndexForScan,
params.moduleAddress,
params.moduleSetupData,
- );
+ ]);
const desiredV1Account = result.find(
- (smartAccountInfo) =>
+ (smartAccountInfo: { factoryVersion: string; currentVersion: string; deploymentIndex: { toString: () => any } }) =>
smartAccountInfo.factoryVersion === "v1" &&
smartAccountInfo.currentVersion === "2.0.0" &&
- smartAccountInfo.deploymentIndex.toNumber() === params.index,
+ Number(smartAccountInfo.deploymentIndex.toString()) === params.index,
);
if (desiredV1Account) {
const smartAccountAddress = desiredV1Account.accountAddress;
return smartAccountAddress;
} else {
- return ethers.constants.AddressZero;
+ return ADDRESS_ZERO;
}
} else {
- return ethers.constants.AddressZero;
+ return ADDRESS_ZERO;
}
}
/**
- * return the value to put into the "initCode" field, if the account is not yet deployed.
- * this value holds the "factory" address, followed by this account's information
+ * Return the value to put into the "initCode" field, if the account is not yet deployed.
+ * This value holds the "factory" address, followed by this account's information
*/
- async getAccountInitCode(): Promise {
- if (this.factory == null) {
- if (this.factoryAddress != null && this.factoryAddress !== "") {
- this.factory = SmartAccountFactory_v200__factory.connect(this.factoryAddress, this.provider);
- } else {
- throw new Error("no factory to get initCode");
- }
- }
-
+ async getAccountInitCode(): Promise {
this.isDefaultValidationModuleDefined();
- return hexConcat([
- this.factory.address,
- this.factory.interface.encodeFunctionData("deployCounterFactualAccount", [
- this.defaultValidationModule.getAddress(),
- await this.defaultValidationModule.getInitData(),
- this.index,
- ]),
+ return concatHex([
+ this.factoryAddress as Hex,
+ encodeFunctionData({
+ abi: BiconomyFactoryAbi,
+ functionName: "deployCounterFactualAccount",
+ args: [this.defaultValidationModule.getAddress() as Hex, (await this.defaultValidationModule.getInitData()) as Hex, BigInt(this.index)],
+ }),
]);
}
@@ -322,11 +408,15 @@ export class BiconomySmartAccountV2 extends BaseSmartAccount {
* @param to { target } address of transaction
* @param value represents amount of native tokens
* @param data represent data associated with transaction
- * @returns
+ * @returns encoded data for execute function
*/
- async encodeExecute(to: string, value: BigNumberish, data: BytesLike): Promise {
- const accountContract = await this._getAccountContract();
- return accountContract.interface.encodeFunctionData("execute_ncC", [to, value, data]);
+ async encodeExecute(to: Hex, value: bigint, data: Hex): Promise {
+ // return accountContract.interface.encodeFunctionData("execute_ncC", [to, value, data]) as Hex;
+ return encodeFunctionData({
+ abi: BiconomyAccountAbi,
+ functionName: "execute_ncC",
+ args: [to, value, data],
+ });
}
/**
@@ -334,17 +424,40 @@ export class BiconomySmartAccountV2 extends BaseSmartAccount {
* @param to { target } array of addresses in transaction
* @param value represents array of amount of native tokens associated with each transaction
* @param data represent array of data associated with each transaction
- * @returns
+ * @returns encoded data for executeBatch function
*/
- async encodeExecuteBatch(to: Array, value: Array, data: Array): Promise {
- const accountContract = await this._getAccountContract();
- return accountContract.interface.encodeFunctionData("executeBatch_y6U", [to, value, data]);
+ async encodeExecuteBatch(to: Array, value: Array, data: Array): Promise {
+ return encodeFunctionData({
+ abi: BiconomyAccountAbi,
+ functionName: "executeBatch_y6U",
+ args: [to, value, data],
+ });
+ }
+
+ override async encodeBatchExecute(txs: BatchUserOperationCallData): Promise {
+ const [targets, datas, value] = txs.reduce(
+ (accum, curr) => {
+ accum[0].push(curr.target);
+ accum[1].push(curr.data);
+ accum[2].push(curr.value || BigInt(0));
+
+ return accum;
+ },
+ [[], [], []] as [Hex[], Hex[], bigint[]],
+ );
+
+ return this.encodeExecuteBatch(targets, value, datas);
}
// dummy signature depends on the validation module supplied.
- async getDummySignature(params?: ModuleInfo): Promise {
+ async getDummySignatures(params?: ModuleInfo): Promise {
this.isActiveValidationModuleDefined();
- return this.activeValidationModule.getDummySignature(params);
+ return (await this.activeValidationModule.getDummySignature(params)) as Hex;
+ }
+
+ // TODO: review this
+ getDummySignature(): Hex {
+ throw new Error("Method not implemented! Call getDummySignatures instead.");
}
// Might use provided paymaster instance to get dummy data (from pm service)
@@ -352,7 +465,16 @@ export class BiconomySmartAccountV2 extends BaseSmartAccount {
return "0x";
}
- async signUserOp(userOp: Partial, params?: SendUserOpParams): Promise {
+ validateUserOp(userOp: Partial, requiredFields: UserOperationKey[]): boolean {
+ for (const field of requiredFields) {
+ if (!userOp[field]) {
+ throw new Error(`${String(field)} is missing in the UserOp`);
+ }
+ }
+ return true;
+ }
+
+ async signUserOp(userOp: Partial, params?: SendUserOpParams): Promise {
this.isActiveValidationModuleDefined();
const requiredFields: UserOperationKey[] = [
"sender",
@@ -366,112 +488,391 @@ export class BiconomySmartAccountV2 extends BaseSmartAccount {
"maxPriorityFeePerGas",
"paymasterAndData",
];
- super.validateUserOp(userOp, requiredFields);
+ this.validateUserOp(userOp, requiredFields);
const userOpHash = await this.getUserOpHash(userOp);
- const moduleSig = await this.activeValidationModule.signUserOpHash(userOpHash, params);
+ const moduleSig = (await this.activeValidationModule.signUserOpHash(userOpHash, params)) as Hex;
- // Note: If the account is undeployed, use ERC-6492
- // Review: Should only be needed for signMessage
- /*if (!(await this.isAccountDeployed(await this.getAccountAddress()))) {
- const coder = new ethers.utils.AbiCoder();
- const populatedTransaction = await this.factory?.populateTransaction.deployCounterFactualAccount(
- await this.defaultValidationModule.getAddress(),
- await this.defaultValidationModule.getInitData(),
- this.index,
- );
- moduleSig =
- coder.encode(["address", "bytes", "bytes"], [this.factoryAddress, populatedTransaction?.data, moduleSig]) +
- "6492649264926492649264926492649264926492649264926492649264926492"; // magic suffix
- userOp.signature = moduleSig;
- return userOp as UserOperation;
- }*/
-
- const signatureWithModuleAddress = ethers.utils.defaultAbiCoder.encode(
- ["bytes", "address"],
- [moduleSig, this.activeValidationModule.getAddress()],
- );
+ const signatureWithModuleAddress = this.getSignatureWithModuleAddress(moduleSig, this.activeValidationModule.getAddress() as Hex);
userOp.signature = signatureWithModuleAddress;
- return userOp as UserOperation;
+ return userOp as UserOperationStruct;
}
- getSignatureWithModuleAddress(moduleSignature: string, moduleAddress?: string): string {
- const moduleAddressToUse = moduleAddress ?? this.activeValidationModule.getAddress();
- return ethers.utils.defaultAbiCoder.encode(["bytes", "address"], [moduleSignature, moduleAddressToUse]);
+ getSignatureWithModuleAddress(moduleSignature: Hex, moduleAddress?: Hex): Hex {
+ const moduleAddressToUse = moduleAddress ?? (this.activeValidationModule.getAddress() as Hex);
+ return encodeAbiParameters(parseAbiParameters("bytes, address"), [moduleSignature, moduleAddressToUse]);
+ }
+
+ public async getPaymasterUserOp(
+ userOp: Partial,
+ paymasterServiceData: PaymasterUserOperationDto,
+ ): Promise> {
+ if (paymasterServiceData.mode === PaymasterMode.SPONSORED) {
+ return this.getPaymasterAndData(userOp, paymasterServiceData);
+ } else if (paymasterServiceData.mode === PaymasterMode.ERC20) {
+ if (paymasterServiceData?.feeQuote) {
+ const { feeQuote, spender, maxApproval = false } = paymasterServiceData;
+ Logger.log("there is a feeQuote: ", feeQuote);
+ if (!spender) throw new Error(ERROR_MESSAGES.SPENDER_REQUIRED);
+ if (!feeQuote) throw new Error(ERROR_MESSAGES.FAILED_FEE_QUOTE_FETCH);
+ const partialUserOp = await this.buildTokenPaymasterUserOp(userOp, {
+ ...paymasterServiceData,
+ spender,
+ maxApproval,
+ feeQuote,
+ });
+ return this.getPaymasterAndData(partialUserOp, {
+ ...paymasterServiceData,
+ feeTokenAddress: feeQuote.tokenAddress,
+ calculateGasLimits: true, // Always recommended and especially when using token paymaster
+ });
+ } else if (paymasterServiceData?.preferredToken) {
+ const { preferredToken } = paymasterServiceData;
+ Logger.log("there is a preferred token: ", preferredToken);
+ const feeQuotesResponse = await this.getPaymasterFeeQuotesOrData(userOp, paymasterServiceData);
+ const spender = feeQuotesResponse.tokenPaymasterAddress;
+ const feeQuote = feeQuotesResponse.feeQuotes?.[0];
+ if (!spender) throw new Error(ERROR_MESSAGES.SPENDER_REQUIRED);
+ if (!feeQuote) throw new Error(ERROR_MESSAGES.FAILED_FEE_QUOTE_FETCH);
+ return this.getPaymasterUserOp(userOp, { ...paymasterServiceData, feeQuote, spender }); // Recursively call getPaymasterUserOp with the feeQuote
+ } else {
+ Logger.log("ERC20 mode without feeQuote or preferredToken provided. Passing through unchanged.");
+ return userOp;
+ }
+ }
+ throw new Error("Invalid paymaster mode");
+ }
+
+ private async getPaymasterAndData(
+ userOp: Partial,
+ paymasterServiceData: PaymasterUserOperationDto,
+ ): Promise> {
+ const paymaster = this.paymaster as IHybridPaymaster;
+ const paymasterData = await paymaster.getPaymasterAndData(userOp, paymasterServiceData);
+ return { ...userOp, ...paymasterData };
+ }
+
+ private async getPaymasterFeeQuotesOrData(
+ userOp: Partial,
+ feeQuotesOrData: FeeQuotesOrDataDto,
+ ): Promise {
+ const paymaster = this.paymaster as IHybridPaymaster;
+ const tokenList = feeQuotesOrData?.preferredToken
+ ? [feeQuotesOrData?.preferredToken]
+ : feeQuotesOrData?.tokenList?.length
+ ? feeQuotesOrData?.tokenList
+ : [];
+ return paymaster.getPaymasterFeeQuotesOrData(userOp, { ...feeQuotesOrData, tokenList });
+ }
+
+ /**
+ *
+ * @description This function will retrieve fees from the paymaster in erc20 mode
+ *
+ * @param manyOrOneTransactions Array of {@link Transaction} to be batched and sent. Can also be a single {@link Transaction}.
+ * @param buildUseropDto {@link BuildUserOpOptions}.
+ * @returns Promise
+ *
+ * @example
+ * import { createClient } from "viem"
+ * import { createSmartAccountClient } from "@biconomy/account"
+ * import { createWalletClient, http } from "viem";
+ * import { polygonMumbai } from "viem/chains";
+ *
+ * const signer = createWalletClient({
+ * account,
+ * chain: polygonMumbai,
+ * transport: http(),
+ * });
+ *
+ * const smartAccount = await createSmartAccountClient({ signer, bundlerUrl }); // Retrieve bundler url from dasboard
+ * const encodedCall = encodeFunctionData({
+ * abi: parseAbi(["function safeMint(address to) public"]),
+ * functionName: "safeMint",
+ * args: ["0x..."],
+ * });
+ *
+ * const transaction = {
+ * to: nftAddress,
+ * data: encodedCall
+ * }
+ *
+ * const feeQuotesResponse: FeeQuotesOrDataResponse = await smartAccount.getTokenFees(transaction, { paymasterServiceData: { mode: PaymasterMode.ERC20 } });
+ *
+ * const userSeletedFeeQuote = feeQuotesResponse.feeQuotes?.[0];
+ *
+ * const { wait } = await smartAccount.sendTransaction(transaction, {
+ * paymasterServiceData: {
+ * mode: PaymasterMode.ERC20,
+ * feeQuote: userSeletedFeeQuote,
+ * spender: feeQuotesResponse.tokenPaymasterAddress,
+ * },
+ * });
+ *
+ * const { receipt } = await wait();
+ *
+ */
+ public async getTokenFees(
+ manyOrOneTransactions: Transaction | Transaction[],
+ buildUseropDto: BuildUserOpOptions,
+ ): Promise {
+ const txs = Array.isArray(manyOrOneTransactions) ? manyOrOneTransactions : [manyOrOneTransactions];
+ const userOp = await this.buildUserOp(txs, buildUseropDto);
+ if (!buildUseropDto.paymasterServiceData) throw new Error("paymasterServiceData was not provided");
+ return this.getPaymasterFeeQuotesOrData(userOp, buildUseropDto.paymasterServiceData);
}
/**
*
* @param userOp
* @param params
- * @description This function call will take 'unsignedUserOp' as an input, sign it with the owner key, and send it to the bundler.
+ * @description This function will take a user op as an input, sign it with the owner key, and send it to the bundler.
* @returns Promise
+ * Sends a user operation
+ *
+ * - Docs: https://docs.biconomy.io/Account/transactions/userpaid#send-useroperation
+ *
+ * @param userOp Partial<{@link UserOperationStruct}> the userOp params to be sent.
+ * @param params {@link SendUserOpParams}.
+ * @returns Promise<{@link UserOpResponse}> that you can use to track user operation.
+ *
+ * @example
+ * import { createClient } from "viem"
+ * import { createSmartAccountClient } from "@biconomy/account"
+ * import { createWalletClient, http } from "viem";
+ * import { polygonMumbai } from "viem/chains";
+ *
+ * const signer = createWalletClient({
+ * account,
+ * chain: polygonMumbai,
+ * transport: http(),
+ * });
+ *
+ * const smartAccount = await createSmartAccountClient({ signer, bundlerUrl }); // Retrieve bundler url from dasboard
+ * const encodedCall = encodeFunctionData({
+ * abi: parseAbi(["function safeMint(address to) public"]),
+ * functionName: "safeMint",
+ * args: ["0x..."],
+ * });
+ *
+ * const transaction = {
+ * to: nftAddress,
+ * data: encodedCall
+ * }
+ *
+ * const userOp = await smartAccount.buildUserOp([transaction]);
+ *
+ * const { wait } = await smartAccount.sendUserOp(userOp);
+ * const { receipt } = await wait();
+ *
*/
- async sendUserOp(userOp: Partial, params?: SendUserOpParams): Promise {
- Logger.log("userOp received in base account ", userOp);
+ async sendUserOp(userOp: Partial, params?: SendUserOpParams): Promise {
delete userOp.signature;
const userOperation = await this.signUserOp(userOp, params);
- const bundlerResponse = await this.sendSignedUserOp(userOperation, params);
+ const bundlerResponse = await this.sendSignedUserOp(userOperation, params?.simulationType);
+ return bundlerResponse;
+ }
+
+ /**
+ *
+ * @param userOp - The signed user operation to send
+ * @param simulationType - The type of simulation to perform ("validation" | "validation_and_execution")
+ * @description This function call will take 'signedUserOp' as input and send it to the bundler
+ * @returns
+ */
+ async sendSignedUserOp(userOp: UserOperationStruct, simulationType?: SimulationType): Promise {
+ const requiredFields: UserOperationKey[] = [
+ "sender",
+ "nonce",
+ "initCode",
+ "callData",
+ "callGasLimit",
+ "verificationGasLimit",
+ "preVerificationGas",
+ "maxFeePerGas",
+ "maxPriorityFeePerGas",
+ "paymasterAndData",
+ "signature",
+ ];
+ this.validateUserOp(userOp, requiredFields);
+ if (!this.bundler) throw new Error("Bundler is not provided");
+ Logger.warn("userOp being sent to the bundler", userOp);
+ const bundlerResponse = await this.bundler.sendUserOp(userOp, simulationType);
return bundlerResponse;
}
- private async getBuildUserOpNonce(nonceOptions: NonceOptions | undefined): Promise {
- let nonce = BigNumber.from(0);
+ async getUserOpHash(userOp: Partial): Promise {
+ const userOpHash = keccak256(packUserOp(userOp, true) as Hex);
+ const enc = encodeAbiParameters(parseAbiParameters("bytes32, address, uint256"), [userOpHash, this.entryPoint.address, BigInt(this.chainId)]);
+ return keccak256(enc);
+ }
+
+ async estimateUserOpGas(userOp: Partial): Promise> {
+ if (!this.bundler) throw new Error("Bundler is not provided");
+ const requiredFields: UserOperationKey[] = ["sender", "nonce", "initCode", "callData"];
+ this.validateUserOp(userOp, requiredFields);
+
+ const finalUserOp = userOp;
+
+ // Making call to bundler to get gas estimations for userOp
+ const { callGasLimit, verificationGasLimit, preVerificationGas, maxFeePerGas, maxPriorityFeePerGas } =
+ await this.bundler.estimateUserOpGas(userOp);
+ // if neither user sent gas fee nor the bundler, estimate gas from provider
+ if (!userOp.maxFeePerGas && !userOp.maxPriorityFeePerGas && (!maxFeePerGas || !maxPriorityFeePerGas)) {
+ const feeData = await this.provider.estimateFeesPerGas();
+ if (feeData.maxFeePerGas?.toString()) {
+ finalUserOp.maxFeePerGas = ("0x" + feeData.maxFeePerGas.toString(16)) as Hex;
+ } else if (feeData.gasPrice?.toString()) {
+ finalUserOp.maxFeePerGas = ("0x" + feeData.gasPrice.toString(16)) as Hex;
+ } else {
+ finalUserOp.maxFeePerGas = ("0x" + (await this.provider.getGasPrice()).toString(16)) as Hex;
+ }
+
+ if (feeData.maxPriorityFeePerGas?.toString()) {
+ finalUserOp.maxPriorityFeePerGas = ("0x" + feeData.maxPriorityFeePerGas?.toString()) as Hex;
+ } else if (feeData.gasPrice?.toString()) {
+ finalUserOp.maxPriorityFeePerGas = toHex(Number(feeData.gasPrice?.toString()));
+ } else {
+ finalUserOp.maxPriorityFeePerGas = ("0x" + (await this.provider.getGasPrice()).toString(16)) as Hex;
+ }
+ } else {
+ finalUserOp.maxFeePerGas = toHex(Number(maxFeePerGas)) ?? userOp.maxFeePerGas;
+ finalUserOp.maxPriorityFeePerGas = toHex(Number(maxPriorityFeePerGas)) ?? userOp.maxPriorityFeePerGas;
+ }
+ finalUserOp.verificationGasLimit = toHex(Number(verificationGasLimit)) ?? userOp.verificationGasLimit;
+ finalUserOp.callGasLimit = toHex(Number(callGasLimit)) ?? userOp.callGasLimit;
+ finalUserOp.preVerificationGas = toHex(Number(preVerificationGas)) ?? userOp.preVerificationGas;
+ if (!finalUserOp.paymasterAndData) {
+ finalUserOp.paymasterAndData = "0x";
+ }
+
+ return finalUserOp;
+ }
+
+ // Could call it nonce space
+ async getNonce(nonceKey?: number): Promise {
+ const nonceSpace = nonceKey ?? 0;
+ try {
+ const address = await this.getAddress();
+ return await this.entryPoint.read.getNonce([address, BigInt(nonceSpace)]);
+ } catch (e) {
+ return BigInt(0);
+ }
+ }
+
+ private async getBuildUserOpNonce(nonceOptions: NonceOptions | undefined): Promise {
+ let nonce = BigInt(0);
try {
if (nonceOptions?.nonceOverride) {
- nonce = BigNumber.from(nonceOptions?.nonceOverride);
+ nonce = BigInt(nonceOptions?.nonceOverride);
} else {
const _nonceSpace = nonceOptions?.nonceKey ?? 0;
nonce = await this.getNonce(_nonceSpace);
}
} catch (error) {
// Not throwing this error as nonce would be 0 if this.getNonce() throw exception, which is expected flow for undeployed account
- Logger.log("Error while getting nonce for the account. This is expected for undeployed accounts set nonce to 0");
+ Logger.warn("Error while getting nonce for the account. This is expected for undeployed accounts set nonce to 0");
}
return nonce;
}
- private async getGasFeeValues(
- overrides: Overrides | undefined,
- skipBundlerGasEstimation: boolean | undefined,
- ): Promise<{ maxFeePerGas?: BigNumberish | undefined; maxPriorityFeePerGas?: BigNumberish | undefined }> {
- const gasFeeValues = {
- maxFeePerGas: overrides?.maxFeePerGas,
- maxPriorityFeePerGas: overrides?.maxPriorityFeePerGas,
- };
- try {
- if (this.bundler && !gasFeeValues.maxFeePerGas && !gasFeeValues.maxPriorityFeePerGas && (skipBundlerGasEstimation ?? true)) {
- const gasFeeEstimation = await this.bundler.getGasFeeValues();
- gasFeeValues.maxFeePerGas = gasFeeEstimation.maxFeePerGas;
- gasFeeValues.maxPriorityFeePerGas = gasFeeEstimation.maxPriorityFeePerGas;
- }
- return gasFeeValues;
- } catch (error: any) {
- Logger.error("Error while getting gasFeeValues from bundler. Provided bundler might not have getGasFeeValues endpoint", error);
- return gasFeeValues;
- }
+ /**
+ * Sends a transaction (builds and sends a user op in sequence)
+ *
+ * - Docs: https://docs.biconomy.io/Account/transactions/userpaid#send-transaction
+ *
+ * @param manyOrOneTransactions Array of {@link Transaction} to be batched and sent. Can also be a single {@link Transaction}.
+ * @param buildUseropDto {@link BuildUserOpOptions}.
+ * @returns Promise<{@link UserOpResponse}> that you can use to track user operation.
+ *
+ * @example
+ * import { createClient } from "viem"
+ * import { createSmartAccountClient } from "@biconomy/account"
+ * import { createWalletClient, http } from "viem";
+ * import { polygonMumbai } from "viem/chains";
+ *
+ * const signer = createWalletClient({
+ * account,
+ * chain: polygonMumbai,
+ * transport: http(),
+ * });
+ *
+ * const smartAccount = await createSmartAccountClient({ signer, bundlerUrl }); // Retrieve bundler url from dasboard
+ * const encodedCall = encodeFunctionData({
+ * abi: parseAbi(["function safeMint(address to) public"]),
+ * functionName: "safeMint",
+ * args: ["0x..."],
+ * });
+ *
+ * const transaction = {
+ * to: nftAddress,
+ * data: encodedCall
+ * }
+ *
+ * const { waitForTxHash } = await smartAccount.sendTransaction(transaction);
+ * const { transactionHash, userOperationReceipt } = await wait();
+ *
+ */
+ async sendTransaction(manyOrOneTransactions: Transaction | Transaction[], buildUseropDto?: BuildUserOpOptions): Promise {
+ const userOp = await this.buildUserOp(Array.isArray(manyOrOneTransactions) ? manyOrOneTransactions : [manyOrOneTransactions], buildUseropDto);
+ return this.sendUserOp(userOp, { simulationType: buildUseropDto?.simulationType, ...buildUseropDto?.params });
}
- async buildUserOp(transactions: Transaction[], buildUseropDto?: BuildUserOpOptions): Promise> {
- const to = transactions.map((element: Transaction) => element.to);
- const data = transactions.map((element: Transaction) => element.data ?? "0x");
- const value = transactions.map((element: Transaction) => element.value ?? BigNumber.from("0"));
+ /**
+ * Builds a user operation
+ *
+ * - Docs: https://docs.biconomy.io/Account/transactions/userpaid#build-useroperation
+ *
+ * @param transactions Array of {@link Transaction} to be sent.
+ * @param buildUseropDto {@link BuildUserOpOptions}.
+ * @returns Promise> the built user operation to be sent.
+ *
+ * @example
+ * import { createClient } from "viem"
+ * import { createSmartAccountClient } from "@biconomy/account"
+ * import { createWalletClient, http } from "viem";
+ * import { polygonMumbai } from "viem/chains";
+ *
+ * const signer = createWalletClient({
+ * account,
+ * chain: polygonMumbai,
+ * transport: http(),
+ * });
+ *
+ * const smartAccount = await createSmartAccountClient({ signer, bundlerUrl }); // Retrieve bundler url from dasboard
+ * const encodedCall = encodeFunctionData({
+ * abi: parseAbi(["function safeMint(address to) public"]),
+ * functionName: "safeMint",
+ * args: ["0x..."],
+ * });
+ *
+ * const transaction = {
+ * to: nftAddress,
+ * data: encodedCall
+ * }
+ *
+ * const userOp = await smartAccount.buildUserOp([{ to: "0x...", data: encodedCall }]);
+ *
+ */
+ async buildUserOp(transactions: Transaction[], buildUseropDto?: BuildUserOpOptions): Promise> {
+ const to = transactions.map((element: Transaction) => element.to as Hex);
+ const data = transactions.map((element: Transaction) => (element.data as Hex) ?? "0x");
+ const value = transactions.map((element: Transaction) => (element.value as bigint) ?? BigInt(0));
const initCodeFetchPromise = this.getInitCode();
- const dummySignatureFetchPromise = this.getDummySignature(buildUseropDto?.params);
+ const dummySignatureFetchPromise = this.getDummySignatures(buildUseropDto?.params);
- const [nonceFromFetch, initCode, signature, finalGasFeeValue] = await Promise.all([
+ const [nonceFromFetch, initCode, signature] = await Promise.all([
this.getBuildUserOpNonce(buildUseropDto?.nonceOptions),
initCodeFetchPromise,
dummySignatureFetchPromise,
- this.getGasFeeValues(buildUseropDto?.overrides, buildUseropDto?.skipBundlerGasEstimation),
]);
if (transactions.length === 0) {
throw new Error("Transactions array cannot be empty");
}
- let callData = "";
+ let callData: Hex = "0x";
if (transactions.length > 1 || buildUseropDto?.forceEncodeForBatch) {
callData = await this.encodeExecuteBatch(to, value, data);
} else {
@@ -479,47 +880,44 @@ export class BiconomySmartAccountV2 extends BaseSmartAccount {
callData = await this.encodeExecute(to[0], value[0], data[0]);
}
- let userOp: Partial = {
- sender: await this.getAccountAddress(),
- nonce: nonceFromFetch,
+ let userOp: Partial = {
+ sender: (await this.getAccountAddress()) as Hex,
+ nonce: toHex(nonceFromFetch),
initCode,
callData: callData,
- maxFeePerGas: finalGasFeeValue.maxFeePerGas || undefined,
- maxPriorityFeePerGas: finalGasFeeValue.maxPriorityFeePerGas || undefined,
};
// for this Smart Account current validation module dummy signature will be used to estimate gas
userOp.signature = signature;
// Note: Can change the default behaviour of calling estimations using bundler/local
- userOp = await this.estimateUserOpGas({
- userOp,
- overrides: buildUseropDto?.overrides,
- skipBundlerGasEstimation: buildUseropDto?.skipBundlerGasEstimation,
- paymasterServiceData: buildUseropDto?.paymasterServiceData,
- });
- userOp.paymasterAndData = userOp.paymasterAndData ?? "0x";
+ userOp = await this.estimateUserOpGas(userOp);
+
+ if (buildUseropDto?.paymasterServiceData) {
+ userOp = await this.getPaymasterUserOp(userOp, buildUseropDto.paymasterServiceData);
+ }
+
Logger.log("UserOp after estimation ", userOp);
return userOp;
}
- private validateUserOpAndPaymasterRequest(userOp: Partial, tokenPaymasterRequest: BiconomyTokenPaymasterRequest): void {
+ private validateUserOpAndPaymasterRequest(userOp: Partial, tokenPaymasterRequest: BiconomyTokenPaymasterRequest): void {
if (isNullOrUndefined(userOp.callData)) {
throw new Error("UserOp callData cannot be undefined");
}
const feeTokenAddress = tokenPaymasterRequest?.feeQuote?.tokenAddress;
- Logger.log("Requested fee token is ", feeTokenAddress);
+ Logger.warn("Requested fee token is ", feeTokenAddress);
- if (!feeTokenAddress || feeTokenAddress == ethers.constants.AddressZero) {
+ if (!feeTokenAddress || feeTokenAddress === ADDRESS_ZERO) {
throw new Error("Invalid or missing token address. Token address must be part of the feeQuote in tokenPaymasterRequest");
}
const spender = tokenPaymasterRequest?.spender;
- Logger.log("Spender address is ", spender);
+ Logger.warn("Spender address is ", spender);
- if (!spender || spender == ethers.constants.AddressZero) {
+ if (!spender || spender === ADDRESS_ZERO) {
throw new Error("Invalid or missing spender address. Sepnder address must be part of tokenPaymasterRequest");
}
}
@@ -534,29 +932,28 @@ export class BiconomySmartAccountV2 extends BaseSmartAccount {
* @returns updated userOp with new callData, callGasLimit
*/
async buildTokenPaymasterUserOp(
- userOp: Partial,
+ userOp: Partial,
tokenPaymasterRequest: BiconomyTokenPaymasterRequest,
- ): Promise> {
+ ): Promise> {
this.validateUserOpAndPaymasterRequest(userOp, tokenPaymasterRequest);
try {
- let batchTo: Array = [];
- let batchValue: Array = [];
- let batchData: Array = [];
+ let batchTo: Array = [];
+ let batchValue: Array = [];
+ let batchData: Array = [];
let newCallData = userOp.callData;
- Logger.log("Received information about fee token address and quote ", tokenPaymasterRequest);
+ Logger.warn("Received information about fee token address and quote ", tokenPaymasterRequest);
- if (this.paymaster && this.paymaster instanceof BiconomyPaymaster) {
+ if (this.paymaster && this.paymaster instanceof Paymaster) {
// Make a call to paymaster.buildTokenApprovalTransaction() with necessary details
// Review: might request this form of an array of Transaction
const approvalRequest: Transaction = await (this.paymaster as IHybridPaymaster).buildTokenApprovalTransaction(
tokenPaymasterRequest,
- this.provider,
);
- Logger.log("ApprovalRequest is for erc20 token ", approvalRequest.to);
+ Logger.warn("ApprovalRequest is for erc20 token ", approvalRequest.to);
- if (approvalRequest.data == "0x" || approvalRequest.to == ethers.constants.AddressZero) {
+ if (approvalRequest.data === "0x" || approvalRequest.to === ADDRESS_ZERO) {
return userOp;
}
@@ -564,18 +961,18 @@ export class BiconomySmartAccountV2 extends BaseSmartAccount {
throw new Error("UserOp callData cannot be undefined");
}
- const account = await this._getAccountContract();
-
- const decodedSmartAccountData = account.interface.parseTransaction({
- data: userOp.callData!.toString(),
+ const decodedSmartAccountData = decodeFunctionData({
+ abi: BiconomyAccountAbi,
+ data: userOp.callData as Hex,
});
+
if (!decodedSmartAccountData) {
throw new Error("Could not parse userOp call data for this smart account");
}
- const smartAccountExecFunctionName = decodedSmartAccountData.name;
+ const smartAccountExecFunctionName = decodedSmartAccountData.functionName;
- Logger.log(`Originally an ${smartAccountExecFunctionName} method call for Biconomy Account V2`);
+ Logger.warn(`Originally an ${smartAccountExecFunctionName} method call for Biconomy Account V2`);
if (smartAccountExecFunctionName === "execute" || smartAccountExecFunctionName === "execute_ncC") {
const methodArgsSmartWalletExecuteCall = decodedSmartAccountData.args;
const toOriginal = methodArgsSmartWalletExecuteCall[0];
@@ -587,23 +984,25 @@ export class BiconomySmartAccountV2 extends BaseSmartAccount {
batchData.push(dataOriginal);
} else if (smartAccountExecFunctionName === "executeBatch" || smartAccountExecFunctionName === "executeBatch_y6U") {
const methodArgsSmartWalletExecuteCall = decodedSmartAccountData.args;
- batchTo = methodArgsSmartWalletExecuteCall[0];
- batchValue = methodArgsSmartWalletExecuteCall[1];
- batchData = methodArgsSmartWalletExecuteCall[2];
+ batchTo = [...methodArgsSmartWalletExecuteCall[0]];
+ batchValue = [...methodArgsSmartWalletExecuteCall[1]];
+ batchData = [...methodArgsSmartWalletExecuteCall[2]];
}
if (approvalRequest.to && approvalRequest.data && approvalRequest.value) {
- batchTo = [approvalRequest.to, ...batchTo];
- batchValue = [approvalRequest.value, ...batchValue];
- batchData = [approvalRequest.data, ...batchData];
+ batchTo = [approvalRequest.to as Hex, ...batchTo];
+ batchValue = [BigInt(Number(approvalRequest.value.toString())), ...batchValue];
+ batchData = [approvalRequest.data as Hex, ...batchData];
newCallData = await this.encodeExecuteBatch(batchTo, batchValue, batchData);
}
- const finalUserOp: Partial = {
+ const finalUserOp: Partial = {
...userOp,
callData: newCallData,
};
+ // Optionally Requesting to update gas limits again (especially callGasLimit needs to be re-calculated)
+
return finalUserOp;
}
} catch (error) {
@@ -614,125 +1013,98 @@ export class BiconomySmartAccountV2 extends BaseSmartAccount {
return userOp;
}
- async signUserOpHash(userOpHash: string, params?: SendUserOpParams): Promise {
+ async signUserOpHash(userOpHash: string, params?: ModuleInfo): Promise {
this.isActiveValidationModuleDefined();
- const moduleSig = await this.activeValidationModule.signUserOpHash(userOpHash, params);
+ const moduleSig = (await this.activeValidationModule.signUserOpHash(userOpHash, params)) as Hex;
- const signatureWithModuleAddress = ethers.utils.defaultAbiCoder.encode(
- ["bytes", "address"],
- [moduleSig, this.activeValidationModule.getAddress()],
- );
+ const signatureWithModuleAddress = encodeAbiParameters(parseAbiParameters("bytes, address"), [
+ moduleSig,
+ this.activeValidationModule.getAddress() as Hex,
+ ]);
return signatureWithModuleAddress;
}
- async signMessage(message: Bytes | string): Promise {
+ async signMessage(message: string | Uint8Array): Promise {
this.isActiveValidationModuleDefined();
- const dataHash = ethers.utils.arrayify(ethers.utils.hashMessage(message));
+ const dataHash = typeof message === "string" ? toBytes(message) : message;
let signature = await this.activeValidationModule.signMessage(dataHash);
+ const potentiallyIncorrectV = parseInt(signature.slice(-2), 16);
+ if (![27, 28].includes(potentiallyIncorrectV)) {
+ const correctV = potentiallyIncorrectV + 27;
+ signature = signature.slice(0, -2) + correctV.toString(16);
+ }
if (signature.slice(0, 2) !== "0x") {
signature = "0x" + signature;
}
-
- // If the account is undeployed, use ERC-6492
- if (!(await this.isAccountDeployed(await this.getAccountAddress()))) {
- const coder = new ethers.utils.AbiCoder();
- const populatedTransaction = await this.factory?.populateTransaction.deployCounterFactualAccount(
- await this.defaultValidationModule.getAddress(),
- await this.defaultValidationModule.getInitData(),
- this.index,
- );
- signature =
- coder.encode(["address", "bytes", "bytes"], [this.factoryAddress, populatedTransaction?.data, signature]) +
- "6492649264926492649264926492649264926492649264926492649264926492"; // magic suffix
- }
- return signature;
- }
-
- async getAllTokenBalances(balancesDto: BalancesDto): Promise {
- return this.nodeClient.getAllTokenBalances(balancesDto);
+ return signature as Hex;
}
- async getTotalBalanceInUsd(balancesDto: BalancesDto): Promise {
- return this.nodeClient.getTotalBalanceInUsd(balancesDto);
- }
-
- async getSmartAccountsByOwner(smartAccountByOwnerDto: SmartAccountByOwnerDto): Promise {
- return this.nodeClient.getSmartAccountsByOwner(smartAccountByOwnerDto);
- }
-
- async getTransactionsByAddress(chainId: number, address: string): Promise {
- return this.nodeClient.getTransactionByAddress(chainId, address);
- }
-
- async getTransactionByHash(txHash: string): Promise {
- return this.nodeClient.getTransactionByHash(txHash);
- }
-
- async getAllSupportedChains(): Promise {
- return this.nodeClient.getAllSupportedChains();
- }
-
- getImplementationAddress(): string {
- return this.implementationAddress;
- }
-
- async enableModule(moduleAddress: string): Promise {
+ async enableModule(moduleAddress: Hex): Promise {
const tx: Transaction = await this.getEnableModuleData(moduleAddress);
const partialUserOp = await this.buildUserOp([tx]);
return this.sendUserOp(partialUserOp);
}
- async getEnableModuleData(moduleAddress: string): Promise {
- const accountContract = await this._getAccountContract();
- const data = accountContract.interface.encodeFunctionData("enableModule", [moduleAddress]);
+ async getEnableModuleData(moduleAddress: Hex): Promise {
+ const callData = encodeFunctionData({
+ abi: BiconomyAccountAbi,
+ functionName: "enableModule",
+ args: [moduleAddress],
+ });
const tx: Transaction = {
- to: await this.getAccountAddress(),
- value: "0",
- data: data as string,
+ to: await this.getAddress(),
+ value: "0x00",
+ data: callData,
};
return tx;
}
- async getSetupAndEnableModuleData(moduleAddress: string, moduleSetupData: string): Promise {
- const accountContract = await this._getAccountContract();
- const data = accountContract.interface.encodeFunctionData("setupAndEnableModule", [moduleAddress, moduleSetupData]);
+ async getSetupAndEnableModuleData(moduleAddress: Hex, moduleSetupData: Hex): Promise {
+ const callData = encodeFunctionData({
+ abi: BiconomyAccountAbi,
+ functionName: "setupAndEnableModule",
+ args: [moduleAddress, moduleSetupData],
+ });
const tx: Transaction = {
- to: await this.getAccountAddress(),
- value: "0",
- data: data,
+ to: await this.getAddress(),
+ value: "0x00",
+ data: callData,
};
return tx;
}
- async disableModule(prevModule: string, moduleAddress: string): Promise {
+ async disableModule(prevModule: Hex, moduleAddress: Hex): Promise {
const tx: Transaction = await this.getDisableModuleData(prevModule, moduleAddress);
const partialUserOp = await this.buildUserOp([tx]);
return this.sendUserOp(partialUserOp);
}
- async getDisableModuleData(prevModule: string, moduleAddress: string): Promise {
- const accountContract = await this._getAccountContract();
- const data = accountContract.interface.encodeFunctionData("disableModule", [prevModule, moduleAddress]);
+ async getDisableModuleData(prevModule: Hex, moduleAddress: Hex): Promise {
+ const callData = encodeFunctionData({
+ abi: BiconomyAccountAbi,
+ functionName: "disableModule",
+ args: [prevModule, moduleAddress],
+ });
const tx: Transaction = {
- to: await this.getAccountAddress(),
- value: "0",
- data: data,
+ to: await this.getAddress(),
+ value: "0x00",
+ data: callData,
};
return tx;
}
- async isModuleEnabled(moduleName: string): Promise {
+ async isModuleEnabled(moduleAddress: Hex): Promise {
const accountContract = await this._getAccountContract();
- return accountContract.isModuleEnabled(moduleName);
+ return accountContract.read.isModuleEnabled([moduleAddress]);
}
+ // Review
async getAllModules(pageSize?: number): Promise> {
pageSize = pageSize ?? 100;
const accountContract = await this._getAccountContract();
- // Note: If page size is lower then on the next page start module would be module at the end of first page and not SENTINEL_MODULE
- const result: Array> = await accountContract.getModulesPaginated(this.SENTINEL_MODULE, pageSize);
+ const result = await accountContract.read.getModulesPaginated([this.SENTINEL_MODULE as Hex, BigInt(pageSize)]);
const modules: Array = result[0] as Array;
return modules;
}
diff --git a/packages/account/src/SmartAccount.ts b/packages/account/src/SmartAccount.ts
deleted file mode 100644
index 7ca709e97..000000000
--- a/packages/account/src/SmartAccount.ts
+++ /dev/null
@@ -1,314 +0,0 @@
-import { JsonRpcProvider } from "@ethersproject/providers";
-import { BigNumber, Signer, BytesLike } from "ethers";
-import { ISmartAccount } from "./interfaces/ISmartAccount";
-import { defaultAbiCoder, keccak256, arrayify } from "ethers/lib/utils";
-import { UserOperation, ChainId } from "@biconomy/core-types";
-import { calcPreVerificationGas, DefaultGasLimits } from "./utils/Preverificaiton";
-import { packUserOp, isNullOrUndefined } from "@biconomy/common";
-
-import { IBundler, UserOpResponse } from "@biconomy/bundler";
-import { IPaymaster, PaymasterAndDataResponse } from "@biconomy/paymaster";
-import { Logger } from "@biconomy/common";
-import { IEntryPoint } from "@account-abstraction/contracts";
-import { SponsorUserOperationDto, BiconomyPaymaster, IHybridPaymaster, PaymasterMode } from "@biconomy/paymaster";
-import { SmartAccountConfig, SendUserOpDto, EstimateUserOpGasParams } from "./utils/Types";
-import { DefaultGasLimit } from "./utils/Constants";
-
-type UserOperationKey = keyof UserOperation;
-
-// Notice: only to be used as base class for child class BiconomySmartAccount(V1)
-export abstract class SmartAccount implements ISmartAccount {
- bundler!: IBundler;
-
- paymaster!: IPaymaster;
-
- initCode = "0x";
-
- // Ideally proxy should be defined in child class, if it's meant to be of type Biconomy SmartAccount
- proxy!: any;
-
- owner!: string;
-
- provider!: JsonRpcProvider;
-
- entryPoint!: IEntryPoint;
-
- chainId!: ChainId;
-
- signer!: Signer;
-
- smartAccountConfig: SmartAccountConfig;
-
- constructor(_smartAccountConfig: SmartAccountConfig) {
- this.smartAccountConfig = _smartAccountConfig;
- }
-
- setEntryPointAddress(entryPointAddress: string): void {
- this.smartAccountConfig.entryPointAddress = entryPointAddress;
- }
-
- private validateUserOp(userOp: Partial, requiredFields: UserOperationKey[]): boolean {
- for (const field of requiredFields) {
- if (isNullOrUndefined(userOp[field])) {
- throw new Error(`${String(field)} is missing in the UserOp`);
- }
- }
- return true;
- }
-
- isProxyDefined(): boolean {
- if (!this.proxy) throw new Error("Proxy is undefined");
-
- return true;
- }
-
- isSignerDefined(): boolean {
- if (!this.signer) throw new Error("Signer is undefined");
-
- return true;
- }
-
- isProviderDefined(): boolean {
- if (!this.provider) throw new Error("Provider is undefined");
-
- return true;
- }
-
- abstract getDummySignature(): string;
-
- async calculateUserOpGasValues(userOp: Partial): Promise> {
- if (!this.provider) throw new Error("Provider is not present for making rpc calls");
- const feeData = await this.provider.getFeeData();
- userOp.maxFeePerGas = userOp.maxFeePerGas ?? feeData.maxFeePerGas ?? feeData.gasPrice ?? (await this.provider.getGasPrice());
- userOp.maxPriorityFeePerGas =
- userOp.maxPriorityFeePerGas ?? feeData.maxPriorityFeePerGas ?? feeData.gasPrice ?? (await this.provider.getGasPrice());
- if (userOp.initCode)
- userOp.verificationGasLimit =
- userOp.verificationGasLimit !== null || userOp.verificationGasLimit !== undefined
- ? userOp.verificationGasLimit
- : await this.getVerificationGasLimit(userOp.initCode);
- userOp.callGasLimit =
- userOp.callGasLimit !== null || userOp.callGasLimit !== undefined
- ? userOp.callGasLimit
- : await this.provider.estimateGas({
- from: this.smartAccountConfig.entryPointAddress,
- to: userOp.sender,
- data: userOp.callData,
- });
- userOp.preVerificationGas =
- userOp.preVerificationGas !== null || userOp.preVerificationGas !== undefined ? userOp.preVerificationGas : this.getPreVerificationGas(userOp);
- return userOp;
- }
-
- async estimateUserOpGas(params: EstimateUserOpGasParams): Promise> {
- let userOp = params.userOp;
- const { overrides, skipBundlerGasEstimation, paymasterServiceData } = params;
- const requiredFields: UserOperationKey[] = ["sender", "nonce", "initCode", "callData"];
- this.validateUserOp(userOp, requiredFields);
-
- let finalUserOp = userOp;
- const skipBundlerCall = skipBundlerGasEstimation ?? true;
- // Override gas values in userOp if provided in overrides params
- if (overrides) {
- userOp = { ...userOp, ...overrides };
- }
-
- Logger.log("userOp in estimation", userOp);
-
- if (skipBundlerCall) {
- if (this.paymaster && this.paymaster instanceof BiconomyPaymaster) {
- if (isNullOrUndefined(userOp.maxFeePerGas) || isNullOrUndefined(userOp.maxPriorityFeePerGas)) {
- throw new Error("maxFeePerGas and maxPriorityFeePerGas are required for skipBundlerCall mode");
- }
- if (paymasterServiceData?.mode === PaymasterMode.SPONSORED) {
- const v1BiconomyInfo = {
- name: "BICONOMY",
- version: "1.0.0",
- };
- const smartAccountInfo = paymasterServiceData?.smartAccountInfo ?? v1BiconomyInfo;
- paymasterServiceData.smartAccountInfo = smartAccountInfo;
-
- // Making call to paymaster to get gas estimations for userOp
- const { callGasLimit, verificationGasLimit, preVerificationGas, paymasterAndData } = await (
- this.paymaster as IHybridPaymaster
- ).getPaymasterAndData(userOp, paymasterServiceData);
- if (paymasterAndData === "0x" && (callGasLimit === undefined || verificationGasLimit === undefined || preVerificationGas === undefined)) {
- throw new Error("Since you intend to use sponsorship paymaster, please check and make sure policies are set on the dashboard");
- }
- finalUserOp.verificationGasLimit = verificationGasLimit ?? userOp.verificationGasLimit;
- finalUserOp.callGasLimit = callGasLimit ?? userOp.callGasLimit;
- finalUserOp.preVerificationGas = preVerificationGas ?? userOp.preVerificationGas;
- finalUserOp.paymasterAndData = paymasterAndData ?? userOp.paymasterAndData;
- } else {
- // use dummy values for gas limits as fee quote call will ignore this later.
- finalUserOp.callGasLimit = DefaultGasLimit.callGasLimit;
- finalUserOp.verificationGasLimit = DefaultGasLimit.verificationGasLimit;
- finalUserOp.preVerificationGas = DefaultGasLimit.preVerificationGas;
- }
- } else {
- Logger.warn("Skipped paymaster call. If you are using paymasterAndData, generate data externally");
- finalUserOp = await this.calculateUserOpGasValues(userOp);
- finalUserOp.paymasterAndData = "0x";
- }
- } else {
- if (!this.bundler) throw new Error("Bundler is not provided");
- delete userOp.maxFeePerGas;
- delete userOp.maxPriorityFeePerGas;
- // Making call to bundler to get gas estimations for userOp
- const { callGasLimit, verificationGasLimit, preVerificationGas, maxFeePerGas, maxPriorityFeePerGas } =
- await this.bundler.estimateUserOpGas(userOp);
- // if neither user sent gas fee nor the bundler, estimate gas from provider
- if (
- isNullOrUndefined(userOp.maxFeePerGas) &&
- isNullOrUndefined(userOp.maxPriorityFeePerGas) &&
- (isNullOrUndefined(maxFeePerGas) || isNullOrUndefined(maxPriorityFeePerGas))
- ) {
- const feeData = await this.provider.getFeeData();
- finalUserOp.maxFeePerGas = feeData.maxFeePerGas ?? feeData.gasPrice ?? (await this.provider.getGasPrice());
- finalUserOp.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? feeData.gasPrice ?? (await this.provider.getGasPrice());
- } else {
- finalUserOp.maxFeePerGas = maxFeePerGas ?? userOp.maxFeePerGas;
- finalUserOp.maxPriorityFeePerGas = maxPriorityFeePerGas ?? userOp.maxPriorityFeePerGas;
- }
- finalUserOp.verificationGasLimit = verificationGasLimit ?? userOp.verificationGasLimit;
- finalUserOp.callGasLimit = callGasLimit ?? userOp.callGasLimit;
- finalUserOp.preVerificationGas = preVerificationGas ?? userOp.preVerificationGas;
- finalUserOp.paymasterAndData = "0x";
- }
- return finalUserOp;
- }
-
- async isAccountDeployed(address: string): Promise {
- this.isProviderDefined();
- const contractCode = await this.provider.getCode(address);
- return contractCode !== "0x";
- }
-
- // Would only be used if paymaster is attached
- async getPaymasterAndData(userOp: Partial): Promise {
- if (this.paymaster) {
- const paymasterAndDataResponse: PaymasterAndDataResponse = await this.paymaster.getPaymasterAndData(userOp);
- return paymasterAndDataResponse.paymasterAndData;
- }
- return "0x";
- }
-
- nonce(): Promise {
- this.isProxyDefined();
- return this.proxy.nonce();
- }
-
- async signUserOpHash(userOpHash: string, signer?: Signer): Promise {
- if (signer) {
- return signer.signMessage(arrayify(userOpHash));
- }
- if (this.signer) {
- return this.signer.signMessage(arrayify(userOpHash));
- }
- throw new Error("No signer provided to sign userOp");
- }
-
- getPreVerificationGas(userOp: Partial): BigNumber {
- return calcPreVerificationGas(userOp);
- }
-
- async getVerificationGasLimit(initCode: BytesLike): Promise {
- // Verification gas should be max(initGas(wallet deployment) + validateUserOp + validatePaymasterUserOp , postOp)
-
- const initGas = await this.estimateCreationGas(initCode as string);
- const validateUserOpGas = BigNumber.from(DefaultGasLimits.validatePaymasterUserOpGas + DefaultGasLimits.validateUserOpGas);
- const postOpGas = BigNumber.from(DefaultGasLimits.postOpGas);
-
- let verificationGasLimit = BigNumber.from(validateUserOpGas).add(initGas);
-
- if (BigNumber.from(postOpGas).gt(verificationGasLimit)) {
- verificationGasLimit = postOpGas;
- }
- return verificationGasLimit;
- }
-
- async getUserOpHash(userOp: Partial): Promise {
- const userOpHash = keccak256(packUserOp(userOp, true));
- const enc = defaultAbiCoder.encode(["bytes32", "address", "uint256"], [userOpHash, this.entryPoint.address, this.chainId]);
- return keccak256(enc);
- }
-
- abstract getSmartAccountAddress(_accountIndex: number): Promise;
-
- async estimateCreationGas(initCode: string): Promise {
- if (initCode == null || initCode === "0x") return BigNumber.from("0");
- const deployerAddress = initCode.substring(0, 42);
- const deployerCallData = "0x" + initCode.substring(42);
- return this.provider.estimateGas({ to: deployerAddress, data: deployerCallData });
- }
-
- async signUserOp(userOp: Partial): Promise {
- const requiredFields: UserOperationKey[] = [
- "sender",
- "nonce",
- "initCode",
- "callData",
- "callGasLimit",
- "verificationGasLimit",
- "preVerificationGas",
- "maxFeePerGas",
- "maxPriorityFeePerGas",
- "paymasterAndData",
- ];
- this.validateUserOp(userOp, requiredFields);
- const userOpHash = await this.getUserOpHash(userOp);
- let signature = await this.signUserOpHash(userOpHash, this.signer);
- const potentiallyIncorrectV = parseInt(signature.slice(-2), 16);
- if (![27, 28].includes(potentiallyIncorrectV)) {
- const correctV = potentiallyIncorrectV + 27;
- signature = signature.slice(0, -2) + correctV.toString(16);
- }
- if (signature.slice(0, 2) !== "0x") {
- signature = "0x" + signature;
- }
- userOp.signature = signature;
- return userOp as UserOperation;
- }
-
- /**
- *
- * @param userOp
- * @description This function call will take 'unsignedUserOp' as an input, sign it with the owner key, and send it to the bundler.
- * @returns Promise
- */
- async sendUserOp(userOp: Partial, params?: SendUserOpDto): Promise {
- Logger.log("userOp received in base account ", userOp);
- delete userOp.signature;
- const userOperation = await this.signUserOp(userOp);
- const bundlerResponse = await this.sendSignedUserOp(userOperation, params);
- return bundlerResponse;
- }
-
- /**
- *
- * @param userOp
- * @description This function call will take 'signedUserOp' as input and send it to the bundler
- * @returns
- */
- async sendSignedUserOp(userOp: UserOperation, params?: SendUserOpDto): Promise {
- const requiredFields: UserOperationKey[] = [
- "sender",
- "nonce",
- "initCode",
- "callData",
- "callGasLimit",
- "verificationGasLimit",
- "preVerificationGas",
- "maxFeePerGas",
- "maxPriorityFeePerGas",
- "paymasterAndData",
- "signature",
- ];
- this.validateUserOp(userOp, requiredFields);
- Logger.log("userOp validated");
- if (!this.bundler) throw new Error("Bundler is not provided");
- Logger.log("userOp being sent to the bundler", userOp);
- const bundlerResponse = await this.bundler.sendUserOp(userOp, params?.simulationType);
- return bundlerResponse;
- }
-}
diff --git a/packages/account/src/abi/AccountResolver.ts b/packages/account/src/abi/AccountResolver.ts
new file mode 100644
index 000000000..0cbffc749
--- /dev/null
+++ b/packages/account/src/abi/AccountResolver.ts
@@ -0,0 +1,106 @@
+export const AccountResolverAbi = [
+ {
+ inputs: [
+ { internalType: "address", name: "_v1Factory", type: "address" },
+ { internalType: "address", name: "_v2Factory", type: "address" },
+ { internalType: "address", name: "_ecdsaModule", type: "address" },
+ ],
+ stateMutability: "nonpayable",
+ type: "constructor",
+ },
+ {
+ inputs: [],
+ name: "ecdsaOwnershipModule",
+ outputs: [{ internalType: "address", name: "", type: "address" }],
+ stateMutability: "view",
+ type: "function",
+ },
+ {
+ inputs: [
+ { internalType: "address", name: "_eoa", type: "address" },
+ { internalType: "uint8", name: "_maxIndex", type: "uint8" },
+ ],
+ name: "resolveAddresses",
+ outputs: [
+ {
+ components: [
+ { internalType: "address", name: "accountAddress", type: "address" },
+ { internalType: "address", name: "factoryAddress", type: "address" },
+ { internalType: "address", name: "currentImplementation", type: "address" },
+ { internalType: "string", name: "currentVersion", type: "string" },
+ { internalType: "string", name: "factoryVersion", type: "string" },
+ { internalType: "uint256", name: "deploymentIndex", type: "uint256" },
+ ],
+ internalType: "struct IAddressResolver.SmartAccountResult[]",
+ name: "",
+ type: "tuple[]",
+ },
+ ],
+ stateMutability: "view",
+ type: "function",
+ },
+ {
+ inputs: [
+ { internalType: "address", name: "_eoa", type: "address" },
+ { internalType: "uint8", name: "_maxIndex", type: "uint8" },
+ { internalType: "address", name: "_moduleAddress", type: "address" },
+ { internalType: "bytes", name: "_moduleSetupData", type: "bytes" },
+ ],
+ name: "resolveAddressesFlexibleForV2",
+ outputs: [
+ {
+ components: [
+ { internalType: "address", name: "accountAddress", type: "address" },
+ { internalType: "address", name: "factoryAddress", type: "address" },
+ { internalType: "address", name: "currentImplementation", type: "address" },
+ { internalType: "string", name: "currentVersion", type: "string" },
+ { internalType: "string", name: "factoryVersion", type: "string" },
+ { internalType: "uint256", name: "deploymentIndex", type: "uint256" },
+ ],
+ internalType: "struct IAddressResolver.SmartAccountResult[]",
+ name: "",
+ type: "tuple[]",
+ },
+ ],
+ stateMutability: "view",
+ type: "function",
+ },
+ {
+ inputs: [
+ { internalType: "address", name: "_eoa", type: "address" },
+ { internalType: "uint8", name: "_maxIndex", type: "uint8" },
+ ],
+ name: "resolveAddressesV1",
+ outputs: [
+ {
+ components: [
+ { internalType: "address", name: "accountAddress", type: "address" },
+ { internalType: "address", name: "factoryAddress", type: "address" },
+ { internalType: "address", name: "currentImplementation", type: "address" },
+ { internalType: "string", name: "currentVersion", type: "string" },
+ { internalType: "string", name: "factoryVersion", type: "string" },
+ { internalType: "uint256", name: "deploymentIndex", type: "uint256" },
+ ],
+ internalType: "struct IAddressResolver.SmartAccountResult[]",
+ name: "",
+ type: "tuple[]",
+ },
+ ],
+ stateMutability: "view",
+ type: "function",
+ },
+ {
+ inputs: [],
+ name: "smartAccountFactoryV1",
+ outputs: [{ internalType: "address", name: "", type: "address" }],
+ stateMutability: "view",
+ type: "function",
+ },
+ {
+ inputs: [],
+ name: "smartAccountFactoryV2",
+ outputs: [{ internalType: "address", name: "", type: "address" }],
+ stateMutability: "view",
+ type: "function",
+ },
+] as const;
diff --git a/packages/account/src/abi/Factory.ts b/packages/account/src/abi/Factory.ts
new file mode 100644
index 000000000..3498dd958
--- /dev/null
+++ b/packages/account/src/abi/Factory.ts
@@ -0,0 +1,141 @@
+export const BiconomyFactoryAbi = [
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: true,
+ internalType: "address",
+ name: "account",
+ type: "address",
+ },
+ {
+ indexed: true,
+ internalType: "address",
+ name: "initialAuthModule",
+ type: "address",
+ },
+ {
+ indexed: true,
+ internalType: "uint256",
+ name: "index",
+ type: "uint256",
+ },
+ ],
+ name: "AccountCreation",
+ type: "event",
+ },
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: true,
+ internalType: "address",
+ name: "account",
+ type: "address",
+ },
+ {
+ indexed: true,
+ internalType: "address",
+ name: "initialAuthModule",
+ type: "address",
+ },
+ ],
+ name: "AccountCreationWithoutIndex",
+ type: "event",
+ },
+ {
+ inputs: [],
+ name: "accountCreationCode",
+ outputs: [
+ {
+ internalType: "bytes",
+ name: "",
+ type: "bytes",
+ },
+ ],
+ stateMutability: "pure",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "moduleSetupContract",
+ type: "address",
+ },
+ {
+ internalType: "bytes",
+ name: "moduleSetupData",
+ type: "bytes",
+ },
+ ],
+ name: "deployAccount",
+ outputs: [
+ {
+ internalType: "address",
+ name: "proxy",
+ type: "address",
+ },
+ ],
+ stateMutability: "nonpayable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "moduleSetupContract",
+ type: "address",
+ },
+ {
+ internalType: "bytes",
+ name: "moduleSetupData",
+ type: "bytes",
+ },
+ {
+ internalType: "uint256",
+ name: "index",
+ type: "uint256",
+ },
+ ],
+ name: "deployCounterFactualAccount",
+ outputs: [
+ {
+ internalType: "address",
+ name: "proxy",
+ type: "address",
+ },
+ ],
+ stateMutability: "nonpayable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "moduleSetupContract",
+ type: "address",
+ },
+ {
+ internalType: "bytes",
+ name: "moduleSetupData",
+ type: "bytes",
+ },
+ {
+ internalType: "uint256",
+ name: "index",
+ type: "uint256",
+ },
+ ],
+ name: "getAddressForCounterFactualAccount",
+ outputs: [
+ {
+ internalType: "address",
+ name: "_account",
+ type: "address",
+ },
+ ],
+ stateMutability: "view",
+ type: "function",
+ },
+] as const;
diff --git a/packages/account/src/abi/SmartAccount.ts b/packages/account/src/abi/SmartAccount.ts
new file mode 100644
index 000000000..3542c17d1
--- /dev/null
+++ b/packages/account/src/abi/SmartAccount.ts
@@ -0,0 +1,944 @@
+export const BiconomyAccountAbi = [
+ {
+ inputs: [],
+ name: "AlreadyInitialized",
+ type: "error",
+ },
+ {
+ inputs: [],
+ name: "BaseImplementationCannotBeZero",
+ type: "error",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "caller",
+ type: "address",
+ },
+ ],
+ name: "CallerIsNotAnEntryPoint",
+ type: "error",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "caller",
+ type: "address",
+ },
+ ],
+ name: "CallerIsNotEntryPoint",
+ type: "error",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "caller",
+ type: "address",
+ },
+ ],
+ name: "CallerIsNotEntryPointOrOwner",
+ type: "error",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "caller",
+ type: "address",
+ },
+ ],
+ name: "CallerIsNotEntryPointOrSelf",
+ type: "error",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "caller",
+ type: "address",
+ },
+ ],
+ name: "CallerIsNotOwner",
+ type: "error",
+ },
+ {
+ inputs: [],
+ name: "DelegateCallsOnly",
+ type: "error",
+ },
+ {
+ inputs: [],
+ name: "EntryPointCannotBeZero",
+ type: "error",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "implementationAddress",
+ type: "address",
+ },
+ ],
+ name: "InvalidImplementation",
+ type: "error",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "caller",
+ type: "address",
+ },
+ ],
+ name: "MixedAuthFail",
+ type: "error",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "module",
+ type: "address",
+ },
+ ],
+ name: "ModuleAlreadyEnabled",
+ type: "error",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "expectedModule",
+ type: "address",
+ },
+ {
+ internalType: "address",
+ name: "returnedModule",
+ type: "address",
+ },
+ {
+ internalType: "address",
+ name: "prevModule",
+ type: "address",
+ },
+ ],
+ name: "ModuleAndPrevModuleMismatch",
+ type: "error",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "module",
+ type: "address",
+ },
+ ],
+ name: "ModuleCannotBeZeroOrSentinel",
+ type: "error",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "module",
+ type: "address",
+ },
+ ],
+ name: "ModuleNotEnabled",
+ type: "error",
+ },
+ {
+ inputs: [],
+ name: "ModulesAlreadyInitialized",
+ type: "error",
+ },
+ {
+ inputs: [],
+ name: "ModulesSetupExecutionFailed",
+ type: "error",
+ },
+ {
+ inputs: [],
+ name: "OwnerCanNotBeSelf",
+ type: "error",
+ },
+ {
+ inputs: [],
+ name: "OwnerCannotBeZero",
+ type: "error",
+ },
+ {
+ inputs: [],
+ name: "OwnerProvidedIsSame",
+ type: "error",
+ },
+ {
+ inputs: [],
+ name: "TransferToZeroAddressAttempt",
+ type: "error",
+ },
+ {
+ inputs: [
+ {
+ internalType: "uint256",
+ name: "destLength",
+ type: "uint256",
+ },
+ {
+ internalType: "uint256",
+ name: "valueLength",
+ type: "uint256",
+ },
+ {
+ internalType: "uint256",
+ name: "funcLength",
+ type: "uint256",
+ },
+ {
+ internalType: "uint256",
+ name: "operationLength",
+ type: "uint256",
+ },
+ ],
+ name: "WrongBatchProvided",
+ type: "error",
+ },
+ {
+ inputs: [
+ {
+ internalType: "bytes",
+ name: "contractSignature",
+ type: "bytes",
+ },
+ ],
+ name: "WrongContractSignature",
+ type: "error",
+ },
+ {
+ inputs: [
+ {
+ internalType: "uint256",
+ name: "uintS",
+ type: "uint256",
+ },
+ {
+ internalType: "uint256",
+ name: "contractSignatureLength",
+ type: "uint256",
+ },
+ {
+ internalType: "uint256",
+ name: "signatureLength",
+ type: "uint256",
+ },
+ ],
+ name: "WrongContractSignatureFormat",
+ type: "error",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "moduleAddressProvided",
+ type: "address",
+ },
+ ],
+ name: "WrongValidationModule",
+ type: "error",
+ },
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: false,
+ internalType: "address",
+ name: "module",
+ type: "address",
+ },
+ ],
+ name: "DisabledModule",
+ type: "event",
+ },
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: false,
+ internalType: "address",
+ name: "module",
+ type: "address",
+ },
+ ],
+ name: "EnabledModule",
+ type: "event",
+ },
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: true,
+ internalType: "address",
+ name: "module",
+ type: "address",
+ },
+ ],
+ name: "ExecutionFromModuleFailure",
+ type: "event",
+ },
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: true,
+ internalType: "address",
+ name: "module",
+ type: "address",
+ },
+ ],
+ name: "ExecutionFromModuleSuccess",
+ type: "event",
+ },
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: true,
+ internalType: "address",
+ name: "oldImplementation",
+ type: "address",
+ },
+ {
+ indexed: true,
+ internalType: "address",
+ name: "newImplementation",
+ type: "address",
+ },
+ ],
+ name: "ImplementationUpdated",
+ type: "event",
+ },
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: false,
+ internalType: "address",
+ name: "module",
+ type: "address",
+ },
+ {
+ indexed: false,
+ internalType: "address",
+ name: "to",
+ type: "address",
+ },
+ {
+ indexed: false,
+ internalType: "uint256",
+ name: "value",
+ type: "uint256",
+ },
+ {
+ indexed: false,
+ internalType: "bytes",
+ name: "data",
+ type: "bytes",
+ },
+ {
+ indexed: false,
+ internalType: "enum Enum.Operation",
+ name: "operation",
+ type: "uint8",
+ },
+ ],
+ name: "ModuleTransaction",
+ type: "event",
+ },
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: true,
+ internalType: "address",
+ name: "sender",
+ type: "address",
+ },
+ {
+ indexed: true,
+ internalType: "uint256",
+ name: "value",
+ type: "uint256",
+ },
+ ],
+ name: "SmartAccountReceivedNativeToken",
+ type: "event",
+ },
+ {
+ inputs: [],
+ name: "VERSION",
+ outputs: [
+ {
+ internalType: "string",
+ name: "",
+ type: "string",
+ },
+ ],
+ stateMutability: "pure",
+ type: "function",
+ },
+ {
+ inputs: [],
+ name: "addDeposit",
+ outputs: [],
+ stateMutability: "payable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "prevModule",
+ type: "address",
+ },
+ {
+ internalType: "address",
+ name: "module",
+ type: "address",
+ },
+ ],
+ name: "disableModule",
+ outputs: [],
+ stateMutability: "nonpayable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "module",
+ type: "address",
+ },
+ ],
+ name: "enableModule",
+ outputs: [],
+ stateMutability: "nonpayable",
+ type: "function",
+ },
+ {
+ inputs: [],
+ name: "entryPoint",
+ outputs: [
+ {
+ internalType: "contract IEntryPoint",
+ name: "",
+ type: "address",
+ },
+ ],
+ stateMutability: "view",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address[]",
+ name: "to",
+ type: "address[]",
+ },
+ {
+ internalType: "uint256[]",
+ name: "value",
+ type: "uint256[]",
+ },
+ {
+ internalType: "bytes[]",
+ name: "data",
+ type: "bytes[]",
+ },
+ {
+ internalType: "enum Enum.Operation[]",
+ name: "operations",
+ type: "uint8[]",
+ },
+ ],
+ name: "execBatchTransactionFromModule",
+ outputs: [
+ {
+ internalType: "bool",
+ name: "success",
+ type: "bool",
+ },
+ ],
+ stateMutability: "nonpayable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "to",
+ type: "address",
+ },
+ {
+ internalType: "uint256",
+ name: "value",
+ type: "uint256",
+ },
+ {
+ internalType: "bytes",
+ name: "data",
+ type: "bytes",
+ },
+ {
+ internalType: "enum Enum.Operation",
+ name: "operation",
+ type: "uint8",
+ },
+ {
+ internalType: "uint256",
+ name: "txGas",
+ type: "uint256",
+ },
+ ],
+ name: "execTransactionFromModule",
+ outputs: [
+ {
+ internalType: "bool",
+ name: "success",
+ type: "bool",
+ },
+ ],
+ stateMutability: "nonpayable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "to",
+ type: "address",
+ },
+ {
+ internalType: "uint256",
+ name: "value",
+ type: "uint256",
+ },
+ {
+ internalType: "bytes",
+ name: "data",
+ type: "bytes",
+ },
+ {
+ internalType: "enum Enum.Operation",
+ name: "operation",
+ type: "uint8",
+ },
+ ],
+ name: "execTransactionFromModule",
+ outputs: [
+ {
+ internalType: "bool",
+ name: "",
+ type: "bool",
+ },
+ ],
+ stateMutability: "nonpayable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "to",
+ type: "address",
+ },
+ {
+ internalType: "uint256",
+ name: "value",
+ type: "uint256",
+ },
+ {
+ internalType: "bytes",
+ name: "data",
+ type: "bytes",
+ },
+ {
+ internalType: "enum Enum.Operation",
+ name: "operation",
+ type: "uint8",
+ },
+ ],
+ name: "execTransactionFromModuleReturnData",
+ outputs: [
+ {
+ internalType: "bool",
+ name: "success",
+ type: "bool",
+ },
+ {
+ internalType: "bytes",
+ name: "returnData",
+ type: "bytes",
+ },
+ ],
+ stateMutability: "nonpayable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "dest",
+ type: "address",
+ },
+ {
+ internalType: "uint256",
+ name: "value",
+ type: "uint256",
+ },
+ {
+ internalType: "bytes",
+ name: "func",
+ type: "bytes",
+ },
+ ],
+ name: "execute",
+ outputs: [],
+ stateMutability: "nonpayable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address[]",
+ name: "dest",
+ type: "address[]",
+ },
+ {
+ internalType: "uint256[]",
+ name: "value",
+ type: "uint256[]",
+ },
+ {
+ internalType: "bytes[]",
+ name: "func",
+ type: "bytes[]",
+ },
+ ],
+ name: "executeBatch",
+ outputs: [],
+ stateMutability: "nonpayable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address[]",
+ name: "dest",
+ type: "address[]",
+ },
+ {
+ internalType: "uint256[]",
+ name: "value",
+ type: "uint256[]",
+ },
+ {
+ internalType: "bytes[]",
+ name: "func",
+ type: "bytes[]",
+ },
+ ],
+ name: "executeBatch_y6U",
+ outputs: [],
+ stateMutability: "nonpayable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "dest",
+ type: "address",
+ },
+ {
+ internalType: "uint256",
+ name: "value",
+ type: "uint256",
+ },
+ {
+ internalType: "bytes",
+ name: "func",
+ type: "bytes",
+ },
+ ],
+ name: "execute_ncC",
+ outputs: [],
+ stateMutability: "nonpayable",
+ type: "function",
+ },
+ {
+ inputs: [],
+ name: "getDeposit",
+ outputs: [
+ {
+ internalType: "uint256",
+ name: "",
+ type: "uint256",
+ },
+ ],
+ stateMutability: "view",
+ type: "function",
+ },
+ {
+ inputs: [],
+ name: "getImplementation",
+ outputs: [
+ {
+ internalType: "address",
+ name: "_implementation",
+ type: "address",
+ },
+ ],
+ stateMutability: "view",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "start",
+ type: "address",
+ },
+ {
+ internalType: "uint256",
+ name: "pageSize",
+ type: "uint256",
+ },
+ ],
+ name: "getModulesPaginated",
+ outputs: [
+ {
+ internalType: "address[]",
+ name: "array",
+ type: "address[]",
+ },
+ {
+ internalType: "address",
+ name: "next",
+ type: "address",
+ },
+ ],
+ stateMutability: "view",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "handler",
+ type: "address",
+ },
+ {
+ internalType: "address",
+ name: "moduleSetupContract",
+ type: "address",
+ },
+ {
+ internalType: "bytes",
+ name: "moduleSetupData",
+ type: "bytes",
+ },
+ ],
+ name: "init",
+ outputs: [
+ {
+ internalType: "address",
+ name: "",
+ type: "address",
+ },
+ ],
+ stateMutability: "nonpayable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "module",
+ type: "address",
+ },
+ ],
+ name: "isModuleEnabled",
+ outputs: [
+ {
+ internalType: "bool",
+ name: "",
+ type: "bool",
+ },
+ ],
+ stateMutability: "view",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "uint192",
+ name: "_key",
+ type: "uint192",
+ },
+ ],
+ name: "nonce",
+ outputs: [
+ {
+ internalType: "uint256",
+ name: "",
+ type: "uint256",
+ },
+ ],
+ stateMutability: "view",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "setupContract",
+ type: "address",
+ },
+ {
+ internalType: "bytes",
+ name: "setupData",
+ type: "bytes",
+ },
+ ],
+ name: "setupAndEnableModule",
+ outputs: [
+ {
+ internalType: "address",
+ name: "",
+ type: "address",
+ },
+ ],
+ stateMutability: "nonpayable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "_implementation",
+ type: "address",
+ },
+ ],
+ name: "updateImplementation",
+ outputs: [],
+ stateMutability: "nonpayable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ components: [
+ {
+ internalType: "address",
+ name: "sender",
+ type: "address",
+ },
+ {
+ internalType: "uint256",
+ name: "nonce",
+ type: "uint256",
+ },
+ {
+ internalType: "bytes",
+ name: "initCode",
+ type: "bytes",
+ },
+ {
+ internalType: "bytes",
+ name: "callData",
+ type: "bytes",
+ },
+ {
+ internalType: "uint256",
+ name: "callGasLimit",
+ type: "uint256",
+ },
+ {
+ internalType: "uint256",
+ name: "verificationGasLimit",
+ type: "uint256",
+ },
+ {
+ internalType: "uint256",
+ name: "preVerificationGas",
+ type: "uint256",
+ },
+ {
+ internalType: "uint256",
+ name: "maxFeePerGas",
+ type: "uint256",
+ },
+ {
+ internalType: "uint256",
+ name: "maxPriorityFeePerGas",
+ type: "uint256",
+ },
+ {
+ internalType: "bytes",
+ name: "paymasterAndData",
+ type: "bytes",
+ },
+ {
+ internalType: "bytes",
+ name: "signature",
+ type: "bytes",
+ },
+ ],
+ internalType: "struct UserOperation",
+ name: "userOp",
+ type: "tuple",
+ },
+ {
+ internalType: "bytes32",
+ name: "userOpHash",
+ type: "bytes32",
+ },
+ {
+ internalType: "uint256",
+ name: "missingAccountFunds",
+ type: "uint256",
+ },
+ ],
+ name: "validateUserOp",
+ outputs: [
+ {
+ internalType: "uint256",
+ name: "",
+ type: "uint256",
+ },
+ ],
+ stateMutability: "nonpayable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "address payable",
+ name: "withdrawAddress",
+ type: "address",
+ },
+ {
+ internalType: "uint256",
+ name: "amount",
+ type: "uint256",
+ },
+ ],
+ name: "withdrawDepositTo",
+ outputs: [],
+ stateMutability: "payable",
+ type: "function",
+ },
+] as const;
diff --git a/packages/account/src/index.ts b/packages/account/src/index.ts
index d43249476..615c0a6da 100644
--- a/packages/account/src/index.ts
+++ b/packages/account/src/index.ts
@@ -1,9 +1,43 @@
-export * from "./interfaces/ISmartAccount";
-export * from "./interfaces/IBaseSmartAccount";
-export * from "./interfaces/IBiconomySmartAccount";
-export * from "./utils/Types";
-export * from "./SmartAccount";
-export * from "./BiconomySmartAccount";
-export * from "./utils/Constants";
-export * from "./BiconomySmartAccountV2";
-export * from "./utils/VoidSigner";
+import { BiconomySmartAccountV2 } from "./BiconomySmartAccountV2.js";
+import { type BiconomySmartAccountV2Config } from "./utils/Types.js";
+
+export * from "./utils/Types.js";
+export * from "./utils/Constants.js";
+export * from "./BiconomySmartAccountV2.js";
+
+export { WalletClientSigner, LocalAccountSigner, type SmartAccountSigner } from "@alchemy/aa-core";
+export {
+ BiconomyPaymaster as Paymaster,
+ type IPaymaster,
+ PaymasterMode,
+ type IHybridPaymaster,
+ type PaymasterFeeQuote,
+ type SponsorUserOperationDto,
+ type FeeQuotesOrDataResponse,
+ createPaymaster,
+} from "@biconomy/paymaster";
+export { EthersSigner, convertSigner, type LightSigner } from "@biconomy/common";
+export {
+ Bundler,
+ type IBundler,
+ extractChainIdFromBundlerUrl,
+ type UserOpResponse,
+ type UserOpStatus,
+ type UserOpReceipt,
+ createBundler,
+} from "@biconomy/bundler";
+export {
+ createECDSAOwnershipValidationModule,
+ createERC20SessionValidationModule,
+ createBatchedSessionRouterModule,
+ createSessionKeyManagerModule,
+ createMultiChainValidationModule,
+ DEFAULT_ECDSA_OWNERSHIP_MODULE,
+ DEFAULT_SESSION_KEY_MANAGER_MODULE,
+ DEFAULT_MULTICHAIN_MODULE,
+ DEFAULT_BATCHED_SESSION_ROUTER_MODULE,
+} from "@biconomy/modules";
+
+export const createSmartAccountClient = BiconomySmartAccountV2.create;
+
+export type SmartWalletConfig = BiconomySmartAccountV2Config;
diff --git a/packages/account/src/interfaces/IBaseSmartAccount.ts b/packages/account/src/interfaces/IBaseSmartAccount.ts
deleted file mode 100644
index 64cc5fc92..000000000
--- a/packages/account/src/interfaces/IBaseSmartAccount.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { UserOperation } from "@biconomy/core-types";
-import { BigNumberish, Bytes, BytesLike, BigNumber } from "ethers";
-/**
- * Interface for Smart Contract Wallet aka Smart Account.
- * This SA does not have to implement ERC4337 interfaces
- */
-export interface INon4337Account {
- estimateCreationGas(_initCode: string): Promise;
- getNonce(): Promise;
- signMessage(_message: Bytes | string): Promise;
- getAccountAddress(_accountIndex?: number): Promise;
-}
-
-export interface IBaseSmartAccount extends INon4337Account {
- getVerificationGasLimit(_initCode: BytesLike): Promise;
- getPreVerificationGas(_userOp: Partial): Promise;
- signUserOp(_userOp: UserOperation): Promise;
- signUserOpHash(_userOpHash: string): Promise;
- getUserOpHash(_userOp: Partial): Promise;
- getAccountInitCode(): Promise;
- getDummySignature(): Promise;
-}
diff --git a/packages/account/src/interfaces/IBiconomySmartAccount.ts b/packages/account/src/interfaces/IBiconomySmartAccount.ts
deleted file mode 100644
index 2534ae461..000000000
--- a/packages/account/src/interfaces/IBiconomySmartAccount.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { UserOperation, Transaction } from "@biconomy/core-types";
-import {
- SupportedChainsResponse,
- BalancesResponse,
- BalancesDto,
- UsdBalanceResponse,
- SmartAccountByOwnerDto,
- SmartAccountsResponse,
- SCWTransactionResponse,
-} from "@biconomy/node-client";
-import { Overrides, InitilizationData } from "../utils/Types";
-import { BigNumberish, BytesLike } from "ethers";
-import { ISmartAccount } from "./ISmartAccount";
-import { Signer } from "ethers";
-
-export interface IBiconomySmartAccount extends ISmartAccount {
- init(_initilizationData?: InitilizationData): Promise;
- initializeAccountAtIndex(_accountIndex: number): void;
- getExecuteCallData(_to: string, _value: BigNumberish, _data: BytesLike): string;
- getExecuteBatchCallData(_to: Array, _value: Array, _data: Array): string;
- buildUserOp(_transactions: Transaction[], _overrides?: Overrides): Promise>;
- getAllTokenBalances(_balancesDto: BalancesDto): Promise;
- getTotalBalanceInUsd(_balancesDto: BalancesDto): Promise;
- getSmartAccountsByOwner(_smartAccountByOwnerDto: SmartAccountByOwnerDto): Promise;
- getTransactionsByAddress(_chainId: number, _address: string): Promise;
- getTransactionByHash(_txHash: string): Promise;
- getAllSupportedChains(): Promise;
- attachSigner(_signer: Signer): Promise;
-}
diff --git a/packages/account/src/interfaces/ISmartAccount.ts b/packages/account/src/interfaces/ISmartAccount.ts
deleted file mode 100644
index fce4a8f8c..000000000
--- a/packages/account/src/interfaces/ISmartAccount.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { UserOperation } from "@biconomy/core-types";
-import { UserOpResponse } from "@biconomy/bundler";
-export interface ISmartAccount {
- getSmartAccountAddress(_accountIndex: number): Promise;
- signUserOp(_userOp: UserOperation): Promise;
- sendUserOp(_userOp: UserOperation): Promise;
- sendSignedUserOp(_userOp: UserOperation): Promise;
-}
diff --git a/packages/account/src/utils/Constants.ts b/packages/account/src/utils/Constants.ts
index 40a5c053e..d6f52fc7a 100644
--- a/packages/account/src/utils/Constants.ts
+++ b/packages/account/src/utils/Constants.ts
@@ -1,4 +1,3 @@
-import { ChainId } from "@biconomy/core-types";
import {
EntryPointAddresses,
BiconomyFactories,
@@ -8,6 +7,8 @@ import {
BiconomyImplementationsByVersion,
} from "./Types";
+export const ADDRESS_ZERO = "0x0000000000000000000000000000000000000000";
+
// will always be latest entrypoint address
export const DEFAULT_ENTRYPOINT_ADDRESS = "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789";
export const ENTRYPOINT_ADDRESSES: EntryPointAddresses = {
@@ -43,7 +44,7 @@ export const BICONOMY_IMPLEMENTATION_ADDRESSES_BY_VERSION: BiconomyImplementatio
Object.entries(BICONOMY_IMPLEMENTATION_ADDRESSES).map(([k, v]) => [v, k]),
);
-export const EIP1559_UNSUPPORTED_NETWORKS: Array = [97, 56, 1442, 1101];
+export const EIP1559_UNSUPPORTED_NETWORKS: Array = [97, 56, 1442, 1101];
export const PROXY_CREATION_CODE =
"0x6080346100aa57601f61012038819003918201601f19168301916001600160401b038311848410176100af578084926020946040528339810103126100aa57516001600160a01b0381168082036100aa5715610065573055604051605a90816100c68239f35b60405162461bcd60e51b815260206004820152601e60248201527f496e76616c696420696d706c656d656e746174696f6e206164647265737300006044820152606490fd5b600080fd5b634e487b7160e01b600052604160045260246000fdfe608060405230546000808092368280378136915af43d82803e156020573d90f35b3d90fdfea2646970667358221220a03b18dce0be0b4c9afe58a9eb85c35205e2cf087da098bbf1d23945bf89496064736f6c63430008110033";
@@ -55,3 +56,9 @@ export const DefaultGasLimit = {
verificationGasLimit: 1000000,
preVerificationGas: 100000,
};
+
+export const ERROR_MESSAGES = {
+ SPENDER_REQUIRED: "spender is required for ERC20 mode",
+ NO_FEE_QUOTE: "FeeQuote was not provided, please call smartAccount.getTokenFees() to get feeQuote",
+ FAILED_FEE_QUOTE_FETCH: "Failed to fetch fee quote",
+};
diff --git a/packages/account/src/utils/Preverificaiton.ts b/packages/account/src/utils/Preverificaiton.ts
deleted file mode 100644
index 5b32b0054..000000000
--- a/packages/account/src/utils/Preverificaiton.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-import { UserOperation } from "@biconomy/core-types";
-import { NotPromise, packUserOp } from "@biconomy/common"; // '@account-abstraction/utils'
-import { arrayify, hexlify } from "ethers/lib/utils";
-import { BigNumber } from "ethers";
-export interface GasOverheads {
- /**
- * fixed overhead for entire handleOp bundle.
- */
- fixed: number;
-
- /**
- * per userOp overhead, added on top of the above fixed per-bundle.
- */
- perUserOp: number;
-
- /**
- * overhead for userOp word (32 bytes) block
- */
- perUserOpWord: number;
-
- // perCallDataWord: number
-
- /**
- * zero byte cost, for calldata gas cost calculations
- */
- zeroByte: number;
-
- /**
- * non-zero byte cost, for calldata gas cost calculations
- */
- nonZeroByte: number;
-
- /**
- * expected bundle size, to split per-bundle overhead between all ops.
- */
- bundleSize: number;
-
- /**
- * expected length of the userOp signature.
- */
- sigSize: number;
-}
-
-export interface VerificationGasLimits {
- /**
- * per userOp gasLimit for validateUserOp()
- * called from entrypoint to the account
- * should consider max execution
- */
- validateUserOpGas: number;
-
- /**
- * per userOp gasLimit for validatePaymasterUserOp()
- * called from entrypoint to the paymaster
- * should consider max execution
- */
- validatePaymasterUserOpGas: number;
-
- /**
- * per userOp gasLimit for postOp()
- * called from entrypoint to the paymaster
- * should consider max execution for paymaster/s this account may use
- */
- postOpGas: number;
-}
-
-export const DefaultGasOverheads: GasOverheads = {
- fixed: 21000,
- perUserOp: 18300,
- perUserOpWord: 4,
- zeroByte: 4,
- nonZeroByte: 16,
- bundleSize: 1,
- sigSize: 65,
-};
-
-export const DefaultGasLimits: VerificationGasLimits = {
- validateUserOpGas: 100000,
- validatePaymasterUserOpGas: 100000,
- postOpGas: 10877,
-};
-
-/**
- * calculate the preVerificationGas of the given UserOperation
- * preVerificationGas (by definition) is the cost overhead that can't be calculated on-chain.
- * it is based on parameters that are defined by the Ethereum protocol for external transactions.
- * @param userOp filled userOp to calculate. The only possible missing fields can be the signature and preVerificationGas itself
- * @param overheads gas overheads to use, to override the default values
- */
-export function calcPreVerificationGas(userOp: Partial>, overheads?: Partial): BigNumber {
- const ov = { ...DefaultGasOverheads, ...(overheads ?? {}) };
- /* eslint-disable @typescript-eslint/no-explicit-any */
- const p: NotPromise = {
- // dummy values, in case the UserOp is incomplete.
- paymasterAndData: "0x",
- preVerificationGas: BigNumber.from(21000), // dummy value, just for calldata cost
- signature: hexlify(Buffer.alloc(ov.sigSize, 1)), // dummy signature
- ...userOp,
- } as any;
-
- const packed = arrayify(packUserOp(p, false));
- const lengthInWord = (packed.length + 31) / 32;
- /**
- * general explanation
- * 21000 base gas
- * ~ 18300 gas per userOp : corresponds to _validateAccountAndPaymasterValidationData() method,
- * Some lines in _handlePostOp() after actualGasCost calculation and compensate() method called in handleOps() method
- * plus any gas overhead that can't be tracked on-chain
- * (if bundler needs to charge the premium one way is to increase this value for ops to sign)
- */
- const callDataCost = packed.map((x) => (x === 0 ? ov.zeroByte : ov.nonZeroByte)).reduce((sum, x) => sum + x);
- const ret = Math.round(callDataCost + ov.fixed / ov.bundleSize + ov.perUserOp + ov.perUserOpWord * lengthInWord);
- if (ret) {
- return BigNumber.from(ret);
- } else {
- throw new Error("can't calculate preVerificationGas");
- }
-}
diff --git a/packages/account/src/utils/Types.ts b/packages/account/src/utils/Types.ts
index 942166b5b..0967d0f8f 100644
--- a/packages/account/src/utils/Types.ts
+++ b/packages/account/src/utils/Types.ts
@@ -1,151 +1,179 @@
-import { Signer } from "ethers";
-import { BigNumberish, BigNumber } from "ethers";
+import { BigNumberish, SmartAccountSigner, UserOperationStruct } from "@alchemy/aa-core";
import { IBundler } from "@biconomy/bundler";
-import { IPaymaster, PaymasterFeeQuote, SponsorUserOperationDto } from "@biconomy/paymaster";
+import {
+ type FeeQuotesOrDataDto,
+ type IPaymaster,
+ type PaymasterFeeQuote,
+ PaymasterMode,
+ type SmartAccountData,
+ type SponsorUserOperationDto,
+} from "@biconomy/paymaster";
import { BaseValidationModule, ModuleInfo } from "@biconomy/modules";
-import { Provider } from "@ethersproject/providers";
-import { GasOverheads } from "./Preverificaiton";
-import { UserOperation, ChainId } from "@biconomy/core-types";
-import { WalletClientSigner } from "@alchemy/aa-core";
+import { Hex, WalletClient } from "viem";
+import { SupportedSigner } from "@biconomy/common";
-export type EntryPointAddresses = {
- [address: string]: string;
-};
-
-export type BiconomyFactories = {
- [address: string]: string;
-};
-
-export type BiconomyImplementations = {
- [address: string]: string;
-};
-
-export type EntryPointAddressesByVersion = {
- [version: string]: string;
-};
-
-export type BiconomyFactoriesByVersion = {
- [version: string]: string;
-};
-
-export type BiconomyImplementationsByVersion = {
- [version: string]: string;
-};
+export type EntryPointAddresses = Record;
+export type BiconomyFactories = Record;
+export type BiconomyImplementations = Record;
+export type EntryPointAddressesByVersion = Record;
+export type BiconomyFactoriesByVersion = Record;
+export type BiconomyImplementationsByVersion = Record;
export type SmartAccountConfig = {
+ /** entryPointAddress: address of the smart account factory */
entryPointAddress: string;
+ /** factoryAddress: address of the smart account factory */
bundler?: IBundler;
};
-/**
- * Enum representing available validation modules.
- *
- * - `ECDSA_OWNERSHIP`: Default module for ECDSA ownership validation.
- * - `MULTICHAIN`: Default module for multi-chain validation.
- * - If you don't provide any module, ECDSA_OWNERSHIP will be used as default
- */
-/*export enum AuthorizationModuleType {
- ECDSA_OWNERSHIP = DEFAULT_ECDSA_OWNERSHIP_MODULE,
- // MULTICHAIN = DEFAULT_MULTICHAIN_MODULE,
-}*/
-
-export type BaseSmartAccountConfig = ConditionalBundlerProps & {
- // owner?: Signer // can be in child classes
+export interface GasOverheads {
+ /** fixed: fixed gas overhead */
+ fixed: number;
+ /** perUserOp: per user operation gas overhead */
+ perUserOp: number;
+ /** perUserOpWord: per user operation word gas overhead */
+ perUserOpWord: number;
+ /** zeroByte: per byte gas overhead */
+ zeroByte: number;
+ /** nonZeroByte: per non zero byte gas overhead */
+ nonZeroByte: number;
+ /** bundleSize: per signature bundleSize */
+ bundleSize: number;
+ /** sigSize: sigSize gas overhead */
+ sigSize: number;
+}
+
+export type BaseSmartAccountConfig = {
+ /** index: helps to not conflict with other smart account instances */
index?: number;
- provider?: Provider;
+ /** provider: WalletClientSigner from viem */
+ provider?: WalletClient;
+ /** entryPointAddress: address of the smart account entry point */
entryPointAddress?: string;
+ /** accountAddress: address of the smart account, potentially counterfactual */
accountAddress?: string;
+ /** overheads: {@link GasOverheads} */
overheads?: Partial;
- paymaster?: IPaymaster; // PaymasterAPI
- chainId: ChainId;
+ /** paymaster: {@link IPaymaster} interface */
+ paymaster?: IPaymaster;
+ /** chainId: chainId of the network */
+ chainId?: number;
};
export type BiconomyTokenPaymasterRequest = {
+ /** feeQuote: {@link PaymasterFeeQuote} */
feeQuote: PaymasterFeeQuote;
- spender: string;
+ /** spender: The address of the spender who is paying for the transaction, this can usually be set to feeQuotesResponse.tokenPaymasterAddress */
+ spender: Hex;
+ /** maxApproval: If set to true, the paymaster will approve the maximum amount of tokens required for the transaction. Not recommended */
maxApproval?: boolean;
};
-export type BiconomySmartAccountConfig = {
- signer: Signer;
- rpcUrl?: string;
- chainId: ChainId;
- entryPointAddress?: string;
- bundler?: IBundler;
- paymaster?: IPaymaster;
- nodeClientUrl?: string;
-};
-
-type RequireAtLeastOne = Pick> &
+export type RequireAtLeastOne = Pick> &
{
[K in Keys]-?: Required> & Partial>>;
}[Keys];
-type ConditionalValidationProps = RequireAtLeastOne<
- {
- defaultValidationModule: BaseValidationModule;
- signer: Signer | WalletClientSigner;
- },
- "defaultValidationModule" | "signer"
->;
-
-type ConditionalBundlerProps = RequireAtLeastOne<
+export type ConditionalBundlerProps = RequireAtLeastOne<
{
bundler: IBundler;
bundlerUrl: string;
},
"bundler" | "bundlerUrl"
>;
+export type ResolvedBundlerProps = {
+ bundler: IBundler;
+};
+export type ConditionalValidationProps = RequireAtLeastOne<
+ {
+ defaultValidationModule: BaseValidationModule;
+ signer: SupportedSigner;
+ },
+ "defaultValidationModule" | "signer"
+>;
-export type BiconomySmartAccountV2Config = BaseSmartAccountConfig &
- ConditionalValidationProps & {
- factoryAddress?: string;
- senderAddress?: string;
- implementationAddress?: string;
- defaultFallbackHandler?: string;
- biconomyPaymasterApiKey?: string;
- rpcUrl?: string;
- nodeClientUrl?: string;
- activeValidationModule?: BaseValidationModule;
- scanForUpgradedAccountsFromV1?: boolean;
- maxIndexForScan?: number;
- };
+export type ResolvedValidationProps = {
+ /** defaultValidationModule: {@link BaseValidationModule} */
+ defaultValidationModule: BaseValidationModule;
+ /** activeValidationModule: {@link BaseValidationModule}. The active validation module. Will default to the defaultValidationModule */
+ activeValidationModule: BaseValidationModule;
+ /** signer: ethers Wallet, viemWallet or alchemys SmartAccountSigner */
+ signer: SmartAccountSigner;
+ /** chainId: chainId of the network */
+ chainId: number;
+};
+
+export type BiconomySmartAccountV2ConfigBaseProps = {
+ /** Factory address of biconomy factory contract or some other contract you have deployed on chain */
+ factoryAddress?: Hex;
+ /** Sender address: If you want to override the Signer address with some other address and get counterfactual address can use this to pass the EOA and get SA address */
+ senderAddress?: Hex;
+ /** implementation of smart contract address or some other contract you have deployed and want to override */
+ implementationAddress?: Hex;
+ /** defaultFallbackHandler: override the default fallback contract address */
+ defaultFallbackHandler?: Hex;
+ /** rpcUrl: Explicitly set the rpc else it is pulled out of the signer. */
+ rpcUrl?: string; // as good as Provider
+ /** biconomyPaymasterApiKey: The API key retrieved from the Biconomy dashboard */
+ biconomyPaymasterApiKey?: string;
+ /** activeValidationModule: The active validation module. Will default to the defaultValidationModule */
+ activeValidationModule?: BaseValidationModule;
+ /** scanForUpgradedAccountsFromV1: set to true if you you want the userwho was using biconomy SA v1 to upgrade to biconomy SA v2 */
+ scanForUpgradedAccountsFromV1?: boolean;
+ /** the index of SA the EOA have generated and till which indexes the upgraded SA should scan */
+ maxIndexForScan?: number;
+};
+export type BiconomySmartAccountV2Config = BiconomySmartAccountV2ConfigBaseProps &
+ BaseSmartAccountConfig &
+ ConditionalBundlerProps &
+ ConditionalValidationProps;
+
+export type BiconomySmartAccountV2ConfigConstructorProps = BiconomySmartAccountV2ConfigBaseProps &
+ BaseSmartAccountConfig &
+ ResolvedBundlerProps &
+ ResolvedValidationProps;
export type BuildUserOpOptions = {
- overrides?: Overrides;
- skipBundlerGasEstimation?: boolean;
+ /** overrides: Explicitly set gas values */
+ // overrides?: Overrides;
+ /** Not currently in use */
+ // skipBundlerGasEstimation?: boolean;
+ /** params relevant to the module, mostly relevant to sessions */
params?: ModuleInfo;
+ /** nonceOptions: For overriding the nonce */
nonceOptions?: NonceOptions;
+ /** forceEncodeForBatch: For encoding the user operation for batch */
forceEncodeForBatch?: boolean;
- paymasterServiceData?: SponsorUserOperationDto;
+ /** paymasterServiceData: Options specific to transactions that involve a paymaster */
+ paymasterServiceData?: PaymasterUserOperationDto;
+ /** simulationType: Determine which parts of the tx a bundler will simulate: "validation" | "validation_and_execution". */
+ simulationType?: SimulationType;
};
export type NonceOptions = {
+ /** nonceKey: The key to use for nonce */
nonceKey?: number;
+ /** nonceOverride: The nonce to use for the transaction */
nonceOverride?: number;
};
-// Used in AccountV1
-export type SendUserOpDto = {
- signer?: Signer;
- simulationType?: SimulationType;
-};
-
-// Generic options in AccountV2
-export type SendUserOpOptions = {
- simulationType?: SimulationType;
-};
-
export type SimulationType = "validation" | "validation_and_execution";
export type Overrides = {
- callGasLimit?: BigNumberish;
- verificationGasLimit?: BigNumberish;
- preVerificationGas?: BigNumberish;
- maxFeePerGas?: BigNumberish;
- maxPriorityFeePerGas?: BigNumberish;
- paymasterData?: string;
- signature?: string;
+ /* Value used by inner account execution */
+ callGasLimit?: Hex;
+ /* Actual gas used by the validation of this UserOperation */
+ verificationGasLimit?: Hex;
+ /* Gas overhead of this UserOperation */
+ preVerificationGas?: Hex;
+ /* Maximum fee per gas (similar to EIP-1559 max_fee_per_gas) */
+ maxFeePerGas?: Hex;
+ /* Maximum priority fee per gas (similar to EIP-1559 max_priority_fee_per_gas) */
+ maxPriorityFeePerGas?: Hex;
+ /* Address of paymaster sponsoring the transaction, followed by extra data to send to the paymaster ("0x" for self-sponsored transaction) */
+ paymasterData?: Hex;
+ /* Data passed into the account along with the nonce during the verification step */
+ signature?: Hex;
};
export type InitilizationData = {
@@ -153,47 +181,97 @@ export type InitilizationData = {
signerAddress?: string;
};
+export type PaymasterUserOperationDto = SponsorUserOperationDto &
+ FeeQuotesOrDataDto & {
+ /** mode: sponsored or erc20 */
+ mode: PaymasterMode;
+ /** Always recommended, especially when using token paymaster */
+ calculateGasLimits?: boolean;
+ /** Expiry duration in seconds */
+ expiryDuration?: number;
+ /** Webhooks to be fired after user op is sent */
+ webhookData?: Record;
+ /** Smart account meta data */
+ smartAccountInfo?: SmartAccountData;
+ /** the fee-paying token address */
+ feeTokenAddress?: string;
+ /** The fee quote */
+ feeQuote?: PaymasterFeeQuote;
+ /** The address of the spender. This is usually set to FeeQuotesOrDataResponse.tokenPaymasterAddress */
+ spender?: Hex;
+ /** Not recommended */
+ maxApproval?: boolean;
+ };
+
export type InitializeV2Data = {
accountIndex?: number;
};
export type EstimateUserOpGasParams = {
- userOp: Partial;
- overrides?: Overrides;
- skipBundlerGasEstimation?: boolean;
+ userOp: Partial;
+ // overrides?: Overrides;
+ /** Currrently has no effect */
+ // skipBundlerGasEstimation?: boolean;
+ /** paymasterServiceData: Options specific to transactions that involve a paymaster */
paymasterServiceData?: SponsorUserOperationDto;
};
export interface TransactionDetailsForUserOp {
+ /** target: The address of the contract to call */
target: string;
+ /** data: The data to send to the contract */
data: string;
+ /** value: The value to send to the contract */
value?: BigNumberish;
+ /** gasLimit: The gas limit to use for the transaction */
gasLimit?: BigNumberish;
+ /** maxFeePerGas: The maximum fee per gas to use for the transaction */
maxFeePerGas?: BigNumberish;
+ /** maxPriorityFeePerGas: The maximum priority fee per gas to use for the transaction */
maxPriorityFeePerGas?: BigNumberish;
+ /** nonce: The nonce to use for the transaction */
nonce?: BigNumberish;
}
export type CounterFactualAddressParam = {
index?: number;
validationModule?: BaseValidationModule;
+ /** scanForUpgradedAccountsFromV1: set to true if you you want the userwho was using biconomy SA v1 to upgrade to biconomy SA v2 */
scanForUpgradedAccountsFromV1?: boolean;
+ /** the index of SA the EOA have generated and till which indexes the upgraded SA should scan */
maxIndexForScan?: number;
};
export type QueryParamsForAddressResolver = {
- eoaAddress: string;
+ eoaAddress: Hex;
index: number;
- moduleAddress: string;
- moduleSetupData: string;
+ moduleAddress: Hex;
+ moduleSetupData: Hex;
maxIndexForScan?: number;
};
export type SmartAccountInfo = {
- accountAddress: string;
- factoryAddress: string;
+ /** accountAddress: The address of the smart account */
+ accountAddress: Hex;
+ /** factoryAddress: The address of the smart account factory */
+ factoryAddress: Hex;
+ /** currentImplementation: The address of the current implementation */
currentImplementation: string;
+ /** currentVersion: The version of the smart account */
currentVersion: string;
+ /** factoryVersion: The version of the factory */
factoryVersion: string;
- deploymentIndex: BigNumber;
+ /** deploymentIndex: The index of the deployment */
+ deploymentIndex: BigNumberish;
};
+
+export type ValueOrData = RequireAtLeastOne<
+ {
+ value: BigNumberish | string;
+ data: string;
+ },
+ "value" | "data"
+>;
+export type Transaction = {
+ to: string;
+} & ValueOrData;
diff --git a/packages/account/src/utils/Utils.ts b/packages/account/src/utils/Utils.ts
new file mode 100644
index 000000000..d905506c9
--- /dev/null
+++ b/packages/account/src/utils/Utils.ts
@@ -0,0 +1,45 @@
+import { encodeAbiParameters, parseAbiParameters, keccak256, Hex } from "viem";
+import type { UserOperationStruct } from "@alchemy/aa-core";
+
+/**
+ * pack the userOperation
+ * @param op
+ * @param forSignature "true" if the hash is needed to calculate the getUserOpHash()
+ * "false" to pack entire UserOp, for calculating the calldata cost of putting it on-chain.
+ */
+export function packUserOp(op: Partial, forSignature = true): string {
+ if (!op.initCode || !op.callData || !op.paymasterAndData) throw new Error("Missing userOp properties");
+ if (forSignature) {
+ return encodeAbiParameters(parseAbiParameters("address, uint256, bytes32, bytes32, uint256, uint256, uint256, uint256, uint256, bytes32"), [
+ op.sender as Hex,
+ BigInt(op.nonce as Hex),
+ keccak256(op.initCode as Hex),
+ keccak256(op.callData as Hex),
+ BigInt(op.callGasLimit as Hex),
+ BigInt(op.verificationGasLimit as Hex),
+ BigInt(op.preVerificationGas as Hex),
+ BigInt(op.maxFeePerGas as Hex),
+ BigInt(op.maxPriorityFeePerGas as Hex),
+ keccak256(op.paymasterAndData as Hex),
+ ]);
+ } else {
+ // for the purpose of calculating gas cost encode also signature (and no keccak of bytes)
+ return encodeAbiParameters(parseAbiParameters("address, uint256, bytes, bytes, uint256, uint256, uint256, uint256, uint256, bytes, bytes"), [
+ op.sender as Hex,
+ BigInt(op.nonce as Hex),
+ op.initCode as Hex,
+ op.callData as Hex,
+ BigInt(op.callGasLimit as Hex),
+ BigInt(op.verificationGasLimit as Hex),
+ BigInt(op.preVerificationGas as Hex),
+ BigInt(op.maxFeePerGas as Hex),
+ BigInt(op.maxPriorityFeePerGas as Hex),
+ op.paymasterAndData as Hex,
+ op.signature as Hex,
+ ]);
+ }
+}
+
+export const isNullOrUndefined = (value: any): value is undefined => {
+ return value === null || value === undefined;
+};
diff --git a/packages/account/src/utils/VoidSigner.ts b/packages/account/src/utils/VoidSigner.ts
deleted file mode 100644
index d028c56ad..000000000
--- a/packages/account/src/utils/VoidSigner.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { Provider, TransactionRequest } from "@ethersproject/providers";
-import { BigNumberish, BytesLike, Bytes, Signer } from "ethers";
-import { LogLevel, Logger } from "@ethersproject/logger";
-const logger = new Logger("signer");
-
-export interface TypedDataDomain {
- name?: string;
- version?: string;
- chainId?: BigNumberish;
- verifyingContract?: string;
- salt?: BytesLike;
-}
-
-export interface TypedDataField {
- name: string;
- type: string;
-}
-
-export type Deferrable = {
- [K in keyof T]: T[K] | Promise;
-};
-
-export class VoidSigner extends Signer {
- readonly address: string;
-
- readonly provider?: Provider;
-
- constructor(_address: string, _provider?: Provider) {
- super();
- this.address = _address;
- this.provider = _provider;
- }
-
- getAddress(): Promise