diff --git a/.github/workflows/deploy-site.yml b/.github/workflows/deploy-site.yml new file mode 100644 index 000000000..f32416cfa --- /dev/null +++ b/.github/workflows/deploy-site.yml @@ -0,0 +1,71 @@ +name: Deploy VitePress site to Pages +on: + workflow_dispatch: + inputs: + no_base_path: + required: false + default: false + type: boolean + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: pages + cancel-in-progress: false + +jobs: + # Build job + build: + runs-on: ubuntu-latest + name: Build + env: + NO_BASE_PATH: ${{ inputs.no_base_path }} + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 # Not needed if lastUpdated is not enabled + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: "18.10" + cache: yarn + + - name: Setup Pages + uses: actions/configure-pages@v3 + + - name: Install dependencies + run: yarn + + - name: Build with VitePress + working-directory: site + run: | + yarn build + touch .vitepress/dist/.nojekyll + + - name: Upload artifact + uses: actions/upload-pages-artifact@v2 + with: + path: site/.vitepress/dist + + # Deployment job + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + needs: build + runs-on: ubuntu-latest + name: Deploy + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 + env: + NO_BASE_PATH: ${{ inputs.no_base_path }} diff --git a/.github/workflows/on-pull-request.yml b/.github/workflows/on-pull-request.yml index c83e3cbd4..f3d25b532 100644 --- a/.github/workflows/on-pull-request.yml +++ b/.github/workflows/on-pull-request.yml @@ -10,9 +10,9 @@ jobs: - uses: actions/checkout@v2 - name: Setup Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: - node-version: "16.20" + node-version: "18.10" cache: "yarn" - name: Install dependencies @@ -28,9 +28,9 @@ jobs: - uses: actions/checkout@v2 - name: Setup Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: - node-version: "16.20" + node-version: "18.10" cache: "yarn" - name: Install dependencies diff --git a/.github/workflows/publish-package.yml b/.github/workflows/publish-package.yml index f1fdce1c6..17d3e82f8 100644 --- a/.github/workflows/publish-package.yml +++ b/.github/workflows/publish-package.yml @@ -15,31 +15,33 @@ on: default: false type: boolean +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: pages + cancel-in-progress: false + # If we ever migrate this to not be manual, we HAVE to check that the commit # it is running against DOES NOT contain [skip-ci] in the commit message jobs: - build_test_and_deploy: - name: Build, Test, and Deploy + build_and_test: + name: Build and Test runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: token: ${{ secrets.ALCHEMY_BOT_PAT }} - - name: Set Github User Details - run: | - git config --global user.name "Alchemy Bot" - git config --global user.email "alchemy-bot@alchemy.com" - - - name: Set NPM Permissions - env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc - - name: Set Node.js 16.x uses: actions/setup-node@v3 with: - node-version: "16.20" + node-version: "18.10" cache: "yarn" - name: Install dependencies @@ -54,9 +56,42 @@ jobs: API_KEY: ${{ secrets.API_KEY }} OWNER_MNEMONIC: ${{ secrets.OWNER_MNEMONIC }} PAYMASTER_POLICY_ID: ${{ secrets.PAYMASTER_POLICY_ID }} + UNDEPLOYED_OWNER_MNEMONIC: ${{ secrets.UNDEPLOYED_OWNER_MNEMONIC }} + LIGHT_ACCOUNT_OWNER_MNEMONIC: ${{ secrets.LIGHT_ACCOUNT_OWNER_MNEMONIC }} run: yarn test:e2e + deploy: + name: Deploy + needs: build_and_test + runs-on: ubuntu-latest + if: inputs.publish + steps: + - name: Set Github User Details + run: | + git config --global user.name "Alchemy Bot" + git config --global user.email "alchemy-bot@alchemy.com" + + - name: Set NPM Permissions + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc + # Lerna publish will, compute the new version, update files, run precommit hooks, tag, publish, and push change log - name: Publish using Lerna - if: inputs.publish run: yarn lerna publish --no-private --yes --no-verify-access + + publish_site: + name: Publish Docs Site + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + needs: deploy + runs-on: ubuntu-latest + if: inputs.publish + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 + env: + # TODO(moldy): just remove this altogether once we're live + NO_BASE_PATH: true diff --git a/.gitignore b/.gitignore index 89597e3f4..96a3ab002 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,7 @@ lerna-debug.log .tool-versions # example ignores -examples/contracts/**/out \ No newline at end of file +examples/contracts/**/out + +docs/.vitepress/dist +docs/.vitepress/cache \ No newline at end of file diff --git a/CODEOWNERS b/CODEOWNERS index d2ee7a029..a20872123 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @moldy530 @rthomare @dancoombs @mokok123 @avasisht23 +* @moldy530 @rthomare @dancoombs @mokok123 @avasisht23 @denniswon diff --git a/README.md b/README.md index 6b4feb31b..87a1c2206 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ⚠️ This repo is actively being developed and certain features might not be fully implemented yet or are subject to change ⚠️ -Alchemy's Account Abstraction Kit is an SDK that enables easy interactions with ERC-4337 compliant smart accounts. It supports bringing your own Account Contracts or using any of the currently available accounts. The SDK is built on top of `viem` to enable a lighter-weight bundle and is published with ESM default exports though also supports CJS. +Alchemy's Account Abstraction Kit is an SDK that enables easy interactions with [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) compliant smart accounts. It supports bringing your own Account Contracts or using any of the currently available accounts. The SDK is built on top of `viem` to enable a lighter-weight bundle and is published with ESM default exports though also supports CJS. ## Getting Started @@ -73,13 +73,13 @@ const owner: SmartAccountSigner = LocalAccountSigner.mnemonicToAccountSigner(MNEMONIC); // 2. initialize the provider and connect it to the account -const provider = new SmartAccountProvider( +const provider = new SmartAccountProvider({ // the demo key below is public and rate-limited, it's better to create a new one // you can get started with a free account @ https://www.alchemy.com/ - "https://polygon-mumbai.g.alchemy.com/v2/demo", // rpcUrl - "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", // entryPointAddress - polygonMumbai // chain -).connect( + rpcProvider: "https://polygon-mumbai.g.alchemy.com/v2/demo", + entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", + chain: polygonMumbai, +}).connect( (rpcClient) => new SimpleSmartContractAccount({ entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", @@ -139,7 +139,7 @@ let provider = new AlchemyProvider({ ); // [OPTIONAL] Use Alchemy Gas Manager -provider = provider.withAlchemyGasManager({ +provider.withAlchemyGasManager({ policyId: PAYMASTER_POLICY_ID, entryPoint: ENTRYPOINT_ADDRESS, }); @@ -208,16 +208,16 @@ const { hash } = await signer.sendUserOperation({ The primary interfaces are the `SmartAccountProvider` and `BaseSmartContractAccount`. -The `SmartAccountProvider` is an ERC-1193 compliant Provider that wraps JSON RPC methods and some Wallet Methods (signing, sendTransaction, etc). It also provides two utility methods for sending UserOperations: +The `SmartAccountProvider` is an [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) compliant Provider that wraps JSON RPC methods and some Wallet Methods (signing, sendTransaction, etc). It also provides two utility methods for sending UserOperations: 1. `sendUserOperation` -- this takes in `target`, `callData`, and an optional `value` which then constructs a UserOperation (UO), sends it, and returns the `hash` of the UO. It handles estimating gas, fetching fee data, (optionally) requesting paymasterAndData, and lastly signing. This is done via a middleware stack that runs in a specific order. The middleware order is `getDummyPaymasterData` => `estimateGas` => `getFeeData` => `getPaymasterAndData`. The paymaster fields are set to `0x` by default. They can be changed using `provider.withPaymasterMiddleware`. -2. `sendTransaction` -- this takes in a traditional Transaction Request object which then gets converted into a UO. Currently, the only data being used from the Transaction Request object is `from`, `to`, `data` and `value`. Support for other fields is coming soon. +2. `sendTransaction` -- this takes in a traditional Transaction Request object which then gets converted into a UO. Note that `to` field of transaction is required, and among other fields of transaction, only `data`, `value`, `maxFeePerGas`, `maxPriorityFeePerGas` fields are considered if given. Support for other fields is coming soon. If you want to add support for your own `SmartAccounts` then you will need to provide an implementation of `BaseSmartContractAccount`. You can see an example of this in [SimpleSmartContractAccount](packages/core/src/account/simple.ts). You will need to implement 4 methods: 1. `getDummySignature` -- this method should return a signature that will not `revert` during validation. It does not have to pass validation, just not cause the contract to revert. This is required for gas estimation so that the gas estimate are accurate. 2. `encodeExecute` -- this method should return the abi encoded function data for a call to your contract's `execute` method -3. `signMessage` -- this should return an ERC-191 compliant message and is used to sign UO Hashes +3. `signMessage` -- this should return an [EIP-191](https://eips.ethereum.org/EIPS/eip-191) compliant message and is used to sign UO Hashes 4. `getAccountInitCode` -- this should return the init code that will be used to create an account if one does not exist. Usually this is the concatenation of the account's factory address and the abi encoded function data of the account factory's `createAccount` method. ### Paymaster Middleware diff --git a/examples/aa-simple-dapp/next-env.d.ts b/examples/aa-simple-dapp/next-env.d.ts new file mode 100644 index 000000000..4f11a03dc --- /dev/null +++ b/examples/aa-simple-dapp/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/examples/alchemy-daapp/README.md b/examples/alchemy-daapp/README.md index eec3aa3fb..3e729947c 100644 --- a/examples/alchemy-daapp/README.md +++ b/examples/alchemy-daapp/README.md @@ -32,16 +32,16 @@ export const alchemyApiKey = ""; export const serverConfigs: Record = { [polygonMumbai.id]: { nftContractAddress: "", - simpleAccountFactoryAddress: "", + lightAccountFactoryAddress: "", gasManagerPolicyId: "", chain: polygonMumbai, }, // Repeat for other chains as needed }; ``` -### **🗒️ Notes:** for `nftContractAddress` and `simpleAccountFactoryAddress` +### **🗒️ Notes:** for `nftContractAddress` and `lightAccountFactoryAddress` - There are already contract addresses deployed for them across [various chains here](https://github.com/alchemyplatform/aa-sdk/blob/main/examples/alchemy-daapp/src/configs/clientConfigs.ts). -- We used eth-infinitism's [simpleAccountFactory here](https://github.com/eth-infinitism/account-abstraction/blob/main/contracts/samples/SimpleAccountFactory.sol). +- We used Alchemy's [lightAccountFactory here]. todo(ajay): post the repo when live - Finally, the contracts sibling package has the copy of the [NFT contract](https://github.com/alchemyplatform/aa-sdk/tree/main/examples/contracts/DAAppNFT/src) along instructions on [how to deploy it](https://github.com/alchemyplatform/aa-sdk/blob/main/examples/contracts/README.md). 4. Update the serverConfigs.ts file with your alchemy API keys: @@ -60,7 +60,7 @@ yarn dev ## How This Works The [`onboarding-controller.ts`](https://github.com/alchemyplatform/aa-sdk/blob/master/examples/alchemy-daapp/src/surfaces/onboarding/OnboardingController.ts) file contains the onboarding controller, which handles the onboarding process for new users. The controller uses the Alchemy SDK to create a new account and mint an NFT. -In the [`clientConfigs.ts`](https://github.com/alchemyplatform/aa-sdk/blob/main/examples/alchemy-daapp/src/configs/clientConfigs.ts) file, you will find the configuration for the DAApp, including the nft contract address, simple account factory address, gas manager policy id, rpc url, and chain. You'll also find an example NFT contract in [`examples/contracts/DAAppNFT`](https://github.com/alchemyplatform/aa-sdk/tree/main/examples/contracts/DAAppNFT) +In the [`clientConfigs.ts`](https://github.com/alchemyplatform/aa-sdk/blob/main/examples/alchemy-daapp/src/configs/clientConfigs.ts) file, you will find the configuration for the DAApp, including the nft contract address, light account factory address, gas manager policy id, rpc url, and chain. You'll also find an example NFT contract in [`examples/contracts/DAAppNFT`](https://github.com/alchemyplatform/aa-sdk/tree/main/examples/contracts/DAAppNFT) You can replace the default values with your contract addresses and policy ids and add or remove chains as needed. diff --git a/examples/alchemy-daapp/package.json b/examples/alchemy-daapp/package.json index ede8b3db9..12526191d 100644 --- a/examples/alchemy-daapp/package.json +++ b/examples/alchemy-daapp/package.json @@ -9,6 +9,7 @@ "lint": "next lint" }, "dependencies": { + "@alchemy/aa-accounts": "latest", "@alchemy/aa-alchemy": "latest", "@alchemy/aa-core": "latest", "@chakra-ui/react": "^2.6.1", @@ -24,7 +25,7 @@ "next": "^13.4.1", "react": "18.2.0", "react-dom": "18.2.0", - "viem": "^1.10.9", + "viem": "^1.16.2", "wagmi": "^1.3.11", "zod": "^3.21.4" }, diff --git a/examples/alchemy-daapp/src/clients/simpleAccountSigner.ts b/examples/alchemy-daapp/src/clients/lightAccountSigner.ts similarity index 85% rename from examples/alchemy-daapp/src/clients/simpleAccountSigner.ts rename to examples/alchemy-daapp/src/clients/lightAccountSigner.ts index a4e0c8475..48a3fdcff 100644 --- a/examples/alchemy-daapp/src/clients/simpleAccountSigner.ts +++ b/examples/alchemy-daapp/src/clients/lightAccountSigner.ts @@ -3,7 +3,7 @@ import { useCallback } from "react"; import { toHex } from "viem"; import { useWalletClient } from "wagmi"; -type SimpleSmartAccountSignerResult = +type LightSmartAccountSignerResult = | { isLoading: false; owner: SmartAccountSigner; @@ -13,8 +13,9 @@ type SimpleSmartAccountSignerResult = owner: undefined; }; -export function useSimpleAccountSigner(): SimpleSmartAccountSignerResult { +export function useLightAccountSigner(): LightSmartAccountSignerResult { const walletClientQuery = useWalletClient(); + const signerType = "json-rpc" // We need this to by pass a viem bug https://github.com/wagmi-dev/viem/issues/606 const signMessage = useCallback( (data: string | Uint8Array) => @@ -48,5 +49,5 @@ export function useSimpleAccountSigner(): SimpleSmartAccountSignerResult { owner: undefined, }; } - return { isLoading: false, owner: { signMessage, signTypedData, getAddress } }; + return { isLoading: false, owner: { signerType, signMessage, signTypedData, getAddress } }; } diff --git a/examples/alchemy-daapp/src/configs/clientConfigs.ts b/examples/alchemy-daapp/src/configs/clientConfigs.ts index 3f29e81db..e3c2f229c 100644 --- a/examples/alchemy-daapp/src/configs/clientConfigs.ts +++ b/examples/alchemy-daapp/src/configs/clientConfigs.ts @@ -13,7 +13,6 @@ import { env } from "~/env.mjs"; export interface DAAppConfiguration { nftContractAddress: `0x${string}`; - simpleAccountFactoryAddress: `0x${string}`; gasManagerPolicyId: string; rpcUrl: string; chain: Chain; @@ -23,56 +22,48 @@ export interface DAAppConfiguration { export const daappConfigurations: Record = { [polygon.id]: { nftContractAddress: "0xb7b9424ef3d1b9086b7e53276c4aad68a1dd971c", - simpleAccountFactoryAddress: "0x15Ba39375ee2Ab563E8873C8390be6f2E2F50232", gasManagerPolicyId: env.NEXT_PUBLIC_POLYGON_POLICY_ID, rpcUrl: `/api/rpc/proxy?chainId=${polygon.id}`, chain: polygon, }, [polygonMumbai.id]: { nftContractAddress: "0x5679b0de84bba361d31b2e7152ab20f0f8565245", - simpleAccountFactoryAddress: "0x9406Cc6185a346906296840746125a0E44976454", gasManagerPolicyId: env.NEXT_PUBLIC_MUMBAI_POLICY_ID, rpcUrl: `/api/rpc/proxy?chainId=${polygonMumbai.id}`, chain: polygonMumbai, }, [sepolia.id]: { nftContractAddress: "0x5679b0de84bba361d31b2e7152ab20f0f8565245", - simpleAccountFactoryAddress: "0x9406cc6185a346906296840746125a0e44976454", gasManagerPolicyId: env.NEXT_PUBLIC_SEPOLIA_POLICY_ID, rpcUrl: `/api/rpc/proxy?chainId=${sepolia.id}`, chain: sepolia, }, [arbitrum.id]: { nftContractAddress: "0xb7b9424ef3d1b9086b7e53276c4aad68a1dd971c", - simpleAccountFactoryAddress: "0x15Ba39375ee2Ab563E8873C8390be6f2E2F50232", gasManagerPolicyId: env.NEXT_PUBLIC_ARB_POLICY_ID, rpcUrl: `/api/rpc/proxy?chainId=${arbitrum.id}`, chain: arbitrum, }, [optimism.id]: { nftContractAddress: "0x835629117Abb8cfe20a2e8717C691905A4725b7c", - simpleAccountFactoryAddress: "0x15Ba39375ee2Ab563E8873C8390be6f2E2F50232", gasManagerPolicyId: env.NEXT_PUBLIC_OPT_POLICY_ID, rpcUrl: `/api/rpc/proxy?chainId=${optimism.id}`, chain: optimism, }, [optimismGoerli.id]: { nftContractAddress: "0x835629117Abb8cfe20a2e8717C691905A4725b7c", - simpleAccountFactoryAddress: "0x9406cc6185a346906296840746125a0e44976454", gasManagerPolicyId: env.NEXT_PUBLIC_OPT_GOERLI_POLICY_ID, rpcUrl: `/api/rpc/proxy?chainId=${optimismGoerli.id}`, chain: optimismGoerli, }, [base.id]: { nftContractAddress: "0x835629117Abb8cfe20a2e8717C691905A4725b7c", - simpleAccountFactoryAddress: "0x15Ba39375ee2Ab563E8873C8390be6f2E2F50232", gasManagerPolicyId: env.NEXT_PUBLIC_BASE_POLICY_ID, rpcUrl: `/api/rpc/proxy?chainId=${base.id}`, chain: base, }, [baseGoerli.id]: { nftContractAddress: "0x835629117Abb8cfe20a2e8717C691905A4725b7c", - simpleAccountFactoryAddress: "0x15Ba39375ee2Ab563E8873C8390be6f2E2F50232", gasManagerPolicyId: env.NEXT_PUBLIC_BASE_GOERLI_POLICY_ID, rpcUrl: `/api/rpc/proxy?chainId=${baseGoerli.id}`, chain: baseGoerli, diff --git a/examples/alchemy-daapp/src/surfaces/onboarding/OnboardingController.ts b/examples/alchemy-daapp/src/surfaces/onboarding/OnboardingController.ts index a64139b7c..73c0d30d2 100644 --- a/examples/alchemy-daapp/src/surfaces/onboarding/OnboardingController.ts +++ b/examples/alchemy-daapp/src/surfaces/onboarding/OnboardingController.ts @@ -1,6 +1,6 @@ +import { LightSmartContractAccount } from "@alchemy/aa-accounts"; import { withAlchemyGasManager } from "@alchemy/aa-alchemy"; import { - SimpleSmartContractAccount, SmartAccountProvider, createPublicErc4337Client, type SmartAccountSigner @@ -99,23 +99,22 @@ const onboardingStepHandlers: Record< const chain: Chain = context.chain!; const entryPointAddress = context.entrypointAddress; - let baseSigner = new SmartAccountProvider( - appConfig.rpcUrl, + let baseSigner = new SmartAccountProvider({ + rpcProvider: appConfig.rpcUrl, entryPointAddress, chain, - undefined, - { + feeOpts: { txMaxRetries: 60, - } - ).connect((provider: any) => { + }, + }).connect((provider: any) => { if (!context.owner) { throw new Error("No owner for account was found"); } - return new SimpleSmartContractAccount({ + return new LightSmartContractAccount({ entryPointAddress, chain, owner: context.owner, - factoryAddress: appConfig.simpleAccountFactoryAddress, + factoryAddress: appConfig.lightAccountFactoryAddress, rpcClient: provider, }); }); @@ -343,4 +342,4 @@ export function useOnboardingOrchestrator( go, reset, }; -} +} \ No newline at end of file diff --git a/examples/alchemy-daapp/src/surfaces/onboarding/OnboardingPage.tsx b/examples/alchemy-daapp/src/surfaces/onboarding/OnboardingPage.tsx index eefaf44d9..e48d61def 100644 --- a/examples/alchemy-daapp/src/surfaces/onboarding/OnboardingPage.tsx +++ b/examples/alchemy-daapp/src/surfaces/onboarding/OnboardingPage.tsx @@ -21,7 +21,7 @@ import { useMutation } from "@tanstack/react-query"; import { useRouter } from "next/router"; import { memo, useState } from "react"; import { useAccount } from "wagmi"; -import { useSimpleAccountSigner } from "~/clients/simpleAccountSigner"; +import { useLightAccountSigner } from "~/clients/lightAccountSigner"; import { LoadingScreen } from "~/surfaces/shared/LoadingScreen"; import { queryClient } from "../../clients/query"; import { useOnboardingOrchestrator } from "./OnboardingController"; @@ -29,7 +29,7 @@ import { OnboardingStepIdentifier } from "./OnboardingDataModels"; export function OnboardingPage() { const { isConnected } = useAccount(); - const ownerResult = useSimpleAccountSigner(); + const ownerResult = useLightAccountSigner(); if (isConnected && !ownerResult.isLoading) { return ; } else { diff --git a/lerna.json b/lerna.json index 78c8483fa..1fc684302 100644 --- a/lerna.json +++ b/lerna.json @@ -5,7 +5,6 @@ "useNx": true, "npmClient": "yarn", "conventionalCommits": true, - "conventionalPrerelease": true, "changelog": true, "command": { "version": { diff --git a/package.json b/package.json index a177c7545..520023613 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,14 @@ "name": "root", "type": "module", "private": true, + "engines": { + "node": "18.10" + }, "workspaces": [ "packages/*", "templates/*", - "examples/*" + "examples/*", + "site" ], "scripts": { "build": "lerna run build --ignore=alchemy-daapp", diff --git a/packages/accounts/package.json b/packages/accounts/package.json index a0a2f55ac..b70ae475c 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -61,6 +61,6 @@ "homepage": "https://github.com/alchemyplatform/aa-sdk#readme", "gitHead": "ee46e8bb857de3b631044fa70714ea706d9e317d", "dependencies": { - "viem": "^1.5.3" + "viem": "^1.16.2" } } diff --git a/packages/accounts/src/index.ts b/packages/accounts/src/index.ts index 9666dfc67..b47f9b54a 100644 --- a/packages/accounts/src/index.ts +++ b/packages/accounts/src/index.ts @@ -16,3 +16,7 @@ export { ValidatorMode, } from "./kernel-zerodev/validator/base.js"; export type { KernelBaseValidatorParams } from "./kernel-zerodev/validator/base.js"; + +//light-account exports +export { LightSmartContractAccount } from "./light-account/account.js"; +export { getDefaultLightAccountFactory } from "./light-account/utils.js"; diff --git a/packages/accounts/src/kernel-zerodev/README.md b/packages/accounts/src/kernel-zerodev/README.md index 3f046f5d0..3f9befe9f 100644 --- a/packages/accounts/src/kernel-zerodev/README.md +++ b/packages/accounts/src/kernel-zerodev/README.md @@ -18,7 +18,7 @@ import { type ValidatorMode, } from "@alchemy/aa-accounts"; import { mnemonicToAccount } from "viem/accounts"; -import { PrivateKeySigner } from "@alchemy/aa-core"; +import { LocalAccountSigner } from "@alchemy/aa-core"; import { polygonMumbai } from "viem/chains"; import { toHex } from "viem"; @@ -27,7 +27,7 @@ const KERNEL_ACCOUNT_FACTORY_ADDRESS = // 1. define the EOA owner of the Smart Account // This is just one exapmle of how to interact with EOAs, feel free to use any other interface -const owner = PrivateKeySigner.privateKeyToAccountSigner(PRIVATE_KEY); +const owner = LocalAccountSigner.privateKeyToAccountSigner(PRIVATE_KEY); const validator: KernelBaseValidator = new KernelBaseValidator({ validatorAddress: "0x180D6465F921C7E0DEA0040107D342c87455fFF5", @@ -72,10 +72,10 @@ const { hash } = provider.sendUserOperation({ The primary interfaces are the `KernelAccountProvider`, `KernelSmartContractAccount` and `KernelBaseValidator` -The `KernelAccountProvider` is an ERC-1193 compliant Provider built on top of Alchemy's `SmartAccountProvider` +The `KernelAccountProvider` is an [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) compliant Provider built on top of Alchemy's `SmartAccountProvider` 1. `sendUserOperation` -- this takes in `target`, `callData`, and an optional `value` which then constructs a UserOperation (UO), sends it, and returns the `hash` of the UO. It handles estimating gas, fetching fee data, (optionally) requesting paymasterAndData, and lastly signing. This is done via a middleware stack that runs in a specific order. The middleware order is `getDummyPaymasterData` => `estimateGas` => `getFeeData` => `getPaymasterAndData`. The paymaster fields are set to `0x` by default. They can be changed using `provider.withPaymasterMiddleware`. -2. `sendTransaction` -- this takes in a traditional Transaction Request object which then gets converted into a UO. Currently, the only data being used from the Transaction Request object is `from`, `to`, `data` and `value`. Support for other fields is coming soon. +2. `sendTransaction` -- this takes in a traditional Transaction Request object which then gets converted into a UO. This takes in a traditional Transaction Request object which then gets converted into a UO. Note that `to` field of transaction is required, and among other fields of transaction, only `data`, `value`, `maxFeePerGas`, `maxPriorityFeePerGas` fields are considered if given. Support for other fields is coming soon. `KernelSmartContractAccount` is Kernel's implementation of `BaseSmartContractAccount`. 6 main methods are implemented @@ -83,13 +83,13 @@ The `KernelAccountProvider` is an ERC-1193 compliant Provider built on top of Al 2. `encodeExecute` -- this method should return the abi encoded function data for a call to your contract's `execute` method 3. `encodeExecuteDelegate` -- this method should return the abi encoded function data for a `delegate` call to your contract's `execute` method 4. `signMessage` -- this is used to sign UO Hashes -5. `signWithEip6492` -- this should return an ERC-191 and EIP-6492 compliant message used to personal_sign +5. `signWithEip6492` -- this should return an [ERC-191](https://eips.ethereum.org/EIPS/eip-191) and [ERC-6492](https://eips.ethereum.org/EIPS/eip-6492) compliant message used to personal_sign 6. `getAccountInitCode` -- this should return the init code that will be used to create an account if one does not exist. Usually this is the concatenation of the account's factory address and the abi encoded function data of the account factory's `createAccount` method. The `KernelBaseValidator` is a plugin that modify how transactions are validated. It allows for extension and implementation of arbitrary validation logic. It implements 3 methods: 1. `getAddress` -- this returns the address of the validator -2. `getOwner` -- this returns the eligible signer's address for the active smart wallet +2. `getOwnerAddress` -- this returns the eligible signer's address for the active smart wallet 3. `signMessageWithValidatorParams` -- this method signs the userop hash using signer object and then concats additional params based on validator mode. ## Contributing diff --git a/packages/accounts/src/kernel-zerodev/__tests__/account.test.ts b/packages/accounts/src/kernel-zerodev/__tests__/account.test.ts index 2a8f89caf..aedf2309f 100644 --- a/packages/accounts/src/kernel-zerodev/__tests__/account.test.ts +++ b/packages/accounts/src/kernel-zerodev/__tests__/account.test.ts @@ -1,4 +1,4 @@ -import { PrivateKeySigner } from "@alchemy/aa-core"; +import { LocalAccountSigner } from "@alchemy/aa-core"; import { type Address, type Hex } from "viem"; import { generatePrivateKey } from "viem/accounts"; import { polygonMumbai } from "viem/chains"; @@ -23,7 +23,7 @@ describe("Kernel Account Tests", () => { entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" as Address, }; - const owner = PrivateKeySigner.privateKeyToAccountSigner(config.privateKey); + const owner = LocalAccountSigner.privateKeyToAccountSigner(config.privateKey); const mockOwner = new MockSigner(); const validator: KernelBaseValidator = new KernelBaseValidator({ @@ -38,11 +38,11 @@ describe("Kernel Account Tests", () => { owner: mockOwner, }); - const provider = new KernelAccountProvider( - config.rpcProvider, - config.entryPointAddress, - config.chain - ); + const provider = new KernelAccountProvider({ + rpcProvider: config.rpcProvider, + entryPointAddress: config.entryPointAddress, + chain: config.chain, + }); function account(index: bigint, owner = mockOwner) { const accountParams: KernelSmartAccountParams = { diff --git a/packages/accounts/src/kernel-zerodev/__tests__/mocks/mock-signer.ts b/packages/accounts/src/kernel-zerodev/__tests__/mocks/mock-signer.ts index c4f517832..104d21d5f 100644 --- a/packages/accounts/src/kernel-zerodev/__tests__/mocks/mock-signer.ts +++ b/packages/accounts/src/kernel-zerodev/__tests__/mocks/mock-signer.ts @@ -6,6 +6,8 @@ import type { } from "@alchemy/aa-core"; export class MockSigner implements SmartAccountSigner { + signerType = "mock"; + getAddress(): Promise
{ return Promise.resolve("0x48D4d3536cDe7A257087206870c6B6E76e3D4ff4"); } diff --git a/packages/accounts/src/kernel-zerodev/account.ts b/packages/accounts/src/kernel-zerodev/account.ts index 0fde79f8b..067f5a387 100644 --- a/packages/accounts/src/kernel-zerodev/account.ts +++ b/packages/accounts/src/kernel-zerodev/account.ts @@ -4,7 +4,6 @@ import { type BatchUserOperationCallData, type SmartAccountSigner, } from "@alchemy/aa-core"; -import type { Address } from "abitype"; import { parseAbiParameters } from "abitype"; import { concatHex, @@ -21,12 +20,12 @@ import { KernelFactoryAbi } from "./abis/KernelFactoryAbi.js"; import { MultiSendAbi } from "./abis/MultiSendAbi.js"; import { encodeCall } from "./utils.js"; import { KernelBaseValidator, ValidatorMode } from "./validator/base.js"; +import type { KernelUserOperationCallData } from "./types.js"; export interface KernelSmartAccountParams< TTransport extends Transport | FallbackTransport = Transport > extends BaseSmartAccountParams { owner: SmartAccountSigner; - factoryAddress: Address; index?: bigint; defaultValidator: KernelBaseValidator; validator?: KernelBaseValidator; @@ -35,17 +34,15 @@ export interface KernelSmartAccountParams< export class KernelSmartContractAccount< TTransport extends Transport | FallbackTransport = Transport > extends BaseSmartContractAccount { - private owner: SmartAccountSigner; - private readonly factoryAddress: Address; + protected owner: SmartAccountSigner; private readonly index: bigint; private defaultValidator: KernelBaseValidator; private validator: KernelBaseValidator; - constructor(params: KernelSmartAccountParams) { + constructor(params: KernelSmartAccountParams) { super(params); this.index = params.index ?? 0n; this.owner = params.owner; - this.factoryAddress = params.factoryAddress; this.defaultValidator = params.defaultValidator!; this.validator = params.validator ?? params.defaultValidator!; } @@ -62,19 +59,11 @@ export class KernelSmartContractAccount< } } - async encodeExecuteDelegate( - target: Hex, - value: bigint, - data: Hex - ): Promise { - return this.encodeExecuteAction(target, value, data, 1); - } - override async encodeBatchExecute( - _txs: BatchUserOperationCallData + txs: BatchUserOperationCallData ): Promise<`0x${string}`> { const multiSendData: `0x${string}` = concatHex( - _txs.map((tx) => encodeCall(tx)) + txs.map((tx: KernelUserOperationCallData) => encodeCall(tx)) ); return encodeFunctionData({ abi: MultiSendAbi, @@ -83,6 +72,23 @@ export class KernelSmartContractAccount< }); } + signMessage(msg: Uint8Array | string): Promise { + const formattedMessage = typeof msg === "string" ? toBytes(msg) : msg; + return this.validator.signMessageWithValidatorParams(formattedMessage); + } + + protected async getAccountInitCode(): Promise { + return concatHex([this.factoryAddress, await this.getFactoryInitCode()]); + } + + async encodeExecuteDelegate( + target: Hex, + value: bigint, + data: Hex + ): Promise { + return this.encodeExecuteAction(target, value, data, 1); + } + async signWithEip6492(msg: string | Uint8Array): Promise { try { const formattedMessage = typeof msg === "string" ? toBytes(msg) : msg; @@ -105,11 +111,6 @@ export class KernelSmartContractAccount< } } - signMessage(msg: Uint8Array | string): Promise { - const formattedMessage = typeof msg === "string" ? toBytes(msg) : msg; - return this.validator.signMessageWithValidatorParams(formattedMessage); - } - protected encodeExecuteAction( target: Hex, value: bigint, @@ -122,9 +123,6 @@ export class KernelSmartContractAccount< args: [target, value, data, code], }); } - protected async getAccountInitCode(): Promise { - return concatHex([this.factoryAddress, await this.getFactoryInitCode()]); - } protected async getFactoryInitCode(): Promise { try { @@ -133,7 +131,7 @@ export class KernelSmartContractAccount< functionName: "createAccount", args: [ this.defaultValidator.getAddress(), - await this.defaultValidator.getOwner(), + await this.defaultValidator.getOwnerAddress(), this.index, ], }); diff --git a/packages/accounts/src/kernel-zerodev/e2e-tests/kernel-account.test.ts b/packages/accounts/src/kernel-zerodev/e2e-tests/kernel-account.test.ts index e1b873b17..5719a1bda 100644 --- a/packages/accounts/src/kernel-zerodev/e2e-tests/kernel-account.test.ts +++ b/packages/accounts/src/kernel-zerodev/e2e-tests/kernel-account.test.ts @@ -19,7 +19,7 @@ import { import { KernelAccountProvider } from "../provider.js"; import type { KernelUserOperationCallData } from "../types.js"; import { KernelBaseValidator, ValidatorMode } from "../validator/base.js"; -import { RPC_URL, API_KEY, OWNER_MNEMONIC } from "./constants.js"; +import { API_KEY, OWNER_MNEMONIC, RPC_URL } from "./constants.js"; import { MockSigner } from "./mocks/mock-signer.js"; describe("Kernel Account Tests", () => { @@ -38,6 +38,7 @@ describe("Kernel Account Tests", () => { const ownerAccount = mnemonicToAccount(OWNER_MNEMONIC); const owner: SmartAccountSigner = { + signerType: "kernel-zerodev", signMessage: async (msg) => ownerAccount.signMessage({ message: { raw: toHex(msg) }, @@ -61,11 +62,11 @@ describe("Kernel Account Tests", () => { owner: mockOwner, }); - const provider = new KernelAccountProvider( - config.rpcProvider, - config.entryPointAddress, - config.chain - ); + const provider = new KernelAccountProvider({ + rpcProvider: config.rpcProvider, + entryPointAddress: config.entryPointAddress, + chain: config.chain, + }); function connect(index: bigint, owner = mockOwner) { return provider.connect((_provider) => account(index, owner)); diff --git a/packages/accounts/src/kernel-zerodev/e2e-tests/mocks/mock-signer.ts b/packages/accounts/src/kernel-zerodev/e2e-tests/mocks/mock-signer.ts index c4f517832..104d21d5f 100644 --- a/packages/accounts/src/kernel-zerodev/e2e-tests/mocks/mock-signer.ts +++ b/packages/accounts/src/kernel-zerodev/e2e-tests/mocks/mock-signer.ts @@ -6,6 +6,8 @@ import type { } from "@alchemy/aa-core"; export class MockSigner implements SmartAccountSigner { + signerType = "mock"; + getAddress(): Promise
{ return Promise.resolve("0x48D4d3536cDe7A257087206870c6B6E76e3D4ff4"); } diff --git a/packages/accounts/src/kernel-zerodev/validator/__tests__/base.test.ts b/packages/accounts/src/kernel-zerodev/validator/__tests__/base.test.ts index 5b0a642df..8783ac995 100644 --- a/packages/accounts/src/kernel-zerodev/validator/__tests__/base.test.ts +++ b/packages/accounts/src/kernel-zerodev/validator/__tests__/base.test.ts @@ -20,7 +20,7 @@ describe("Base Validator Test", () => { }); it("should return proper owner address", async () => { - expect(await validator.getOwner()).eql(dummyAddress); + expect(await validator.getOwnerAddress()).eql(dummyAddress); }); it("should sign hash properly", async () => { diff --git a/packages/accounts/src/kernel-zerodev/validator/__tests__/mocks/mock-signer-validator.ts b/packages/accounts/src/kernel-zerodev/validator/__tests__/mocks/mock-signer-validator.ts index 5421d2b9f..8a99a502b 100644 --- a/packages/accounts/src/kernel-zerodev/validator/__tests__/mocks/mock-signer-validator.ts +++ b/packages/accounts/src/kernel-zerodev/validator/__tests__/mocks/mock-signer-validator.ts @@ -1,6 +1,17 @@ -import type { Address, Hex, SmartAccountSigner } from "@alchemy/aa-core"; +import type { + Address, + Hex, + SignTypedDataParams, + SmartAccountSigner, +} from "@alchemy/aa-core"; export class MockSignerValidator implements SmartAccountSigner { + signerType = "mock-validator"; + + signTypedData(params: SignTypedDataParams): Promise<`0x${string}`> { + return Promise.resolve("0xMOCK_SIGN_TYPED_DATA"); + } + getAddress(): Promise
{ return Promise.resolve("0xabcfC3DB1e0f5023F5a4f40c03D149f316E6A5cc"); } diff --git a/packages/accounts/src/kernel-zerodev/validator/base.ts b/packages/accounts/src/kernel-zerodev/validator/base.ts index 7a07d468a..cb159b1cc 100644 --- a/packages/accounts/src/kernel-zerodev/validator/base.ts +++ b/packages/accounts/src/kernel-zerodev/validator/base.ts @@ -33,7 +33,7 @@ export class KernelBaseValidator { return this.validatorAddress; } - async getOwner(): Promise { + async getOwnerAddress(): Promise { return this.owner.getAddress(); } async signMessageWithValidatorParams( diff --git a/packages/accounts/src/light-account/__tests__/account.test.ts b/packages/accounts/src/light-account/__tests__/account.test.ts new file mode 100644 index 000000000..096db634d --- /dev/null +++ b/packages/accounts/src/light-account/__tests__/account.test.ts @@ -0,0 +1,100 @@ +import { + LocalAccountSigner, + SmartAccountProvider, + type BatchUserOperationCallData, + type SmartAccountSigner, +} from "@alchemy/aa-core"; +import { polygonMumbai, type Chain } from "viem/chains"; +import { describe, it } from "vitest"; +import { LightSmartContractAccount } from "../account.js"; + +describe("Light Account Tests", () => { + const dummyMnemonic = + "test test test test test test test test test test test test"; + const owner: SmartAccountSigner = + LocalAccountSigner.mnemonicToAccountSigner(dummyMnemonic); + const chain = polygonMumbai; + + it("should correctly sign the message", async () => { + const signer = givenConnectedProvider({ owner, chain }); + expect( + await signer.signMessage( + "0xa70d0af2ebb03a44dcd0714a8724f622e3ab876d0aa312f0ee04823285d6fb1b" + ) + ).toBe( + "0x33b1b0d34ba3252cd8abac8147dc08a6e14a6319462456a34468dd5713e38dda3a43988460011af94b30fa3efefcf9d0da7d7522e06b7bd8bff3b65be4aee5b31c" + ); + }); + + it("should correctly sign typed data", async () => { + const signer = givenConnectedProvider({ owner, chain }); + expect( + await signer.signTypedData({ + types: { + Request: [{ name: "hello", type: "string" }], + }, + primaryType: "Request", + message: { + hello: "world", + }, + }) + ).toBe( + "0xda1aeed13916d5723579f26cb9116155945d3581d642c38d8e2bce9fc969014f3eb599fa375df3d6e8181c8f04db64819186ac44cf5fd2bdd90e9f8543c579461b" + ); + }); + + it("should correctly encode transferOwnership data", async () => { + expect( + LightSmartContractAccount.encodeTransferOwnership( + "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + ) + ).toBe( + "0xf2fde38b000000000000000000000000deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + ); + }); + + it("should correctly encode batch transaction data", async () => { + const signer = givenConnectedProvider({ owner, chain }); + const data = [ + { + target: "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", + data: "0xdeadbeef", + }, + { + target: "0x8ba1f109551bd432803012645ac136ddd64dba72", + data: "0xcafebabe", + }, + ] satisfies BatchUserOperationCallData; + + expect(await signer.account.encodeBatchExecute(data)).toMatchInlineSnapshot( + '"0x18dfb3c7000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000deadbeefdeadbeefdeadbeefdeadbeefdeadbeef0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba720000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004deadbeef000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004cafebabe00000000000000000000000000000000000000000000000000000000"' + ); + }); +}); + +const givenConnectedProvider = ({ + owner, + chain, +}: { + owner: SmartAccountSigner; + chain: Chain; +}) => + new SmartAccountProvider({ + rpcProvider: `${chain.rpcUrls.alchemy.http[0]}/${"test"}`, + entryPointAddress: "0xENTRYPOINT_ADDRESS", + chain, + }).connect((provider) => { + const account = new LightSmartContractAccount({ + entryPointAddress: "0xENTRYPOINT_ADDRESS", + chain, + owner, + factoryAddress: "0xLIGHT_ACCOUNT_FACTORY_ADDRESS", + rpcClient: provider, + }); + + account.getAddress = vi.fn( + async () => "0xb856DBD4fA1A79a46D426f537455e7d3E79ab7c4" + ); + + return account; + }); diff --git a/packages/accounts/src/light-account/abis/LightAccountAbi.ts b/packages/accounts/src/light-account/abis/LightAccountAbi.ts new file mode 100644 index 000000000..9826a8a43 --- /dev/null +++ b/packages/accounts/src/light-account/abis/LightAccountAbi.ts @@ -0,0 +1,603 @@ +export const LightAccountAbi = [ + { + inputs: [ + { + internalType: "contract IEntryPoint", + name: "anEntryPoint", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "previousAdmin", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "newAdmin", + type: "address", + }, + ], + name: "AdminChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "beacon", + type: "address", + }, + ], + name: "BeaconUpgraded", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint8", + name: "version", + type: "uint8", + }, + ], + name: "Initialized", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "contract IEntryPoint", + name: "entryPoint", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + ], + name: "LightAccountInitialized", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "implementation", + type: "address", + }, + ], + name: "Upgraded", + type: "event", + }, + { + inputs: [], + name: "addDeposit", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "entryPoint", + outputs: [ + { + internalType: "contract IEntryPoint", + name: "", + type: "address", + }, + ], + stateMutability: "view", + 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: "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", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "getDeposit", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getNonce", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "anOwner", + type: "address", + }, + ], + name: "initialize", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "digest", + type: "bytes32", + }, + { + internalType: "bytes", + name: "signature", + type: "bytes", + }, + ], + name: "isValidSignature", + outputs: [ + { + internalType: "bytes4", + name: "", + type: "bytes4", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "uint256[]", + name: "", + type: "uint256[]", + }, + { + internalType: "uint256[]", + name: "", + type: "uint256[]", + }, + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + name: "onERC1155BatchReceived", + outputs: [ + { + internalType: "bytes4", + name: "", + type: "bytes4", + }, + ], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "uint256", + name: "", + type: "uint256", + }, + { + internalType: "uint256", + name: "", + type: "uint256", + }, + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + name: "onERC1155Received", + outputs: [ + { + internalType: "bytes4", + name: "", + type: "bytes4", + }, + ], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "uint256", + name: "", + type: "uint256", + }, + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + name: "onERC721Received", + outputs: [ + { + internalType: "bytes4", + name: "", + type: "bytes4", + }, + ], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "proxiableUUID", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes4", + name: "interfaceId", + type: "bytes4", + }, + ], + name: "supportsInterface", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "uint256", + name: "", + type: "uint256", + }, + { + internalType: "bytes", + name: "", + type: "bytes", + }, + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + name: "tokensReceived", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "newImplementation", + type: "address", + }, + ], + name: "upgradeTo", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "newImplementation", + type: "address", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + ], + name: "upgradeToAndCall", + outputs: [], + stateMutability: "payable", + 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: "validationData", + type: "uint256", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address payable", + name: "withdrawAddress", + type: "address", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "withdrawDepositTo", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + stateMutability: "payable", + type: "receive", + }, +] as const; diff --git a/packages/accounts/src/light-account/abis/LightAccountFactoryAbi.ts b/packages/accounts/src/light-account/abis/LightAccountFactoryAbi.ts new file mode 100644 index 000000000..a4a2f8711 --- /dev/null +++ b/packages/accounts/src/light-account/abis/LightAccountFactoryAbi.ts @@ -0,0 +1,74 @@ +export const LightAccountFactoryAbi = [ + { + inputs: [ + { + internalType: "contract IEntryPoint", + name: "_entryPoint", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "accountImplementation", + outputs: [ + { + internalType: "contract LightAccount", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address", + }, + { + internalType: "uint256", + name: "salt", + type: "uint256", + }, + ], + name: "createAccount", + outputs: [ + { + internalType: "contract LightAccount", + name: "ret", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address", + }, + { + internalType: "uint256", + name: "salt", + type: "uint256", + }, + ], + name: "getAddress", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/packages/accounts/src/light-account/account.ts b/packages/accounts/src/light-account/account.ts new file mode 100644 index 000000000..f4415b26d --- /dev/null +++ b/packages/accounts/src/light-account/account.ts @@ -0,0 +1,115 @@ +import { + SimpleSmartContractAccount, + SmartAccountProvider, + type SignTypedDataParams, + type SmartAccountSigner, +} from "@alchemy/aa-core"; +import { + concatHex, + decodeFunctionResult, + encodeFunctionData, + type Address, + type FallbackTransport, + type Hash, + type Hex, + type Transport, +} from "viem"; +import { LightAccountAbi } from "./abis/LightAccountAbi.js"; +import { LightAccountFactoryAbi } from "./abis/LightAccountFactoryAbi.js"; + +export class LightSmartContractAccount< + TTransport extends Transport | FallbackTransport = Transport +> extends SimpleSmartContractAccount { + override async signTypedData(params: SignTypedDataParams): Promise { + return this.owner.signTypedData(params); + } + + /** + * Returns the on-chain EOA owner address of the account. + * + * @returns {Address} the on-chain EOA owner of the account + */ + async getOwnerAddress(): Promise
{ + const callResult = await this.rpcProvider.call({ + to: await this.getAddress(), + data: encodeFunctionData({ + abi: LightAccountAbi, + functionName: "owner", + }), + }); + + if (callResult.data == null) { + throw new Error("could not get on-chain owner"); + } + + const decodedCallResult = decodeFunctionResult({ + abi: LightAccountAbi, + functionName: "owner", + data: callResult.data, + }); + + if (decodedCallResult !== (await this.owner.getAddress())) { + throw new Error("on-chain owner does not match account owner"); + } + + return decodedCallResult; + } + + /** + * Encodes the transferOwnership function call using the LightAccount ABI. + * + * @param newOwner - the new owner of the account + * @returns {Hex} the encoded function call + */ + static encodeTransferOwnership(newOwner: Address): Hex { + return encodeFunctionData({ + abi: LightAccountAbi, + functionName: "transferOwnership", + args: [newOwner], + }); + } + + /** + * Transfers ownership of the account to the newOwner on-chain and also updates the owner of the account. + * Optionally waits for the transaction to be mined. + * + * @param provider - the provider to use to send the transaction + * @param newOwner - the new owner of the account + * @param waitForTxn - whether or not to wait for the transaction to be mined + * @returns {Hash} the userOperation hash, or transaction hash if `waitForTxn` is true + */ + static async transferOwnership< + TTransport extends Transport | FallbackTransport = Transport + >( + provider: SmartAccountProvider & { + account: LightSmartContractAccount; + }, + newOwner: SmartAccountSigner, + waitForTxn: boolean = false + ): Promise { + const data = this.encodeTransferOwnership(await newOwner.getAddress()); + const result = await provider.sendUserOperation({ + target: await provider.getAddress(), + data, + }); + + provider.account.owner = newOwner; + + if (waitForTxn) { + return provider.waitForUserOperationTransaction(result.hash); + } + + return result.hash; + } + + protected override async getAccountInitCode(): Promise<`0x${string}`> { + return concatHex([ + this.factoryAddress, + encodeFunctionData({ + abi: LightAccountFactoryAbi, + functionName: "createAccount", + args: [await this.owner.getAddress(), this.index], + }), + ]); + } +} diff --git a/packages/accounts/src/light-account/e2e-tests/constants.ts b/packages/accounts/src/light-account/e2e-tests/constants.ts new file mode 100644 index 000000000..b5b5f901e --- /dev/null +++ b/packages/accounts/src/light-account/e2e-tests/constants.ts @@ -0,0 +1,6 @@ +export const RPC_URL = process.env.RPC_URL!; +export const API_KEY = process.env.API_KEY!; +export const UNDEPLOYED_OWNER_MNEMONIC = process.env.UNDEPLOYED_OWNER_MNEMONIC!; +export const LIGHT_ACCOUNT_OWNER_MNEMONIC = + process.env.LIGHT_ACCOUNT_OWNER_MNEMONIC!; +// todo(ajay): replace with OWNER_MNEMONIC when light account factory address when live (accidentally moved owner account to a different mnemonic during testing) diff --git a/packages/accounts/src/light-account/e2e-tests/light-account.test.ts b/packages/accounts/src/light-account/e2e-tests/light-account.test.ts new file mode 100644 index 000000000..3939f94e5 --- /dev/null +++ b/packages/accounts/src/light-account/e2e-tests/light-account.test.ts @@ -0,0 +1,205 @@ +import { + LocalAccountSigner, + SmartAccountProvider, + type SmartAccountSigner, +} from "@alchemy/aa-core"; +import { isAddress, type Address, type Chain, type Hash } from "viem"; +import { generatePrivateKey } from "viem/accounts"; +import { sepolia } from "viem/chains"; +import { LightSmartContractAccount } from "../account.js"; +import { + API_KEY, + LIGHT_ACCOUNT_OWNER_MNEMONIC, + RPC_URL, + UNDEPLOYED_OWNER_MNEMONIC, +} from "./constants.js"; + +const ENTRYPOINT_ADDRESS = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"; +const LIGHT_ACCOUNT_FACTORY_ADDRESS = + "0xDC31c846DA74400C732edb0fE888A2e4ADfBb8b1"; +// todo(ajay): replace with official factory address when live + +describe("Light Account Tests", () => { + const chain = sepolia; + const owner: SmartAccountSigner = LocalAccountSigner.mnemonicToAccountSigner( + LIGHT_ACCOUNT_OWNER_MNEMONIC + ); + const undeployedOwner = LocalAccountSigner.mnemonicToAccountSigner( + UNDEPLOYED_OWNER_MNEMONIC + ); + + it("should succesfully get counterfactual address", async () => { + const signer = givenConnectedProvider({ owner, chain }); + expect(await signer.getAddress()).toMatchInlineSnapshot( + '"0x7eDdc16B15259E5541aCfdebC46929873839B872"' + ); + }); + + it("should sign typed data successfully", async () => { + const signer = givenConnectedProvider({ owner, chain }); + const typedData = { + types: { + Request: [{ name: "hello", type: "string" }], + }, + primaryType: "Request", + message: { + hello: "world", + }, + }; + expect(await signer.signTypedData(typedData)).toBe( + await owner.signTypedData(typedData) + ); + }); + + it("should sign message successfully", async () => { + const signer = givenConnectedProvider({ owner, chain }); + expect(await signer.signMessage("test")).toBe( + await owner.signMessage("test") + ); + }); + + it("should sign typed data with 6492 successfully for undeployed account", async () => { + const undeployedSigner = givenConnectedProvider({ + owner: undeployedOwner, + chain, + }); + const typedData = { + types: { + Request: [{ name: "hello", type: "string" }], + }, + primaryType: "Request", + message: { + hello: "world", + }, + }; + expect( + await undeployedSigner.signTypedDataWith6492(typedData) + ).toMatchInlineSnapshot( + '"0x000000000000000000000000dc31c846da74400c732edb0fe888a2e4adfbb8b1000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000445fbfb9cf000000000000000000000000ef9d7530d16df66481adf291dc9a12b44c7f7df00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041591a9422219a5f2bc87ee24a82a6d5ef9674bf7408a2a289984de258466d148e75efb65b487ffbfcb061b268b1b667d8d7d4eac2c3d9d2d0a52d49c891be567c1c000000000000000000000000000000000000000000000000000000000000006492649264926492649264926492649264926492649264926492649264926492"' + ); + }); + + it("should sign message with 6492 successfully for undeployed account", async () => { + const undeployedSigner = givenConnectedProvider({ + owner: undeployedOwner, + chain, + }); + expect( + await undeployedSigner.signMessageWith6492("test") + ).toMatchInlineSnapshot( + '"0x000000000000000000000000dc31c846da74400c732edb0fe888a2e4adfbb8b1000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000445fbfb9cf000000000000000000000000ef9d7530d16df66481adf291dc9a12b44c7f7df00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041be34ecce63c5248d5cda407e7da319be3c861e6e2c5d30c9630cd35dcb55e56205c482503552883923f79e751ea3671cbb84d65b18af33cd3034aeb7d529da9a1b000000000000000000000000000000000000000000000000000000000000006492649264926492649264926492649264926492649264926492649264926492"' + ); + }); + + /** + * Need to add test eth to the counterfactual address for this test to pass. + * For current balance, @see: https://sepolia.etherscan.io/address/0x7eDdc16B15259E5541aCfdebC46929873839B872 + */ + it("should execute successfully", async () => { + const signer = givenConnectedProvider({ owner, chain }); + const result = await signer.sendUserOperation({ + target: await signer.getAddress(), + data: "0x", + }); + const txnHash = signer.waitForUserOperationTransaction(result.hash as Hash); + + await expect(txnHash).resolves.not.toThrowError(); + }, 50000); + + it("should fail to execute if account address is not deployed and not correct", async () => { + const accountAddress = "0xc33AbD9621834CA7c6Fc9f9CC3c47b9c17B03f9F"; + const newSigner = givenConnectedProvider({ owner, chain, accountAddress }); + + const result = newSigner.sendUserOperation({ + target: await newSigner.getAddress(), + data: "0x", + }); + + await expect(result).rejects.toThrowError(); + }); + + it("should get counterfactual for undeployed account", async () => { + const owner = LocalAccountSigner.privateKeyToAccountSigner( + generatePrivateKey() + ); + const provider = givenConnectedProvider({ owner, chain }); + + const address = provider.getAddress(); + await expect(address).resolves.not.toThrowError(); + expect(isAddress(await address)).toBe(true); + }); + + it("should get owner successfully", async () => { + const signer = givenConnectedProvider({ owner, chain }); + expect(await signer.account.getOwnerAddress()).toMatchInlineSnapshot( + '"0x65eaA2AfDF6c97295bA44C458abb00FebFB3a5FA"' + ); + expect(await signer.account.getOwnerAddress()).toBe( + await owner.getAddress() + ); + }); + + it("should transfer ownership successfully", async () => { + const signer = givenConnectedProvider({ owner, chain }); + // create a throwaway address + const throwawayOwner = LocalAccountSigner.privateKeyToAccountSigner( + generatePrivateKey() + ); + const provider = givenConnectedProvider({ owner: throwawayOwner, chain }); + + // fund the throwaway address + const fundThrowawayResult = await signer.sendUserOperation({ + target: await provider.getAddress(), + data: "0x", + value: BigInt("0x10000000000000"), + }); + const fundThrowawayTxnHash = signer.waitForUserOperationTransaction( + fundThrowawayResult.hash + ); + await expect(fundThrowawayTxnHash).resolves.not.toThrowError(); + + // create new owner and transfer ownership + const newThrowawayOwner = LocalAccountSigner.privateKeyToAccountSigner( + generatePrivateKey() + ); + const result = await LightSmartContractAccount.transferOwnership( + provider, + newThrowawayOwner + ); + const txnHash = provider.waitForUserOperationTransaction(result); + await expect(txnHash).resolves.not.toThrowError(); + + expect(await provider.account.getOwnerAddress()).not.toBe( + await throwawayOwner.getAddress() + ); + expect(await provider.account.getOwnerAddress()).toBe( + await newThrowawayOwner.getAddress() + ); + }, 100000); +}); + +const givenConnectedProvider = ({ + owner, + chain, + accountAddress, +}: { + owner: SmartAccountSigner; + chain: Chain; + accountAddress?: Address; +}) => + new SmartAccountProvider({ + rpcProvider: + RPC_URL != null ? RPC_URL : `${chain.rpcUrls.alchemy.http[0]}/${API_KEY}`, + entryPointAddress: ENTRYPOINT_ADDRESS, + chain, + }).connect( + (provider) => + new LightSmartContractAccount({ + entryPointAddress: ENTRYPOINT_ADDRESS, + chain, + owner, + factoryAddress: LIGHT_ACCOUNT_FACTORY_ADDRESS, + rpcClient: provider, + accountAddress, + }) + ); diff --git a/packages/accounts/src/light-account/utils.ts b/packages/accounts/src/light-account/utils.ts new file mode 100644 index 000000000..450fea94c --- /dev/null +++ b/packages/accounts/src/light-account/utils.ts @@ -0,0 +1,39 @@ +import type { Address, Chain } from "viem"; +import { + arbitrum, + arbitrumGoerli, + base, + baseGoerli, + goerli, + mainnet, + optimism, + optimismGoerli, + polygon, + polygonMumbai, + sepolia, +} from "viem/chains"; + +/** + * Utility method returning the light account factory address given a {@link chains.Chain} object + * + * @param chain - a {@link chains.Chain} object + * @returns a {@link abi.Address} for the given chain + * @throws if the chain doesn't have an address currently deployed + */ +export const getDefaultLightAccountFactory = (chain: Chain): Address => { + switch (chain.id) { + case mainnet.id: + case sepolia.id: + case goerli.id: + case polygon.id: + case polygonMumbai.id: + case optimism.id: + case optimismGoerli.id: + case arbitrum.id: + case arbitrumGoerli.id: + case base.id: + case baseGoerli.id: + return "0x000000893a26168158fbeadd9335be5bc96592e2"; + } + throw new Error("could not find light account factory address"); +}; diff --git a/packages/alchemy/e2e-tests/simple-account.test.ts b/packages/alchemy/e2e-tests/simple-account.test.ts index 97444f3bb..8ab393a96 100644 --- a/packages/alchemy/e2e-tests/simple-account.test.ts +++ b/packages/alchemy/e2e-tests/simple-account.test.ts @@ -2,7 +2,7 @@ import { SimpleSmartContractAccount, type SmartAccountSigner, } from "@alchemy/aa-core"; -import { toHex, type Hash } from "viem"; +import { toHex, type Address, type Chain, type Hash } from "viem"; import { mnemonicToAccount } from "viem/accounts"; import { polygonMumbai } from "viem/chains"; import { AlchemyProvider } from "../src/provider.js"; @@ -23,28 +23,16 @@ describe("Simple Account Tests", () => { getAddress: async () => ownerAccount.address, }; const chain = polygonMumbai; - const signer = new AlchemyProvider({ - apiKey: API_KEY!, - chain, - entryPointAddress: ENTRYPOINT_ADDRESS, - }).connect( - (provider) => - new SimpleSmartContractAccount({ - entryPointAddress: ENTRYPOINT_ADDRESS, - chain, - owner, - factoryAddress: SIMPLE_ACCOUNT_FACTORY_ADDRESS, - rpcClient: provider, - }) - ); it("should succesfully get counterfactual address", async () => { + const signer = givenConnectedProvider({ owner, chain }); expect(await signer.getAddress()).toMatchInlineSnapshot( `"0xb856DBD4fA1A79a46D426f537455e7d3E79ab7c4"` ); }); it("should execute successfully", async () => { + const signer = givenConnectedProvider({ owner, chain }); const result = await signer.sendUserOperation({ target: await signer.getAddress(), data: "0x", @@ -56,24 +44,10 @@ describe("Simple Account Tests", () => { it("should fail to execute if account address is not deployed and not correct", async () => { const accountAddress = "0xc33AbD9621834CA7c6Fc9f9CC3c47b9c17B03f9F"; - const newSigner = new AlchemyProvider({ - apiKey: API_KEY!, - chain, - entryPointAddress: ENTRYPOINT_ADDRESS, - }).connect( - (provider) => - new SimpleSmartContractAccount({ - entryPointAddress: ENTRYPOINT_ADDRESS, - chain, - owner, - factoryAddress: SIMPLE_ACCOUNT_FACTORY_ADDRESS, - rpcClient: provider, - accountAddress, - }) - ); + const signer = givenConnectedProvider({ owner, chain, accountAddress }); - const result = newSigner.sendUserOperation({ - target: await newSigner.getAddress(), + const result = signer.sendUserOperation({ + target: await signer.getAddress(), data: "0x", }); @@ -81,13 +55,16 @@ describe("Simple Account Tests", () => { }); it("should successfully execute with alchemy paymaster info", async () => { - const newSigner = signer.withAlchemyGasManager({ + const signer = givenConnectedProvider({ + owner, + chain, + }).withAlchemyGasManager({ policyId: PAYMASTER_POLICY_ID, entryPoint: ENTRYPOINT_ADDRESS, }); - const result = await newSigner.sendUserOperation({ - target: await newSigner.getAddress(), + const result = await signer.sendUserOperation({ + target: await signer.getAddress(), data: "0x", }); const txnHash = signer.waitForUserOperationTransaction(result.hash as Hash); @@ -96,7 +73,7 @@ describe("Simple Account Tests", () => { }, 50000); it("should successfully override fees in alchemy paymaster", async () => { - const newSigner = signer + const signer = givenConnectedProvider({ owner, chain }) .withAlchemyGasManager({ policyId: PAYMASTER_POLICY_ID, entryPoint: ENTRYPOINT_ADDRESS, @@ -109,45 +86,101 @@ describe("Simple Account Tests", () => { // this should fail since we set super low fees await expect( async () => - await newSigner.sendUserOperation({ - target: await newSigner.getAddress(), + await signer.sendUserOperation({ + target: await signer.getAddress(), data: "0x", }) ).rejects.toThrow(); }, 50000); it("should successfully use paymaster with fee opts", async () => { - const newSigner = new AlchemyProvider({ - apiKey: API_KEY!, + const signer = givenConnectedProvider({ + owner, chain, - entryPointAddress: ENTRYPOINT_ADDRESS, feeOpts: { baseFeeBufferPercent: 50n, maxPriorityFeeBufferPercent: 50n, preVerificationGasBufferPercent: 50n, }, - }) - .connect( - (provider) => - new SimpleSmartContractAccount({ - entryPointAddress: ENTRYPOINT_ADDRESS, - chain, - owner, - factoryAddress: SIMPLE_ACCOUNT_FACTORY_ADDRESS, - rpcClient: provider, - }) - ) - .withAlchemyGasManager({ - policyId: PAYMASTER_POLICY_ID, - entryPoint: ENTRYPOINT_ADDRESS, - }); + }); - const result = await newSigner.sendUserOperation({ - target: await newSigner.getAddress(), + const result = await signer.sendUserOperation({ + target: await signer.getAddress(), data: "0x", }); const txnHash = signer.waitForUserOperationTransaction(result.hash as Hash); await expect(txnHash).resolves.not.toThrowError(); }, 50000); + + it("should execute successfully via drop and replace", async () => { + const signer = givenConnectedProvider({ + owner, + chain, + }); + + const result = await signer.sendUserOperation({ + target: await signer.getAddress(), + data: "0x", + }); + const replacedResult = await signer.dropAndReplaceUserOperation( + result.request + ); + + const txnHash = signer.waitForUserOperationTransaction(replacedResult.hash); + await expect(txnHash).resolves.not.toThrowError(); + }, 50000); + + it("should execute successfully via drop and replace when using paymaster", async () => { + const signer = givenConnectedProvider({ + owner, + chain, + }).withAlchemyGasManager({ + policyId: PAYMASTER_POLICY_ID, + entryPoint: ENTRYPOINT_ADDRESS, + }); + + const result = await signer.sendUserOperation({ + target: await signer.getAddress(), + data: "0x", + }); + const replacedResult = await signer.dropAndReplaceUserOperation( + result.request + ); + + const txnHash = signer.waitForUserOperationTransaction(replacedResult.hash); + await expect(txnHash).resolves.not.toThrowError(); + }, 50000); }); + +const givenConnectedProvider = ({ + owner, + chain, + accountAddress, + feeOpts, +}: { + owner: SmartAccountSigner; + chain: Chain; + accountAddress?: Address; + feeOpts?: { + baseFeeBufferPercent?: bigint; + maxPriorityFeeBufferPercent?: bigint; + preVerificationGasBufferPercent?: bigint; + }; +}) => + new AlchemyProvider({ + apiKey: API_KEY!, + chain, + entryPointAddress: ENTRYPOINT_ADDRESS, + feeOpts, + }).connect( + (provider) => + new SimpleSmartContractAccount({ + entryPointAddress: ENTRYPOINT_ADDRESS, + chain, + owner, + factoryAddress: SIMPLE_ACCOUNT_FACTORY_ADDRESS, + rpcClient: provider, + accountAddress, + }) + ); diff --git a/packages/alchemy/package.json b/packages/alchemy/package.json index 2c883bb1d..477f77cc2 100644 --- a/packages/alchemy/package.json +++ b/packages/alchemy/package.json @@ -45,7 +45,7 @@ "vitest": "^0.31.0" }, "dependencies": { - "viem": "^1.10.9" + "viem": "^1.16.2" }, "peerDependencies": { "@alchemy/aa-core": "^0.1.0-alpha.1" diff --git a/packages/alchemy/src/__tests__/provider.test.ts b/packages/alchemy/src/__tests__/provider.test.ts index 5c42aef27..9bbe7334a 100644 --- a/packages/alchemy/src/__tests__/provider.test.ts +++ b/packages/alchemy/src/__tests__/provider.test.ts @@ -4,7 +4,7 @@ import { type BatchUserOperationCallData, type SmartAccountSigner, } from "@alchemy/aa-core"; -import { toHex } from "viem"; +import { toHex, type Chain } from "viem"; import { mnemonicToAccount } from "viem/accounts"; import { polygonMumbai } from "viem/chains"; import { AlchemyProvider } from "../provider.js"; @@ -22,35 +22,10 @@ describe("Alchemy Provider Tests", () => { getAddress: async () => ownerAccount.address, }; const chain = polygonMumbai; - const signer = new AlchemyProvider({ - rpcUrl: "https://eth-mainnet.g.alchemy.com/v2", - jwt: "test", - chain, - entryPointAddress: "0xENTRYPOINT_ADDRESS", - }).connect((provider) => { - const account = new SimpleSmartContractAccount({ - entryPointAddress: "0xENTRYPOINT_ADDRESS", - chain, - owner, - factoryAddress: "0xSIMPLE_ACCOUNT_FACTORY_ADDRESS", - rpcClient: provider, - }); - - account.getAddress = vi.fn( - async () => "0xb856DBD4fA1A79a46D426f537455e7d3E79ab7c4" - ); - - return account; - }); it("should have a JWT propety", async () => { const spy = vi.spyOn(AACoreModule, "createPublicErc4337Client"); - new AlchemyProvider({ - rpcUrl: "https://eth-mainnet.g.alchemy.com/v2", - jwt: "test", - chain, - entryPointAddress: "0xENTRYPOINT_ADDRESS", - }); + givenConnectedProvider({ owner, chain }); expect(spy.mock.calls[0][0].fetchOptions).toMatchInlineSnapshot(` { "headers": { @@ -61,6 +36,7 @@ describe("Alchemy Provider Tests", () => { }); it("should correctly sign the message", async () => { + const signer = givenConnectedProvider({ owner, chain }); expect( // TODO: expose sign message on the provider too await signer.account.signMessage( @@ -72,6 +48,7 @@ describe("Alchemy Provider Tests", () => { }); it("should correctly encode batch transaction data", async () => { + const signer = givenConnectedProvider({ owner, chain }); const account = signer.account; const data = [ { @@ -89,3 +66,31 @@ describe("Alchemy Provider Tests", () => { ); }); }); + +const givenConnectedProvider = ({ + owner, + chain, +}: { + owner: SmartAccountSigner; + chain: Chain; +}) => + new AlchemyProvider({ + rpcUrl: "https://eth-mainnet.g.alchemy.com/v2", + jwt: "test", + chain, + entryPointAddress: "0xENTRYPOINT_ADDRESS", + }).connect((provider) => { + const account = new SimpleSmartContractAccount({ + entryPointAddress: "0xENTRYPOINT_ADDRESS", + chain, + owner, + factoryAddress: "0xSIMPLE_ACCOUNT_FACTORY_ADDRESS", + rpcClient: provider, + }); + + account.getAddress = vi.fn( + async () => "0xb856DBD4fA1A79a46D426f537455e7d3E79ab7c4" + ); + + return account; + }); diff --git a/packages/alchemy/src/index.ts b/packages/alchemy/src/index.ts index 0c61fe42c..a906220c1 100644 --- a/packages/alchemy/src/index.ts +++ b/packages/alchemy/src/index.ts @@ -1,10 +1,7 @@ export { withAlchemyGasFeeEstimator } from "./middleware/gas-fees.js"; - -export { - withAlchemyGasManager, - alchemyPaymasterAndDataMiddleware, -} from "./middleware/gas-manager.js"; +export { withAlchemyGasManager } from "./middleware/gas-manager.js"; export { SupportedChains } from "./chains.js"; + export { AlchemyProvider } from "./provider.js"; export type { AlchemyProviderConfig, ConnectionConfig } from "./provider.js"; diff --git a/packages/alchemy/src/middleware/gas-fees.ts b/packages/alchemy/src/middleware/gas-fees.ts index b689790c3..7add85bde 100644 --- a/packages/alchemy/src/middleware/gas-fees.ts +++ b/packages/alchemy/src/middleware/gas-fees.ts @@ -1,4 +1,5 @@ import type { AlchemyProvider } from "../provider.js"; +import type { ClientWithAlchemyMethods } from "./client.js"; export const withAlchemyGasFeeEstimator = ( provider: AlchemyProvider, @@ -12,7 +13,8 @@ export const withAlchemyGasFeeEstimator = ( throw new Error("baseFeePerGas is null"); } const priorityFeePerGas = BigInt( - await provider.alchemyClient.request({ + // it's a fair assumption that if someone is using this Alchemy Middleware, then they are using Alchemy RPC + await (provider.rpcClient as ClientWithAlchemyMethods).request({ method: "rundler_maxPriorityFeePerGas", params: [], }) diff --git a/packages/alchemy/src/middleware/gas-manager.ts b/packages/alchemy/src/middleware/gas-manager.ts index fcd60ec1f..8fc72a03d 100644 --- a/packages/alchemy/src/middleware/gas-manager.ts +++ b/packages/alchemy/src/middleware/gas-manager.ts @@ -1,10 +1,10 @@ import { deepHexlify, resolveProperties, - type ConnectedSmartAccountProvider, type UserOperationRequest, } from "@alchemy/aa-core"; -import type { Address, Transport } from "viem"; +import type { Address } from "viem"; +import type { AlchemyProvider } from "../provider.js"; import type { ClientWithAlchemyMethods } from "./client.js"; export interface AlchemyGasManagerConfig { @@ -13,86 +13,57 @@ export interface AlchemyGasManagerConfig { } /** - * This uses the alchemy RPC method: `alchemy_requestGasAndPaymasterAndData` to get all of the gas estimates + paymaster data - * in one RPC call. It will no-op the gas estimator and fee data getter middleware and set a custom middleware that makes the RPC call + * This middleware wraps the Alchemy Gas Manager APIs to provide more flexible UserOperation gas sponsorship. + * + * If `estimateGas` is true, it will use `alchemy_requestGasAndPaymasterAndData` to get all of the gas estimates + paymaster data + * in one RPC call. * - * @param provider - the smart account provider to override to use the alchemy paymaster - * @param config - the alchemy paymaster configuration - * @returns the provider augmented to use the alchemy paymaster + * Otherwise, it will use `alchemy_requestPaymasterAndData` to get only paymaster data, allowing you + * to customize the gas and fee estimation middleware. + * + * @param provider - the smart account provider to override to use the alchemy gas manager + * @param config - the alchemy gas manager configuration + * @param estimateGas - if true, this will use `alchemy_requestGasAndPaymasterAndData` else will use `alchemy_requestPaymasterAndData` + * @returns the provider augmented to use the alchemy gas manager */ -export const withAlchemyGasManager = < - T extends Transport, - Provider extends ConnectedSmartAccountProvider ->( - provider: Provider, - config: AlchemyGasManagerConfig -): Provider => { - return ( - provider - // no-op gas estimator - .withGasEstimator(async () => ({ - callGasLimit: 0n, - preVerificationGas: 0n, - verificationGasLimit: 0n, - })) - // no-op fee because the alchemy api will do it - // can set this after the fact to do fee overrides. - .withFeeDataGetter(async () => ({ - maxFeePerGas: 0n, - maxPriorityFeePerGas: 0n, - })) - .withPaymasterMiddleware({ - paymasterDataMiddleware: async (struct) => { - const userOperation: UserOperationRequest = deepHexlify( - await resolveProperties(struct) - ); - - let feeOverride = undefined; - if (BigInt(userOperation.maxFeePerGas) > 0n) { - feeOverride = { - maxFeePerGas: userOperation.maxFeePerGas, - maxPriorityFeePerGas: userOperation.maxPriorityFeePerGas, - }; - } - - const result = await ( - provider.rpcClient as ClientWithAlchemyMethods - ).request({ - method: "alchemy_requestGasAndPaymasterAndData", - params: [ - { - policyId: config.policyId, - entryPoint: config.entryPoint, - userOperation: userOperation, - dummySignature: provider.account.getDummySignature(), - feeOverride: feeOverride, - }, - ], - }); - - return result; - }, - }) - ); +export const withAlchemyGasManager = ( + provider: AlchemyProvider, + config: AlchemyGasManagerConfig, + estimateGas: boolean = true +): AlchemyProvider => { + return estimateGas + ? provider + // no-op gas estimator + .withGasEstimator(async () => ({ + callGasLimit: 0n, + preVerificationGas: 0n, + verificationGasLimit: 0n, + })) + // no-op fee because the alchemy api will do it + .withFeeDataGetter(async (struct) => ({ + maxFeePerGas: (await struct.maxFeePerGas) ?? 0n, + maxPriorityFeePerGas: (await struct.maxPriorityFeePerGas) ?? 0n, + })) + .withPaymasterMiddleware( + withAlchemyGasAndPaymasterAndDataMiddleware(provider, config) + ) + : provider.withPaymasterMiddleware( + withAlchemyPaymasterAndDataMiddleware(provider, config) + ); }; /** - * This is the middleware for calling the alchemy paymaster API which does not estimate gas. It's recommend to use + * This uses the alchemy RPC method: `alchemy_requestPaymasterAndData`, which does not estimate gas. It's recommend to use * this middleware if you want more customization over the gas and fee estimation middleware, including setting * non-default buffer values for the fee/gas estimation. * - * @param config {@link AlchemyPaymasterConfig} + * @param config - the alchemy gas manager configuration * @returns middleware overrides for paymaster middlewares */ -export const alchemyPaymasterAndDataMiddleware = < - T extends Transport, - Provider extends ConnectedSmartAccountProvider ->( - provider: Provider, +const withAlchemyPaymasterAndDataMiddleware = ( + provider: AlchemyProvider, config: AlchemyGasManagerConfig -): Parameters< - ConnectedSmartAccountProvider["withPaymasterMiddleware"] ->["0"] => ({ +): Parameters["0"] => ({ dummyPaymasterDataMiddleware: async (_struct) => { switch (provider.rpcClient.chain.id) { case 1: @@ -126,3 +97,46 @@ export const alchemyPaymasterAndDataMiddleware = < return { paymasterAndData }; }, }); + +/** + * This uses the alchemy RPC method: `alchemy_requestGasAndPaymasterAndData` to get all of the gas estimates + paymaster data + * in one RPC call. It will no-op the gas estimator and fee data getter middleware and set a custom middleware that makes the RPC call. + * + * @param config - the alchemy gas manager configuration + * @returns middleware overrides for paymaster middlewares + */ +const withAlchemyGasAndPaymasterAndDataMiddleware = ( + provider: AlchemyProvider, + config: AlchemyGasManagerConfig +): Parameters["0"] => ({ + paymasterDataMiddleware: async (struct) => { + const userOperation: UserOperationRequest = deepHexlify( + await resolveProperties(struct) + ); + + let feeOverride = undefined; + if (userOperation.maxFeePerGas && BigInt(userOperation.maxFeePerGas) > 0n) { + feeOverride = { + maxFeePerGas: userOperation.maxFeePerGas, + maxPriorityFeePerGas: userOperation.maxPriorityFeePerGas, + }; + } + + const result = await ( + provider.rpcClient as ClientWithAlchemyMethods + ).request({ + method: "alchemy_requestGasAndPaymasterAndData", + params: [ + { + policyId: config.policyId, + entryPoint: config.entryPoint, + userOperation: userOperation, + dummySignature: userOperation.signature, + feeOverride: feeOverride, + }, + ], + }); + + return result; + }, +}); diff --git a/packages/alchemy/src/provider.ts b/packages/alchemy/src/provider.ts index e081a70b2..8f842f857 100644 --- a/packages/alchemy/src/provider.ts +++ b/packages/alchemy/src/provider.ts @@ -1,13 +1,12 @@ import { - BaseSmartContractAccount, SmartAccountProvider, createPublicErc4337Client, deepHexlify, resolveProperties, type AccountMiddlewareFn, - type SmartAccountProviderOpts, + type SmartAccountProviderConfig, } from "@alchemy/aa-core"; -import { type Address, type Chain, type HttpTransport } from "viem"; +import { type HttpTransport } from "viem"; import { arbitrum, arbitrumGoerli, @@ -15,10 +14,8 @@ import { optimismGoerli, } from "viem/chains"; import { SupportedChains } from "./chains.js"; -import type { ClientWithAlchemyMethods } from "./middleware/client.js"; import { withAlchemyGasFeeEstimator } from "./middleware/gas-fees.js"; import { - alchemyPaymasterAndDataMiddleware, withAlchemyGasManager, type AlchemyGasManagerConfig, } from "./middleware/gas-manager.js"; @@ -30,10 +27,6 @@ export type ConnectionConfig = | { rpcUrl: string; apiKey?: never; jwt: string }; export type AlchemyProviderConfig = { - chain: Chain | number; - entryPointAddress: Address; - account?: BaseSmartContractAccount; - opts?: SmartAccountProviderOpts; feeOpts?: { /** this adds a percent buffer on top of the base fee estimated (default 50%) * NOTE: this is only applied if the default fee estimator is used. @@ -56,17 +49,16 @@ export type AlchemyProviderConfig = { */ preVerificationGasBufferPercent?: bigint; }; -} & ConnectionConfig; +} & Omit & + ConnectionConfig; export class AlchemyProvider extends SmartAccountProvider { - alchemyClient: ClientWithAlchemyMethods; private pvgBuffer: bigint; private feeOptsSet: boolean; constructor({ chain, entryPointAddress, - account, opts, feeOpts, ...connectionConfig @@ -94,9 +86,8 @@ export class AlchemyProvider extends SmartAccountProvider { }), }); - super(client, entryPointAddress, _chain, account, opts); + super({ rpcProvider: client, entryPointAddress, chain: _chain, opts }); - this.alchemyClient = this.rpcClient as ClientWithAlchemyMethods; withAlchemyGasFeeEstimator( this, feeOpts?.baseFeeBufferPercent ?? 50n, @@ -121,7 +112,7 @@ export class AlchemyProvider extends SmartAccountProvider { this.feeOptsSet = !!feeOpts; } - gasEstimator: AccountMiddlewareFn = async (struct) => { + override gasEstimator: AccountMiddlewareFn = async (struct) => { const request = deepHexlify(await resolveProperties(struct)); const estimates = await this.rpcClient.estimateUserOperationGas( request, @@ -136,19 +127,19 @@ export class AlchemyProvider extends SmartAccountProvider { }; }; - withAlchemyGasManager(config: AlchemyGasManagerConfig) { + /** + * This methods adds the Alchemy Gas Manager middleware to the provider. + * + * @param config - the Alchemy Gas Manager configuration + * @returns {AlchemyProvider} - a new AlchemyProvider with the Gas Manager middleware + */ + withAlchemyGasManager(config: AlchemyGasManagerConfig): AlchemyProvider { if (!this.isConnected()) { throw new Error( "AlchemyProvider: account is not set, did you call `connect` first?" ); } - if (this.feeOptsSet) { - return this.withPaymasterMiddleware( - alchemyPaymasterAndDataMiddleware(this, config) - ); - } else { - return withAlchemyGasManager(this, config); - } + return withAlchemyGasManager(this, config, !this.feeOptsSet); } } diff --git a/packages/core/e2e-tests/simple-account.test.ts b/packages/core/e2e-tests/simple-account.test.ts index 6cc1aebe7..f97fe4434 100644 --- a/packages/core/e2e-tests/simple-account.test.ts +++ b/packages/core/e2e-tests/simple-account.test.ts @@ -1,4 +1,4 @@ -import { isAddress, type Hash } from "viem"; +import { isAddress, type Address, type Chain, type Hash } from "viem"; import { generatePrivateKey } from "viem/accounts"; import { polygonMumbai } from "viem/chains"; import { SimpleSmartContractAccount } from "../src/account/simple.js"; @@ -15,28 +15,16 @@ describe("Simple Account Tests", () => { const owner: SmartAccountSigner = LocalAccountSigner.mnemonicToAccountSigner(OWNER_MNEMONIC); const chain = polygonMumbai; - const signer = new SmartAccountProvider( - RPC_URL != null ? RPC_URL : `${chain.rpcUrls.alchemy.http[0]}/${API_KEY}`, - ENTRYPOINT_ADDRESS, - chain - ).connect( - (provider) => - new SimpleSmartContractAccount({ - entryPointAddress: ENTRYPOINT_ADDRESS, - chain, - owner, - factoryAddress: SIMPLE_ACCOUNT_FACTORY_ADDRESS, - rpcClient: provider, - }) - ); it("should succesfully get counterfactual address", async () => { + const signer = givenConnectedProvider({ owner, chain }); expect(await signer.getAddress()).toMatchInlineSnapshot( `"0xb856DBD4fA1A79a46D426f537455e7d3E79ab7c4"` ); }); it("should execute successfully", async () => { + const signer = givenConnectedProvider({ owner, chain }); const result = await signer.sendUserOperation({ target: await signer.getAddress(), data: "0x", @@ -48,24 +36,10 @@ describe("Simple Account Tests", () => { it("should fail to execute if account address is not deployed and not correct", async () => { const accountAddress = "0xc33AbD9621834CA7c6Fc9f9CC3c47b9c17B03f9F"; - const newSigner = new SmartAccountProvider( - RPC_URL != null ? RPC_URL : `${chain.rpcUrls.alchemy.http[0]}/${API_KEY}`, - ENTRYPOINT_ADDRESS, - chain - ).connect( - (provider) => - new SimpleSmartContractAccount({ - entryPointAddress: ENTRYPOINT_ADDRESS, - chain, - owner, - factoryAddress: SIMPLE_ACCOUNT_FACTORY_ADDRESS, - rpcClient: provider, - accountAddress, - }) - ); + const signer = givenConnectedProvider({ owner, chain, accountAddress }); - const result = newSigner.sendUserOperation({ - target: await newSigner.getAddress(), + const result = signer.sendUserOperation({ + target: await signer.getAddress(), data: "0x", }); @@ -76,23 +50,37 @@ describe("Simple Account Tests", () => { const owner = LocalAccountSigner.privateKeyToAccountSigner( generatePrivateKey() ); - const provider = new SmartAccountProvider( - RPC_URL != null ? RPC_URL : `${chain.rpcUrls.alchemy.http[0]}/${API_KEY}`, - ENTRYPOINT_ADDRESS, - chain - ).connect( - (rpcClient) => - new SimpleSmartContractAccount({ - entryPointAddress: ENTRYPOINT_ADDRESS, - chain, - owner, - factoryAddress: SIMPLE_ACCOUNT_FACTORY_ADDRESS, - rpcClient, - }) - ); + const signer = givenConnectedProvider({ owner, chain }); - const address = provider.getAddress(); + const address = signer.getAddress(); await expect(address).resolves.not.toThrowError(); expect(isAddress(await address)).toBe(true); }); }); + +const givenConnectedProvider = ({ + owner, + chain, + accountAddress, +}: { + owner: SmartAccountSigner; + chain: Chain; + accountAddress?: Address; +}) => { + return new SmartAccountProvider({ + rpcProvider: + RPC_URL != null ? RPC_URL : `${chain.rpcUrls.alchemy.http[0]}/${API_KEY}`, + entryPointAddress: ENTRYPOINT_ADDRESS, + chain, + }).connect( + (provider) => + new SimpleSmartContractAccount({ + entryPointAddress: ENTRYPOINT_ADDRESS, + chain, + owner, + factoryAddress: SIMPLE_ACCOUNT_FACTORY_ADDRESS, + rpcClient: provider, + accountAddress, + }) + ); +}; diff --git a/packages/core/package.json b/packages/core/package.json index 88d8a68c9..cf6ff593c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -52,7 +52,7 @@ "dependencies": { "abitype": "^0.8.3", "eventemitter3": "^5.0.1", - "viem": "^1.10.9" + "viem": "^1.16.2" }, "repository": { "type": "git", diff --git a/packages/core/src/__tests__/utils.test.ts b/packages/core/src/__tests__/utils.test.ts index b76b82005..76c152044 100644 --- a/packages/core/src/__tests__/utils.test.ts +++ b/packages/core/src/__tests__/utils.test.ts @@ -1,5 +1,5 @@ import type { UserOperationRequest } from "../types"; -import { getUserOperationHash } from "../utils.js"; +import { getUserOperationHash } from "../utils/index.js"; describe("Utils Tests", () => { const ENTRYPOINT_ADDRESS = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"; diff --git a/packages/core/src/account/__tests__/simple.test.ts b/packages/core/src/account/__tests__/simple.test.ts index 0dc24b550..c5c61ff31 100644 --- a/packages/core/src/account/__tests__/simple.test.ts +++ b/packages/core/src/account/__tests__/simple.test.ts @@ -1,10 +1,10 @@ -import { polygonMumbai } from "viem/chains"; +import { polygonMumbai, type Chain } from "viem/chains"; import { describe, it } from "vitest"; import { SmartAccountProvider } from "../../provider/base.js"; import { LocalAccountSigner } from "../../signer/local-account.js"; +import { type SmartAccountSigner } from "../../signer/types.js"; import type { BatchUserOperationCallData } from "../../types.js"; import { SimpleSmartContractAccount } from "../simple.js"; -import { type SmartAccountSigner } from "../../signer/types.js"; describe("Account Simple Tests", () => { const dummyMnemonic = @@ -12,27 +12,9 @@ describe("Account Simple Tests", () => { const owner: SmartAccountSigner = LocalAccountSigner.mnemonicToAccountSigner(dummyMnemonic); const chain = polygonMumbai; - const signer = new SmartAccountProvider( - `${chain.rpcUrls.alchemy.http[0]}/${"test"}`, - "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", - chain - ).connect((provider) => { - const account = new SimpleSmartContractAccount({ - entryPointAddress: "0xENTRYPOINT_ADDRESS", - chain, - owner, - factoryAddress: "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - rpcClient: provider, - }); - - account.getAddress = vi.fn( - async () => "0xb856DBD4fA1A79a46D426f537455e7d3E79ab7c4" - ); - - return account; - }); it("should correctly sign the message", async () => { + const signer = givenConnectedProvider({ owner, chain }); expect( await signer.signMessage( "0xa70d0af2ebb03a44dcd0714a8724f622e3ab876d0aa312f0ee04823285d6fb1b" @@ -43,6 +25,7 @@ describe("Account Simple Tests", () => { }); it("should correctly encode batch transaction data", async () => { + const signer = givenConnectedProvider({ owner, chain }); const data = [ { target: "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", @@ -59,3 +42,30 @@ describe("Account Simple Tests", () => { ); }); }); + +const givenConnectedProvider = ({ + owner, + chain, +}: { + owner: SmartAccountSigner; + chain: Chain; +}) => + new SmartAccountProvider({ + rpcProvider: `${chain.rpcUrls.alchemy.http[0]}/${"test"}`, + entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", + chain, + }).connect((provider) => { + const account = new SimpleSmartContractAccount({ + entryPointAddress: "0xENTRYPOINT_ADDRESS", + chain, + owner, + factoryAddress: "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", + rpcClient: provider, + }); + + account.getAddress = vi.fn( + async () => "0xb856DBD4fA1A79a46D426f537455e7d3E79ab7c4" + ); + + return account; + }); diff --git a/packages/core/src/account/base.ts b/packages/core/src/account/base.ts index 5d32f7802..3dbe29f25 100644 --- a/packages/core/src/account/base.ts +++ b/packages/core/src/account/base.ts @@ -3,6 +3,7 @@ import { getContract, type Chain, type GetContractReturnType, + type Hash, type Hex, type HttpTransport, type PublicClient, @@ -15,6 +16,8 @@ import type { SupportedTransports, } from "../client/types.js"; import { Logger } from "../logger.js"; +import type { SmartAccountSigner } from "../signer/types.js"; +import { wrapSignatureWith6492 } from "../signer/utils.js"; import type { BatchUserOperationCallData } from "../types.js"; import type { ISmartContractAccount, SignTypedDataParams } from "./types.js"; @@ -29,6 +32,8 @@ export interface BaseSmartAccountParams< > { rpcClient: string | PublicErc4337Client; entryPointAddress: Address; + factoryAddress: Address; + owner?: SmartAccountSigner | undefined; chain: Chain; accountAddress?: Address; } @@ -37,27 +42,61 @@ export abstract class BaseSmartContractAccount< TTransport extends SupportedTransports = Transport > implements ISmartContractAccount { + protected factoryAddress: Address; protected deploymentState: DeploymentState = DeploymentState.UNDEFINED; protected accountAddress?: Address; + protected owner: SmartAccountSigner | undefined; protected entryPoint: GetContractReturnType< typeof EntryPointAbi, PublicClient, Chain >; protected entryPointAddress: Address; - protected rpcProvider: PublicErc4337Client; + protected rpcProvider: + | PublicErc4337Client + | PublicErc4337Client; constructor(params: BaseSmartAccountParams) { this.entryPointAddress = params.entryPointAddress; - this.rpcProvider = + + const rpcUrl = + typeof params.rpcClient === "string" + ? params.rpcClient + : params.rpcClient.transport.type === "http" + ? ( + params.rpcClient.transport as ReturnType["config"] & + ReturnType["value"] + ).url || params.chain.rpcUrls.default.http[0] + : undefined; + + const fetchOptions = typeof params.rpcClient === "string" - ? createPublicErc4337Client({ - chain: params.chain, - rpcUrl: params.rpcClient, - }) - : params.rpcClient; + ? undefined + : params.rpcClient.transport.type === "http" + ? ( + params.rpcClient.transport as ReturnType["config"] & + ReturnType["value"] + ).fetchOptions + : undefined; + + this.rpcProvider = rpcUrl + ? createPublicErc4337Client({ + chain: params.chain, + rpcUrl, + fetchOptions: { + ...fetchOptions, + headers: { + ...fetchOptions?.headers, + "Alchemy-AA-SDK-Signer": params.owner?.signerType, + "Alchemy-AA-SDK-Factory-Address": params.factoryAddress, + }, + }, + }) + : (params.rpcClient as PublicErc4337Client); this.accountAddress = params.accountAddress; + this.factoryAddress = params.factoryAddress; + this.owner = params.owner; this.entryPoint = getContract({ address: params.entryPointAddress, @@ -68,35 +107,127 @@ export abstract class BaseSmartContractAccount< }); } - abstract getDummySignature(): `0x${string}`; + // #region abstract-methods + + /** + * This method should return a signature that will not `revert` during validation. + * It does not have to pass validation, just not cause the contract to revert. + * This is required for gas estimation so that the gas estimate are accurate. + * + */ + abstract getDummySignature(): Hash; + + /** + * this method should return the abi encoded function data for a call to your contract's `execute` method + * + * @param target -- equivalent to `to` in a normal transaction + * @param value -- equivalent to `value` in a normal transaction + * @param data -- equivalent to `data` in a normal transaction + * @returns abi encoded function data for a call to your contract's `execute` method + */ abstract encodeExecute( target: string, value: bigint, data: string - ): Promise<`0x${string}`>; - abstract signMessage(msg: string | Uint8Array): Promise<`0x${string}`>; + ): Promise; - protected abstract getAccountInitCode(): Promise<`0x${string}`>; + /** + * this should return an ERC-191 compliant message and is used to sign UO Hashes + * + * @param msg -- the message to sign + */ + abstract signMessage(msg: string | Uint8Array): Promise; - async signMessageWith6492(_msg: string | Uint8Array): Promise<`0x${string}`> { - throw new Error("signMessageWith6492 not supported"); - } + /** + * this should return the init code that will be used to create an account if one does not exist. + * Usually this is the concatenation of the account's factory address and the abi encoded function data of the account factory's `createAccount` method. + */ + protected abstract getAccountInitCode(): Promise; + + // #endregion abstract-methods + // #region optional-methods + + /** + * If your contract supports signing and verifying typed data, + * you should implement this method. + * + * @param _params -- Typed Data params to sign + */ async signTypedData(_params: SignTypedDataParams): Promise<`0x${string}`> { throw new Error("signTypedData not supported"); } + /** + * This method should wrap the result of `signMessage` as per + * [EIP-6492](https://eips.ethereum.org/EIPS/eip-6492) + * + * @param msg -- the message to sign + */ + async signMessageWith6492(msg: string | Uint8Array): Promise<`0x${string}`> { + const [isDeployed, signature] = await Promise.all([ + this.isAccountDeployed(), + this.signMessage(msg), + ]); + + return this.create6492Signature(isDeployed, signature); + } + + /** + * Similar to the signMessageWith6492 method above, + * this method should wrap the result of `signTypedData` as per + * [EIP-6492](https://eips.ethereum.org/EIPS/eip-6492) + * + * @param params -- Typed Data params to sign + */ async signTypedDataWith6492( - _params: SignTypedDataParams + params: SignTypedDataParams ): Promise<`0x${string}`> { - throw new Error("signTypedDataWith6492 not supported"); + const [isDeployed, signature] = await Promise.all([ + this.isAccountDeployed(), + this.signTypedData(params), + ]); + + return this.create6492Signature(isDeployed, signature); } + private async create6492Signature( + isDeployed: boolean, + signature: Hash + ): Promise { + if (isDeployed) { + return signature; + } + + const [factoryAddress, factoryCalldata] = + await this.parseFactoryAddressFromAccountInitCode(); + + Logger.debug( + `[BaseSmartContractAccount](create6492Signature)\ + factoryAddress: ${factoryAddress}, factoryCalldata: ${factoryCalldata}` + ); + + return wrapSignatureWith6492({ + factoryAddress, + factoryCalldata, + signature, + }); + } + + /** + * Not all contracts support batch execution. + * If your contract does, this method should encode a list of + * transactions into the call data that will be passed to your + * contract's batch execution method. + * + * @param _txs -- the transactions to batch execute + */ async encodeBatchExecute( _txs: BatchUserOperationCallData ): Promise<`0x${string}`> { throw new Error("encodeBatchExecute not supported"); } + // #endregion optional-methods async getNonce(): Promise { if (!(await this.isAccountDeployed())) { @@ -110,9 +241,9 @@ export abstract class BaseSmartContractAccount< if (this.deploymentState === DeploymentState.DEPLOYED) { return "0x"; } - const contractCode = await this.rpcProvider.getContractCode( - await this.getAddress() - ); + const contractCode = await this.rpcProvider.getBytecode({ + address: await this.getAddress(), + }); if ((contractCode?.length ?? 0) > 2) { this.deploymentState = DeploymentState.DEPLOYED; @@ -150,6 +281,14 @@ export abstract class BaseSmartContractAccount< return this.accountAddress; } + getOwner(): SmartAccountSigner | undefined { + return this.owner; + } + + getFactoryAddress(): Address { + return this.factoryAddress; + } + // Extra implementations async isAccountDeployed(): Promise { return (await this.getDeploymentState()) === DeploymentState.DEPLOYED; @@ -165,4 +304,19 @@ export abstract class BaseSmartContractAccount< return this.deploymentState; } } + + /** + * https://eips.ethereum.org/EIPS/eip-4337#first-time-account-creation + * The initCode field (if non-zero length) is parsed as a 20-byte address, + * followed by calldata to pass to this address. + * The factory address is the first 40 char after the 0x, and the callData is the rest. + */ + protected async parseFactoryAddressFromAccountInitCode(): Promise< + [Address, Hex] + > { + const initCode = await this.getAccountInitCode(); + const factoryAddress = `0x${initCode.substring(2, 42)}` as Address; + const factoryCalldata = `0x${initCode.substring(42)}` as Hex; + return [factoryAddress, factoryCalldata]; + } } diff --git a/packages/core/src/account/simple.ts b/packages/core/src/account/simple.ts index 8ce1b9329..6c2a2aec7 100644 --- a/packages/core/src/account/simple.ts +++ b/packages/core/src/account/simple.ts @@ -20,7 +20,6 @@ export interface SimpleSmartAccountParams< TTransport extends Transport | FallbackTransport = Transport > extends BaseSmartAccountParams { owner: SmartAccountSigner; - factoryAddress: Address; index?: bigint; } @@ -28,15 +27,12 @@ export class SimpleSmartContractAccount< TTransport extends Transport | FallbackTransport = Transport > extends BaseSmartContractAccount { protected owner: SmartAccountSigner; - protected factoryAddress: Address; protected index: bigint; - constructor(params: SimpleSmartAccountParams) { + constructor(params: SimpleSmartAccountParams) { super(params); - - this.index = params.index ?? 0n; this.owner = params.owner; - this.factoryAddress = params.factoryAddress; + this.index = params.index ?? 0n; } getDummySignature(): `0x${string}` { @@ -56,9 +52,9 @@ export class SimpleSmartContractAccount< } override async encodeBatchExecute( - _txs: BatchUserOperationCallData + txs: BatchUserOperationCallData ): Promise<`0x${string}`> { - const [targets, datas] = _txs.reduce( + const [targets, datas] = txs.reduce( (accum, curr) => { accum[0].push(curr.target); accum[1].push(curr.data); diff --git a/packages/core/src/account/types.ts b/packages/core/src/account/types.ts index fb28199de..ca5cde864 100644 --- a/packages/core/src/account/types.ts +++ b/packages/core/src/account/types.ts @@ -2,6 +2,7 @@ import type { Address } from "abitype"; import type { Hash, Hex } from "viem"; import type { SignTypedDataParameters } from "viem/accounts"; import type { BatchUserOperationCallData } from "../types"; +import type { SmartAccountSigner } from "../signer/types"; export type SignTypedDataParams = Omit; @@ -77,4 +78,15 @@ export interface ISmartContractAccount { * @returns the address of the account */ getAddress(): Promise
; + + /** + * @returns the smart contract account owner instance if it exists. + * It is optional for a smart contract account to have an owner account. + */ + getOwner(): SmartAccountSigner | undefined; + + /** + * @returns the address of the factory contract for the smart contract account + */ + getFactoryAddress(): Address; } diff --git a/packages/core/src/client/create-client.ts b/packages/core/src/client/create-client.ts index 720913025..9852a24bb 100644 --- a/packages/core/src/client/create-client.ts +++ b/packages/core/src/client/create-client.ts @@ -3,45 +3,36 @@ import { createPublicClient, http, type Chain, + type Client, type FallbackTransport, type Hash, type Hex, type HttpTransport, + type HttpTransportConfig, + type PublicActions, type PublicClient, + type PublicRpcSchema, type Transport, - type HttpTransportConfig, } from "viem"; import type { - EIP1193RequestFn, - PublicRpcSchema, -} from "viem/dist/types/types/eip1193"; -import type { - BigNumberish, UserOperationEstimateGasResponse, UserOperationReceipt, UserOperationRequest, UserOperationResponse, } from "../types.js"; import { VERSION } from "../version.js"; -import type { Erc337RpcSchema, PublicErc4337Client } from "./types.js"; +import type { Erc4337RpcSchema, PublicErc4337Client } from "./types.js"; -export const createPublicErc4337FromClient: < - T extends Transport | FallbackTransport = Transport ->( - client: PublicClient -) => PublicErc4337Client = < - T extends Transport | FallbackTransport = Transport ->( - client: PublicClient -): PublicErc4337Client => { - const clientAdapter = client as PublicClient & { - request: EIP1193RequestFn< - [Erc337RpcSchema[number], PublicRpcSchema[number]] - >; - }; +export const erc4337ClientActions = (client: Client) => { + const clientAdapter = client as Client< + Transport, + Chain, + undefined, + [...PublicRpcSchema, ...Erc4337RpcSchema], + PublicActions + >; return { - ...clientAdapter, estimateUserOperationGas( request: UserOperationRequest, entryPoint: string @@ -82,45 +73,19 @@ export const createPublicErc4337FromClient: < params: [], }); }, + }; +}; - getMaxPriorityFeePerGas(): Promise { - return clientAdapter.request({ - method: "eth_maxPriorityFeePerGas", - params: [], - }); - }, - - async getFeeData(): Promise<{ - maxFeePerGas?: BigNumberish; - maxPriorityFeePerGas?: BigNumberish; - }> { - // viem doesn't support getFeeData, so looking at ethers: https://github.com/ethers-io/ethers.js/blob/main/lib.esm/providers/abstract-provider.js#L472 - // also keeping this implementation the same as ethers so that the middlewares work consistently - const block = await clientAdapter.getBlock({ - blockTag: "latest", - }); - - if (block && block.baseFeePerGas) { - const maxPriorityFeePerGas = BigInt(1500000000); - return { - maxPriorityFeePerGas, - maxFeePerGas: block.baseFeePerGas * BigInt(2) + maxPriorityFeePerGas, - }; - } - - return { - maxFeePerGas: 0, - maxPriorityFeePerGas: 0, - }; - }, - - async getContractCode(address: string): Promise { - const code = await clientAdapter.getBytecode({ - address: address as Address, - }); - return code ?? "0x"; - }, - } as PublicErc4337Client; +export const createPublicErc4337FromClient: < + T extends Transport | FallbackTransport = Transport +>( + client: PublicClient +) => PublicErc4337Client = < + T extends Transport | FallbackTransport = Transport +>( + client: PublicClient +): PublicErc4337Client => { + return client.extend(erc4337ClientActions); }; export const createPublicErc4337Client = ({ diff --git a/packages/core/src/client/types.ts b/packages/core/src/client/types.ts index 7ac461c0b..9bce1d36c 100644 --- a/packages/core/src/client/types.ts +++ b/packages/core/src/client/types.ts @@ -1,19 +1,15 @@ import type { Address } from "abitype"; import type { Chain, + Client, FallbackTransport, Hash, - Hex, HttpTransport, - PublicClient, + PublicActions, + PublicRpcSchema, Transport, } from "viem"; import type { - EIP1193RequestFn, - PublicRpcSchema, -} from "viem/dist/types/types/eip1193"; -import type { - BigNumberish, UserOperationEstimateGasResponse, UserOperationReceipt, UserOperationRequest, @@ -22,7 +18,7 @@ import type { export type SupportedTransports = Transport | FallbackTransport | HttpTransport; -export type Erc337RpcSchema = [ +export type Erc4337RpcSchema = [ { Method: "eth_sendUserOperation"; Parameters: [UserOperationRequest, Address]; @@ -47,15 +43,10 @@ export type Erc337RpcSchema = [ Method: "eth_supportedEntryPoints"; Parameters: []; ReturnType: Address[]; - }, - { - Method: "eth_maxPriorityFeePerGas"; - Parameters: []; - ReturnType: BigNumberish; } ]; -export interface Erc4337Actions { +export type Erc4337Actions = { /** * calls `eth_estimateUserOperationGas` and returns the result * @@ -65,7 +56,7 @@ export interface Erc4337Actions { */ estimateUserOperationGas( request: UserOperationRequest, - entryPoint: string + entryPoint: Address ): Promise; /** @@ -77,13 +68,13 @@ export interface Erc4337Actions { */ sendUserOperation( request: UserOperationRequest, - entryPoint: string - ): Promise; + entryPoint: Address + ): Promise; /** * calls `eth_getUserOperationByHash` and returns the {@link UserOperationResponse} * - * @param hash - the hash of the UserOperation to get the receipt for + * @param hash - the hash of the UserOperation to fetch * @returns - {@link UserOperationResponse} */ getUserOperationByHash(hash: Hash): Promise; @@ -102,20 +93,13 @@ export interface Erc4337Actions { * @returns - {@link Address}[] */ getSupportedEntryPoints(): Promise; -} - -export interface PublicErc4337Client - extends PublicClient, - Erc4337Actions { - request: EIP1193RequestFn<[PublicRpcSchema[number], Erc337RpcSchema[number]]>; - - // below methods are not all erc4337 methods, but are the methods we need in the SmartContractAccountProvideer - getMaxPriorityFeePerGas(): Promise; - - getFeeData(): Promise<{ - maxFeePerGas?: BigNumberish; - maxPriorityFeePerGas?: BigNumberish; - }>; +}; - getContractCode(address: string): Promise; -} +export type PublicErc4337Client = + Client< + T, + Chain, + undefined, + [...PublicRpcSchema, ...Erc4337RpcSchema], + PublicActions & Erc4337Actions + >; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index d9cd908b5..67787ec6d 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -11,16 +11,18 @@ export type { BaseSmartAccountParams } from "./account/base.js"; export { SimpleSmartContractAccount } from "./account/simple.js"; export type { SimpleSmartAccountParams } from "./account/simple.js"; export type * from "./account/types.js"; -export { WalletClientSigner } from "./signer/wallet-client.js"; -export { HdAccountSigner } from "./signer/hd-account.js"; export { LocalAccountSigner } from "./signer/local-account.js"; -export { PrivateKeySigner } from "./signer/private-key.js"; export type { SmartAccountSigner } from "./signer/types.js"; -export { verifyEIP6492Signature, wrapWith6492 } from "./signer/utils.js"; +export { + verifyEIP6492Signature, + wrapSignatureWith6492, +} from "./signer/utils.js"; +export { WalletClientSigner } from "./signer/wallet-client.js"; export { createPublicErc4337Client, createPublicErc4337FromClient, + erc4337ClientActions, } from "./client/create-client.js"; export type * from "./client/types.js"; @@ -32,21 +34,23 @@ export { export { SmartAccountProvider, noOpMiddleware } from "./provider/base.js"; export type { - ConnectedSmartAccountProvider, + SmartAccountProviderConfig, SmartAccountProviderOpts, } from "./provider/base.js"; export type * from "./provider/types.js"; export type * from "./types.js"; -export type * from "./utils.js"; +export type * from "./utils/index.js"; export { asyncPipe, + bigIntMax, + bigIntPercent, deepHexlify, defineReadOnly, getChain, getUserOperationHash, resolveProperties, -} from "./utils.js"; +} from "./utils/index.js"; export { Logger } from "./logger.js"; export type { LogLevel } from "./logger.js"; diff --git a/packages/core/src/provider/__tests__/base.test.ts b/packages/core/src/provider/__tests__/base.test.ts index 001182dc6..d4ad36b80 100644 --- a/packages/core/src/provider/__tests__/base.test.ts +++ b/packages/core/src/provider/__tests__/base.test.ts @@ -14,11 +14,11 @@ import { SmartAccountProvider } from "../base.js"; describe("Base Tests", () => { let retryMsDelays: number[] = []; - const providerMock = new SmartAccountProvider( - "ALCHEMY_RPC_URL", - "0xENTRYPOINT_ADDRESS", - polygonMumbai - ); + const providerMock = new SmartAccountProvider({ + rpcProvider: "ALCHEMY_RPC_URL", + entryPointAddress: "0xENTRYPOINT_ADDRESS", + chain: polygonMumbai, + }); const givenGetUserOperationFailsNTimes = (times: number) => { const mock = vi.spyOn(providerMock, "getUserOperationReceipt"); @@ -103,6 +103,8 @@ describe("Base Tests", () => { entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", rpcClient: providerMock.rpcClient, getAddress: async () => "0xMOCK_ADDRESS", + getFactoryAddress: () => "0xMOCK_FACOTRY_ADDRESS", + getOwner: () => undefined, } as any; // This says the await is not important... it is. the method is not marked sync because we don't need it to be, diff --git a/packages/core/src/provider/base.ts b/packages/core/src/provider/base.ts index f74f77687..592502639 100644 --- a/packages/core/src/provider/base.ts +++ b/packages/core/src/provider/base.ts @@ -5,13 +5,16 @@ import { type Address, type Chain, type Hash, + type HttpTransport, type RpcTransactionRequest, type Transaction, type Transport, } from "viem"; import { arbitrum, arbitrumGoerli } from "viem/chains"; -import { BaseSmartContractAccount } from "../account/base.js"; -import type { SignTypedDataParams } from "../account/types.js"; +import type { + ISmartContractAccount, + SignTypedDataParams, +} from "../account/types.js"; import { createPublicErc4337Client } from "../client/create-client.js"; import type { PublicErc4337Client, @@ -23,17 +26,20 @@ import { type UserOperationCallData, type UserOperationOverrides, type UserOperationReceipt, + type UserOperationRequest, type UserOperationResponse, type UserOperationStruct, } from "../types.js"; import { asyncPipe, + bigIntMax, + bigIntPercent, deepHexlify, defineReadOnly, getUserOperationHash, resolveProperties, type Deferrable, -} from "../utils.js"; +} from "../utils/index.js"; import type { AccountMiddlewareFn, AccountMiddlewareOverrideFn, @@ -76,10 +82,13 @@ const minPriorityFeePerBidDefaults = new Map([ [arbitrumGoerli.id, 10_000_000n], ]); -export type ConnectedSmartAccountProvider< +export type SmartAccountProviderConfig< TTransport extends SupportedTransports = Transport -> = SmartAccountProvider & { - account: BaseSmartContractAccount; +> = { + rpcProvider: string | PublicErc4337Client; + chain: Chain; + entryPointAddress: Address; + opts?: SmartAccountProviderOpts; }; export class SmartAccountProvider< @@ -91,19 +100,26 @@ export class SmartAccountProvider< private txMaxRetries: number; private txRetryIntervalMs: number; private txRetryMulitplier: number; + readonly account?: ISmartContractAccount; + protected entryPointAddress: Address; + protected chain: Chain; minPriorityFeePerBid: bigint; - rpcClient: PublicErc4337Client; - - constructor( - rpcProvider: string | PublicErc4337Client, - protected entryPointAddress: Address, - protected chain: Chain, - readonly account?: BaseSmartContractAccount, - opts?: SmartAccountProviderOpts - ) { + rpcClient: + | PublicErc4337Client + | PublicErc4337Client; + + constructor({ + rpcProvider, + entryPointAddress, + chain, + opts, + }: SmartAccountProviderConfig) { super(); + this.entryPointAddress = entryPointAddress; + this.chain = chain; + this.txMaxRetries = opts?.txMaxRetries ?? 5; this.txRetryIntervalMs = opts?.txRetryIntervalMs ?? 2000; this.txRetryMulitplier = opts?.txRetryMulitplier ?? 1.5; @@ -253,14 +269,6 @@ export class SmartAccountProvider< }; }); - const bigIntMax = (...args: bigint[]) => { - if (!args.length) { - return undefined; - } - - return args.reduce((m, c) => (m > c ? m : c)); - }; - const maxFeePerGas = bigIntMax( ...requests .filter((x) => x.maxFeePerGas != null) @@ -333,25 +341,22 @@ export class SmartAccountProvider< } const initCode = await this.account.getInitCode(); - const uoStruct = await asyncPipe( - this.dummyPaymasterDataMiddleware, - this.feeDataGetter, - this.gasEstimator, - this.paymasterDataMiddleware, - this.customMiddleware ?? noOpMiddleware, - // This applies the overrides if they've been passed in - async (struct) => ({ ...struct, ...overrides }) - )({ - initCode, - sender: this.getAddress(), - nonce: this.account.getNonce(), - callData: Array.isArray(data) - ? this.account.encodeBatchExecute(data) - : this.account.encodeExecute(data.target, data.value ?? 0n, data.data), - signature: this.account.getDummySignature(), - } as Deferrable); - - return resolveProperties(uoStruct); + return this._runMiddlewareStack( + { + initCode, + sender: this.getAddress(), + nonce: this.account.getNonce(), + callData: Array.isArray(data) + ? this.account.encodeBatchExecute(data) + : this.account.encodeExecute( + data.target, + data.value ?? 0n, + data.data + ), + signature: this.account.getDummySignature(), + } as Deferrable, + overrides + ); }; sendUserOperation = async ( @@ -366,6 +371,54 @@ export class SmartAccountProvider< return this._sendUserOperation(uoStruct); }; + dropAndReplaceUserOperation = async ( + uoToDrop: UserOperationRequest + ): Promise => { + const uoToSubmit = { + initCode: uoToDrop.initCode, + sender: uoToDrop.sender, + nonce: uoToDrop.nonce, + callData: uoToDrop.callData, + signature: uoToDrop.signature, + } as UserOperationStruct; + + // Run once to get the fee estimates + // This can happen at any part of the middleware stack, so we want to run it all + const { maxFeePerGas, maxPriorityFeePerGas } = + await this._runMiddlewareStack(uoToSubmit); + + const overrides: UserOperationOverrides = { + maxFeePerGas: bigIntMax( + BigInt(maxFeePerGas ?? 0n), + bigIntPercent(uoToDrop.maxFeePerGas, 110n) + ), + maxPriorityFeePerGas: bigIntMax( + BigInt(maxPriorityFeePerGas ?? 0n), + bigIntPercent(uoToDrop.maxPriorityFeePerGas, 110n) + ), + }; + + const uoToSend = await this._runMiddlewareStack(uoToSubmit, overrides); + return this._sendUserOperation(uoToSend); + }; + + private _runMiddlewareStack = async ( + uo: Deferrable, + overrides?: UserOperationOverrides + ) => { + const result = await asyncPipe( + this.dummyPaymasterDataMiddleware, + this.feeDataGetter, + this.gasEstimator, + // run this before paymaster middleware + async (struct) => ({ ...struct, ...overrides }), + this.customMiddleware ?? noOpMiddleware, + this.paymasterDataMiddleware + )(uo); + + return resolveProperties(result); + }; + private _sendUserOperation = async (uoStruct: UserOperationStruct) => { if (!this.account) { throw new Error("account not connected"); @@ -430,8 +483,9 @@ export class SmartAccountProvider< }; readonly feeDataGetter: AccountMiddlewareFn = async (struct) => { - const maxPriorityFeePerGas = await this.rpcClient.getMaxPriorityFeePerGas(); - const feeData = await this.rpcClient.getFeeData(); + const maxPriorityFeePerGas = + await this.rpcClient.estimateMaxPriorityFeePerGas(); + const feeData = await this.rpcClient.estimateFeesPerGas(); if (!feeData.maxFeePerGas || !feeData.maxPriorityFeePerGas) { throw new Error( "feeData is missing maxFeePerGas or maxPriorityFeePerGas" @@ -498,12 +552,38 @@ export class SmartAccountProvider< return this; }; - connect( - fn: (provider: PublicErc4337Client) => BaseSmartContractAccount - ): this & { account: BaseSmartContractAccount } { + connect( + fn: ( + provider: + | PublicErc4337Client + | PublicErc4337Client + ) => TAccount + ): this & { account: TAccount } { const account = fn(this.rpcClient); defineReadOnly(this, "account", account); + if (this.rpcClient.transport.type === "http") { + const { url = this.chain.rpcUrls.default.http[0], fetchOptions } = this + .rpcClient.transport as ReturnType["config"] & + ReturnType["value"]; + + const signer = account.getOwner(); + const factoryAddress = account.getFactoryAddress(); + + this.rpcClient = createPublicErc4337Client({ + chain: this.chain, + rpcUrl: url, + fetchOptions: { + ...fetchOptions, + headers: { + ...fetchOptions?.headers, + "Alchemy-AA-SDK-Signer": signer?.signerType, + "Alchemy-AA-SDK-Factory-Address": factoryAddress, + }, + }, + }); + } + this.emit("connect", { chainId: toHex(this.chain.id), }); @@ -512,7 +592,7 @@ export class SmartAccountProvider< .getAddress() .then((address) => this.emit("accountsChanged", [address])); - return this as this & { account: typeof account }; + return this as unknown as this & { account: TAccount }; } disconnect(): this & { account: undefined } { @@ -526,7 +606,9 @@ export class SmartAccountProvider< return this as this & { account: undefined }; } - isConnected(): this is ConnectedSmartAccountProvider { + isConnected(): this is this & { + account: TAccount; + } { return this.account !== undefined; } diff --git a/packages/core/src/provider/types.ts b/packages/core/src/provider/types.ts index 1ce756334..692294a89 100644 --- a/packages/core/src/provider/types.ts +++ b/packages/core/src/provider/types.ts @@ -1,8 +1,16 @@ import type { Address } from "abitype"; -import type { Hash, Hex, RpcTransactionRequest, Transport } from "viem"; +import type { + Hash, + Hex, + HttpTransport, + RpcTransactionRequest, + Transport, +} from "viem"; import type { SignTypedDataParameters } from "viem/accounts"; -import type { BaseSmartContractAccount } from "../account/base.js"; -import type { SignTypedDataParams } from "../account/types.js"; +import type { + ISmartContractAccount, + SignTypedDataParams, +} from "../account/types.js"; import type { PublicErc4337Client, SupportedTransports, @@ -15,7 +23,7 @@ import type { UserOperationResponse, UserOperationStruct, } from "../types.js"; -import type { Deferrable } from "../utils.js"; +import type { Deferrable } from "../utils"; type WithRequired = Required>; type WithOptional = Pick, K>; @@ -34,7 +42,7 @@ export interface ProviderEvents { } export type SendUserOperationResult = { - hash: string; + hash: Hash; request: UserOperationRequest; }; @@ -72,14 +80,16 @@ export type FeeDataMiddleware = AccountMiddlewareOverrideFn< export interface ISmartAccountProvider< TTransport extends SupportedTransports = Transport > { - readonly rpcClient: PublicErc4337Client; + readonly rpcClient: + | PublicErc4337Client + | PublicErc4337Client; readonly dummyPaymasterDataMiddleware: AccountMiddlewareFn; readonly paymasterDataMiddleware: AccountMiddlewareFn; readonly gasEstimator: AccountMiddlewareFn; readonly feeDataGetter: AccountMiddlewareFn; readonly customMiddleware?: AccountMiddlewareFn; - readonly account?: BaseSmartContractAccount; + readonly account?: ISmartContractAccount; /** * Sends a user operation using the connected account. @@ -89,6 +99,7 @@ export interface ISmartAccountProvider< * 2. feeDataGetter -- sets maxfeePerGas and maxPriorityFeePerGas * 3. gasEstimator -- calls eth_estimateUserOperationGas * 4. paymasterMiddleware -- used to set paymasterAndData. (default: "0x") + * 5. customMiddleware -- allows you to override any of the results returned by previous middlewares * * @param data - either {@link UserOperationCallData} or {@link BatchUserOperationCallData} * @returns - {@link SendUserOperationResult} containing the hash and request @@ -97,6 +108,16 @@ export interface ISmartAccountProvider< data: UserOperationCallData | BatchUserOperationCallData ) => Promise; + /** + * Attempts to drop and replace an existing user operation by increasing fees + * + * @param data - an existing user operation request returned by `sendUserOperation` + * @returns - {@link SendUserOperationResult} containing the hash and request + */ + dropAndReplaceUserOperation: ( + data: UserOperationRequest + ) => Promise; + /** * Allows you to get the unsigned UserOperation struct with all of the middleware run on it * @@ -139,7 +160,7 @@ export interface ISmartAccountProvider< * calls `eth_getUserOperationReceipt` and returns the {@link UserOperationReceipt} * * @param hash - the hash of the UserOperation to get the receipt for - * @returns - {@link UserOperationResponse} + * @returns - {@link UserOperationReceipt} */ getUserOperationReceipt: (hash: Hash) => Promise; @@ -185,7 +206,7 @@ export interface ISmartAccountProvider< * This method is used to sign typed data as per ERC-712 * * @param params - {@link SignTypedDataParameters} - * @returns the signed hash for the message passed + * @returns the signed hash for the typed data passed */ signTypedData: (params: SignTypedDataParameters) => Promise; @@ -211,6 +232,11 @@ export interface ISmartAccountProvider< */ getAddress: () => Promise
; + /** + * @returns boolean flag indicating if the account is connected + */ + isConnected: () => boolean; + // Middelware Overriders /** * This method allows you to override the default dummy paymaster data middleware and get paymaster @@ -247,9 +273,9 @@ export interface ISmartAccountProvider< withFeeDataGetter: (override: FeeDataMiddleware) => this; /** - * This adds a final middleware step to the middleware stack that runs right before signature verification. - * This can be used if you have an RPC that does most of the functions of the other middlewares for you and - * you want to delegate that work to that RPC instead of chaining together multiple RPC calls via the default middlwares. + * Adds a function to the middleware call stack that runs before calling the paymaster middleware. + * It can be used to override or add additional functionality. + * Like modifying the user operation, making an additional RPC call, or logging data. * * @param override - the UO transform function to run * @returns @@ -262,9 +288,13 @@ export interface ISmartAccountProvider< * * @param fn - a function that given public rpc client, returns a smart contract account */ - connect( - fn: (provider: PublicErc4337Client) => BaseSmartContractAccount - ): this & { account: BaseSmartContractAccount }; + connect( + fn: ( + provider: + | PublicErc4337Client + | PublicErc4337Client + ) => TAccount + ): this & { account: TAccount }; /** * Allows for disconnecting the account from the provider so you can connect the provider to another account instance diff --git a/packages/core/src/signer/__tests__/local-account.test.ts b/packages/core/src/signer/__tests__/local-account.test.ts index a8891172c..280f3a50a 100644 --- a/packages/core/src/signer/__tests__/local-account.test.ts +++ b/packages/core/src/signer/__tests__/local-account.test.ts @@ -4,9 +4,9 @@ describe("Local Account Signer Tests", () => { describe("Using HD Account", () => { const dummyMnemonic = "test test test test test test test test test test test test"; - const signer = LocalAccountSigner.mnemonicToAccountSigner(dummyMnemonic); it("should sign a hex message properly", async () => { + const signer = LocalAccountSigner.mnemonicToAccountSigner(dummyMnemonic); expect( await signer.signMessage("0xabcfC3DB1e0f5023F5a4f40c03D149f316E6A5cc") ).toMatchInlineSnapshot( @@ -15,6 +15,7 @@ describe("Local Account Signer Tests", () => { }); it("should sign a string message properly", async () => { + const signer = LocalAccountSigner.mnemonicToAccountSigner(dummyMnemonic); expect( await signer.signMessage("icanbreakthistestcase") ).toMatchInlineSnapshot( @@ -29,6 +30,7 @@ describe("Local Account Signer Tests", () => { }); it("should sign a byte array correctly", async () => { + const signer = LocalAccountSigner.mnemonicToAccountSigner(dummyMnemonic); expect( await signer.signMessage(new TextEncoder().encode("hello, I'm moldy")) ).toEqual(await signer.signMessage("hello, I'm moldy")); @@ -38,10 +40,10 @@ describe("Local Account Signer Tests", () => { describe("Using Private Key Account", () => { const dummyPrivateKey = "0x022430a80f723d8789f0d4fb346bdd013b546e4b96fcacf8aceca2b1a65a19dc"; - const signer = - LocalAccountSigner.privateKeyToAccountSigner(dummyPrivateKey); it("should sign a hex message properly", async () => { + const signer = + LocalAccountSigner.privateKeyToAccountSigner(dummyPrivateKey); expect( await signer.signMessage("0xabcfC3DB1e0f5023F5a4f40c03D149f316E6A5cc") ).toMatchInlineSnapshot( @@ -50,6 +52,8 @@ describe("Local Account Signer Tests", () => { }); it("should sign a string message properly", async () => { + const signer = + LocalAccountSigner.privateKeyToAccountSigner(dummyPrivateKey); expect( await signer.signMessage("icanbreakthistestcase") ).toMatchInlineSnapshot( @@ -64,6 +68,8 @@ describe("Local Account Signer Tests", () => { }); it("should sign a byte array correctly", async () => { + const signer = + LocalAccountSigner.privateKeyToAccountSigner(dummyPrivateKey); expect( await signer.signMessage(new TextEncoder().encode("hello, I'm moldy")) ).toEqual(await signer.signMessage("hello, I'm moldy")); diff --git a/packages/core/src/signer/hd-account.ts b/packages/core/src/signer/hd-account.ts deleted file mode 100644 index cf3642cef..000000000 --- a/packages/core/src/signer/hd-account.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { HDAccount } from "viem"; -import { LocalAccountSigner } from "./local-account.js"; - -/** - * @deprecated use LocalAccountSigner instead - */ -export class HdAccountSigner extends LocalAccountSigner {} diff --git a/packages/core/src/signer/local-account.ts b/packages/core/src/signer/local-account.ts index 2fb20a1af..f690d65ed 100644 --- a/packages/core/src/signer/local-account.ts +++ b/packages/core/src/signer/local-account.ts @@ -1,14 +1,24 @@ -import { isHex, type HDAccount, type Hex, type PrivateKeyAccount } from "viem"; +import { + isHex, + type HDAccount, + type Hex, + type LocalAccount, + type PrivateKeyAccount, +} from "viem"; import { mnemonicToAccount, privateKeyToAccount } from "viem/accounts"; import type { SignTypedDataParams } from "../account/types.js"; import type { SmartAccountSigner } from "./types.js"; -export class LocalAccountSigner - implements SmartAccountSigner +export class LocalAccountSigner< + T extends HDAccount | PrivateKeyAccount | LocalAccount +> implements SmartAccountSigner { private owner: T; + signerType: string; + constructor(owner: T) { this.owner = owner; + this.signerType = owner.type; // type: "local" } readonly signMessage: (msg: string | Uint8Array) => Promise<`0x${string}`> = ( diff --git a/packages/core/src/signer/private-key.ts b/packages/core/src/signer/private-key.ts deleted file mode 100644 index 8e0a2bc96..000000000 --- a/packages/core/src/signer/private-key.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { type PrivateKeyAccount } from "viem"; -import { LocalAccountSigner } from "./local-account.js"; - -/** - * @deprecated use LocalAccountSigner instead - */ -export class PrivateKeySigner extends LocalAccountSigner {} diff --git a/packages/core/src/signer/types.ts b/packages/core/src/signer/types.ts index de02b5afc..2616ad992 100644 --- a/packages/core/src/signer/types.ts +++ b/packages/core/src/signer/types.ts @@ -3,6 +3,7 @@ import type { Hash, Hex } from "viem"; import type { SignTypedDataParams } from "../account/types.js"; export interface SmartAccountSigner { + signerType: string; signMessage: (msg: Uint8Array | Hex | string) => Promise; signTypedData: (params: SignTypedDataParams) => Promise; getAddress: () => Promise
; diff --git a/packages/core/src/signer/utils.ts b/packages/core/src/signer/utils.ts index 3c69648b7..cf98c7f00 100644 --- a/packages/core/src/signer/utils.ts +++ b/packages/core/src/signer/utils.ts @@ -4,25 +4,39 @@ import { parseAbiParameters, type Address, type Hash, - type Hex, type PublicClient, + type Hex, } from "viem"; -type SignWith6492Params = { +export type SignWith6492Params = { factoryAddress: Address; - initCode: Hex; + factoryCalldata: Hex; + signature: Hash; +}; + +type VerifyEIP6492SignatureParams = { + signer: Address; + hash: Hash; signature: Hash; + client: PublicClient; }; -export const wrapWith6492 = ({ +export const wrapSignatureWith6492 = ({ factoryAddress, - initCode, + factoryCalldata, signature, }: SignWith6492Params): Hash => { + // wrap the signature as follows: https://eips.ethereum.org/EIPS/eip-6492 + // concat( + // abi.encode( + // (create2Factory, factoryCalldata, originalERC1271Signature), + // (address, bytes, bytes)), + // magicBytes + // ) return concat([ encodeAbiParameters(parseAbiParameters("address, bytes, bytes"), [ factoryAddress, - initCode, + factoryCalldata, signature, ]), "0x6492649264926492649264926492649264926492649264926492649264926492", @@ -38,12 +52,7 @@ export const verifyEIP6492Signature = async ({ hash, signature, client, -}: { - signer: Address; - hash: Hash; - signature: Hash; - client: PublicClient; -}): Promise => { +}: VerifyEIP6492SignatureParams): Promise => { const result = await client.call({ data: concat([ universalValidatorByteCode, diff --git a/packages/core/src/signer/wallet-client.ts b/packages/core/src/signer/wallet-client.ts index 5192d0ea3..4752ae9f6 100644 --- a/packages/core/src/signer/wallet-client.ts +++ b/packages/core/src/signer/wallet-client.ts @@ -1,18 +1,23 @@ import { getAddress, + isHex, type ByteArray, type Hex, type WalletClient, - isHex, } from "viem"; -import type { SmartAccountSigner } from "./types"; import type { SignTypedDataParams } from "../account/types"; +import type { SmartAccountSigner } from "./types"; export class WalletClientSigner implements SmartAccountSigner { + signerType: string; private client: WalletClient; - constructor(client: WalletClient) { + constructor(client: WalletClient, signerType: string) { this.client = client; + if (!signerType) { + throw new Error("Valid signerType param is required."); + } + this.signerType = signerType; } getAddress: () => Promise<`0x${string}`> = async () => { diff --git a/packages/core/src/utils/bigint.ts b/packages/core/src/utils/bigint.ts new file mode 100644 index 000000000..e8716f7e7 --- /dev/null +++ b/packages/core/src/utils/bigint.ts @@ -0,0 +1,32 @@ +import type { BigNumberish } from "../types"; + +/** + * Returns the max bigint in a list of bigints + * + * @param args a list of bigints to get the max of + * @returns the max bigint in the list + */ +export const bigIntMax = (...args: bigint[]) => { + if (!args.length) { + return undefined; + } + + return args.reduce((m, c) => (m > c ? m : c)); +}; + +/** + * Useful if you want to increment a bigint by N% or decrement by N% + * + * example: + * ``` + * const tenPercentIncrease = bigIntPercent(100n, 110n); + * const tenPercentDecrease = bigIntPercent(100n, 90n); + * ``` + * + * @param base -- the base bigint that we want to apply a percent to + * @param percent -- the percent to apply to the base + * @returns the base multiplied by the percent and divided by 100 + */ +export const bigIntPercent = (base: BigNumberish, percent: bigint) => { + return (BigInt(base) * percent) / 100n; +}; diff --git a/packages/core/src/utils.ts b/packages/core/src/utils/index.ts similarity index 96% rename from packages/core/src/utils.ts rename to packages/core/src/utils/index.ts index 65f6894fa..110dadbf3 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils/index.ts @@ -1,7 +1,7 @@ -import type { Address, Hex } from "viem"; +import type { Address, Hash, Hex } from "viem"; import { encodeAbiParameters, hexToBigInt, keccak256, toHex } from "viem"; import * as chains from "viem/chains"; -import type { PromiseOrValue, UserOperationRequest } from "./types.js"; +import type { PromiseOrValue, UserOperationRequest } from "../types.js"; /** * Utility method for converting a chainId to a {@link chains.Chain} object @@ -101,7 +101,7 @@ export function getUserOperationHash( request: UserOperationRequest, entryPointAddress: Address, chainId: bigint -): string { +): Hash { const encoded = encodeAbiParameters( [{ type: "bytes32" }, { type: "address" }, { type: "uint256" }], [keccak256(packUo(request)), entryPointAddress, chainId] @@ -155,3 +155,5 @@ export function defineReadOnly( writable: false, }); } + +export * from "./bigint.js"; diff --git a/packages/ethers/e2e-tests/simple-account.test.ts b/packages/ethers/e2e-tests/simple-account.test.ts index 78b895ff8..21f15d413 100644 --- a/packages/ethers/e2e-tests/simple-account.test.ts +++ b/packages/ethers/e2e-tests/simple-account.test.ts @@ -1,6 +1,10 @@ -import { getChain, SimpleSmartContractAccount } from "@alchemy/aa-core"; +import { + SimpleSmartContractAccount, + getChain, + type Address, +} from "@alchemy/aa-core"; import { Wallet } from "@ethersproject/wallet"; -import { Alchemy, Network } from "alchemy-sdk"; +import { Alchemy, Network, type AlchemyProvider } from "alchemy-sdk"; import { EthersProviderAdapter } from "../src/provider-adapter.js"; import { convertWalletToAccountSigner } from "../src/utils.js"; import { API_KEY, OWNER_MNEMONIC, RPC_URL } from "./constants.js"; @@ -17,27 +21,16 @@ describe("Simple Account Tests", async () => { }); const alchemyProvider = await alchemy.config.getProvider(); const owner = Wallet.fromMnemonic(OWNER_MNEMONIC); - const signer = EthersProviderAdapter.fromEthersProvider( - alchemyProvider, - ENTRYPOINT_ADDRESS - ).connectToAccount( - (rpcClient) => - new SimpleSmartContractAccount({ - entryPointAddress: ENTRYPOINT_ADDRESS, - chain: getChain(alchemyProvider.network.chainId), - owner: convertWalletToAccountSigner(owner), - factoryAddress: SIMPLE_ACCOUNT_FACTORY_ADDRESS, - rpcClient, - }) - ); it("should succesfully get counterfactual address", async () => { + const signer = givenConnectedProvider({ alchemyProvider, owner }); expect(await signer.getAddress()).toMatchInlineSnapshot( `"0xb856DBD4fA1A79a46D426f537455e7d3E79ab7c4"` ); }); it("should execute successfully", async () => { + const signer = givenConnectedProvider({ alchemyProvider, owner }); const result = await signer.sendUserOperation({ target: (await signer.getAddress()) as `0x${string}`, data: "0x", @@ -51,20 +44,11 @@ describe("Simple Account Tests", async () => { it("should fail to execute if account address is not deployed and not correct", async () => { const accountAddress = "0xc33AbD9621834CA7c6Fc9f9CC3c47b9c17B03f9F"; - const signer = EthersProviderAdapter.fromEthersProvider( + const signer = givenConnectedProvider({ alchemyProvider, - ENTRYPOINT_ADDRESS - ).connectToAccount( - (rpcClient) => - new SimpleSmartContractAccount({ - entryPointAddress: ENTRYPOINT_ADDRESS, - chain: getChain(alchemyProvider.network.chainId), - owner: convertWalletToAccountSigner(owner), - factoryAddress: SIMPLE_ACCOUNT_FACTORY_ADDRESS, - rpcClient, - accountAddress, - }) - ); + owner, + accountAddress, + }); const result = signer.sendUserOperation({ target: (await signer.getAddress()) as `0x${string}`, @@ -74,3 +58,27 @@ describe("Simple Account Tests", async () => { await expect(result).rejects.toThrowError(); }, 20000); }); + +const givenConnectedProvider = ({ + alchemyProvider, + owner, + accountAddress, +}: { + alchemyProvider: AlchemyProvider; + owner: Wallet; + accountAddress?: Address; +}) => + EthersProviderAdapter.fromEthersProvider( + alchemyProvider, + ENTRYPOINT_ADDRESS + ).connectToAccount( + (rpcClient) => + new SimpleSmartContractAccount({ + entryPointAddress: ENTRYPOINT_ADDRESS, + chain: getChain(alchemyProvider.network.chainId), + owner: convertWalletToAccountSigner(owner), + factoryAddress: SIMPLE_ACCOUNT_FACTORY_ADDRESS, + rpcClient, + accountAddress, + }) + ); diff --git a/packages/ethers/package.json b/packages/ethers/package.json index 93cc4fa98..af604ba17 100644 --- a/packages/ethers/package.json +++ b/packages/ethers/package.json @@ -61,7 +61,7 @@ "@ethersproject/keccak256": "^5.7.0", "@ethersproject/providers": "^5.7.2", "@ethersproject/wallet": "^5.7.0", - "viem": "^1.5.3" + "viem": "^1.16.2" }, "repository": { "type": "git", diff --git a/packages/ethers/src/__tests__/provider-adapter.test.ts b/packages/ethers/src/__tests__/provider-adapter.test.ts index aca95313f..fa16451fe 100644 --- a/packages/ethers/src/__tests__/provider-adapter.test.ts +++ b/packages/ethers/src/__tests__/provider-adapter.test.ts @@ -1,6 +1,6 @@ import { getChain, SimpleSmartContractAccount } from "@alchemy/aa-core"; import { Wallet } from "@ethersproject/wallet"; -import { Alchemy, Network } from "alchemy-sdk"; +import { Alchemy, Network, type AlchemyProvider } from "alchemy-sdk"; import { EthersProviderAdapter } from "../../src/provider-adapter.js"; import { convertWalletToAccountSigner } from "../utils.js"; @@ -14,7 +14,27 @@ describe("Simple Account Tests", async () => { "legal winner thank year wave sausage worth useful legal winner thank yellow"; const owner = Wallet.fromMnemonic(dummyMnemonic); const alchemyProvider = await alchemy.config.getProvider(); - const signer = EthersProviderAdapter.fromEthersProvider( + + it("should correctly sign the message", async () => { + const signer = givenConnectedProvider({ alchemyProvider, owner }); + expect( + await signer.signMessage( + "0xa70d0af2ebb03a44dcd0714a8724f622e3ab876d0aa312f0ee04823285d6fb1b" + ) + ).toBe( + "0xbfe07c95623df55ae939ddf4757563286472ef8c0ebe4b84d5e774a653b7eb67735cb5b63d15bb18510d64a97e6e3001a5f9818f89f2f7f076e559248a7ccf7d1c" + ); + }); +}); + +const givenConnectedProvider = ({ + alchemyProvider, + owner, +}: { + alchemyProvider: AlchemyProvider; + owner: Wallet; +}) => + EthersProviderAdapter.fromEthersProvider( alchemyProvider, "0xENTRYPOINT_ADDRESS" ).connectToAccount((rpcClient) => { @@ -32,14 +52,3 @@ describe("Simple Account Tests", async () => { return account; }); - - it("should correctly sign the message", async () => { - expect( - await signer.signMessage( - "0xa70d0af2ebb03a44dcd0714a8724f622e3ab876d0aa312f0ee04823285d6fb1b" - ) - ).toBe( - "0xbfe07c95623df55ae939ddf4757563286472ef8c0ebe4b84d5e774a653b7eb67735cb5b63d15bb18510d64a97e6e3001a5f9818f89f2f7f076e559248a7ccf7d1c" - ); - }); -}); diff --git a/packages/ethers/src/account-signer.ts b/packages/ethers/src/account-signer.ts index d9e856e83..6f6abb5b4 100644 --- a/packages/ethers/src/account-signer.ts +++ b/packages/ethers/src/account-signer.ts @@ -1,9 +1,9 @@ import { - BaseSmartContractAccount, resolveProperties, type AccountMiddlewareFn, type FeeDataMiddleware, type GasEstimatorMiddleware, + type ISmartContractAccount, type PaymasterAndDataMiddleware, type PublicErc4337Client, } from "@alchemy/aa-core"; @@ -24,14 +24,22 @@ const hexlifyOptional = (value: any): `0x${string}` | undefined => { return hexlify(value) as `0x${string}`; }; -export class AccountSigner extends Signer { - private account?: BaseSmartContractAccount; +export class AccountSigner< + TAccount extends ISmartContractAccount +> extends Signer { + private account?: TAccount; sendUserOperation; waitForUserOperationTransaction; constructor(readonly provider: EthersProviderAdapter) { super(); + if (!this.provider.accountProvider.isConnected()) { + throw new Error( + "provider must be connected to an acccount to create a Signer" + ); + } + this.account = this.provider.accountProvider.account; this.sendUserOperation = @@ -113,7 +121,7 @@ export class AccountSigner extends Signer { return this.provider.getPublicErc4337Client(); } - connect(provider: EthersProviderAdapter): AccountSigner { + connect(provider: EthersProviderAdapter): AccountSigner { return new AccountSigner(provider); } } diff --git a/packages/ethers/src/provider-adapter.ts b/packages/ethers/src/provider-adapter.ts index 5fc424396..918f1f951 100644 --- a/packages/ethers/src/provider-adapter.ts +++ b/packages/ethers/src/provider-adapter.ts @@ -1,5 +1,4 @@ import { - BaseSmartContractAccount, SmartAccountProvider, getChain, type AccountMiddlewareFn, @@ -7,6 +6,7 @@ import { type FeeDataMiddleware, type GasEstimatorMiddleware, type HttpTransport, + type ISmartContractAccount, type PaymasterAndDataMiddleware, type PublicErc4337Client, } from "@alchemy/aa-core"; @@ -20,7 +20,9 @@ export type EthersProviderAdapterOpts = entryPointAddress: Address; chainId: number; } - | { accountProvider: SmartAccountProvider }; + | { + accountProvider: SmartAccountProvider; + }; /** Lightweight Adapter for SmartAccountProvider to enable Signer Creation */ export class EthersProviderAdapter extends JsonRpcProvider { @@ -31,11 +33,11 @@ export class EthersProviderAdapter extends JsonRpcProvider { this.accountProvider = opts.accountProvider; } else { const chain = getChain(opts.chainId); - this.accountProvider = new SmartAccountProvider( - opts.rpcProvider, - opts.entryPointAddress, - chain - ); + this.accountProvider = new SmartAccountProvider({ + rpcProvider: opts.rpcProvider, + entryPointAddress: opts.entryPointAddress, + chain, + }); } } @@ -54,20 +56,18 @@ export class EthersProviderAdapter extends JsonRpcProvider { /** * Connects the Provider to an Account and returns a Signer * - * @param fn - a function that takes the account provider's rpcClient and returns a BaseSmartContractAccount + * @param fn - a function that takes the account provider's rpcClient and returns an ISmartContractAccount * @returns an {@link AccountSigner} that can be used to sign and send user operations */ - connectToAccount( - fn: (rpcClient: PublicErc4337Client) => BaseSmartContractAccount - ): AccountSigner { - defineReadOnly(this, "accountProvider", this.accountProvider.connect(fn)); - return this.getAccountSigner(); - } + connectToAccount( + fn: (rpcClient: PublicErc4337Client) => TAccount + ): AccountSigner { + defineReadOnly( + this, + "accountProvider", + this.accountProvider.connect(fn) + ); - /** - * @returns an {@link AccountSigner} using this as the underlying provider - */ - getAccountSigner(): AccountSigner { return new AccountSigner(this); } @@ -99,6 +99,8 @@ export class EthersProviderAdapter extends JsonRpcProvider { } /** + * Creates an instance of EthersProviderAdapter from an ethers.js JsonRpcProvider. + * * @param provider - the ethers JSON RPC provider to convert * @param entryPointAddress - the entrypoint address that will be used for UserOperations * @returns an instance of {@link EthersProviderAdapter} diff --git a/packages/ethers/src/utils.ts b/packages/ethers/src/utils.ts index bf4518de9..e0bbcaef3 100644 --- a/packages/ethers/src/utils.ts +++ b/packages/ethers/src/utils.ts @@ -3,10 +3,16 @@ import type { Signer } from "@ethersproject/abstract-signer"; import { Wallet } from "@ethersproject/wallet"; import type { SignTypedDataParameters } from "viem/accounts"; +/** + * Converts a ethersjs Wallet to a SmartAccountSigner + * @param wallet - the Wallet to convert + * @returns {SmartAccountSigner} - a signer that can be used to sign and send user operations + */ export const convertWalletToAccountSigner = ( wallet: Wallet ): SmartAccountSigner => { return { + signerType: "local", getAddress: async () => Promise.resolve(wallet.address as `0x${string}`), signMessage: async (msg: Uint8Array | string) => (await wallet.signMessage(msg)) as `0x${string}`, @@ -23,10 +29,16 @@ export const convertWalletToAccountSigner = ( }; }; +/** + * Converts a ethers.js Signer to a SmartAccountSigner + * @param signer - the Signer to convert + * @returns {SmartAccountSigner} - a signer that can be used to sign and send user operations + */ export const convertEthersSignerToAccountSigner = ( signer: Signer ): SmartAccountSigner => { return { + signerType: "json-rpc", getAddress: async () => signer.getAddress() as Promise
, signMessage: async (msg: Uint8Array | string) => (await signer.signMessage(msg)) as `0x${string}`, diff --git a/site/.vitepress/config.ts b/site/.vitepress/config.ts new file mode 100644 index 000000000..3319e9851 --- /dev/null +++ b/site/.vitepress/config.ts @@ -0,0 +1,523 @@ +import { $ } from "execa"; +import { defineConfig } from "vitepress"; + +// This makes sure that this works in forked repos as well +const getRepoRoute = $.sync`git rev-parse --show-toplevel`; +const { stdout: basePath } = $.sync`basename ${getRepoRoute}`; + +const noBasePath = process.env.NO_BASE_PATH === "true"; + +// https://vitepress.dev/reference/site-config +export default defineConfig({ + title: "Account Kit", + description: "Account Abstraction Legos", + base: noBasePath ? undefined : `/${basePath}`, + themeConfig: { + // https://vitepress.dev/reference/default-theme-config + nav: [ + { text: "Docs", link: "/getting-started" }, + { + text: "Examples", + link: "https://github.com/alchemyplatform/aa-sdk/tree/main/examples", + }, + ], + + search: { + provider: "local", + }, + + sidebar: [ + { text: "Introduction", link: "/introduction" }, + { text: "Why Account Kit", link: "/why-account-kit" }, + { text: "Getting Started", link: "/getting-started" }, + { + text: "Packages Overview", + link: "/packages/overview", + }, + { + text: "Using Smart Accounts", + base: "/smart-accounts", + items: [ + { text: "Overview", link: "/overview" }, + { + text: "Choosing a Smart Account", + base: "/smart-accounts/accounts", + link: "/overview", + items: [ + { text: "Light Account", link: "/light-account" }, + { text: "Modular Account", link: "/modular-account" }, + { text: "Using Your Own", link: "/using-your-own" }, + ], + }, + { + text: "Choosing a Signer", + base: "/smart-accounts/signers", + link: "/overview", + items: [ + { text: "Capsule", link: "/capsule" }, + { text: "Fireblocks", link: "/fireblocks" }, + { text: "Lit Protocol", link: "/lit" }, + { text: "Magic.Link", link: "/magic-link" }, + { text: "Portal", link: "/portal" }, + { text: "Privy", link: "/privy" }, + { text: "Turnkey", link: "/turnkey" }, + { text: "Web3Auth", link: "/web3auth" }, + { text: "Externally Owned Account", link: "/eoa" }, + { text: "Using Your Own", link: "/using-your-own" }, + { text: "Contributing", link: "/contributing" }, + ], + }, + { text: "Sponsoring Gas", link: "/sponsoring-gas" }, + { text: "Batching Transactions", link: "/batching-transactions" }, + { text: "Transferring Ownership", link: "/transferring-ownership" }, + ], + }, + { text: "ERC-6900", link: "/erc-6900" }, + // Per Package docs + { + text: "aa-core", + base: "/packages/aa-core", + link: "/", + collapsed: true, + items: [ + { + text: "Provider", + collapsed: true, + link: "/introduction", + base: "/packages/aa-core/provider", + items: [ + { + text: "sendUserOperation", + link: "/sendUserOperation", + }, + { + text: "buildUserOperation", + link: "/buildUserOperation", + }, + { + text: "buildUserOperationFromTx", + link: "/buildUserOperationFromTx", + }, + { + text: "waitForUserOperationTransaction", + link: "/waitForUserOperationTransaction", + }, + { + text: "getUserOperationByHash", + link: "/getUserOperationByHash", + }, + { + text: "getUserOperationReceipt", + link: "/getUserOperationReceipt", + }, + { + text: "sendTransaction", + link: "/sendTransaction", + }, + { + text: "sendTransactions", + link: "/sendTransactions", + }, + { + text: "request", + link: "/request", + }, + { + text: "signMessage", + link: "/signMessage", + }, + { + text: "signTypedData", + link: "/signTypedData", + }, + { + text: "signMessageWith6492", + link: "/signMessageWith6492", + }, + { + text: "signTypedDataWith6492", + link: "/signTypedDataWith6492", + }, + { + text: "getAddress", + link: "/getAddress", + }, + { + text: "isConnected", + link: "/isConnected", + }, + { + text: "withPaymasterMiddleware", + link: "/withPaymasterMiddleware", + }, + { + text: "withGasEstimator", + link: "/withGasEstimator", + }, + { + text: "withFeeDataGetter", + link: "/withFeeDataGetter", + }, + { + text: "withCustomMiddleware", + link: "/withCustomMiddleware", + }, + { + text: "connect", + link: "/connect", + }, + { + text: "disconnect", + link: "/disconnect", + }, + { + text: "dummyPaymasterDataMiddleware", + link: "/dummyPaymasterDataMiddleware", + }, + { + text: "paymasterDataMiddleware", + link: "/paymasterDataMiddleware", + }, + { + text: "gasEstimator", + link: "/gasEstimator", + }, + { + text: "feeDataGetter", + link: "/feeDataGetter", + }, + { + text: "customMiddleware", + link: "/customMiddleware", + }, + ], + }, + { + text: "Accounts", + link: "/introduction", + base: "/packages/aa-core/accounts", + collapsed: true, + items: [ + { + text: "Required Methods", + collapsed: true, + base: "/packages/aa-core/accounts/required", + items: [ + { + text: "getDummySignature", + link: "/getDummySignature", + }, + { + text: "getAccountInitCode", + link: "/getAccountInitCode", + }, + { + text: "signMessage", + link: "/signMessage", + }, + { + text: "encodeExecute", + link: "/encodeExecute", + }, + ], + }, + { + text: "Optional Methods", + collapsed: true, + base: "/packages/aa-core/accounts/optional", + items: [ + { + text: "encodeBatchExecute", + link: "/encodeBatchExecute", + }, + { + text: "signTypedData", + link: "/signTypedData", + }, + { + text: "signMessageWith6492", + link: "/signMessageWith6492", + }, + { + text: "signTypedDataWith6492", + link: "/signTypedDataWith6492", + }, + ], + }, + { + text: "Other Methods", + collapsed: true, + base: "/packages/aa-core/accounts/other", + items: [ + { + text: "getAddress", + link: "/getAddress", + }, + { + text: "getNonce", + link: "/getNonce", + }, + { + text: "getDeplymentState", + link: "/getDeplymentStates", + }, + { + text: "isAccountDeployed", + link: "/isAccountDeployeds", + }, + ], + }, + ], + }, + { + text: "Signers", + base: "/packages/aa-core/signers", + collapsed: true, + items: [ + { text: "WalletClientSigner", link: "/wallet-client" }, + { text: "LocalAccountSigner", link: "/local-account" }, + { + text: "Utils", + collapsed: true, + base: "/packages/aa-core/signers/utils", + items: [ + { + text: "wrapSignatureWith6492", + link: "/wrapSignatureWith6492", + }, + { + text: "verifyEIP6492Signature", + link: "/verifyEIP6492Signature", + }, + ], + }, + ], + }, + { + text: "Public Client", + link: "/", + base: "/packages/aa-core/client", + collapsed: true, + items: [ + { + text: "Actions", + collapsed: true, + base: "/packages/aa-core/client/actions", + items: [ + { text: "sendUserOperation", link: "/sendUserOperation" }, + { + text: "estimateUserOperationGas", + link: "/estimateUserOperationGas", + }, + { + text: "getUserOperationByHash", + link: "/getUserOperationByHash", + }, + { + text: "getUserOperationReceipt", + link: "/getUserOperationReceipt", + }, + { + text: "getSupportedEntryPoints", + link: "/getSupportedEntryPoints", + }, + ], + }, + { + text: "createPublicErc4337Client", + link: "/createPublicErc4337Client", + }, + { + text: "createPublicErc4337FromClient", + link: "/createPublicErc4337FromClient", + }, + { + text: "erc4337ClientActions", + link: "/erc4337ClientActions", + }, + ], + }, + { + text: "Utilities", + base: "/packages/aa-core/utils", + collapsed: true, + items: [ + { text: "asyncPipe", link: "/asyncPipe" }, + { + text: "convertChainIdToCoinType", + link: "/convertChainIdToCoinType", + }, + { + text: "convertCoinTypeToChain", + link: "/convertCoinTypeToChain", + }, + { + text: "convertCoinTypeToChainId", + link: "/convertCoinTypeToChainId", + }, + { text: "deepHexlify", link: "/deepHexlify" }, + { text: "defineReadOnly", link: "/defineReadOnly" }, + { text: "getChain", link: "/getChain" }, + { text: "getUserOperationHash", link: "/getUserOperationHash" }, + { text: "resolveProperties", link: "/resolveProperties" }, + ], + }, + ], + }, + { + text: "aa-alchemy", + base: "/packages/aa-alchemy", + link: "/", + collapsed: true, + items: [ + { + text: "AlchemyProvider", + link: "/introduction", + base: "/packages/aa-alchemy/provider", + collapsed: true, + items: [ + { text: "gasEstimator", link: "/gasEstimator" }, + { text: "withAlchemyGasManager", link: "/withAlchemyGasManager" }, + ], + }, + { + text: "Middleware", + link: "/introduction", + base: "/packages/aa-alchemy/middleware", + collapsed: true, + items: [ + { + text: "withAlchemyGasFeeEstimator", + link: "/withAlchemyGasFeeEstimator", + }, + { text: "withAlchemyGasManager", link: "/withAlchemyGasManager" }, + ], + }, + { + text: "Utils", + collapsed: true, + link: "/introduction", + base: "/packages/aa-alchemy/utils", + items: [{ text: "SupportedChains", link: "/supportedChains" }], + }, + ], + }, + { + text: "aa-accounts", + collapsed: true, + link: "/", + base: "/packages/aa-accounts", + items: [ + { + text: "LightSmartContractAccount", + collapsed: true, + link: "/introduction", + base: "/packages/aa-accounts/light-account", + items: [ + { text: "signMessageWith6492", link: "/signMessageWith6492" }, + { text: "signTypedData", link: "/signTypedData" }, + { text: "signTypedDataWith6492", link: "/signTypedDataWith6492" }, + { text: "getOwnerAddress", link: "/getOwnerAddress" }, + { + text: "encodeTransferOwnership", + link: "/encodeTransferOwnership", + }, + { text: "transferOwnership", link: "/transferOwnership" }, + ], + }, + { text: "Contributing", link: "/contributing" }, + ], + }, + { + text: "aa-ethers", + base: "/packages/aa-ethers", + link: "/", + collapsed: true, + items: [ + { + text: "EthersProviderAdapter", + collapsed: true, + link: "/introduction", + base: "/packages/aa-ethers/provider-adapter", + items: [ + { + text: "send", + link: "/send", + }, + { + text: "connectToAccount", + link: "/connectToAccount", + }, + { + text: "getPublicErc4337Client", + link: "/getPublicErc4337Client", + }, + { + text: "fromEthersProvider", + link: "/fromEthersProvider", + }, + ], + }, + { + text: "AccountSigner", + collapsed: true, + link: "/introduction", + base: "/packages/aa-ethers/account-signer", + items: [ + { + text: "getAddress", + link: "/getAddress", + }, + { + text: "signMessage", + link: "/signMessage", + }, + { + text: "sendTransaction", + link: "/sendTransaction", + }, + { + text: "getPublicErc4337Client", + link: "/getPublicErc4337Client", + }, + { + text: "connect", + link: "/connect", + }, + ], + }, + { + text: "Utils", + collapsed: true, + link: "/introduction", + base: "/packages/aa-ethers/utils", + items: [ + { + text: "convertWalletToAccountSigner", + link: "/convertWalletToAccountSigner", + }, + { + text: "convertEthersSignerToAccountSigner", + link: "/convertEthersSignerToAccountSigner", + }, + ], + }, + ], + }, + ], + + socialLinks: [ + { icon: "github", link: "https://github.com/alchemyplatform/aa-sdk" }, + ], + }, + head: [ + [ + "script", + { + src: "https://static.alchemyapi.io/scripts/anayltics/alchemy-analytics.js", + defer: "defer", + }, + ], + ["link", { rel: "icon", href: "/public/favicon.ico" }], + ], +}); diff --git a/site/.vitepress/theme/index.ts b/site/.vitepress/theme/index.ts new file mode 100644 index 000000000..fa86128b4 --- /dev/null +++ b/site/.vitepress/theme/index.ts @@ -0,0 +1,16 @@ +// https://vitepress.dev/guide/custom-theme +import { h } from "vue"; +import Theme from "vitepress/theme"; +import "./style.css"; + +export default { + extends: Theme, + Layout: () => { + return h(Theme.Layout, null, { + // https://vitepress.dev/guide/extending-default-theme#layout-slots + }); + }, + enhanceApp({ app, router, siteData }) { + // ... + }, +}; diff --git a/site/.vitepress/theme/style.css b/site/.vitepress/theme/style.css new file mode 100644 index 000000000..6fff26cf5 --- /dev/null +++ b/site/.vitepress/theme/style.css @@ -0,0 +1,142 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/** + * Customize default theme styling by overriding CSS variables: + * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css + */ + +/** + * Colors + * + * Each colors have exact same color scale system with 3 levels of solid + * colors with different brightness, and 1 soft color. + * + * - `XXX-1`: The most solid color used mainly for colored text. It must + * satisfy the contrast ratio against when used on top of `XXX-soft`. + * + * - `XXX-2`: The color used mainly for hover state of the button. + * + * - `XXX-3`: The color for solid background, such as bg color of the button. + * It must satisfy the contrast ratio with pure white (#ffffff) text on + * top of it. + * + * - `XXX-soft`: The color used for subtle background such as custom container + * or badges. It must satisfy the contrast ratio when putting `XXX-1` colors + * on top of it. + * + * The soft color must be semi transparent alpha channel. This is crucial + * because it allows adding multiple "soft" colors on top of each other + * to create a accent, such as when having inline code block inside + * custom containers. + * + * - `default`: The color used purely for subtle indication without any + * special meanings attched to it such as bg color for menu hover state. + * + * - `brand`: Used for primary brand colors, such as link text, button with + * brand theme, etc. + * + * - `tip`: Used to indicate useful information. The default theme uses the + * brand color for this by default. + * + * - `warning`: Used to indicate warning to the users. Used in custom + * container, badges, etc. + * + * - `danger`: Used to show error, or dangerous message to the users. Used + * in custom container, badges, etc. + * -------------------------------------------------------------------------- */ + +:root { + --vp-c-default-1: var(--vp-c-gray-1); + --vp-c-default-2: var(--vp-c-gray-2); + --vp-c-default-3: var(--vp-c-gray-3); + --vp-c-default-soft: var(--vp-c-gray-soft); + + --vp-c-brand-1: var(--vp-c-indigo-1); + --vp-c-brand-2: var(--vp-c-indigo-2); + --vp-c-brand-3: var(--vp-c-indigo-3); + --vp-c-brand-soft: var(--vp-c-indigo-soft); + + --vp-c-tip-1: var(--vp-c-brand-1); + --vp-c-tip-2: var(--vp-c-brand-2); + --vp-c-tip-3: var(--vp-c-brand-3); + --vp-c-tip-soft: var(--vp-c-brand-soft); + + --vp-c-warning-1: var(--vp-c-yellow-1); + --vp-c-warning-2: var(--vp-c-yellow-2); + --vp-c-warning-3: var(--vp-c-yellow-3); + --vp-c-warning-soft: var(--vp-c-yellow-soft); + + --vp-c-danger-1: var(--vp-c-red-1); + --vp-c-danger-2: var(--vp-c-red-2); + --vp-c-danger-3: var(--vp-c-red-3); + --vp-c-danger-soft: var(--vp-c-red-soft); +} + +/** + * Component: Button + * -------------------------------------------------------------------------- */ + +:root { + --vp-button-brand-border: transparent; + --vp-button-brand-text: var(--vp-c-white); + --vp-button-brand-bg: var(--vp-c-brand-3); + --vp-button-brand-hover-border: transparent; + --vp-button-brand-hover-text: var(--vp-c-white); + --vp-button-brand-hover-bg: var(--vp-c-brand-2); + --vp-button-brand-active-border: transparent; + --vp-button-brand-active-text: var(--vp-c-white); + --vp-button-brand-active-bg: var(--vp-c-brand-1); +} + +/** + * Component: Home + * -------------------------------------------------------------------------- */ + +:root { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient( + 120deg, + #bd34fe 30%, + #41d1ff + ); + + --vp-home-hero-image-background-image: linear-gradient( + -45deg, + #bd34fe 50%, + #47caff 50% + ); + --vp-home-hero-image-filter: blur(40px); +} + +@media (min-width: 640px) { + :root { + --vp-home-hero-image-filter: blur(56px); + } +} + +@media (min-width: 960px) { + :root { + --vp-home-hero-image-filter: blur(72px); + } +} + +/** + * Component: Custom Block + * -------------------------------------------------------------------------- */ + +:root { + --vp-custom-block-tip-border: transparent; + --vp-custom-block-tip-text: var(--vp-c-text-1); + --vp-custom-block-tip-bg: var(--vp-c-brand-soft); + --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft); +} + +/** + * Component: Algolia + * -------------------------------------------------------------------------- */ + +.DocSearch { + --docsearch-primary-color: var(--vp-c-brand-1) !important; +} diff --git a/site/assets/images/account-kit-overview.png b/site/assets/images/account-kit-overview.png new file mode 100644 index 000000000..004940c50 Binary files /dev/null and b/site/assets/images/account-kit-overview.png differ diff --git a/site/assets/images/erc-6900.png b/site/assets/images/erc-6900.png new file mode 100644 index 000000000..395ef0495 Binary files /dev/null and b/site/assets/images/erc-6900.png differ diff --git a/site/assets/images/policy-id.png b/site/assets/images/policy-id.png new file mode 100644 index 000000000..8968a677e Binary files /dev/null and b/site/assets/images/policy-id.png differ diff --git a/site/erc-6900.md b/site/erc-6900.md new file mode 100644 index 000000000..2cee559ff --- /dev/null +++ b/site/erc-6900.md @@ -0,0 +1,57 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: ERC-6900 + - - meta + - name: description + content: Learn about ERC-6900 Modular Smart Contract Accounts and Plugins + - - meta + - property: og:description + content: Learn about ERC-6900 Modular Smart Contract Accounts and Plugins +--- + +# ERC-6900 + +### **Overview** + +[ERC-6900](https://eips.ethereum.org/EIPS/eip-6900) proposes a set of standards for modular smart account developers. It reflects a modular vision of smart accounts that allow both users and developers to build account experiences that reflect their needs and objectives. + +By standardizing basic functions and interfaces, ERC-6900 seeks to foster a growing ecosystem of wallet and plugin (or module) developers by freeing them to focus on their projects rather than fragmenting their efforts across multiple ecosystem standards. Is also seeks to create a community-defined standard that avoids lock-in by any one platform, allowing users and developers to construct wallets that offer more flexibility and functionality than are possible with standard EOA accounts. + +### **Architecture** + +The standard focuses on the development of modules or plugins, and on the interactions between these plugins and modular smart contract accounts (or “MSCA”). Following [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337), it splits validation and execution functions in order to allow for greater customization within these components while also ensuring that they remain composable. It also adds pre- and post-execution hooks as a third functional component to plugins, enabling more finely grained functionality. + +![ERC-6900](/assets/images/erc-6900.png) + +_[Source](https://eips.ethereum.org/EIPS/eip-6900)_ + +The architecture described in the image above is designed to achieve two technical goals: + +- Provide standards for designing plugins for smart contract accounts. +- Provide standards for how compliant accounts should interact with plugins. + +### Designing Plugins + +The standard seeks to support open innovation in plugin development by standardizing the structures and interactions between three categories of functional components: + +- **Validation functions** ensure the validity of external calls to the smart account. +- **Execution functions** are smart contracts that specify the execution logic for functions within a plugin. +- **Hooks** specify more fine-grained actions and validations that can be designed to occur pre- or post-validation, and pre- or post-execution. + +### **Interacting with plugins** + +ERC-6900 seeks to balance the benefits of open composability in users’ and developers’ choices with the need to maintain fundamental characteristics of security and interoperability. At a high level, it does this by standardizing how accounts and plugins interact with each other, as well as the pre-installation requirements for plugins. + +ERC-6900 provides multiple possible types of interactions with plugins that vary with the nature of the account. For ERC-4437 compliant smart accounts, it follows that standard’s introduction of `Entrypoint` contract calls via UserOperations. The standard also supports calls from other account types, whether EOA or a different smart account standard. + +The standard also builds on earlier work by the Android developer community to standardize the interface between smart accounts and plugins. Each compliant plugin will incorporate a manifest that establishes various functions and hooks that need to be added to the smart account on installation. It will also specify aspects of the plugin (metadata, dependencies and permissions) that are necessary to constrain the plugin’s ability to act on the smart account. + +Taken together, these interactions enable a workflow that supports the seamless installation of plugins in a manner that ensures security and flexibility: + +1. Plugin developers and users set each plugin’s permissions and specifies validation and hooks during installation of the the plugin onto the account. +2. Based on these permissions, plugins can then change account states or execute on behalf of the account. + +For more detailed specifications of transaction flows, see the [ERC-6900 spec](https://eips.ethereum.org/EIPS/eip-6900). diff --git a/site/getting-started.md b/site/getting-started.md new file mode 100644 index 000000000..2c77a97a3 --- /dev/null +++ b/site/getting-started.md @@ -0,0 +1,75 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Getting Started + - - meta + - name: description + content: Getting Started with Alchemy's Account Kit + - - meta + - property: og:description + content: Getting Started with Alchemy's Account Kit +--- + +# Getting Started + +This guide will help you get started with Account Kit by setting up your environment, creating a Light Account (a type of smart account implementation), and sending a User Operation from it. By the end of this guide, you'll have a basic understanding of how to use the SDK and where to look for more advanced use cases. + +## Install the Packages + +First, create an empty directory and initialize it: +::: code-group + +```bash [npm] +mkdir account-kit +cd account-kit +npm init -y +``` + +```bash [yarn] +mkdir account-kit +cd account-kit +yarn init -y +``` + +::: + +Then you'll need to install the required packages: + +::: code-group + +```bash [npm] +npm install @alchemy/aa-alchemy @alchemy/aa-accounts @alchemy/aa-core viem +``` + +```bash [yarn] +yarn add @alchemy/aa-alchemy @alchemy/aa-accounts @alchemy/aa-core viem +``` + +::: + +## A Simple Light Account Example + +Using the SDK, we'll create a Light Account and send a User Operation from it. The Light Account will be owned by an Externally Owned Account (EOA). Here's a demonstration: + +<<< @/snippets/light-account.ts + +This initializes a `provider` for your smart account which is then used to send user operations from it. + +::: tip Note +Remember to: + +1. Replace `"0xYourEOAPrivateKey"` with your actual EOA private key. +2. Set `"ALCHEMY_API_KEY"` with your unique Alchemy API key. +3. Fund your smart account address with some SepoliaETH in order for the user operation to go through. This address is logged to the console when you run the script. +4. Adjust the `target` and `data` fields in the `sendUserOperation` function to match your requirements. + ::: + +## Dive Deeper + +In this guide we initialized `provider` with the `aa-alchemy` package however we could have done the same with the other packages of Account Kit as well. To learn more about the different packages and their use cases, check out the ["Packages Overview"](/packages/overview) page. + +## Next Steps + +To learn about the end-to-end process of integrating smart accounts in your applications check out the section on [using smart accounts](/smart-accounts/overview.html) diff --git a/site/index.md b/site/index.md new file mode 100644 index 000000000..ce408d1a0 --- /dev/null +++ b/site/index.md @@ -0,0 +1,166 @@ +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home +# HTML Metadata +title: account-kit +titleTemplate: :title · TypeScript Interface for ERC-4337 +description: Everything you need to build 4337 accounts for your users +# you can also add HTML or Markdown components below the --- line to add custom HTML or Markdown content (eg: https://github.com/wagmi-dev/viem/blob/main/site/index.md?plain=1) +--- + +
+
+
+
+
+ Account Kit Logo + Account Kit +
+
+ Account Abstraction Legos +
+
+ Everything you need to build 4337 accounts. +
+ +
+ +
+ +::: code-group + +```typescript [getStarted.ts] +const provider = new AlchemyProvider(providerConfig).connect( + (rpcClient) => + new LightSmartContractAccount({ + ...accountConfig, + rpcClient, + }) +); + +const { hash } = await provider.sendUserOperation(uo); +``` + +::: + +
+
+
+ + +
diff --git a/site/introduction.md b/site/introduction.md new file mode 100644 index 000000000..82035e4d6 --- /dev/null +++ b/site/introduction.md @@ -0,0 +1,81 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: What is Account Kit? + - - meta + - name: description + content: Learn about Account Kit + - - meta + - property: og:description + content: Learn about Account Kit +--- + +# What is Account Kit? + +**Account Kit** is a framework to embed smart accounts in your web3 app, unlocking [powerful features](/getting-started) like email/social login, gas sponsorship, batched transactions, and more. The `aa-sdk` makes it easy to integrate and deploy smart accounts, send user operations, and sponsor gas with just a few lines of code. + +## What is included in the Account Kit stack? + +Account Kit is a complete solution for [account abstraction](https://www.alchemy.com/overviews/what-is-account-abstraction). It includes five components: + +- **AA-SDK**: A simple, powerful interface to integrate, deploy, and use smart accounts. The `aa-sdk` orchestrates everything under the hood to make development easy. +- **LightAccount:** Secure, audited smart contract accounts. Easy to deploy, just when your users need them. +- **Signer:** Integrations with the most popular wallet providers. Secure your accounts with email, social login, passkeys, or a self-custodial wallet signer. +- **Gas Manager API:** A programmable API to sponsor gas for UserOps that meet your criteria. +- **Bundler API:** The most reliable ERC-4337 Bundler. Land your UserOps onchain, batch operations, and sponsor gas at massive scale. + +Account Kit Overview + +Let’s dive into each component. + +### AA-SDK + +The `aa-sdk` is a type-safe and performant TypeScript library built on top of [viem](https://viem.sh/) to provide ergonomic methods for sending user operations, sponsoring gas, and deploying smart contract accounts. It handles all the complexity of ERC-4337 under the hood to make account abstraction simple. + +The SDK also implements an EIP-1193 provider interface to easily plug into any popular dapp or wallet connect libraries such as RainbowKit, Wagmi, and Web3Modal. It also includes ethers.js adapters to provide full support for ethers.js apps. + +The `aa-sdk` is modular at every layer of the stack and can be easily extended to fit your custom needs. You can plug in any [smart account](/smart-accounts/accounts/using-your-own) implementation, [Signer](/smart-accounts/signers/overview), gas manager API, RPC provider. + +Get started with `aa-sdk` in our [Getting Started guide](/getting-started) or checkout the [open source repo](https://github.com/alchemyplatform/aa-sdk). + +### LightAccount + +`LightAccount` is a secure, gas-optimized, ERC-4337 smart contract account. + +We started with the Ethereum Foundation’s canonical [SimpleAccount](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/samples/SimpleAccount.sol) and added key improvements for production app developers: + +- Significantly [reduced gas costs](/smart-accounts/accounts/overview#benchmarks) +- ERC-1271 signature support to ensure users can sign messages, such as on Opensea +- Ownership transfer so that users won’t get locked into a single Signer + +`LightAccount` was audited by Quantstamp ([report](https://github.com/alchemyplatform/light-account/blob/main/Quantstamp-Audit.pdf)). + +`LightAccount` is forward-compatible with [ERC-6900](https://eips.ethereum.org/EIPS/eip-6900), a new standard for **modular** smart contract accounts. Once we stabilize the ERC with the [community](https://ethereum-magicians.org/t/erc-6900-modular-smart-contract-accounts-and-plugins/13885/35) and publish a reference implementation, we will release an optional upgrade for `LightAccount` to upgrade to modular EIP-6900 compliant accounts. [Join the discussion](https://ethereum-magicians.org/t/erc-6900-modular-smart-contract-accounts-and-plugins/13885/35) on ERC-6900! + +To learn how to deploy a `LightAccount`, see [LightAccount](/smart-accounts/accounts/light-account). + +### Signers + +A Signer is responsible for securely managing the private key and signing transaction requests on the smart account. Account Kit supports many popular wallet signers including [Magic.link](/smart-accounts/signers/magic-link), [web3auth](/smart-accounts/signers/magic-link), [Turnkey](/smart-accounts/signers/turnkey), Privy, Lit Protocol, Fireblocks, Portal, and [Capsule](/smart-accounts/signers/capsule). It also supports self-custodial wallets like Metamask or Ledger. + +To get started with a signer, read the doc: [How to Choose a Signer](/smart-accounts/signers/overview). + +### Gas Manager API + +The Gas Manager is a programmable API to sponsor gas for UserOps. You can create programmable gas policies to specify exactly which transactions should be sponsored, set strict spending limits per wallet or globally, and allowlist/blocklist particular wallet addresses. This expressive programmability is available through a REST API and an intuitive dashboard interface. + +To learn how to sponsor gas with the Gas Manager API, see the [Sponsoring Gas](/smart-accounts/sponsoring-gas) tutorial. + +### Bundler API + +The Bundler is a mission-critical piece of secondary infrastructure defined in the ERC-4337 spec that is responsible for submitting UserOps from a Smart Account onchain. If your Bundler API is unreliable, then User Operations are going to fail or get stuck. + +We built our [Bundler in Rust](https://www.alchemy.com/blog/open-sourcing-rundler) to handle the highest loads at production scale. It’s able to handle massive scale because we operate it alongside our fleet of nodes powering the biggest dapps in web3 from Opensea to Circle. + +Check out the open source code in our affectionately named [Rundler github repo](https://github.com/alchemyplatform/rundler). + +## Start building with Account Kit + +Account Kit was designed from the ground up to make account abstraction easy. It’s built on top of industry-leading infrastructure that powers applications at massive scale from Opensea to Shopify. Start integrating Account Kit today in the [Getting Started guide](/getting-started). diff --git a/site/package.json b/site/package.json new file mode 100644 index 000000000..97ac17d42 --- /dev/null +++ b/site/package.json @@ -0,0 +1,18 @@ +{ + "name": "docs", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vitepress dev", + "build": "vitepress build", + "preview": "vitepress preview" + }, + "devDependencies": { + "autoprefixer": "^10.4.16", + "execa": "^8.0.1", + "tailwindcss": "^3.3.3", + "vitepress": "^1.0.0-rc.14", + "vue": "^3.2.45" + } +} diff --git a/site/packages/aa-accounts/contributing.md b/site/packages/aa-accounts/contributing.md new file mode 100644 index 000000000..f79bc9a2c --- /dev/null +++ b/site/packages/aa-accounts/contributing.md @@ -0,0 +1,36 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Contributing to aa-accounts + - - meta + - name: description + content: How to add your own Account Implementation to aa-accounts + - - meta + - property: og:description + content: How to add your own Account Implementation to aa-accounts +--- + +# Contributing to `aa-accounts` + +If you are looking to add a new account type, please follow the following structure. + +1. Create a new folder in `src` with the name of your account type in `kebab-case` (we're following kebab casing for files throughout the project). +2. Create a new file in the folder you just created called `account.ts` and add your implementation for `BaseSmartContractAccount` +3. If needed, create a sub-folder in your account folder called `abis` and add your abis as `.ts` files. eg: + +```ts +export const MyContractAbi = [ + // ... +] as const; // the as const is important so we can get correct typing from viem +``` + +4. If you need to extend the `SmartAccountProvider` class, add a file called `provider.ts` and add your implementation for `SmartAccountProvider`. + + - Ideally, your `Account` impl should _just_ work with the base provider provided by `aa-core`. + - If not, consider generalizing the use case and updating SmartAccountProvider + +5. Add some tests for your account and provider (if created) by creating a subfolder in your `account/my-account` called `__tests__` and make sure your files end with the `.test.ts` suffix +6. Export the classes and types you've defined in `src/index.ts` +7. Open a PR and we'll review it as soon as possible! diff --git a/site/packages/aa-accounts/index.md b/site/packages/aa-accounts/index.md new file mode 100644 index 000000000..562ed4627 --- /dev/null +++ b/site/packages/aa-accounts/index.md @@ -0,0 +1,37 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: aa-accounts + - - meta + - name: description + content: aa-accounts landing page and getting started guide + - - meta + - property: og:description + content: aa-accounts landing page and getting started guide +--- + +# `@alchemy/aa-accounts` + +This package contains various implementations of the `BaseSmartContractAccount` class defined in `aa-core`. This repo is community maintained and we welcome contributions! + +## Getting started + +If you are already using the `@alchemy/aa-core` package, you can simply install this package and start using the accounts. If you are not using `@alchemy/aa-core`, you can install it and follow the instructions in the ["Getting Started"](/getting-started) docs to get started. + +::: code-group + +```bash [yarn] +yarn add @alchemy/aa-accounts +``` + +```bash [npm] +npm i @alchemy/aa-accounts +``` + +```bash [pnpm] +pnpm i @alchemy/aa-accounts +``` + +::: diff --git a/site/packages/aa-accounts/light-account/encodeTransferOwnership.md b/site/packages/aa-accounts/light-account/encodeTransferOwnership.md new file mode 100644 index 000000000..ed1f9dc8f --- /dev/null +++ b/site/packages/aa-accounts/light-account/encodeTransferOwnership.md @@ -0,0 +1,43 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: LightSmartContractAccount • encodeTransferOwnership + - - meta + - name: description + content: Overview of the encodeTransferOwnership method on LightSmartContractAccount + - - meta + - property: og:description + content: Overview of the encodeTransferOwnership method on LightSmartContractAccount +--- + +# encodeTransferOwnership + +`encodeTransferOwnership` is a static class method on the `LightSmartContractAccount` which generates the call data necessary to send a userOperation calling `transferOwnership` on the connected smart contract account. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +// encode transfer pownership +const newOwner = LocalAccountSigner.mnemonicToAccountSigner(NEW_OWNER_MNEMONIC); +const encodedTransferOwnershipData = + LightSmartContractAccount.encodeTransferOwnership(newOwner); +``` + +<<< @/snippets/provider.ts +::: + +## Returns + +### `Promise` + +A Promise containing the encoded Hex of the`transferOwnership` function call with the given parameter + +## Parameters + +### `newOwner:
` -- the new owner to transfer ownership to for the smart contract account diff --git a/site/packages/aa-accounts/light-account/getOwnerAddress.md b/site/packages/aa-accounts/light-account/getOwnerAddress.md new file mode 100644 index 000000000..d6730fce0 --- /dev/null +++ b/site/packages/aa-accounts/light-account/getOwnerAddress.md @@ -0,0 +1,37 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: LightSmartContractAccount • getOwnerAddress + - - meta + - name: description + content: Overview of the getOwnerAddress method on LightSmartContractAccount + - - meta + - property: og:description + content: Overview of the getOwnerAddress method on LightSmartContractAccount +--- + +# getOwnerAddress + +`getOwnerAddress` returns the address of the on-chain owner of the account. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +// get owner +const owner = await provider.account.getOwnerAddress(); +``` + +<<< @/snippets/provider.ts +::: + +## Returns + +### `Promise
` + +A Promise containing the address of the smart contract account's owner address. diff --git a/site/packages/aa-accounts/light-account/introduction.md b/site/packages/aa-accounts/light-account/introduction.md new file mode 100644 index 000000000..9372c2e6b --- /dev/null +++ b/site/packages/aa-accounts/light-account/introduction.md @@ -0,0 +1,74 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: LightSmartContractAccount + - - meta + - name: description + content: Overview of the LightSmartContractAccount class in aa-accounts + - - meta + - property: og:description + content: Overview of the LightSmartContractAccount class in aa-accounts +--- + +# Light Account + +`LightSmartContractAccount` is a simple, secure, and cost-effective smart account implementation which extends `SimpleSmartContractAccount` as an implementation of `BaseSmartContractAccount`. It supports features such as owner transfers, [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) message signing, and batched transactions. We recommend using Light Account for most use cases. + +Notable differences between `LightSmartContractAccount` and `SimpleSmartContractAccount` are implementations for: + +1. [`signMessageWith6492`](/packages/aa-accounts/light-account/signMessageWith6492) -- supports message signatures for deployed smart contract accounts, as well as undeployed accounts (counterfactual addresses) using [ERC-6492](https://eips.ethereum.org/EIPS/eip-6492). +2. [`signTypedData`](/packages/aa-accounts/light-account/signTypedData) -- supports typed data signatures from the smart contract account's owner address. +3. [`signTypedDataWith6492`](/packages/aa-accounts/light-account/signTypedDataWith6492) -- supports typed data signatures for deployed smart contract accounts, as well as undeployed accounts (counterfactual addresses) using ERC-6492. +4. [`getOwnerAddress`](/packages/aa-accounts/light-account/getOwnerAddress) -- returns the on-chain owner address of the account. +5. [`encodeTransferOwnership`](/packages/aa-accounts/light-account/encodeTransferOwnership) -- encodes the transferOwnership function call using the LightAccount ABI. +6. [`transferOwnership`](/packages/aa-accounts/light-account/transferOwnership) -- transfers ownership of the account to a new owner, and returns either the UO hash or transaction hash. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +// sign message (works for undeployed and deployed accounts) +const signedMessageWith6492 = provider.signMessageWith6492("test"); + +// sign typed data +const signedTypedData = provider.signTypedData("test"); + +// sign typed data (works for undeployed and deployed accounts), using +const signedTypedDataWith6492 = provider.signTypedDataWith6492({ + types: { + Request: [{ name: "hello", type: "string" }], + }, + primaryType: "Request", + message: { + hello: "world", + }, +}); + +// get owner address +const owner = await provider.account.getOwnerAddress(); + +// encode transfer pownership +const newOwner = LocalAccountSigner.mnemonicToAccountSigner(NEW_OWNER_MNEMONIC); +const encodedTransferOwnershipData = + LightSmartContractAccount.encodedTransferOwnership(newOwner); + +// transfer ownership +const result = await LightSmartContractAccount.transferOwnership( + provider, + newOwner + true, // wait for txn with UO to be mined +); +``` + +<<< @/snippets/provider.ts +::: + +## Developer Links + +[LightAccount Github Repo](https://github.com/alchemyplatform/light-account) +[Quantstamp Audit Report](https://github.com/alchemyplatform/light-account/blob/main/Quantstamp-Audit.pdf) diff --git a/site/packages/aa-accounts/light-account/signMessageWith6492.md b/site/packages/aa-accounts/light-account/signMessageWith6492.md new file mode 100644 index 000000000..ddf6b27a1 --- /dev/null +++ b/site/packages/aa-accounts/light-account/signMessageWith6492.md @@ -0,0 +1,41 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: LightSmartContractAccount • signMessageWith6492 + - - meta + - name: description + content: Overview of the signMessageWith6492 method on LightSmartContractAccount + - - meta + - property: og:description + content: Overview of the signMessageWith6492 method on LightSmartContractAccount +--- + +# signMessageWith6492 + +`signMessageWith6492` supports signing messages for deployed smart contract accounts, as well as undeployed accounts (counterfactual addresses) using [ERC-6492](https://eips.ethereum.org/EIPS/eip-6492). + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +// sign message (works for undeployed and deployed accounts) +const signedMessageWith6492 = provider.signMessageWith6492("test"); +``` + +<<< @/snippets/provider.ts +::: + +## Returns + +### `Promise` + +A Promise containing the signature of the message, additionally wrapped in EIP-6492 format if the account is undeployed. + +## Parameters + +### `msg: string | Uint8Array)` -- the message to sign diff --git a/site/packages/aa-accounts/light-account/signTypedData.md b/site/packages/aa-accounts/light-account/signTypedData.md new file mode 100644 index 000000000..ac9ebb329 --- /dev/null +++ b/site/packages/aa-accounts/light-account/signTypedData.md @@ -0,0 +1,76 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: LightSmartContractAccount • signTypedData + - - meta + - name: description + content: Overview of the signTypedData method on LightSmartContractAccount + - - meta + - property: og:description + content: Overview of the signTypedData method on LightSmartContractAccount +--- + +# signTypedData + +`signTypedData` supports signing typed data from the smart contract account's owner address. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +// sign typed data +const signedTypedData = provider.signTypedData({ + domain: { + name: "Ether Mail", + version: "1", + chainId: 1, + verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + }, + types: { + Person: [ + { name: "name", type: "string" }, + { name: "wallet", type: "address" }, + ], + Mail: [ + { name: "from", type: "Person" }, + { name: "to", type: "Person" }, + { name: "contents", type: "string" }, + ], + }, + primaryType: "Mail", + message: { + from: { + name: "Cow", + wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + }, + to: { + name: "Bob", + wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + }, + contents: "Hello, Bob!", + }, +}); +``` + +<<< @/snippets/provider.ts +::: + +## Returns + +### `Promise` + +A Promise containing the signature of the typed data. + +## Parameters + +### `params: SignTypedDataParams` -- the typed data to sign + +- `domain: TypedDataDomain` -- The typed data domain +- `types: Object` -- the type definitions for the typed data +- `primaryType: inferred String` -- the primary type to extract from types and use in value +- `message: inferred from types & primaryType` -- the message, inferred from diff --git a/site/packages/aa-accounts/light-account/signTypedDataWith6492.md b/site/packages/aa-accounts/light-account/signTypedDataWith6492.md new file mode 100644 index 000000000..5f774a24d --- /dev/null +++ b/site/packages/aa-accounts/light-account/signTypedDataWith6492.md @@ -0,0 +1,76 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: LightSmartContractAccount • signTypedDataWith6492 + - - meta + - name: description + content: Overview of the signTypedDataWith6492 method on LightSmartContractAccount + - - meta + - property: og:description + content: Overview of the signTypedDataWith6492 method on LightSmartContractAccount +--- + +# signTypedDataWith6492 + +`signTypedDataWith6492` supports signing typed data for deployed smart contract accounts, as well as undeployed accounts (counterfactual addresses) using [ERC-6492](https://eips.ethereum.org/EIPS/eip-6492). + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +// sign typed data (works for undeployed and deployed accounts) +const signedTypedDataWith6492 = provider.signTypedDataWith6492({ + domain: { + name: "Ether Mail", + version: "1", + chainId: 1, + verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + }, + types: { + Person: [ + { name: "name", type: "string" }, + { name: "wallet", type: "address" }, + ], + Mail: [ + { name: "from", type: "Person" }, + { name: "to", type: "Person" }, + { name: "contents", type: "string" }, + ], + }, + primaryType: "Mail", + message: { + from: { + name: "Cow", + wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + }, + to: { + name: "Bob", + wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + }, + contents: "Hello, Bob!", + }, +}); +``` + +<<< @/snippets/provider.ts +::: + +## Returns + +### `Promise` + +A Promise containing the signature of the typed data, additionally wrapped in ERC-6492 format if the account is undeployed. + +## Parameters + +### `params: SignTypedDataParams` -- the typed data to sign + +- `domain: TypedDataDomain` -- The typed data domain +- `types: Object` -- the type definitions for the typed data +- `primaryType: inferred String` -- the primary type to extract from types and use in value +- `message: inferred from types & primaryType` -- the message, inferred from diff --git a/site/packages/aa-accounts/light-account/transferOwnership.md b/site/packages/aa-accounts/light-account/transferOwnership.md new file mode 100644 index 000000000..aa8d47c69 --- /dev/null +++ b/site/packages/aa-accounts/light-account/transferOwnership.md @@ -0,0 +1,48 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: LightSmartContractAccount • transferOwnership + - - meta + - name: description + content: Overview of the transferOwnership method on LightSmartContractAccount + - - meta + - property: og:description + content: Overview of the transferOwnership method on LightSmartContractAccount +--- + +# transferOwnership + +`transferOwnership` is a static class method on the `LightSmartContractAccount` which sends a UO that transfers ownership of the account to a new owner, and returns either the UO hash or transaction hash. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +// transfer ownership +const newOwner = LocalAccountSigner.mnemonicToAccountSigner(NEW_OWNER_MNEMONIC); +const result = await LightSmartContractAccount.transferOwnership( + provider, + newOwner + true, // wait for txn with UO to be mined +); +``` + +<<< @/snippets/provider.ts +::: + +## Returns + +### `Promise<0x${string}>` + +A Promise containing the hash of either the UO or transaction containing the UO which transferred ownership of the smart contract account's owner. + +## Parameters + +- `provider: SmartAccountProvider & { account: LightSmartContractAccount }` -- the provider to use to send the transaction +- `newOwner: Address` -- the new owner of the account +- `waitForTxn?: boolean` -- optionally, wait for the transaction to be mined with the UO diff --git a/site/packages/aa-alchemy/index.md b/site/packages/aa-alchemy/index.md new file mode 100644 index 000000000..16879c30a --- /dev/null +++ b/site/packages/aa-alchemy/index.md @@ -0,0 +1,44 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: aa-alchemy + - - meta + - name: description + content: aa-alchemy landing page and getting started guide + - - meta + - property: og:description + content: aa-alchemy landing page and getting started guide +--- + +# `@alchemy/aa-alchemy` + +This package contains `AlchemyProvider`, an implementation of `SmartAccountProvider` class defined in `aa-core`. It also contains middleware for accessing the Alchemy Gas Manager (an [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) Paymaster) and for doing Fee Estimates according to the expectations of the Alchemy Rundler (an ERC-4337 Bundler). You may also find the util methods helpful. This repo is community maintained and we welcome contributions! + +## Getting started + +If you are already using the `@alchemy/aa-core` package, you can simply install this package and start using the `AlchemyProvider`. If you are not using `@alchemy/aa-core`, you can install it and follow the instructions in the ["Getting Started"](/getting-started) docs to get started. + +::: code-group + +```bash [yarn] +yarn add @alchemy/aa-alchemy +``` + +```bash [npm] +npm i @alchemy/aa-alchemy +``` + +```bash [pnpm] +pnpm i @alchemy/aa-alchemy +``` + +::: + +Then, you can create a provider like so: +::: code-group + +<<< @/snippets/provider.ts + +::: diff --git a/site/packages/aa-alchemy/middleware/introduction.md b/site/packages/aa-alchemy/middleware/introduction.md new file mode 100644 index 000000000..a8afc04c3 --- /dev/null +++ b/site/packages/aa-alchemy/middleware/introduction.md @@ -0,0 +1,55 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Middleware + - - meta + - name: description + content: Overview of the Middleware methods in aa-alchemy + - - meta + - property: og:description + content: Overview of the Middleware methods in aa-alchemy +--- + +# Middleware + +The `aa-alchemy` package contains middleware you can use to easily leverage the Alchemy Rundler (an [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) Bundler) and Alchemy Gas Manager (an [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) Paymaster). + +Currently, `aa-alchemy` has implementations for: + +1. [`withAlchemyGasFeeEstimator`](/packages/aa-alchemy/middleware/withAlchemyGasFeeEstimator) -- estimates gas fees according to the expectations of the Alchemy Rundler. +2. [`withAlchemyGasManager`](/packages/aa-alchemy/middleware/withAlchemyGasManager) -- adds the Alchemy Gas Manager middleware to the provider. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +import { + withAlchemyGasFeeEstimator, + withAlchemyGasManager, +} from "@alchemy/aa-alchemy"; + +// use Alchemy Gas Fee Estimator to estimate fees according to the expectations of the Alchemy Rundler. +// this is already set on AlchemyProvider, but you can always use the middleware directly to create a new instance. +const providerWithGasFeeEstimator = withAlchemyGasFeeEstimator( + provider, + 50n, + 50n +); + +// use Alchemy Gas Manager to sponsorship transactions +const providerWithGasManager = withAlchemyGasManager( + provider, + { + policyId: PAYMASTER_POLICY_ID, + entryPoint: ENTRYPOINT_ADDRESS, + }, + true // if true, uses `alchemy_requestGasAndPaymasterAndData`, otherwise uses `alchemy_requestPaymasterAndData` +); +``` + +<<< @/snippets/provider.ts +::: diff --git a/site/packages/aa-alchemy/middleware/withAlchemyGasFeeEstimator.md b/site/packages/aa-alchemy/middleware/withAlchemyGasFeeEstimator.md new file mode 100644 index 000000000..153c102d0 --- /dev/null +++ b/site/packages/aa-alchemy/middleware/withAlchemyGasFeeEstimator.md @@ -0,0 +1,51 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Middleware • withAlchemyGasFeeEstimator + - - meta + - name: description + content: Overview of the withAlchemyGasFeeEstimator method in aa-alchemy + - - meta + - property: og:description + content: Overview of the withAlchemyGasFeeEstimator method in aa-alchemy +--- + +# withAlchemyGasFeeEstimator + +`withAlchemyGasFeeEstimator` is a middleware method you can use to easily leverage the Alchemy Rundler (an [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) Bundler) for estimating gas fees for user operations. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +import { withAlchemyGasFeeEstimator } from "@alchemy/aa-alchemy"; + +// use Alchemy Gas Fee Estimator to estimate gas fees according to the expectations of the Alchemy Rundler. +// this is already set on AlchemyProvider, but you can always use the middleware directly to create a new instance. +const providerWithGasFeeEstimator = withAlchemyGasFeeEstimator( + provider, + 50n, + 50n +); +``` + +<<< @/snippets/provider.ts +::: + +## Returns + +### `AlchemyProvider` + +A new instance of an `AlchemyProvider` with the same attributes as the input, now with middleware for gas fee estimation. + +## Parameters + +### `provider: AlchemyProvider` -- an `AlchemyProvider` instance + +### `baseFeeBufferPercent: bigint` -- buffer percentage to adjust the `baseFee` of a UO request sent through the provider + +### `maxPriorityFeeBufferPercent: bigint` -- buffer percentage to adjust the `maxPriorityFee` of a UO request sent through the provider diff --git a/site/packages/aa-alchemy/middleware/withAlchemyGasManager.md b/site/packages/aa-alchemy/middleware/withAlchemyGasManager.md new file mode 100644 index 000000000..b83c157db --- /dev/null +++ b/site/packages/aa-alchemy/middleware/withAlchemyGasManager.md @@ -0,0 +1,56 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Middleware • withAlchemyGasManager + - - meta + - name: description + content: Overview of the withAlchemyGasManager method in aa-alchemy + - - meta + - property: og:description + content: Overview of the withAlchemyGasManager method in aa-alchemy +--- + +# withAlchemyGasManager + +`withAlchemyGasManager` is a middleware method you can use to easily leverage the Alchemy Gas Manager (an [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) Paymaster) for sponsoring user operations. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +import { withAlchemyGasManager } from "@alchemy/aa-alchemy"; + +// use Alchemy Gas Manager to sponsorship transactions +const providerWithGasManager = withAlchemyGasManager( + provider, + { + policyId: PAYMASTER_POLICY_ID, + entryPoint: ENTRYPOINT_ADDRESS, + }, + true // If true, uses `alchemy_requestGasAndPaymasterAndData`, otherwise uses `alchemy_requestPaymasterAndData` +); +``` + +<<< @/snippets/provider.ts +::: + +## Returns + +### `AlchemyProvider` + +A new instance of an `AlchemyProvider` with the same attributes as the input, now with middleware for accessing the Alchemy Gas Manager to sponsor UserOperations. + +## Parameters + +### `provider: AlchemyProvider` -- an `AlchemyProvider` instance + +### `AlchemyGasManagerConfig: AlchemyGasManagerConfig` + +- `policyId: string` -- the Alchemy Gas Manager policy ID +- `entryPoint: Address` -- the entrypoint contract address + +### `estimateGas: boolean` -- a flag to additionally estimate gas as part of diff --git a/site/packages/aa-alchemy/provider/gasEstimator.md b/site/packages/aa-alchemy/provider/gasEstimator.md new file mode 100644 index 000000000..4d63e890b --- /dev/null +++ b/site/packages/aa-alchemy/provider/gasEstimator.md @@ -0,0 +1,48 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: AlchemyProvider • gasEstimator + - - meta + - name: description + content: Overview of the gasEstimator method on Alchemy Provider in aa-alchemy + - - meta + - property: og:description + content: Overview of the gasEstimator method on Alchemy Provider in aa-alchemy +--- + +# gasEstimator + +`gasEstimator` is an override of the same middleware on `SmartAccountProvider`. As part of the middleware stack that the `AlchemyProvider` would run on each UO built and sent, this middleware estimates gas using the Alchemy Rundler (an [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) Bundler), and updates `callGasLimit`, `verificationGasLimit`, and `preVerificationGas` in a UO request with appropriate buffers. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +// building a UO struct will use the overrided `gasEstimator` middleware on AlchemyProvider +const uoStruct = await provider.buildUserOperation({ + target: TO_ADDRESS, + data: ENCODED_DATA, + value: VALUE, // optional +}); +const uoHash = await provider.sendUserOperation(uoStruct); +``` + +<<< @/snippets/provider.ts +::: + +## Returns + +### `Promise>` + +the resulting user operation struct after gas estimation, run as part of a middleware chain when building and sending UserOperations. + +## Parameters + +### `struct: Deferrable` -- the struct containing UserOperation fields, where each field may be asychronously returned from the middleware used to generate its final value. + +Note: You typically will call this method as part of a middleware chain when building and sending UserOperations, so the parameters of `UserOperationStruct` should be generated for you, as long as you pass in the initial parameters needed for [sendUserOperation](/packages/aa-core/provider/sendUserOperation). diff --git a/site/packages/aa-alchemy/provider/introduction.md b/site/packages/aa-alchemy/provider/introduction.md new file mode 100644 index 000000000..7506131e3 --- /dev/null +++ b/site/packages/aa-alchemy/provider/introduction.md @@ -0,0 +1,47 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: AlchemyProvider + - - meta + - name: description + content: Overview of the AlchemyProvider class in aa-alchemy + - - meta + - property: og:description + content: Overview of the AlchemyProvider class in aa-alchemy +--- + +# AlchemyProvider + +`AlchemyProvider` is an extension of the `SmartAccountProvider` implementation. It's a simpler interface you can use to leverage the Alchemy stack - JSON-RPC requests via API Key or JSON Web Token (JWT), Alchemy Rundler (an [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) Bundler), and Alchemy Gas Manager (an [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) Paymaster). + +Notable differences between `AlchemyProvider` and `SmartAccountProvider` are implementations for: + +1. [`gasEstimator`](/packages/aa-alchemy/provider/gasEstimator) -- overrides the `SmartAccountProvider` gas estimator. +2. [`withAlchemyGasManager`](/packages/aa-alchemy/provider/withAlchemyGasManager) -- adds the Alchemy Gas Manager middleware to the provider. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +// building a UO struct will use the overrided `gasEstimator` middleware on AlchemyProvider +const uoStruct = await provider.buildUserOperation({ + target: TO_ADDRESS, + data: ENCODED_DATA, + value: VALUE, // optional +}); +const uoHash = await provider.sendUserOperation(uoStruct); + +// use Alchemy Gas Manager to sponsorship transactions +const providerWithGasManager = provider.withAlchemyGasManager({ + policyId: PAYMASTER_POLICY_ID, + entryPoint: ENTRYPOINT_ADDRESS, +}); +``` + +<<< @/snippets/provider.ts +::: diff --git a/site/packages/aa-alchemy/provider/withAlchemyGasManager.md b/site/packages/aa-alchemy/provider/withAlchemyGasManager.md new file mode 100644 index 000000000..a218db905 --- /dev/null +++ b/site/packages/aa-alchemy/provider/withAlchemyGasManager.md @@ -0,0 +1,48 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: AlchemyProvider • withAlchemyGasManager + - - meta + - name: description + content: Overview of the withAlchemyGasManager method on Alchemy Provider in aa-alchemy + - - meta + - property: og:description + content: Overview of the withAlchemyGasManager method on Alchemy Provider in aa-alchemy +--- + +# withAlchemyGasManager + +`withAlchemyGasManager` is a method on `AlchemyProvider` that you can optionally call to create a new provider instance with added middleware leveraging the Alchemy Gas Manager (an [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) Paymaster). Under the hood, this will call the [`withAlchemyGasManager`](/packages/aa-alchemy/middleware/withAlchemyGasManager.ts). + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] + +// use Alchemy Gas Manager to sponsorship transactions +const providerWithGasManager = provider.withAlchemyGasManager({ + policyId: PAYMASTER_POLICY_ID, + entryPoint: ENTRYPOINT_ADDRESS, +}); +``` + +<<< @/snippets/provider.ts +::: + +## Returns + +### `AlchemyProvider` + +A new instance of an `AlchemyProvider` with the same attributes as the input, now with middleware for accessing the Alchemy Gas Manager to sponsor UserOperations. + +## Parameters + +### `config: AlchemyGasManagerConfig` + +- `policyId: string` -- the Alchemy Gas Manager policy ID +- `entryPoint: Address` -- the entrypoint contract address for the chain the provider is used for diff --git a/site/packages/aa-alchemy/utils/introduction.md b/site/packages/aa-alchemy/utils/introduction.md new file mode 100644 index 000000000..340ce52ed --- /dev/null +++ b/site/packages/aa-alchemy/utils/introduction.md @@ -0,0 +1,37 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Utils + - - meta + - name: description + content: Overview of the Utils methods in aa-alchemy + - - meta + - property: og:description + content: Overview of the Utils methods in aa-alchemy +--- + +# Utils + +`aa-alchemy` offers util methods to speed up your development with `AlchemyProvider`. + +Notable util methods include: + +1. [`SupportedChains`](/packages/aa-alchemy/utils/supportedChains) -- calls `eth_estimateUserOperationGas` and returns the result. + +## Usage + +::: code-group + +```ts [example.ts] +import { SupportedChains } from "@alchemy/aa-alchemy"; + +// eth mainnet +const mainnet = SupportedChains.get(1); + +// bsc is unsupported, so the variable will be undefined +const bsc = SupportedChains.get(56); +``` + +::: diff --git a/site/packages/aa-alchemy/utils/supportedChains.md b/site/packages/aa-alchemy/utils/supportedChains.md new file mode 100644 index 000000000..f1e2c081b --- /dev/null +++ b/site/packages/aa-alchemy/utils/supportedChains.md @@ -0,0 +1,43 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Utils • SupportedChains + - - meta + - name: description + content: Overview of the SupportedChains util method in aa-alchemy + - - meta + - property: og:description + content: Overview of the SupportedChains util method in aa-alchemy +--- + +# SupportedChains + +`SupportedChains` provides a mapping from chain ID to a viem `Chain` object for all chains supported by the Alchemy RPC. This util includes mappings for both supported mainnets and supported testnets. + +## Usage + +::: code-group + +```ts [example.ts] +import { SupportedChains } from "@alchemy/aa-alchemy"; + +// eth mainnet +const mainnet = SupportedChains.get(1); + +// bsc mainnet is unsupported, so the variable will be undefined +const bsc = SupportedChains.get(56); +``` + +::: + +## Returns + +### `Chain` + +the associated viem `Chain` object. + +## Parameters + +### `chainId: number` -- the struct containig UserOperation fields, where each field may be asychronously returned from the middleware used to generate its final value. diff --git a/site/packages/aa-core/accounts/introduction.md b/site/packages/aa-core/accounts/introduction.md new file mode 100644 index 000000000..d9986e38b --- /dev/null +++ b/site/packages/aa-core/accounts/introduction.md @@ -0,0 +1,59 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: ISmartContractAccount + - - meta + - name: description + content: Overview of ISmartContractAccount class exported by aa-core accounts + - - meta + - property: og:description + content: Overview of ISmartContractAccount class exported by aa-core accounts +--- + +# ISmartContractAccount + +`ISmartContractAccount` defines how you would interact with your Smart Contract Account. + +## BaseSmartContractAccount + +The `BaseSmartContractAccount` is an abstract class that provides the base implementation the `ISmartContractAccount` interface to provide the ease of creating your own Smart Contract Account. Any class that extends and implements `BaseSmartContractAccount` may also expose additional methods that support its connecting [SmartAccountProvider](/packages/aa-core/provider/introduction). + +`BaseSmartContractAccount` contains abstract methods that requires implementation from any class that extends the class. + +### Required Methods To Implement + +- [`getDummySignature`](/packages/aa-core/accounts/required/getDummySignature) -- this method returns a signature that will not `revert` during validation. It does not have to pass validation, just not cause the contract to revert. This is required for gas estimation so that the gas estimate are accurate. +- [`encodeExecute`](/packages/aa-core/accounts/required/encodeExecute) -- this method returns the abi encoded function data for a call to your contract's `execute` method +- [`signMessage`](/packages/aa-core/accounts/required/signMessage) -- this method returns an [EIP-191](https://eips.ethereum.org/EIPS/eip-191) compliant message and is used to sign UO Hashes +- [`getAccountInitCode`](/packages/aa-core/accounts/required/getAccountInitCode) -- this method returns the account init code that will be used to create an account if one does not exist. Usually this is the concatenation of the account's factory address and the abi encoded function data of the account factory's `createAccount` method. + +### Optional Methods To Implement + +In addition, it provides other optional methods that need to be implemented by the subclass in order to support functionalities such as: + +- [`signTypedData`](/packages/aa-core/accounts/optional/signTypedData) -- Signs typed data per [ERC-712](https://eips.ethereum.org/EIPS/eip-712) +- [`signMessageWith6492`](/packages/aa-core/accounts/optional/signMessageWith6492) -- Wraps the result of `signMessage` as per [EIP-6492](https://eips.ethereum.org/EIPS/eip-6492) for signing the message for deployed smart contract accounts, as well as undeployed accounts +- [`signTypedDataWith6492`](/packages/aa-core/accounts/optional/signTypedDataWith6492) -- Similar to the signMessageWith6492 method above, this method wraps the result of `signTypedData` as per [EIP-6492](https://eips.ethereum.org/EIPS/eip-6492) +- [`encodeBatchExecute`](/packages/aa-core/accounts/optional/encodeBatchExecute) -- If your contract does support batching, encodes a list of transactions into the call data that will be passed to your contract's `batchExecute` method. + +**Note**: `signMessageWith6492` and `signTypedDataWith6492` methods are already implemented on `BaseSmartContractAccount`, so any class that extends and implements `BaseSmartContractAccount` may call this method. + +## SimpleSmartContractAccount + +[SimpleSmartContractAccount](packages/core/src/account/simple.ts) a minimal implementation version of `BaseSmartContractAccount`. It implements the required abstraction methods in `BaseSmartContractAccount`, and additionally implements the optional methods indicated above. + +**Note:** While `SimpleSmartContractAccount` fully implements the `ISmartContractAccount` interface for use as your basic Smart Contract Account, we recommend using our [Light Account](/smart-accounts/accounts/light-account) as it is a simple, yet more secure, and cost-effective smart account implementation. + +## Usage + +::: code-group + +<<< @/snippets/account-core.ts + +<<< @/snippets/account-alchemy.ts + +<<< @/snippets/account-ethers.ts + +::: diff --git a/site/packages/aa-core/accounts/optional/encodeBatchExecute.md b/site/packages/aa-core/accounts/optional/encodeBatchExecute.md new file mode 100644 index 000000000..4da7bd976 --- /dev/null +++ b/site/packages/aa-core/accounts/optional/encodeBatchExecute.md @@ -0,0 +1,66 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: encodeBatchExecute + - - meta + - name: description + content: Overview of the encodeBatchExecute method on BaseSmartContractAccount + - - meta + - property: og:description + content: Overview of the encodeBatchExecute method on BaseSmartContractAccount +--- + +# encodeBatchExecute + +**NOTE**: Not all accounts support batching. + +If your contract does, this method should encode a list of transactions into the call data that will be passed to your contract's `batchExecute` method. + +## Example Implementation + +::: code-group + +```ts [example.ts] +import { SimpleAccountAbi } from "./simple-account-abi"; +// [!code focus:99] +async encodeBatchExecute( + txs: BatchUserOperationCallData +): Promise<`0x${string}`> { + const [targets, datas] = txs.reduce( + (accum, curr) => { + accum[0].push(curr.target); + accum[1].push(curr.data); + + return accum; + }, + [[], []] as [Address[], Hex[]] + ); + + return encodeFunctionData({ + abi: SimpleAccountAbi, + functionName: "executeBatch", + args: [targets, datas], + }); +} +``` + +<<< @/snippets/simple-account-abi.ts +::: + +## Returns + +### `Promise` + +The promise containing the abi encoded function data for a call to your contract's `batchExecute` method + +## Parameters + +### `txs`: `BatchUserOperationCallData = UserOperationCallData[]` + +An array of objects containing the target, value, and data for each transaction as `UserOperationCallData` with following fields: + +- `target`: `string` - The address receiving the call data. Equivalent to `to` in a normal transaction. +- `value`: `bigint` - Optionally, the amount of native token to send. Equivalent to `value` in a normal transaction. +- `data`: `string` - The call data or "0x" if empty. Equivalent to `data` in a normal transaction. diff --git a/site/packages/aa-core/accounts/optional/signMessageWith6492.md b/site/packages/aa-core/accounts/optional/signMessageWith6492.md new file mode 100644 index 000000000..9ea5b499e --- /dev/null +++ b/site/packages/aa-core/accounts/optional/signMessageWith6492.md @@ -0,0 +1,45 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: signMessageWith6492 + - - meta + - name: description + content: Overview of the signMessageWith6492 method on BaseSmartContractAccount + - - meta + - property: og:description + content: Overview of the signMessageWith6492 method on BaseSmartContractAccount +--- + +# signMessageWith6492 + +This method wraps the result of `signMessage` as per [EIP-6492](https://eips.ethereum.org/EIPS/eip-6492) for signing the message for deployed smart contract accounts, as well as undeployed accounts (counterfactual addresses). + +**Note**: This method is already implemented on `BaseSmartContractAccount`, so any class that extends and implements `BaseSmartContractAccount` may call this method. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +const signedMsgWrappedWith6492 = await provider.signMessageWith6492("msg"); +``` + +<<< @/snippets/provider.ts + +::: + +## Returns + +### `Promise` + +A promise containing the signature of the message, additionally wrapped in EIP-6492 format if the account is undeployed + +## Parameters + +### `msg: string | Uint8Array | Hex` + +The message to sign diff --git a/site/packages/aa-core/accounts/optional/signTypedData.md b/site/packages/aa-core/accounts/optional/signTypedData.md new file mode 100644 index 000000000..324f4207d --- /dev/null +++ b/site/packages/aa-core/accounts/optional/signTypedData.md @@ -0,0 +1,79 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: signTypedData + - - meta + - name: description + content: Overview of the signTypedData method on BaseSmartContractAccount + - - meta + - property: og:description + content: Overview of the signTypedData method on BaseSmartContractAccount +--- + +# signTypedData + +If your contract supports signing and verifying typed data, you should implement this method that returns the signed typed data object as per [ERC-712](https://eips.ethereum.org/EIPS/eip-712). + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +// sign typed data +const signedTypedData = provider.signTypedData({ + domain: { + name: "Ether Mail", + version: "1", + chainId: 1, + verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + }, + types: { + Person: [ + { name: "name", type: "string" }, + { name: "wallet", type: "address" }, + ], + Mail: [ + { name: "from", type: "Person" }, + { name: "to", type: "Person" }, + { name: "contents", type: "string" }, + ], + }, + primaryType: "Mail", + message: { + from: { + name: "Cow", + wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + }, + to: { + name: "Bob", + wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + }, + contents: "Hello, Bob!", + }, +}); +``` + +<<< @/snippets/provider.ts + +::: + +### Returns + +### `Promise` + +A promise containing the signature of the typed data. + +## Parameters + +### `SignTypedDataParams` + +The typed data to sign + +- `domain: TypedDataDomain` - The typed data domain +- `types: Object` -- The type definitions for the typed data +- `primaryType: inferred String` -- The primary type to extract from types and use in value +- `message: inferred from types & primaryType` -- The typed message object diff --git a/site/packages/aa-core/accounts/optional/signTypedDataWith6492.md b/site/packages/aa-core/accounts/optional/signTypedDataWith6492.md new file mode 100644 index 000000000..135811f7b --- /dev/null +++ b/site/packages/aa-core/accounts/optional/signTypedDataWith6492.md @@ -0,0 +1,80 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: signTypedDataWith6492 + - - meta + - name: description + content: Overview of the signTypedDataWith6492 method on BaseSmartContractAccount + - - meta + - property: og:description + content: Overview of the signTypedDataWith6492 method on BaseSmartContractAccount +--- + +# signTypedDataWith6492 + +Similar to the signMessageWith6492 method above, this method wraps the result of `signTypedData` as per [EIP-6492](https://eips.ethereum.org/EIPS/eip-6492) to support signing the typed data for deployed smart contract accounts, as well as undeployed accounts (counterfactual addresses). + +**Note**: This method is already implemented on `BaseSmartContractAccount`, so any class that extends and implements `BaseSmartContractAccount` may call this method. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +const signedTypedDataWrappedWith6492 = provider.signTypedDataWith6492({ + domain: { + name: "Ether Mail", + version: "1", + chainId: 1, + verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + }, + types: { + Person: [ + { name: "name", type: "string" }, + { name: "wallet", type: "address" }, + ], + Mail: [ + { name: "from", type: "Person" }, + { name: "to", type: "Person" }, + { name: "contents", type: "string" }, + ], + }, + primaryType: "Mail", + message: { + from: { + name: "Cow", + wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + }, + to: { + name: "Bob", + wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + }, + contents: "Hello, Bob!", + }, +}); +``` + +<<< @/snippets/provider.ts + +::: + +## Returns + +### `Promise` + +A promise containing the signature of the message, additionally wrapped in EIP-6492 format if the account is undeployed + +## Parameters + +### `params: SignTypedDataParams` + +The typed data to sign + +- `domain: TypedDataDomain` -- The typed data domain +- `types: Object` -- The type definitions for the typed data +- `primaryType: inferred String` -- The primary type to extract from types and use in value +- `message: inferred from types & primaryType` -- The typed message object diff --git a/site/packages/aa-core/accounts/other/getAddress.md b/site/packages/aa-core/accounts/other/getAddress.md new file mode 100644 index 000000000..08b286b37 --- /dev/null +++ b/site/packages/aa-core/accounts/other/getAddress.md @@ -0,0 +1,37 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: getAddress + - - meta + - name: description + content: Overview of the getAddress method on BaseSmartContractAccount + - - meta + - property: og:description + content: Overview of the getAddress method on BaseSmartContractAccount +--- + +# getAddress + +Returns the address of the account. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +const address = await provider.getAddress(); +``` + +<<< @/snippets/provider.ts + +::: + +## Returns + +### `Promise` + +A promise that resolves to the address of the account diff --git a/site/packages/aa-core/accounts/other/getDeploymentState.md b/site/packages/aa-core/accounts/other/getDeploymentState.md new file mode 100644 index 000000000..1b582b884 --- /dev/null +++ b/site/packages/aa-core/accounts/other/getDeploymentState.md @@ -0,0 +1,37 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: getDeploymentState + - - meta + - name: description + content: Overview of the getDeploymentState method on BaseSmartContractAccount + - - meta + - property: og:description + content: Overview of the getDeploymentState method on BaseSmartContractAccount +--- + +# getDeploymentState + +Returns the current deployment state as an enum `DeploymentState` (`UNDEFINED`, `NOT_DEPLOYED`, or `DEPLOYED`) for the account + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +const deploymentState = await provider.account.getDeploymentState(); +``` + +<<< @/snippets/provider.ts + +::: + +## Returns + +### `Promise` + +A promise that resolves to the current deployment state as an enum `DeploymentState` (`UNDEFINED`, `NOT_DEPLOYED`, or `DEPLOYED`) for the account diff --git a/site/packages/aa-core/accounts/other/getNonce.md b/site/packages/aa-core/accounts/other/getNonce.md new file mode 100644 index 000000000..c781a336b --- /dev/null +++ b/site/packages/aa-core/accounts/other/getNonce.md @@ -0,0 +1,37 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: getNonce + - - meta + - name: description + content: Overview of the getNonce method on BaseSmartContractAccount + - - meta + - property: og:description + content: Overview of the getNonce method on BaseSmartContractAccount +--- + +# getNonce + +Returns the nonce of the account. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +const address = await provider.account.getNonce(); +``` + +<<< @/snippets/provider.ts + +::: + +## Returns + +### `Promise` + +A promise that resolves to the current nonce of the account diff --git a/site/packages/aa-core/accounts/other/isAccountDeployed.md b/site/packages/aa-core/accounts/other/isAccountDeployed.md new file mode 100644 index 000000000..1b0ad9889 --- /dev/null +++ b/site/packages/aa-core/accounts/other/isAccountDeployed.md @@ -0,0 +1,37 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: isAccountDeployed + - - meta + - name: description + content: Overview of the isAccountDeployed method on BaseSmartContractAccount + - - meta + - property: og:description + content: Overview of the isAccountDeployed method on BaseSmartContractAccount +--- + +# isAccountDeployed + +Returns a boolean flag indicating whether the account has been deployed or not. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +const isAccountDeployed = await provider.account.isAccountDeployed(); +``` + +<<< @/snippets/provider.ts + +::: + +## Returns + +### `Promise` + +A promise that resolves the boolean flag indicating whether the account has been deployed or not diff --git a/site/packages/aa-core/accounts/required/encodeExecute.md b/site/packages/aa-core/accounts/required/encodeExecute.md new file mode 100644 index 000000000..00f878dbc --- /dev/null +++ b/site/packages/aa-core/accounts/required/encodeExecute.md @@ -0,0 +1,54 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: encodeExecute + - - meta + - name: description + content: Overview of the encodeExecute abstract method on BaseSmartContractAccount + - - meta + - property: og:description + content: Overview of the encodeExecute abstract method on BaseSmartContractAccount +--- + +# encodeExecute + +Returns the abi encoded function data for a call to your contract's `execute` method. + +## Example Implementation + +::: code-group + +```ts [example.ts] +import { SimpleAccountAbi } from "./simple-account-abi"; +// [!code focus:99] +async encodeExecute( + target: Hex, + value: bigint, + data: Hex +): Promise<`0x${string}`> { + return encodeFunctionData({ + abi: SimpleAccountAbi, + functionName: "execute", + args: [target, value, data], + }); +} +``` + +<<< @/snippets/simple-account-abi.ts +::: + +## Returns + +### `Promise` + +The promise containing the abi encoded function data for a call to your contract's `execute` method + +## Parameters + +`UserOperationCallData` fields: + +- `target`: `string` - The address receiving the call data. Equivalent to `to` in a normal transaction. +- `value`: `bigint` - Optionally, the amount of native token to send. Equivalent to `value` in a normal transaction. +- `data`: `string` - The call data or "0x" if empty. Equivalent to `data` in a normal transaction. diff --git a/site/packages/aa-core/accounts/required/getAccountInitCode.md b/site/packages/aa-core/accounts/required/getAccountInitCode.md new file mode 100644 index 000000000..cad40e81e --- /dev/null +++ b/site/packages/aa-core/accounts/required/getAccountInitCode.md @@ -0,0 +1,66 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: getAccountInitCode + - - meta + - name: description + content: Overview of the getAccountInitCode abstract method on BaseSmartContractAccount + - - meta + - property: og:description + content: Overview of the getAccountInitCode abstract method on BaseSmartContractAccount +--- + +# getAccountInitCode + +This method should return the init code that will be used to create an account if one does not exist. + +This is [expected](https://github.com/eth-infinitism/account-abstraction/blob/v0.6.0/contracts/core/SenderCreator.sol#L15) to be the concatenation of the account's factory address and the abi encoded function data of the account factory's `createAccount` method. + +**Reference:** + +- [EIP-4337: First-time account creation](https://eips.ethereum.org/EIPS/eip-4337#first-time-account-creation) +- [EntryPoint spec](https://github.com/eth-infinitism/account-abstraction/blob/v0.6.0/contracts/core/SenderCreator.sol#L15) + +## Example Implementation + +::: code-group + +```ts [example.ts] +// [!code focus:99] +async getAccountInitCode(): Promise<`0x${string}`> { + return concatHex([ + this.factoryAddress, + encodeFunctionData({ + abi: SimpleAccountFactoryAbi, + functionName: "createAccount", + args: [await this.owner.getAddress(), this.index], + }), + ]); +} +``` + +::: + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +const initCode = await provider.getAccountInitCode(); +const factoryAddress = `0x${initCode.substring(2, 42)}` as Address; +const factoryCalldata = `0x${initCode.substring(42)}` as Hex; +``` + +<<< @/snippets/provider.ts + +::: + +## Returns + +### `Promise` + +The promise containing the init code for the account diff --git a/site/packages/aa-core/accounts/required/getDummySignature.md b/site/packages/aa-core/accounts/required/getDummySignature.md new file mode 100644 index 000000000..b75d7537f --- /dev/null +++ b/site/packages/aa-core/accounts/required/getDummySignature.md @@ -0,0 +1,57 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: getDummySignature + - - meta + - name: description + content: Overview of the getDummySignature abstract method on BaseSmartContractAccount + - - meta + - property: og:description + content: Overview of the getDummySignature abstract method on BaseSmartContractAccount +--- + +# getDummySignature + +This method should return a signature that will not `revert` during validation. It does not have to pass validation, just not cause the contract to revert. This is required for gas estimation so that the gas estimate are accurate. + +Please note that the dummy signature is specific to the account implementation and the example dummy signature below does not generalize to all account types. This is an example dummy signature for SimpleAccount account implementation. For other account implementations, you may need to provide a different dummy signature. + +**Reference:** + +- [Alchemy Bundler API docs](https://docs.alchemy.com/reference/eth-estimateuseroperationgas#dummy-signature-for-simpleaccount) +- [EIP-4337: `eth_estimateUserOperationGas`](https://eips.ethereum.org/EIPS/eip-4337#-eth_estimateuseroperationgas) + +## Example Implementation + +::: code-group + +```ts [example.ts] +// [!code focus:99] +getDummySignature(): `0x${string}` { + return "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c"; +} +``` + +::: + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +const dummySignature = await provider.getDummySignature(); +``` + +<<< @/snippets/provider.ts + +::: + +## Returns + +### `Hash` + +A dummy signature that doesn't cause the account to revert during estimation diff --git a/site/packages/aa-core/accounts/required/signMessage.md b/site/packages/aa-core/accounts/required/signMessage.md new file mode 100644 index 000000000..3ae8d72cd --- /dev/null +++ b/site/packages/aa-core/accounts/required/signMessage.md @@ -0,0 +1,43 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: signMessage + - - meta + - name: description + content: Overview of the signMessage abstract method on BaseSmartContractAccount + - - meta + - property: og:description + content: Overview of the signMessage abstract method on BaseSmartContractAccount +--- + +# signMessage + +This method should return an [ERC-191](https://eips.ethereum.org/EIPS/eip-191) compliant message and is used to sign `UserOperation` hashes. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +const signedMessage = await provider.signMessage("msg"); +``` + +<<< @/snippets/provider.ts + +::: + +### Returns + +### `Promise` + +A promise containing the signature of the message + +## Parameters + +### `msg`: `string | Uint8Array | Hex` + +The message to sign diff --git a/site/packages/aa-core/client/actions/estimateUserOperationGas.md b/site/packages/aa-core/client/actions/estimateUserOperationGas.md new file mode 100644 index 000000000..84d976d74 --- /dev/null +++ b/site/packages/aa-core/client/actions/estimateUserOperationGas.md @@ -0,0 +1,51 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: estimateUserOperationGas + - - meta + - name: description + content: Overview of the estimateUserOperationGas action available on the PublicErc4337Client + - - meta + - property: og:description + content: Overview of the estimateUserOperationGas action available on the PublicErc4337Client +--- + +# estimateUserOperationGas + +Calls `eth_estimateUserOperationGas` and returns the result + +## Usage + +::: code-group + +```ts [example.ts] +import { client } from "./client"; + +const estimates = await client.estimateUserOperationGas( + { + // ... user operation + }, + "0xEntryPointAddress" +); +``` + +<<< @/snippets/client.ts +::: + +## Returns + +### `Promise` + +The result of the estimate including the `callGasLimit`, `verificationGasLimit`, `preVerificationGas`. + +## Parameters + +### `request: UserOperationRequest` + +The user operation to send + +### `entryPointAddress: Address` + +The address of the entry point to send the user operation to diff --git a/site/packages/aa-core/client/actions/getSupportedEntryPoints.md b/site/packages/aa-core/client/actions/getSupportedEntryPoints.md new file mode 100644 index 000000000..020cb3aad --- /dev/null +++ b/site/packages/aa-core/client/actions/getSupportedEntryPoints.md @@ -0,0 +1,36 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: getSupportedEntryPoints + - - meta + - name: description + content: Overview of the getSupportedEntryPoints action available on the PublicErc4337Client + - - meta + - property: og:description + content: Overview of the getSupportedEntryPoints action available on the PublicErc4337Client +--- + +# getSupportedEntryPoints + +calls `eth_supportedEntryPoints` and returns the entrypoints the RPC supports + +## Usage + +::: code-group + +```ts [example.ts] +import { client } from "./client"; + +const entryPoints = await client.getSupportedEntryPoints(); +``` + +<<< @/snippets/client.ts +::: + +## Returns + +### `Promise` + +The list of supported entry points by the underlying RPC Provider diff --git a/site/packages/aa-core/client/actions/getUserOperationByHash.md b/site/packages/aa-core/client/actions/getUserOperationByHash.md new file mode 100644 index 000000000..1265d6939 --- /dev/null +++ b/site/packages/aa-core/client/actions/getUserOperationByHash.md @@ -0,0 +1,42 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: getUserOperationByHash + - - meta + - name: description + content: Overview of the getUserOperationByHash action available on the PublicErc4337Client + - - meta + - property: og:description + content: Overview of the getUserOperationByHash action available on the PublicErc4337Client +--- + +# getUserOperationByHash + +calls `eth_getUserOperationByHash` and returns the `UserOperationResponse` if found + +## Usage + +::: code-group + +```ts [example.ts] +import { client } from "./client"; + +const uo = await client.getUserOperationByHash("0xUserOperationHash"); +``` + +<<< @/snippets/client.ts +::: + +## Returns + +### `Promise` + +The User Operation if found, otherwise `null` + +## Parameters + +### `hash: Hash` + +The hash of the User Operation to fetch diff --git a/site/packages/aa-core/client/actions/getUserOperationReceipt.md b/site/packages/aa-core/client/actions/getUserOperationReceipt.md new file mode 100644 index 000000000..260090fc1 --- /dev/null +++ b/site/packages/aa-core/client/actions/getUserOperationReceipt.md @@ -0,0 +1,42 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: getUserOperationReceipt + - - meta + - name: description + content: Overview of the getUserOperationReceipt action available on the PublicErc4337Client + - - meta + - property: og:description + content: Overview of the getUserOperationReceipt action available on the PublicErc4337Client +--- + +# getUserOperationReceipt + +calls `eth_getUserOperationReceipt` and returns `UserOperationReceipt` if found otherwise `null` + +## Usage + +::: code-group + +```ts [example.ts] +import { client } from "./client"; + +const receipt = await client.getUserOperationReceipt("0xUserOperationHash"); +``` + +<<< @/snippets/client.ts +::: + +## Returns + +### `Promise` + +The User Operation Receipt if found, otherwise `null` + +## Parameters + +### `hash: Hash` + +The hash of the User Operation to fetch diff --git a/site/packages/aa-core/client/actions/sendUserOperation.md b/site/packages/aa-core/client/actions/sendUserOperation.md new file mode 100644 index 000000000..ee7a96030 --- /dev/null +++ b/site/packages/aa-core/client/actions/sendUserOperation.md @@ -0,0 +1,51 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: sendUserOperation + - - meta + - name: description + content: Overview of the sendUserOperation action available on the PublicErc4337Client + - - meta + - property: og:description + content: Overview of the sendUserOperation action available on the PublicErc4337Client +--- + +# sendUserOperation + +Calls `eth_sendUserOperation` and returns the hash of the sent UserOperation. + +## Usage + +::: code-group + +```ts [example.ts] +import { client } from "./client"; + +const hash = await client.sendUserOperation( + { + // ... user operation + }, + "0xEntryPointAddress" +); +``` + +<<< @/snippets/client.ts +::: + +## Returns + +### `Promise` + +the hash of the sent UserOperation + +## Parameters + +### `request: UserOperationRequest` + +The user operation to send + +### `entryPointAddress: Address` + +The address of the entry point to send the user operation to diff --git a/site/packages/aa-core/client/createPublicErc4337Client.md b/site/packages/aa-core/client/createPublicErc4337Client.md new file mode 100644 index 000000000..2edfdd026 --- /dev/null +++ b/site/packages/aa-core/client/createPublicErc4337Client.md @@ -0,0 +1,53 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: createPublicErc4337Client + - - meta + - name: description + content: Overview of the createPublicErc4337Client method in aa-core utils + - - meta + - property: og:description + content: Overview of the createPublicErc4337Client method in aa-core utils +--- + +# createPublicErc4337Client + +Allows you to create an HTTP-based PublicErc4337Client with a given RPC provider. + +## Usage + +::: code-group + +```ts [example.ts] +import { createPublicErc4337Client } from "@alchemy/aa-core"; +import { mainnet } from "viem/chains"; + +const client = createPublicErc4337Client({ + chain: mainnet, + rpcUrl: "https://eth-mainnet.g.alchemy.com/v2/demo", +}); +``` + +::: + +## Returns + +### `PublicErc4337Client` + +An HTTP-based PublicErc4337Client that supports both traditional RPC methods and the new 4337 methods. + +## Parameters + +### `chain: Chain` + +The chain to connect to. + +### `rpcUrl: string` + +The RPC URL to connect to. + +### `fetchOptions?: HttpTransportConfig["fetchOptions"]` + +Optional set of params that let you override the default fetch options for the HTTP transport. diff --git a/site/packages/aa-core/client/createPublicErc4337FromClient.md b/site/packages/aa-core/client/createPublicErc4337FromClient.md new file mode 100644 index 000000000..d0b816797 --- /dev/null +++ b/site/packages/aa-core/client/createPublicErc4337FromClient.md @@ -0,0 +1,48 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: createPublicErc4337FromClient + - - meta + - name: description + content: Overview of the createPublicErc4337FromClient method in aa-core utils + - - meta + - property: og:description + content: Overview of the createPublicErc4337FromClient method in aa-core utils +--- + +# createPublicErc4337FromClient + +Allows you to create an HTTP-based PublicErc4337Client from an already created PublicClient. + +## Usage + +::: code-group + +```ts [example.ts] +import { createPublicErc4337FromClient } from "@alchemy/aa-core"; +import { mainnet } from "viem/chains"; +import { createPublicClient, http } from "viem"; + +const publicClient = createPublicClient({ + transport: http("https://eth-mainnet.g.alchemy.com/v2/demo"), + chain: mainnet, +}); + +const client = createPublicErc4337FromClient(publicClient); +``` + +::: + +## Returns + +### `PublicErc4337Client` + +An HTTP-based PublicErc4337Client that supports both traditional RPC methods and the new 4337 methods. + +## Parameters + +### `client: PublicClient` + +The client to adapt to a 4337 client diff --git a/site/packages/aa-core/client/erc4337ClientActions.md b/site/packages/aa-core/client/erc4337ClientActions.md new file mode 100644 index 000000000..13458fb55 --- /dev/null +++ b/site/packages/aa-core/client/erc4337ClientActions.md @@ -0,0 +1,43 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: erc4337ClientActions + - - meta + - name: description + content: Overview of the erc4337ClientActions method in aa-core utils + - - meta + - property: og:description + content: Overview of the erc4337ClientActions method in aa-core utils +--- + +# erc4337ClientActions + +Allows you to extend a viem `Client` with the new 4337 methods. + +## Usage + +::: code-group + +```ts [example.ts] +import { erc4337ClientActions } from "@alchemy/aa-core"; +import { createPublicClient } from "viem"; + +const client = createPublicClient({ + chain: mainnet, + rpcUrl: "https://eth-mainnet.g.alchemy.com/v2/demo", +}).extend(erc4337ClientActions); +``` + +## Returns + +### `Erc4337Actions` + +An object containing utility methods for calling the [ERC-4337 Methods](/packages/aa-core/client/#rpc-methods). + +## Parameters + +### `Client` + +A viem Client that supports making JSON RPC calls to a Provider that supports the 4337 methods. diff --git a/site/packages/aa-core/client/index.md b/site/packages/aa-core/client/index.md new file mode 100644 index 000000000..c833703ad --- /dev/null +++ b/site/packages/aa-core/client/index.md @@ -0,0 +1,81 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Public Client + - - meta + - name: description + content: Overview of the Public Client exported by aa-core + - - meta + - property: og:description + content: Overview of the Public Client exported by aa-core +--- + +# Public ERC-4337 Client + +Viem exports a `PublicClient` and utilities for creating the `PublicClient`. We extend that functionality here to provide a `PublicClient` that is also typed to work with the RPC endpoints introduced in [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337). + +The `PublicErc4337Client` also has a number of methods that wrap the RPC Methods below to make it easier to interact with the RPC provider. + +## RPC Methods + +### `eth_sendUserOperation` + +Sends a user operation to the RPC provider to be included in a bundle. + +#### Parameters + +- `[UserOperationRequest, Address]` - The User Operation Request to be submitted and the address of the Entrypoint to be used for the operation. + +#### Returns + +- `Hash` - The hash of the User Operation. + +### `eth_estimateUserOperationGas` + +Sends a user operation to the RPC provider and returns gas estimates for the User Operation. + +#### Parameters + +- `[UserOperationRequest, Address]` - The User Operation Request to be submitted and the address of the Entrypoint to be used for the operation. + +#### Returns + +- `UserOperationEstimateGasResponse` - gas estimates for the UserOperation. + +### `eth_getUserOperationReceipt` + +Given a User Operation hash, returns the User Operation Receipt. + +#### Parameters + +- `[Hash]` - The hash of the User Operation to get the receipt for. + +#### Returns + +- `UserOperationReceipt | null` - The User Operation Receipt or null if the User Operation has not been included in a block yet. + +### `eth_getUserOperationByHash` + +Given a User Operation hash, returns the User Operation. + +#### Parameters + +- `[Hash]` - The hash of the User Operation to get. + +#### Returns + +- `UserOperationResponse | null` - The UserOperation if it exists or null if it does not. + +### `eth_supportedEntryPoints` + +Returns the entry point addresses supported by the RPC provider. + +#### Parameters + +- `[]` - No parameters. + +#### Returns + +- `Address[]` - An array of addresses that are supported by the RPC provider. diff --git a/site/packages/aa-core/index.md b/site/packages/aa-core/index.md new file mode 100644 index 000000000..a8e3dd0f6 --- /dev/null +++ b/site/packages/aa-core/index.md @@ -0,0 +1,46 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: aa-core + - - meta + - name: description + content: Overview of the aa-core package + - - meta + - property: og:description + content: Overview of the aa-core package +--- + +# `@alchemy/aa-core` + +This package contains the core interfaces and components for interacting with 4337 infrastructure. The primary interfaces that it exports are the `SmartAccountProvider` and `BaseSmartContractAccount`. + +The `SmartAccountProvider` is an [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) compliant Provider that wraps JSON RPC methods and some Wallet Methods (signing, sendTransaction, etc). With this Provider, you can submit User Operations to RPC providers, estimate gas, configure a Paymaster, send standard JSON RPC requests, and more. It is not opinionated about which RPC provider you are using and is configurable to work with any RPC provider. Because it implements EIP-1193, it can be used with any web3 library. + +The `BaseSmartContractAccount` interface defines how you would interact with your Smart Contract Account. Any class that extends `BaseSmartContractAccount` may also expose additional methods that allow its connecting `SmartAccountProvider` to provide ergonic utilities for building and submitting User Operations. + +## Getting Started + +To get started, first install the package: + +::: code-group + +```bash [yarn] +yarn add @alchemy/aa-core +``` + +```bash [npm] +npm i @alchemy/aa-core +``` + +```bash [pnpm] +pnpm i @alchemy/aa-core +``` + +::: + +Then, you can create a provider like so: +::: code-group +<<< @/snippets/core-provider.ts +::: diff --git a/site/packages/aa-core/provider/buildUserOperation.md b/site/packages/aa-core/provider/buildUserOperation.md new file mode 100644 index 000000000..a9426c206 --- /dev/null +++ b/site/packages/aa-core/provider/buildUserOperation.md @@ -0,0 +1,76 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: buildUserOperation + - - meta + - name: description + content: Overview of the buildUserOperation method on ISmartAccountProvider + - - meta + - property: og:description + content: Overview of the buildUserOperation method on ISmartAccountProvider +--- + +# buildUserOperation + +Builds an _unsigned_ UserOperation struct with the all of the middleware run on it through the middleware pipeline. + +The order of the middlewares is: + +1. `dummyPaymasterDataMiddleware` -- populates a dummy paymaster data to use in estimation (default: "0x") +2. `feeDataGetter` -- sets maxfeePerGas and maxPriorityFeePerGas +3. `gasEstimator` -- calls eth_estimateUserOperationGas +4. `paymasterMiddleware` -- used to set paymasterAndData. (default: "0x") +5. `customMiddleware` -- allows you to override any of the results returned by previous middlewares + +Note that `to` field of transaction is required, and among other fields of transaction, only `data`, `value`, `maxFeePerGas`, `maxPriorityFeePerGas` fields are considered and optional. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +// build single +const uoStruct = await provider.buildUserOperation({ + target: TO_ADDRESS, + data: ENCODED_DATA, + value: VALUE, // optional +}); +const { hash: uoHash } = await provider.sendUserOperation(uoStruct); + +// build batch +const batchedUoStruct = await provider.buildUserOperation([ + { + data: "0xCalldata", + target: "0xTarget", + }, + { + data: "0xCalldata2", + target: "0xTarget2", + value: 1000n, // in wei + }, +]); +const { hash: batchedUoHash } = await provider.sendUserOperation( + batchedUoStruct +); +``` + +<<< @/snippets/provider.ts +::: + +## Returns + +### `Promise` + +A Promise containing the _unsigned_ UserOperation struct resulting from the middleware pipeline + +## Parameters + +### `UserOperationCallData | UserOperationCallData[]` + +- `target: Address` - the target of the call (equivalent to `to` in a transaction) +- `data: Hex` - can be either `0x` or a call data string +- `value?: bigint` - optionally, set the value in wei you want to send to the target diff --git a/site/packages/aa-core/provider/buildUserOperationFromTx.md b/site/packages/aa-core/provider/buildUserOperationFromTx.md new file mode 100644 index 000000000..a65472b69 --- /dev/null +++ b/site/packages/aa-core/provider/buildUserOperationFromTx.md @@ -0,0 +1,61 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: buildUserOperationFromTx + - - meta + - name: description + content: Overview of the buildUserOperationFromTx method on ISmartAccountProvider + - - meta + - property: og:description + content: Overview of the buildUserOperationFromTx method on ISmartAccountProvider +--- + +# buildUserOperationFromTx + +Converts a traditional Ethereum transaction and builds an _unsigned_ UserOperation struct with the all of the middleware run on it through the middleware pipeline. + +The order of the middlewares is: + +1. `dummyPaymasterDataMiddleware` -- populates a dummy paymaster data to use in estimation (default: "0x") +2. `feeDataGetter` -- sets maxfeePerGas and maxPriorityFeePerGas +3. `gasEstimator` -- calls eth_estimateUserOperationGas +4. `paymasterMiddleware` -- used to set paymasterAndData. (default: "0x") +5. `customMiddleware` -- allows you to override any of the results returned by previous middlewares + +Note that `to` field of transaction is required, and among other fields of transaction, only `data`, `value`, `maxFeePerGas`, `maxPriorityFeePerGas` fields are considered and optional. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +const uoStruct = await provider.buildUserOperationFromTx({ + from, // ignored + to, + data: encodeFunctionData({ + abi: ContractABI.abi, + functionName: "func", + args: [arg1, arg2, ...], + }), +}); +const uoHash = await provider.sendUserOperation(uoStruct); +``` + +<<< @/snippets/provider.ts +::: + +## Returns + +### `Promise` + +A Promise containing the _unsigned_ UserOperation struct converted from the input transaction with all the middleware run on the resulting UserOperation + +## Parameters + +### `tx: RpcTransactionRequest` + +The `RpcTransactionRequest` object representing a traditional ethereum transaction diff --git a/site/packages/aa-core/provider/connect.md b/site/packages/aa-core/provider/connect.md new file mode 100644 index 000000000..a0d3487dd --- /dev/null +++ b/site/packages/aa-core/provider/connect.md @@ -0,0 +1,41 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: connect + - - meta + - name: description + content: Overview of the connect method on SmartAccountProvider + - - meta + - property: og:description + content: Overview of the connect method on SmartAccountProvider +--- + +# connect + +Sets the current account to the smart contract account returned by the given function. + +The function parameter is called with the public rpc client that is used by this provider so the account can make RPC calls. + +This function emits `connect` and `accountsChanged` events to notify listeners about the connection. + +## Usage + +::: code-group + +<<< @/snippets/provider.ts + +::: + +## Returns + +### SmartAccountProvider + +The provider with the account connected + +## Parameters + +### `fn: (provider: PublicErc4337Client) => BaseSmartContractAccount` + +The function that given public rpc client, returns a smart contract account diff --git a/site/packages/aa-core/provider/disconnect.md b/site/packages/aa-core/provider/disconnect.md new file mode 100644 index 000000000..14e004171 --- /dev/null +++ b/site/packages/aa-core/provider/disconnect.md @@ -0,0 +1,38 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: disconnect + - - meta + - name: description + content: Overview of the disconnect method on ISmartAccountProvider + - - meta + - property: og:description + content: Overview of the disconnect method on ISmartAccountProvider +--- + +# disconnect + +Allows for disconnecting the account from the provider so that the provider can connect to another account instance. + +This function emits `disconnect` and `accountsChanged` events to notify listeners about the connection. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +await provider.disconnect(); +``` + +<<< @/snippets/provider.ts +::: + +## Returns + +### `ISmartAccountProvider` + +The provider with the account disconnected diff --git a/site/packages/aa-core/provider/dropAndReplaceUserOperation.md b/site/packages/aa-core/provider/dropAndReplaceUserOperation.md new file mode 100644 index 000000000..0029062a0 --- /dev/null +++ b/site/packages/aa-core/provider/dropAndReplaceUserOperation.md @@ -0,0 +1,52 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: dropAndReplaceUserOperation + - - meta + - name: description + content: Overview of the dropAndReplaceUserOperation method on ISmartAccountProvider + - - meta + - property: og:description + content: Overview of the dropAndReplaceUserOperation method on ISmartAccountProvider +--- + +# dropAndReplaceUserOperation + +Attempts to drop and replace an existing user operation by increasing fees. The fee replacement logic sets the `maxPriorityFee` and `maxPriorityFeePerGas` to the `max(current_estimate, prev_uo * 1.1)` (ie. the current max fee or 110% of the previous fee). + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; + +const { request } = await provider.sendUserOperation({ + data: "0xCalldata", + target: "0xTarget", + value: 0n, +}); + +const { hash: replacedHash } = await provider.dropAndReplaceUserOperation( + request +); +``` + +<<< @/snippets/provider.ts +::: + +## Returns + +### `Promise<{ hash: Hash, request: UserOperationRequest }>` + +A Promise containing the hash of the user operation and the request that was sent. + +**Note**: The hash is not the User Operation Receipt. The user operation still needs to be bundled and included in a block. The user operation result is more of a proof of submission than a receipt. + +## Parameters + +### `UserOperationRequest` + +A previously submitted UserOperation. diff --git a/site/packages/aa-core/provider/getAddress.md b/site/packages/aa-core/provider/getAddress.md new file mode 100644 index 000000000..c5d8cf179 --- /dev/null +++ b/site/packages/aa-core/provider/getAddress.md @@ -0,0 +1,37 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: getAddress + - - meta + - name: description + content: Overview of the getAddress method on ISmartAccountProvider + - - meta + - property: og:description + content: Overview of the getAddress method on ISmartAccountProvider +--- + +# getAddress + +Returns the address of the connected account. Throws error if there is no connected account. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +const address = await provider.getAddress(); +``` + +<<< @/snippets/provider.ts + +::: + +## Returns + +### `Promise
` + +A Promise that resolves to the address of the connected account diff --git a/site/packages/aa-core/provider/getUserOperationByHash.md b/site/packages/aa-core/provider/getUserOperationByHash.md new file mode 100644 index 000000000..65c57135a --- /dev/null +++ b/site/packages/aa-core/provider/getUserOperationByHash.md @@ -0,0 +1,43 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: getUserOperationByHash + - - meta + - name: description + content: Overview of the getUserOperationByHash method on ISmartAccountProvider + - - meta + - property: og:description + content: Overview of the getUserOperationByHash method on ISmartAccountProvider +--- + +# getUserOperationByHash + +Return a UserOperation based on a hash (userOpHash). + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +provider.getUserOperationByHash("0xUserOpResultHash"); +``` + +<<< @/snippets/provider.ts + +::: + +## Returns + +### `Promise` + +A Promise containing the UserOperation if found on-chain or null if not found. + +## Parameters + +### `hash: Hash` + +The hash of the user operation returned from [sendUserOperation](./sendUserOperation). diff --git a/site/packages/aa-core/provider/getUserOperationReceipt.md b/site/packages/aa-core/provider/getUserOperationReceipt.md new file mode 100644 index 000000000..259ae1f0c --- /dev/null +++ b/site/packages/aa-core/provider/getUserOperationReceipt.md @@ -0,0 +1,43 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: ISmartAccountProvider • getUserOperationReceipt + - - meta + - name: description + content: Overview of the getUserOperationReceipt method on ISmartAccountProvider + - - meta + - property: og:description + content: Overview of the getUserOperationReceipt method on ISmartAccountProvider +--- + +# getUserOperationReceipt + +Return a UserOperationReceipt based on a hash (userOpHash). + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +provider.getUserOperationReceipt("0xUserOpResultHash"); +``` + +<<< @/snippets/provider.ts + +::: + +## Returns + +### `Promise` + +A Promise containing the UserOperationReceipt if found on-chain or null if not found. + +## Parameters + +### `hash: Hash` + +The hash of the user operation returned from [sendUserOperation](./sendUserOperation). diff --git a/site/packages/aa-core/provider/introduction.md b/site/packages/aa-core/provider/introduction.md new file mode 100644 index 000000000..f02967865 --- /dev/null +++ b/site/packages/aa-core/provider/introduction.md @@ -0,0 +1,21 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: SmartAccountProvider + - - meta + - name: description + content: Introduction to SmartAccountProvider exported by aa-core provider + - - meta + - property: og:description + content: Introduction to SmartAccountProvider exported by aa-core provider +--- + +# Introduction to SmartAccountProvider + +The `SmartAccountProvider` is an [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) compliant Provider that wraps JSON RPC methods and some Wallet Methods (signing, sendTransaction, etc). With this Provider, you can submit User Operations to RPC providers, estimate gas, configure a Paymaster, send standard JSON RPC requests, and more. It is not opinionated about which RPC provider you are using and is configurable to work with any RPC provider. Because it implements EIP-1193, it can be used with any web3 library. + +## Usage + +<<< @/snippets/core-provider.ts diff --git a/site/packages/aa-core/provider/isConnected.md b/site/packages/aa-core/provider/isConnected.md new file mode 100644 index 000000000..b3ccd492d --- /dev/null +++ b/site/packages/aa-core/provider/isConnected.md @@ -0,0 +1,37 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: isConnected + - - meta + - name: description + content: Overview of the isConnected method on ISmartAccountProvider + - - meta + - property: og:description + content: Overview of the isConnected method on ISmartAccountProvider +--- + +# isConnected + +Returns the boolean flag indicating if the account is connected. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +const isConnected = await provider.isConnected(); +``` + +<<< @/snippets/provider.ts + +::: + +## Returns + +### `boolean` + +The boolean flag indicating if the account is connected diff --git a/site/packages/aa-core/provider/request.md b/site/packages/aa-core/provider/request.md new file mode 100644 index 000000000..41096d7d5 --- /dev/null +++ b/site/packages/aa-core/provider/request.md @@ -0,0 +1,58 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: ISmartAccountProvider • request + - - meta + - name: description + content: Overview of the request method on ISmartAccountProvider + - - meta + - property: og:description + content: Overview of the request method on ISmartAccountProvider +--- + +# request + +[EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) compliant request method using the connected account. Executes various Ethereum-related JSON-RPC methods based on the provided 'method' (the Ethereum JSON-RPC method to be executed) and 'params' (optional array of parameters for the rpc method). + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +const result = await provider.request({ + method: "eth_sendTransaction", + params: [tx], +}); + +const result = await provider.request({ + method: "eth_sign", + params: [address, data], +}); + +const result = await provider.request({ + method: "eth_signTypedData_v4", + params: [address, dataParams], +}); +``` + +<<< @/snippets/provider.ts + +::: + +## Returns + +### `Promise` + +A Promise that resolves to the result of the JSON-RPC call + +## Parameters + +### `args: { method: string; params?: any[] }` + +object containing the method and optional params to execute. +`method` - The Ethereum JSON-RPC method to be executed +`params` - Optional array of parameters specific to the chosen method diff --git a/site/packages/aa-core/provider/sendTransaction.md b/site/packages/aa-core/provider/sendTransaction.md new file mode 100644 index 000000000..9a31e2c23 --- /dev/null +++ b/site/packages/aa-core/provider/sendTransaction.md @@ -0,0 +1,55 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: ISmartAccountProvider • sendTransaction + - - meta + - name: description + content: Overview of the sendTransaction method on ISmartAccountProvider + - - meta + - property: og:description + content: Overview of the sendTransaction method on ISmartAccountProvider +--- + +# sendTransaction + +This takes an ethereum transaction and converts it into a UserOperation, sends the UserOperation, and waits on the receipt of that UserOperation (ie. has it been mined). + +If you don't want to wait for the UserOperation to mine, it's recommended to user [sendUserOperation](./sendUserOperation) instead. + +Note that `to` field of transaction is required, and among other fields of transaction, only `data`, `value`, `maxFeePerGas`, `maxPriorityFeePerGas` fields are considered if given. Support for other fields is coming soon. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +const txHash = await provider.sendTransaction({ + from, // ignored + to, + data: encodeFunctionData({ + abi: ContractABI.abi, + functionName: "func", + args: [arg1, arg2, ...], + }), +}); +``` + +<<< @/snippets/provider.ts + +::: + +## Returns + +### `Promise` + +A Promise containing the transaction hash + +## Parameters + +### `request: RpcTransactionRequest` + +The `RpcTransactionRequest` object representing a traditional ethereum transaction diff --git a/site/packages/aa-core/provider/sendTransactions.md b/site/packages/aa-core/provider/sendTransactions.md new file mode 100644 index 000000000..10e8d893f --- /dev/null +++ b/site/packages/aa-core/provider/sendTransactions.md @@ -0,0 +1,76 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: sendTransactions + - - meta + - name: description + content: Overview of the sendTransactions method on ISmartAccountProvider + - - meta + - property: og:description + content: Overview of the sendTransactions method on ISmartAccountProvider +--- + +# sendTransactions + +This takes a set of ethereum transactions and converts them into one single UserOperation, sends the UserOperation, and waits on the receipt of that UserOperation (ie. has it been mined). If you don't want to wait for the UserOperation to mine, it's recommended to user [sendUserOperation](./sendUserOperation) instead. + +**NOTE**: The account you're sending the transactions _to_ MUST support batch transactions. + +Also note that `to` field of transaction is required, and among other fields of transaction, only `data`, `value`, `maxFeePerGas`, `maxPriorityFeePerGas` fields are considered and optional. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +const txHash = await provider.sendTransactions([ + { + from, // ignored + to, + data: encodeFunctionData({ + abi: ContractABI.abi, + functionName: "func", + args: [arg1, arg2, ...], + }), + }, + { + from, // ignored + to, + data: encodeFunctionData({ + abi: ContractABI.abi, + functionName: "func", + args: [arg1, arg2, ...], + }), + }, + ... + { + from, // ignored + to, + data: encodeFunctionData({ + abi: ContractABI.abi, + functionName: "func", + args: [arg1, arg2, ...], + }), + }, +]); +``` + +<<< @/snippets/provider.ts + +::: + +## Returns + +### `Promise` + +A Promise containing the transaction hash + +## Parameters + +### `request: RpcTransactionRequest[]` + +An `RpcTransactionRequest` array representing a traditional ethereum transaction diff --git a/site/packages/aa-core/provider/sendUserOperation.md b/site/packages/aa-core/provider/sendUserOperation.md new file mode 100644 index 000000000..bed9d948f --- /dev/null +++ b/site/packages/aa-core/provider/sendUserOperation.md @@ -0,0 +1,72 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: sendUserOperation + - - meta + - name: description + content: Overview of the sendUserOperation method on ISmartAccountProvider + - - meta + - property: og:description + content: Overview of the sendUserOperation method on ISmartAccountProvider +--- + +# sendUserOperation + +Sends a user operation or batch of user operations using the connected account. + +Before executing, sendUserOperation will run the user operation through the middleware pipeline. The order of the middlewares is: + +1. `dummyPaymasterDataMiddleware` -- populates a dummy paymaster data to use in estimation (default: "0x") +2. `feeDataGetter` -- sets maxfeePerGas and maxPriorityFeePerGas +3. `gasEstimator` -- calls eth_estimateUserOperationGas +4. `paymasterMiddleware` -- used to set paymasterAndData. (default: "0x") +5. `customMiddleware` -- allows you to override any of the results returned by previous middlewares + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +// send single +provider.sendUserOperation({ + data: "0xCalldata", + target: "0xTarget", + value: 0n, +}); + +// send batch +provider.sendUserOperation([ + { + data: "0xCalldata", + target: "0xTarget", + }, + { + data: "0xCalldata2", + target: "0xTarget2", + value: 1000n, // in wei + }, +]); +``` + +<<< @/snippets/provider.ts +::: + +## Returns + +### `Promise<{ hash: Hash, request: UserOperationRequest }>` + +A Promise containing the hash of the user operation and the request that was sent. + +**Note**: The hash is not the User Operation Receipt. The user operation still needs to be bundled and included in a block. The user operation result is more of a proof of submission than a receipt. + +## Parameters + +### `UserOperationCallData | UserOperationCallData[]` + +- `target: Address` - the target of the call (equivalent to `to` in a transaction) +- `data: Hex` - can be either `0x` or a call data string +- `value?: bigint` - optionally, set the value in wei you want to send to the target diff --git a/site/packages/aa-core/provider/signMessage.md b/site/packages/aa-core/provider/signMessage.md new file mode 100644 index 000000000..eb0740722 --- /dev/null +++ b/site/packages/aa-core/provider/signMessage.md @@ -0,0 +1,43 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: ISmartAccountProvider • signMessage + - - meta + - name: description + content: Overview of the signMessage method on ISmartAccountProvider + - - meta + - property: og:description + content: Overview of the signMessage method on ISmartAccountProvider +--- + +# signMessage + +This method signs messages using the connected account with [ERC-191](https://eips.ethereum.org/EIPS/eip-191) standard. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +const signedMessage = await provider.signMessage("msg"); +``` + +<<< @/snippets/provider.ts + +::: + +## Returns + +### `Promise` + +The signed hash for the message passed + +## Parameters + +### `msg: string | Uint8Array` + +Message to be signed diff --git a/site/packages/aa-core/provider/signMessageWith6492.md b/site/packages/aa-core/provider/signMessageWith6492.md new file mode 100644 index 000000000..b741fb0d7 --- /dev/null +++ b/site/packages/aa-core/provider/signMessageWith6492.md @@ -0,0 +1,44 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: ISmartAccountProvider • signMessageWith6492 + - - meta + - name: description + content: Overview of the signMessageWith6492 method on ISmartAccountProvider + - - meta + - property: og:description + content: Overview of the signMessageWith6492 method on ISmartAccountProvider +--- + +# signMessageWith6492 + +This method supports signing messages for deployed smart contract accounts, as well as undeployed accounts (counterfactual addresses) using [ERC-6492](https://eips.ethereum.org/EIPS/eip-6492). + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +// sign message (works for undeployed and deployed accounts) +const signedMessageWith6492 = provider.signMessageWith6492("test"); +``` + +<<< @/snippets/provider.ts + +::: + +## Returns + +### `Promise` + +A Promise containing the signature of the message, additionally wrapped in EIP-6492 format if the account is undeployed + +## Parameters + +### `msg: string | Uint8Array` + +The message to sign diff --git a/site/packages/aa-core/provider/signTypedData.md b/site/packages/aa-core/provider/signTypedData.md new file mode 100644 index 000000000..6751ebd99 --- /dev/null +++ b/site/packages/aa-core/provider/signTypedData.md @@ -0,0 +1,77 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: ISmartAccountProvider • signTypedData + - - meta + - name: description + content: Overview of the signTypedData method on ISmartAccountProvider + - - meta + - property: og:description + content: Overview of the signTypedData method on ISmartAccountProvider +--- + +# signTypedData + +This method signs sign typed data using the connected account with [ERC-712](https://eips.ethereum.org/EIPS/eip-712). + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +// sign typed data +const signedTypedData = provider.signTypedData({ + domain: { + name: "Ether Mail", + version: "1", + chainId: 1, + verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + }, + types: { + Person: [ + { name: "name", type: "string" }, + { name: "wallet", type: "address" }, + ], + Mail: [ + { name: "from", type: "Person" }, + { name: "to", type: "Person" }, + { name: "contents", type: "string" }, + ], + }, + primaryType: "Mail", + message: { + from: { + name: "Cow", + wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + }, + to: { + name: "Bob", + wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + }, + contents: "Hello, Bob!", + }, +}); +``` + +<<< @/snippets/provider.ts + +::: + +## Returns + +### `Promise` + +A Promise containing the signature of the typed data + +## Parameters + +### `params: SignTypedDataParams` -- the typed data to sign + +- `domain: TypedDataDomain` -- The typed data domain +- `types: Object` -- the type definitions for the typed data +- `primaryType: inferred String` -- the primary type to extract from types and use in value +- `message: inferred from types & primaryType` -- the message, inferred from diff --git a/site/packages/aa-core/provider/signTypedDataWith6492.md b/site/packages/aa-core/provider/signTypedDataWith6492.md new file mode 100644 index 000000000..0ccd65dee --- /dev/null +++ b/site/packages/aa-core/provider/signTypedDataWith6492.md @@ -0,0 +1,79 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: ISmartAccountProvider • signTypedDataWith6492 + - - meta + - name: description + content: Overview of the signTypedDataWith6492 method on ISmartAccountProvider + - - meta + - property: og:description + content: Overview of the signTypedDataWith6492 method on ISmartAccountProvider +--- + +# signTypedDataWith6492 + +This method supports signing typed data for deployed smart contract accounts, as well as undeployed accounts (counterfactual addresses) using [ERC-6492](https://eips.ethereum.org/EIPS/eip-6492). + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +// sign typed data (works for undeployed and deployed accounts) +const signedTypedDataWith6492 = provider.signTypedDataWith6492({ + domain: { + name: "Ether Mail", + version: "1", + chainId: 1, + verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + }, + types: { + Person: [ + { name: "name", type: "string" }, + { name: "wallet", type: "address" }, + ], + Mail: [ + { name: "from", type: "Person" }, + { name: "to", type: "Person" }, + { name: "contents", type: "string" }, + ], + }, + primaryType: "Mail", + message: { + from: { + name: "Cow", + wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + }, + to: { + name: "Bob", + wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + }, + contents: "Hello, Bob!", + }, +}); +``` + +<<< @/snippets/provider.ts + +::: + +## Returns + +### `Promise` + +A Promise containing the signature of the typed data, additionally wrapped in ERC-6492 format if the account is undeployed. + +## Parameters + +### `params: SignTypedDataParams` + +The typed data to sign + +- `domain: TypedDataDomain` -- The typed data domain +- `types: Object` -- the type definitions for the typed data +- `primaryType: inferred String` -- the primary type to extract from types and use in value +- `message: inferred from types & primaryType` -- the message, inferred from diff --git a/site/packages/aa-core/provider/waitForUserOperationTransaction.md b/site/packages/aa-core/provider/waitForUserOperationTransaction.md new file mode 100644 index 000000000..7ef28dd26 --- /dev/null +++ b/site/packages/aa-core/provider/waitForUserOperationTransaction.md @@ -0,0 +1,65 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: waitForUserOperationTransaction + - - meta + - name: description + content: Overview of the waitForUserOperationTransaction method on ISmartAccountProvider + - - meta + - property: og:description + content: Overview of the waitForUserOperationTransaction method on ISmartAccountProvider +--- + +# waitForUserOperationTransaction + +Attempts to fetch for UserOperationReceipt `txMaxRetries` amount of times, at an interval of `txRetryIntervalMs` milliseconds (with a multiplier of `txRetryMulitplier`) using the connected account. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +const userOperationResult = await provider.sendUserOperation({ + data: "0xCalldata", + target: "0xTarget", + value: 0n, +}); + +// [!code focus:99] +provider.waitForUserOperationTransaction({ + hash: result.hash, +}); +``` + +<<< @/snippets/provider.ts +::: + +## Returns + +### `Promise` + +A Promise containing the hash of the transaction the user operation was included in. + +If `txMaxRetries` is exceeded without the user operation included in a block yet, this endpoint will throw an error. You should handle this by retrying with a higher fee and/or changing the retry configurations. + +## Parameters + +### `hash: Hash` + +The hash of the user operation returned from [sendUserOperation](./sendUserOperation). + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +provider.waitForUserOperationTransaction({ + hash: "0xUserOpResultHash", // [!code focus] +}); +``` + +::: diff --git a/site/packages/aa-core/provider/withCustomMiddleware.md b/site/packages/aa-core/provider/withCustomMiddleware.md new file mode 100644 index 000000000..67449d2c5 --- /dev/null +++ b/site/packages/aa-core/provider/withCustomMiddleware.md @@ -0,0 +1,71 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: ISmartAccountProvider • withCustomMiddleware + - - meta + - name: description + content: Overview of the withCustomMiddleware method and accessing the customMiddleware readonly field on ISmartAccountProvider + - - meta + - property: og:description + content: Overview of the withCustomMiddleware method and accessing the customMiddleware readonly field on ISmartAccountProvider +--- + +# withCustomMiddleware + +Adds a function to the end of the middleware call stack before signature verification. It can be used to override or add additional functionality. Like modifying the user operation, making an additional RPC call, or logging data. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; + +// Define the custom middleware override function // [!code focus:99] +// The function below just changes the call data for the userOp +// But you can do anything you want here +const CustomMiddlewareOverrideFunction = async (uoStruct) => { + uoStruct.callData = "0xNEW_CALL_DATA"; // Changing the call data + return uoStruct; +}; + +// Add the custom middleware +provider.withCustomMiddleware(CustomMiddlewareOverrideFunction); + +// Defining the user operation data +const userOpData = { + target: "0xTARGET_ADDRESS", // Replace with the actual target address + data: "0xSOME_DATA", // Replace with the actual data +}; + +// function to call buildUserOperation and log the modified callData +const resultingUO = await provider.buildUserOperation(userOpData); +console.log("Modified callData:", resultingUO.callData); +``` + +<<< @/snippets/provider.ts +::: + +## Returns + +### `ISmartAccountProvider` + +An updated instance of the provider, which now uses the custom middleware. + +## Parameters + +### `override: AccountMiddlewareFn` + +The User Operation (UO) transform function that will run as the custom middleware. + +## `customMiddleware` + +The `customMiddleware` is a readonly field that represents the final middleware step in the stack. It allows overriding any of the results returned by previous middlewares, ensuring customized processing of user operations. + +You can access the current middleware configuration for the provider via: + +```ts +const currentMiddleware = provider.customMiddleware; +``` diff --git a/site/packages/aa-core/provider/withFeeDataGetter.md b/site/packages/aa-core/provider/withFeeDataGetter.md new file mode 100644 index 000000000..d8e7e68d9 --- /dev/null +++ b/site/packages/aa-core/provider/withFeeDataGetter.md @@ -0,0 +1,78 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: ISmartAccountProvider • withFeeDataGetter + - - meta + - name: description + content: Overview of the withFeeDataGetter method and accessing the feeDataGetter readonly field on ISmartAccountProvider + - - meta + - property: og:description + content: Overview of the withFeeDataGetter method and accessing the feeDataGetter readonly field on ISmartAccountProvider +--- + +# withFeeDataGetter + +Overrides the default [`feeDataGetter`](#feedatagetter) middleware. This middleware is used for setting the `maxFeePerGas` and `maxPriorityFeePerGas` fields on the `UserOperation` prior to its execution. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; + +// Define the FeeDataMiddlewareOverrideFunction // [!code focus:99] +const FeeDataMiddlewareOverrideFunction = async (uoStruct) => { + // For demonstration purposes, we're setting hardcoded fee values. + // In a real-world scenario, you might fetch these values from a service + // Or make other determinations. + + // Setting the max fee per gas + uoStruct.maxFeePerGas = "0x500"; + + // Setting the max priority fee per gas + uoStruct.maxPriorityFeePerGas = "0x50"; + + return uoStruct; +}; + +// Integrate the custom fee data middleware with the provider +provider.withFeeDataGetter(FeeDataMiddlewareOverrideFunction); + +// Define the user operation data +const userOpData = { + target: "0xTARGET_ADDRESS", // Replace with your actual target address + data: "0xSOME_DATA", // Replace with your actual data +}; + +const resultingUO = await provider.buildUserOperation(userOpData); +console.log("Modified maxFeePerGas:", resultingUO.maxFeePerGas); +console.log("Modified maxPriorityFeePerGas:", resultingUO.maxPriorityFeePerGas); +``` + +<<< @/snippets/provider.ts +::: + +## Returns + +### `ISmartAccountProvider` + +An updated instance of the provider, which now uses the overridden `feeDataGetter` middleware. + +## Parameters + +### `override: FeeDataMiddleware` + +A function for overriding the default `feeDataGetter` middleware. This middleware is specifically utilized to set the fee-related fields (`maxFeePerGas` and `maxPriorityFeePerGas`) on the `UserOperation` before it's executed. + +## `feeDataGetter` + +The `feeDataGetter` is a readonly field on the `ISmartAccountProvider` interface that represents the default fee data getter middleware. It's used to set the fee-related fields on a `UserOperation` by making calls to the connected `rpcClient` to estimate the maximum priority fee per gas and retrieve fee data. + +You can access the current fee data getter configuration for the provider via: + +```ts +const currentFeeDataGetter = provider.feeDataGetter; +``` diff --git a/site/packages/aa-core/signers/local-account.md b/site/packages/aa-core/signers/local-account.md new file mode 100644 index 000000000..2b3664e18 --- /dev/null +++ b/site/packages/aa-core/signers/local-account.md @@ -0,0 +1,62 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: LocalAccountSigner + - - meta + - name: description + content: Overview of the LocalAccountSigner + - - meta + - property: og:description + content: Overview of the LocalAccountSigner +--- + +# LocalAccountSigner + +The `LocalAccountSigner` allows you to use accounts for which you already have the private key available locally as signers on Smart Contracts. This signer is used with `HDAccount | PrivateKeyAccount | LocalAccount` types from `viem`. + +## Usage + +::: code-group + +```ts [private-key.ts] +import { LocalAccountSigner } from "@alchemy/aa-core"; + +export const signer = + LocalAccountSigner.privateKeyToAccountSigner("0xPrivateKey"); +``` + +```ts [mnemonic.ts] +import { LocalAccountSigner } from "@alchemy/aa-core"; + +export const signer = LocalAccountSigner.mnemonicToAccountSigner("mnemonic"); +``` + +::: + +## Methods + +### `constructor(client: T)` + +Creates a new `LocalAccountSigner` using the underlying account type to sign the messages. + +### `getAddress(): Promise
` + +Returns the public address of the underlying Account + +### `signMessage(msg: string | Hex | ByteArray): Promise` + +Signs and returns a message in [EIP-191 format](https://eips.ethereum.org/EIPS/eip-191) + +### `signTypedData(params: SignTypedDataParams): Promise` + +Signs and returns a message in [EIP-712 format](https://eips.ethereum.org/EIPS/eip-712) using `eth_signTypedData_v4` + +### `static mnemonicToAccountSigner(key: string): LocalAccountSigner` + +Converts a mnemonic phrase into a `LocalAccountSigner` + +### `static privateKeyToAccountSigner(key: Hex): LocalAccountSigner` + +Converts a private key hex into a `LocalAccountSigner` diff --git a/site/packages/aa-core/signers/utils/verifyEIP6492Signature.md b/site/packages/aa-core/signers/utils/verifyEIP6492Signature.md new file mode 100644 index 000000000..3cd091873 --- /dev/null +++ b/site/packages/aa-core/signers/utils/verifyEIP6492Signature.md @@ -0,0 +1,62 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: verifyEIP6492Signature + - - meta + - name: description + content: Description of the verifyEIP6492Signature utility method + - - meta + - property: og:description + content: Description of the verifyEIP6492Signature utility method +--- + +# verifyEIP6492Signature + +Uses a the universal validator defined [here](https://github.com/AmbireTech/signature-validator/blob/main/index.ts#L13C17-L13C17) to verify a signature in the [ERC-6492](https://eips.ethereum.org/EIPS/eip-6492) format. + +## Usage + +::: code-group + +```ts [example.ts] +import { verifyEIP6492Signature } from "@alchemy/aa-core"; +import { http } from "viem"; +import { mainnet } from "viem/chains"; + +const signature = await verifyEIP6492Signature({ + signer: "0xAccountAddress", + hash: "0xHashOfMessageBeingVerified", + signature: "0xSignatureToVerify", + client: createPublicClient({ transport: http(), chain: mainnet }), +}); +``` + +::: + +## Returns + +### `Promise` + +Returns whether or not the signature is valid for the given signer and message hash + +## Parameters + +### `VerifyEIP6492SignatureParams` + +- #### `signer: Address` + + The address of the signer of the message (eg. the smart contract account address) + +- #### `hash: Hash` + + The hash of the message being verified (eg. the result of `hashMessage("hello world")`) + +- #### `signature: Hex` + + The signature to verify (eg. the result of `signMessage("hello world")`) + +- #### `client: Client` + + The viem client used to make an `eth_call` to validate the signature. This client should be connected to the same chain that the undeployed account is on diff --git a/site/packages/aa-core/signers/utils/wrapWith6492.md b/site/packages/aa-core/signers/utils/wrapWith6492.md new file mode 100644 index 000000000..9f2c2a77a --- /dev/null +++ b/site/packages/aa-core/signers/utils/wrapWith6492.md @@ -0,0 +1,55 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: wrapSignatureWith6492 + - - meta + - name: description + content: Description of the wrapSignatureWith6492 utility method + - - meta + - property: og:description + content: Description of the wrapSignatureWith6492 utility method +--- + +# wrapSignatureWith6492 + +Allows you to generate a signature in [ERC-6492](https://eips.ethereum.org/EIPS/eip-6492) format which is useful to verifying signatures of undeployed smart contract accounts. + +## Usage + +::: code-group + +```ts [example.ts] +import { wrapSignatureWith6492 } from "@alchemy/aa-core"; + +const signature = await wrapSignatureWith6492({ + factoryAddress: "0xAccountFactoryAddress", + factoryCallData: "0xfactoryCallData", + signature: "0xSigntatureToWrapAndVerifyLater", +}); +``` + +::: + +## Returns + +### Hash + +The original signature wrapped in ERC-6492 format + +## Paramters + +### `SignWith6492Params` + +- #### `factoryAddress: Hash` + + The factory address that will be used to deploy the smart contract account that you want to verify the signature of + +- #### `factoryCallData: Hex` + + The call data to the factory to create the undeployed account + +- #### `signature: Hex` + + The signature that we want to verify diff --git a/site/packages/aa-core/signers/wallet-client.md b/site/packages/aa-core/signers/wallet-client.md new file mode 100644 index 000000000..8f0d2d9ce --- /dev/null +++ b/site/packages/aa-core/signers/wallet-client.md @@ -0,0 +1,41 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: WalletClientSigner + - - meta + - name: description + content: Overview of the WalletClientSigner + - - meta + - property: og:description + content: Overview of the WalletClientSigner +--- + +# WalletClientSigner + +The `WalletClientSigner` is useful if you want to convert a `viem` `WalletClient` to a `SmartAccountSigner` which can be used as an owner on Smart Contract Accounts + +## Usage + +::: code-group +<<< @/snippets/wallet-client-signer.ts +::: + +## Methods + +### `constructor(client: WalletClient)` + +Creates a new `WalletClient` using the `WalletClient` to sign messages + +### `getAddress(): Promise
` + +Returns the public address of the underlying Wallet + +### `signMessage(msg: string | Hex | ByteArray): Promise` + +Signs and returns a message in [EIP-191 format](https://eips.ethereum.org/EIPS/eip-191) + +### `signTypedData(params: SignTypedDataParams): Promise` + +Signs and returns a message in [EIP-712 format](https://eips.ethereum.org/EIPS/eip-712) using `eth_signTypedData_v4` diff --git a/site/packages/aa-core/utils/asyncPipe.md b/site/packages/aa-core/utils/asyncPipe.md new file mode 100644 index 000000000..2183fd673 --- /dev/null +++ b/site/packages/aa-core/utils/asyncPipe.md @@ -0,0 +1,44 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: asyncPipe + - - meta + - name: description + content: Overview of the asyncPipe method in aa-core utils + - - meta + - property: og:description + content: Overview of the asyncPipe method in aa-core utils +--- + +# `asyncPipe` + +Utility function that allows for piping a series of async functions together that operate on a common type `T` + +## Usage + +::: code-group + +```ts [example.ts] +import { asyncPipe } from "@alchemy/aa-core"; + +const addOne = async (num: number) => num + 1; +const addTwo = async (num: number) => num + 2; + +const result = await asyncPipe(addOne, addTwo)(0); +``` + +::: + +## Returns + +### `T` + +The result of the last function in the pipe chain + +## Parameters + +### `fns: Array<(x: T) => Promise>` + +The array of functions to pipe together diff --git a/site/packages/aa-core/utils/convertChainIdToCoinType.md b/site/packages/aa-core/utils/convertChainIdToCoinType.md new file mode 100644 index 000000000..a7679a280 --- /dev/null +++ b/site/packages/aa-core/utils/convertChainIdToCoinType.md @@ -0,0 +1,46 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: convertChainIdToCoinType + - - meta + - name: description + content: Overview of the convertChainIdToCoinType method in aa-core utils + - - meta + - property: og:description + content: Overview of the convertChainIdToCoinType method in aa-core utils +--- + +# convertChainIdToCoinType + +Converts an ethereum chain ID to an ENS coin type as per [ENSIP-11](https://docs.ens.domains/ens-improvement-proposals/ensip-11-evmchain-address-resolution) and assumes this is how mappings are stored for non mainnet chains. + +::: tip Note +For mainnet, this method will return `60` as the coin type. This comes from [ensip-9](https://docs.ens.domains/ens-improvement-proposals/ensip-9-multichain-address-resolution). +::: + +## Usage + +::: code-group + +```ts [example.ts] +import { convertChainIdToCoinType } from "@alchemy/aa-core"; + +const result = convertChainIdToCoinType(1); +// 60 +``` + +::: + +## Returns + +### `number` + +The converted coin type + +## Parameters + +### `chainId: number` + +The chain ID to convert diff --git a/site/packages/aa-core/utils/convertCoinTypeToChain.md b/site/packages/aa-core/utils/convertCoinTypeToChain.md new file mode 100644 index 000000000..1d072305a --- /dev/null +++ b/site/packages/aa-core/utils/convertCoinTypeToChain.md @@ -0,0 +1,46 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: convertCoinTypeToChain + - - meta + - name: description + content: Overview of the convertCoinTypeToChain method in aa-core utils + - - meta + - property: og:description + content: Overview of the convertCoinTypeToChain method in aa-core utils +--- + +# convertCoinTypeToChain + +Converts a coinType into a viem Chain object. The conversion follows [ENSIP-11](https://docs.ens.domains/ens-improvement-proposals/ensip-11-evmchain-address-resolution). + +::: tip Note +For mainnet, the conversion expects `coinType == 60`. This comes from [ENSIP-9](https://docs.ens.domains/ens-improvement-proposals/ensip-9-multichain-address-resolution). +::: + +## Usage + +::: code-group + +```ts [example.ts] +import { convertCoinTypeToChain } from "@alchemy/aa-core"; + +// mainnet +const result = convertCoinTypeToChain(60); +``` + +::: + +## Returns + +### `Chain` + +A viem `Chain` object that the coinType represents + +## Parameters + +### `coinType: number` + +The coinType to convert to a `Chain` object diff --git a/site/packages/aa-core/utils/convertCoinTypeToChainId.md b/site/packages/aa-core/utils/convertCoinTypeToChainId.md new file mode 100644 index 000000000..250579f59 --- /dev/null +++ b/site/packages/aa-core/utils/convertCoinTypeToChainId.md @@ -0,0 +1,46 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: convertCoinTypeToChainId + - - meta + - name: description + content: Overview of the convertCoinTypeToChainId method in aa-core utils + - - meta + - property: og:description + content: Overview of the convertCoinTypeToChainId method in aa-core utils +--- + +# convertCoinTypeToChainId + +Converts a coinType into a chain ID. The conversion follows [ENSIP-11](https://docs.ens.domains/ens-improvement-proposals/ensip-11-evmchain-address-resolution). + +::: tip Note +For mainnet, the conversion expects `coinType == 60`. This comes from [ENSIP-9](https://docs.ens.domains/ens-improvement-proposals/ensip-9-multichain-address-resolution). +::: + +## Usage + +::: code-group + +```ts [example.ts] +import { convertCoinTypeToChainId } from "@alchemy/aa-core"; + +const result = convertCoinTypeToChainId(60); +// 1 +``` + +::: + +## Returns + +### `number` + +The chain ID + +## Parameters + +### `coinType: number` + +The coinType to convert to a chain ID diff --git a/site/packages/aa-core/utils/deepHexlify.md b/site/packages/aa-core/utils/deepHexlify.md new file mode 100644 index 000000000..81faec2ae --- /dev/null +++ b/site/packages/aa-core/utils/deepHexlify.md @@ -0,0 +1,64 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: deepHexlify + - - meta + - name: description + content: Overview of the deepHexlify method in aa-core utils + - - meta + - property: og:description + content: Overview of the deepHexlify method in aa-core utils +--- + +# deepHexlify + +Utility function that recursively converts all values in an object to hex strings (where applicable). + +## Usage + +::: code-group + +```ts [example.ts] +import { deepHexlify } from "@alchemy/aa-core"; + +const obj = { + aBigInt: 1n, + anInt: 1, + aString: "1", + anArray: [1n, 1, "1"], + anObject: { + aBigInt: 1n, + anInt: 1, + aString: "1", + anArray: [1n, 1, "1"], + }, +}; + +const result = deepHexlify(obj); +/** + * { + * aBigInt: "0x1", + * anInt: "0x1, + * aString: "1", + * anArray: ["0x1", "0x1", "1"], + * anObject: { + * aBigInt: "0x1", + * anInt: "0x1, + * aString: "1", + * anArray: ["0x1", "0x1", "1"], + * } + * } + * / +``` + +## Returns + +The object with all convertible values converted to hex strings. + +## Params + +### `obj: any` + +The object to convert to hex strings. diff --git a/site/packages/aa-core/utils/defineReadOnly.md b/site/packages/aa-core/utils/defineReadOnly.md new file mode 100644 index 000000000..0256c0fdc --- /dev/null +++ b/site/packages/aa-core/utils/defineReadOnly.md @@ -0,0 +1,48 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: defineReadOnly + - - meta + - name: description + content: Overview of the defineReadOnly method in aa-core utils + - - meta + - property: og:description + content: Overview of the defineReadOnly method in aa-core utils +--- + +# `defineReadOnly` + +Let's you overwrite a readonly property on a class. + +Borrowed from [ethers](https://github.com/ethers-io/ethers.js/blob/v5.7/packages/properties/src.ts/index.ts#L7). + +## Usage + +::: code-group + +```ts [example.ts] +import { defineReadOnly } from "@alchemy/aa-core"; + +class Test { + readonly a: number = 1; + setA(a: number) { + defineReadOnly(this, "a", a); + } +} +``` + +## Parameters + +### `object: T` + +The object on which to overwrite the readonly property. + +### `key: K` where `K extends keyof T` + +A key on the object to overwrite. + +### `value: T[K]` + +The value to overwrite the readonly property with. diff --git a/site/packages/aa-core/utils/getChain.md b/site/packages/aa-core/utils/getChain.md new file mode 100644 index 000000000..a5e59ef6a --- /dev/null +++ b/site/packages/aa-core/utils/getChain.md @@ -0,0 +1,42 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: getChain + - - meta + - name: description + content: Overview of the getChain method in aa-core utils + - - meta + - property: og:description + content: Overview of the getChain method in aa-core utils +--- + +# getChain + +This is a utility method for converting a chainId to a `viem` `Chain` object + +## Usage + +::: code-group + +```ts [example.ts] +import { getChain } from "@alchemy/aa-core"; + +const result = getChain(1); +// mainnet +``` + +::: + +## Returns + +### `Chain` + +a `viem` [`Chain`](https://github.com/wagmi-dev/viem/blob/main/src/types/chain.ts) object + +## Parameters + +### `chainId: number` + +The chainId to convert to a `Chain` object diff --git a/site/packages/aa-core/utils/getUserOperationHash.md b/site/packages/aa-core/utils/getUserOperationHash.md new file mode 100644 index 000000000..20df38d19 --- /dev/null +++ b/site/packages/aa-core/utils/getUserOperationHash.md @@ -0,0 +1,60 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: getUserOperationHash + - - meta + - name: description + content: Overview of the getUserOperationHash method in aa-core utils + - - meta + - property: og:description + content: Overview of the getUserOperationHash method in aa-core utils +--- + +# getUserOperationHash + +Generates a hash for a UserOperation valid from entrypoint version 0.6 onwards + +## Usage + +::: code-group + +```ts [example.ts] +import { getUserOperationHash } from "@alchemy/aa-core"; + +const result = getUserOperationHash( + { + // ... the UserOperationRequest + }, + "0xAddress", + 1n +); +// 0xUserOpHash +``` + +::: + +## Returns + +### `Hash` + +The hash of the user operation + +## Paramters + +### `request: UserOperationRequest` + +The UserOperation to hash + +### `entrypointAddress: Address` + +The entrypoint address to use for the UserOperation + +### `chainId: bigint` + +The chainId that this UserOperation will be submitted to + +``` + +``` diff --git a/site/packages/aa-core/utils/resolveProperties.md b/site/packages/aa-core/utils/resolveProperties.md new file mode 100644 index 000000000..aeaf6ebf3 --- /dev/null +++ b/site/packages/aa-core/utils/resolveProperties.md @@ -0,0 +1,44 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: resolveProperties + - - meta + - name: description + content: Overview of the resolveProperties method in aa-core utils + - - meta + - property: og:description + content: Overview of the resolveProperties method in aa-core utils +--- + +# `resolveProperties` + +Await all of the properties of a Deferrable object. A Deferrable object is an object that has properties that are either a Promise or a value. + +## Usage + +::: code-group + +```ts [example.ts] +import { resolveProperties } from "@alchemy/aa-core"; +const result = await resolveProperties({ + foo: new Promise((resolve) => resolve("foo")), + bar: "bar", +}); +// { foo: "foo", bar: "bar" } +``` + +::: + +## Returns + +### `Promise` + +The object with all properties resolved (awaited). + +## Parameters + +### `object: Deferrable` + +The object to resolve properties on. A Deferrable object is an object that has properties that are either a Promise or a value diff --git a/site/packages/aa-ethers/account-signer/connect.md b/site/packages/aa-ethers/account-signer/connect.md new file mode 100644 index 000000000..be640295e --- /dev/null +++ b/site/packages/aa-ethers/account-signer/connect.md @@ -0,0 +1,54 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: AccountSigner • connect + - - meta + - name: description + content: Overview of the connect method on AccountSigner in aa-ethers + - - meta + - property: og:description + content: Overview of the connect method on AccountSigner in aa-ethers +--- + +# connect + +`connect` is a method on `AccountSigner` that you can call to connect an `EthersProviderAdapter` to this signer. This lets the returned `AccountSigner` leverage the provider when signing messages, UserOperations, and transactions for a smart contract account using the owner account. + +## Usage + +::: code-group + +```ts [example.ts] +import { signer } from "./ethers-signer"; + +// changing the provider for the signer +const alchemy = new Alchemy({ + apiKey: process.env.API_KEY!, + network: Network.SEPOLIA, // new chain -> new provider +}); +const ethersProvider = await alchemy.config.getProvider(); +const newProvider = EthersProviderAdapter.fromEthersProvider( + ethersProvider, + entryPointAddress +); + +// connecting the signer +const newSigner = signer.connect(newProvider); +``` + +<<< @/snippets/ethers-signer.ts +::: + +## Returns + +### `AccountSigner` + +A new instance of a connected `AccountSigner` + +## Parameters + +### `provider: EthersProviderAdapter` + +the `EthersProviderAdapter` to connect with this `AccountSigner` diff --git a/site/packages/aa-ethers/account-signer/getAddress.md b/site/packages/aa-ethers/account-signer/getAddress.md new file mode 100644 index 000000000..a9c7f4d16 --- /dev/null +++ b/site/packages/aa-ethers/account-signer/getAddress.md @@ -0,0 +1,37 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: AccountSigner • getAddress + - - meta + - name: description + content: Overview of the getAddress method on AccountSigner in aa-ethers + - - meta + - property: og:description + content: Overview of the getAddress method on AccountSigner in aa-ethers +--- + +# getAddress + +`getAddress` is a method on `AccountSigner` that gets the `AccountSigner`'s smart contract account address. + +## Usage + +::: code-group + +```ts [example.ts] +import { signer } from "./ethers-signer"; + +// get the signer's smart contract account address +const client = await signer.getAddress(); +``` + +<<< @/snippets/ethers-signer.ts +::: + +## Returns + +### `Promise
` + +A promise containing the `Signer`'s smart contract account address diff --git a/site/packages/aa-ethers/account-signer/getPublicErc4337Client.md b/site/packages/aa-ethers/account-signer/getPublicErc4337Client.md new file mode 100644 index 000000000..6f5a22db9 --- /dev/null +++ b/site/packages/aa-ethers/account-signer/getPublicErc4337Client.md @@ -0,0 +1,37 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: AccountSigner • getPublicErc4337Client + - - meta + - name: description + content: Overview of the getPublicErc4337Client method on AccountSigner in aa-ethers + - - meta + - property: og:description + content: Overview of the getPublicErc4337Client method on AccountSigner in aa-ethers +--- + +# getPublicErc4337Client + +`getPublicErc4337Client` is a method on `AccountSigner` that gets the underlying viem client which has [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) capability. + +## Usage + +::: code-group + +```ts [example.ts] +import { signer } from "./ethers-signer"; + +// get the signer's underlying viem client with EIP-4337 capabilties +const client = signer.getPublicErc4337Client(); +``` + +<<< @/snippets/ethers-signer.ts +::: + +## Returns + +### `PublicErc4337Client` + +The provider's underlying `PublicErc4337Client` diff --git a/site/packages/aa-ethers/account-signer/introduction.md b/site/packages/aa-ethers/account-signer/introduction.md new file mode 100644 index 000000000..c42185921 --- /dev/null +++ b/site/packages/aa-ethers/account-signer/introduction.md @@ -0,0 +1,51 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: AccountSigner + - - meta + - name: description + content: Overview of the AccountSigner class in aa-ethers + - - meta + - property: og:description + content: Overview of the AccountSigner class in aa-ethers +--- + +# AccountSigner + +`AccountSigner` is an extension of the ethers.js `Signer` which includes a implementation of `ISmartContractAccount` to integrate [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) smart contract accounts. The interface is similar to a standard `Signer`, with additional methods to leverage the Alchemy Account Abstraction stack. + +Notable differences between `EthersProviderAdapter` and `JsonRpcProvider` are implementations for: + +1. [`getAddress`](/packages/aa-ethers/account-signer/getAddress) -- gets the `AccountSigner`'s smart contract account address. +2. [`signMessage`](/packages/aa-ethers/account-signer/signMessage) -- signs messages with the `AccountSigner`'s owner address. +3. [`sendTransaction`](/packages/aa-ethers/account-signer/sendTransaction) -- sends transactions on behalf of the `AccountSigner`'s smart contract account, with request and response formatted as if you were using the ethers.js library. +4. [`getPublicErc4337Client`](/packages/aa-ethers/account-signer/getPublicErc4337Client) -- gets the underlying viem cliemt with ERC-4337 compatability. +5. [`connect`](/packages/aa-ethers/account-signer/connect) -- connects the inputted provider to an account and returns an `AccountSigner`. + +## Usage + +::: code-group + +```ts [example.ts] +import { signer } from "./ethers-signer"; + +// get the signer's smart contract account address +const address = await signer.getAddress(); + +// sign message with the signer's owner address +const signedMessage = await signer.signMessage("test"); + +// sends transaction on behalf of the smart contract account +const txn = await signer.sendTransaction({ + to: "0xRECIPIENT_ADDRESS", + data: "0xDATA", +}); + +// get the signer's underlying viem client with EIP-4337 capabilties +const client = signer.getPublicErc4337Client(); +``` + +<<< @/snippets/ethers-signer.ts +::: diff --git a/site/packages/aa-ethers/account-signer/sendTransaction.md b/site/packages/aa-ethers/account-signer/sendTransaction.md new file mode 100644 index 000000000..a68a370a5 --- /dev/null +++ b/site/packages/aa-ethers/account-signer/sendTransaction.md @@ -0,0 +1,52 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: AccountSigner • sendTransaction + - - meta + - name: description + content: Overview of the sendTransaction method on AccountSigner in aa-ethers + - - meta + - property: og:description + content: Overview of the sendTransaction method on AccountSigner in aa-ethers +--- + +# sendTransaction + +`sendTransaction` is a method on `AccountSigner` that sends transactions on behalf of the `AccountSigner`'s smart contract account, with request and response formatted as if you were using the ethers.js library. + +Note that `to` field of transaction is required, and among other fields of transaction, only `data`, `value`, `maxFeePerGas`, `maxPriorityFeePerGas` fields are considered and optional. Support for other fields is coming soon. + +## Usage + +::: code-group + +```ts [example.ts] +import { signer } from "./ethers-signer"; + +const txHash = await signer.sendTransaction({ + from, // ignored + to, + data: encodeFunctionData({ + abi: ContractABI.abi, + functionName: "func", + args: [arg1, arg2, ...], + }), +}); +``` + +<<< @/snippets/ethers-signer.ts +::: + +## Returns + +### `Promise` + +A Promise containing the ethers.js `TransactionResponse` object + +## Parameters + +### `transaction: Deferrable` + +The ethers.js `TransactionRequest` object, where each field may be a Promise or its value diff --git a/site/packages/aa-ethers/account-signer/signMessage.md b/site/packages/aa-ethers/account-signer/signMessage.md new file mode 100644 index 000000000..84d5be381 --- /dev/null +++ b/site/packages/aa-ethers/account-signer/signMessage.md @@ -0,0 +1,43 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: AccountSigner • signMessage + - - meta + - name: description + content: Overview of the signMessage method on AccountSigner in aa-ethers + - - meta + - property: og:description + content: Overview of the signMessage method on AccountSigner in aa-ethers +--- + +# signMessage + +`signMessage` is a method on `AccountSigner` that signs messages with the `AccountSigner`'s owner address. + +## Usage + +::: code-group + +```ts [example.ts] +import { signer } from "./ethers-signer"; + +// sign message with the signer's owner address +const signedMessage = await signer.signMessage("test"); +``` + +<<< @/snippets/ethers-signer.ts +::: + +## Returns + +### `Promise` + +A Promise containing the hex signture of the message + +## Parameters + +### `msg: string | Uint8Arra` + +The message to sign diff --git a/site/packages/aa-ethers/index.md b/site/packages/aa-ethers/index.md new file mode 100644 index 000000000..aa58f4e3b --- /dev/null +++ b/site/packages/aa-ethers/index.md @@ -0,0 +1,45 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: aa-ethers + - - meta + - name: description + content: aa-ethers landing page and getting started guide + - - meta + - property: og:description + content: aa-ethers landing page and getting started guide +--- + +# `@alchemy/aa-ethers` + +This package contains `EthersProviderAdapter` and `AccountSigner`, respective extensions of the `JsonRpcProvider` and `Signer` classes defined in `ethers.js` external library. If you currently rely `ethers.js` for web3 development, you can use these `ethers.js`-compatible `JsonRpcProvider` and `Signer` to integrate Account Abstraction into your dApp. You may also find the util methods helpful. This repo is community maintained and we welcome contributions! + +## Getting started + +If you are already using the `@alchemy/aa-core` package, you can simply install this package and start using the `EthersProviderAdapter` and `AccountSigner`. If you are not using `@alchemy/aa-core`, you can install it and follow the instructions in the ["Getting Started"](/getting-started) docs to get started. + +::: code-group + +```bash [yarn] +yarn add @alchemy/aa-ethers +``` + +```bash [npm] +npm i @alchemy/aa-ethers +``` + +```bash [pnpm] +pnpm i @alchemy/aa-ethers +``` + +::: + +You can create a provider and connect it to a signer account like so: +::: code-group + +<<< @/snippets/ethers-signer.ts +<<< @/snippets/ethers-provider.ts + +::: diff --git a/site/packages/aa-ethers/provider-adapter/connectToAccount.md b/site/packages/aa-ethers/provider-adapter/connectToAccount.md new file mode 100644 index 000000000..ec667b7d3 --- /dev/null +++ b/site/packages/aa-ethers/provider-adapter/connectToAccount.md @@ -0,0 +1,37 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: EthersProviderAdapter • connectToAccount + - - meta + - name: description + content: Overview of the connectToAccount method on EthersProviderAdapter in aa-ethers + - - meta + - property: og:description + content: Overview of the connectToAccount method on EthersProviderAdapter in aa-ethers +--- + +# connectToAccount + +`connectToAccount` is a method on `EthersProviderAdapter` that you can optionally call to connect the provider to an account and returns a `AccountSigner`. This enables the returned `AccountSigner` to leverage the provider when signing messages, UserOperations, and transactions for a smart contract account using the owner account. + +## Usage + +::: code-group + +<<< @/snippets/ethers-signer.ts +<<< @/snippets/ethers-provider.ts +::: + +## Returns + +### `AccountSigner` + +A new instance of a connected `AccountSigner`for any implementation class of `ISmartContractAccount` + +## Parameters + +### `fn: (rpcClient: PublicErc4337Client) => TAccount extends ISmartContractAccount` + +A function that takes in the provider's rpcClient and returns an AccountSigner diff --git a/site/packages/aa-ethers/provider-adapter/fromEthersProvider.md b/site/packages/aa-ethers/provider-adapter/fromEthersProvider.md new file mode 100644 index 000000000..45c6e7f5f --- /dev/null +++ b/site/packages/aa-ethers/provider-adapter/fromEthersProvider.md @@ -0,0 +1,40 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: EthersProviderAdapter • fromEthersProvider + - - meta + - name: description + content: Overview of the fromEthersProvider method on EthersProviderAdapter in aa-ethers + - - meta + - property: og:description + content: Overview of the fromEthersProvider class on EthersProviderAdapter in aa-ethers +--- + +# fromEthersProvider + +`fromEthersProvider` is a static method on `EthersProviderAdapter` that converts an ethers.js `JsonRpcProvider` to an `EthersProviderAdapter`. + +## Usage + +::: code-group + +<<< @/snippets/ethers-provider.ts +::: + +## Returns + +### `EthersProviderAdapter` + +An instance of `EthersProviderAdapter` + +## Parameters + +### `provider: JsonRpcProvider` + +The ethers JSON RPC provider to convert + +### `entryPointAddress: Address` + +The entrypoint address that will be used for UserOperations diff --git a/site/packages/aa-ethers/provider-adapter/getPublicErc4337Client.md b/site/packages/aa-ethers/provider-adapter/getPublicErc4337Client.md new file mode 100644 index 000000000..9796ef7a3 --- /dev/null +++ b/site/packages/aa-ethers/provider-adapter/getPublicErc4337Client.md @@ -0,0 +1,37 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: EthersProviderAdapter • getPublicErc4337Client + - - meta + - name: description + content: Overview of the getPublicErc4337Client method on EthersProviderAdapter in aa-ethers + - - meta + - property: og:description + content: Overview of the getPublicErc4337Client method on EthersProviderAdapter in aa-ethers +--- + +# getPublicErc4337Client + +`getPublicErc4337Client` is a method on `EthersProviderAdapter` that gets the underlying viem client which has [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) capability. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./ethers-provider"; + +// get the provider's underlying viem client with EIP-4337 capabilties +const client = provider.getPublicErc4337Client(); +``` + +<<< @/snippets/ethers-provider.ts +::: + +## Returns + +### `PublicErc4337Client` + +The provider's underlying `PublicErc4337Client` diff --git a/site/packages/aa-ethers/provider-adapter/introduction.md b/site/packages/aa-ethers/provider-adapter/introduction.md new file mode 100644 index 000000000..b8dccb849 --- /dev/null +++ b/site/packages/aa-ethers/provider-adapter/introduction.md @@ -0,0 +1,56 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: EthersProviderAdapter + - - meta + - name: description + content: Overview of the EthersProviderAdapter class in aa-ethers + - - meta + - property: og:description + content: Overview of the EthersProviderAdapter class in aa-ethers +--- + +# EthersProviderAdapter + +`EthersProviderAdapter` is an extension of the ethers.js `JsonRpcProvider` which includes a `SmartAccountProvider` field to integrate [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) smart contract accounts. The interface is similar to a standard `JsonRpcProvider`, with additional methods to leverage the Alchemy Account Abstraction stack. + +Notable differences between `EthersProviderAdapter` and `JsonRpcProvider` are implementations for: + +1. [`send`](/packages/aa-ethers/provider-adapter/send) -- sends [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193)-compliant requests through the account provider. +2. [`connectToAccount`](/packages/aa-ethers/provider-adapter/connectToAccount) -- connects the provider to an account and returns an `AccountSigner`. +3. [`getPublicErc4337Client`](/packages/aa-ethers/provider-adapter/getPublicErc4337Client) -- gets the underlying viem cliemt with ERC-4337 compatability. +4. [`fromEthersProvider`](/packages/aa-ethers/provider-adapter/fromEthersProvider) -- static method that converts an ethers.js `JsonRpcProvider` to an `EthersProviderAdapter`. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +// EIP-1193 compliant requests +const chainId = await provider.send("eth_chainId", []); + +// get the provider's underlying viem client with EIP-4337 capabilties +const client = provider.getPublicErc4337Client(); + +// connect the provider to an AccountSigner +const owner: SmartAccountSigner = LocalAccountSigner.mnemonicToAccountSigner( + process.env.YOUR_OWNER_MNEMONIC! +); +const signer = provider.connectToAccount( + (rpcClient) => + new LightSmartContractAccount({ + entryPointAddress: entryPointAddress, + chain: polygonMumbai, + factoryAddress: "0xfactoryAddress", + rpcClient, + owner, + }) +); +``` + +<<< @/snippets/ethers-provider.ts +::: diff --git a/site/packages/aa-ethers/provider-adapter/send.md b/site/packages/aa-ethers/provider-adapter/send.md new file mode 100644 index 000000000..da695a6f6 --- /dev/null +++ b/site/packages/aa-ethers/provider-adapter/send.md @@ -0,0 +1,47 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: EthersProviderAdapter • connectToAccount + - - meta + - name: description + content: Overview of the connectToAccount method on EthersProviderAdapter in aa-ethers + - - meta + - property: og:description + content: Overview of the connectToAccount method on EthersProviderAdapter in aa-ethers +--- + +# send + +`send` is a method on `EthersProviderAdapter` that uses that adapter's `SmartAccountProvider`'s [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193)-compliant `request` method. + +## Usage + +::: code-group + +```ts [example.ts] +import { provider } from "./ethers-provider"; + +// EIP-1193 compliant requests +const chainId = await provider.send("eth_chainId", []); +``` + +<<< @/snippets/ethers-provider.ts +::: + +## Returns + +### `Promise` + +The result of the RPC call + +## Parameters + +### `method: string` + +The RPC method to call + +### `params: any[]` + +The params required by the RPC method diff --git a/site/packages/aa-ethers/utils/convertEthersSignerToAccountSigner.md b/site/packages/aa-ethers/utils/convertEthersSignerToAccountSigner.md new file mode 100644 index 000000000..63eb60ef9 --- /dev/null +++ b/site/packages/aa-ethers/utils/convertEthersSignerToAccountSigner.md @@ -0,0 +1,42 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Utils • convertEthersSignerToAccountSigner + - - meta + - name: description + content: Overview of the convertEthersSignerToAccountSigner method in aa-ethers + - - meta + - property: og:description + content: Overview of the convertEthersSignerToAccountSigner method in aa-ethers +--- + +# Utils + +`convertEthersSignerToAccountSigner` converts your ethers.js `Signer` object into an `SmartAccountSigner` by deriving implementations of its methods: `getAddress`, `signMessage`, and `signTypedData`. + +Note that the `signTypedData` implementation is to throw an error since it is not supported by ethers.js `Signer`. If you're looking for an implementation, consideration using [`convertWalletToAccountSigner`](/packages/aa-ethers/utils/convertWalletToAccountSigner). + +## Usage + +::: code-group + +```ts [example.ts] +// note that `signTypedData` is not supported by the Signer class, and so this util method cannot derive an implementation of said method for LocalAccountSigner +const accountSigner = convertEthersSignerToAccountSigner(wallet); +``` + +::: + +## Returns + +### `SmartAccountSigner` + +An instance of `SmartAccountSigner` with implementations derived from the inputted ethers.js `Signer` + +## Parameters + +### `signer: Signer` + +An ethers.js `Signer` object diff --git a/site/packages/aa-ethers/utils/convertWalletToAccountSigner.md b/site/packages/aa-ethers/utils/convertWalletToAccountSigner.md new file mode 100644 index 000000000..ef5072473 --- /dev/null +++ b/site/packages/aa-ethers/utils/convertWalletToAccountSigner.md @@ -0,0 +1,41 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Utils • convertWalletToAccountSigner + - - meta + - name: description + content: Overview of the convertWalletToAccountSigner method in aa-ethers + - - meta + - property: og:description + content: Overview of the convertWalletToAccountSigner method in aa-ethers +--- + +# Utils + +`convertWalletToAccountSigner` converts your ethers.js `Wallet` object into an `SmartAccountSigner` by deriving implementations of its methods: `getAddress`, `signMessage`, and `signTypedData`. + +## Usage + +::: code-group + +```ts [example.ts] +// Wallet is a subclass of Signer, and so can be used with either convertor method +const wallet = new Wallet(process.env.PRIVATE_KEY!); +const accountSigner = convertWalletToAccountSigner(wallet); +``` + +::: + +## Returns + +### `SmartAccountSigner` + +An instance of `SmartAccountSigner` with implementations derived from the inputted ethers.js `Wallet` + +## Parameters + +### `wallet: Wallet` + +An ethers.js `Wallet` object diff --git a/site/packages/aa-ethers/utils/introduction.md b/site/packages/aa-ethers/utils/introduction.md new file mode 100644 index 000000000..96041114f --- /dev/null +++ b/site/packages/aa-ethers/utils/introduction.md @@ -0,0 +1,37 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Utils + - - meta + - name: description + content: Overview of the Utils methods in aa-ethers + - - meta + - property: og:description + content: Overview of the Utils methods in aa-ethers +--- + +# Utils + +`aa-ethers` offers util methods to speed up your development with `EthersProviderAdapter` and `AccountSigner`. + +Notable util methods include: + +1. [`convertWalletToAccountSigner`](/packages/aa-ethers/utils/convertWalletToAccountSigner) -- converts your ethers.js `Wallet` object into an `SmartAccountSigner` by deriving implementations of its methods. +2. [`convertEthersSignerToAccountSigner`](/packages/aa-ethers/utils/convertEthersSignerToAccountSigner) -- converts your ethers.js `Signer` object into an `SmartAccountSigner` by deriving implementations of its methods. + +## Usage + +::: code-group + +```ts [example.ts] +// Wallet is a subclass of Signer, and so can be used with either convertor method +const wallet = new Wallet(process.env.PRIVATE_KEY!); +const accountSigner1 = convertWalletToAccountSigner(wallet); + +// note that `signTypedData` is not supported by the Signer class, and so this util method cannot derive an implementation of said method for LocalAccountSigner +const accountSigner2 = convertEthersSignerToAccountSigner(wallet); +``` + +::: diff --git a/site/packages/overview.md b/site/packages/overview.md new file mode 100644 index 000000000..c95f6e8af --- /dev/null +++ b/site/packages/overview.md @@ -0,0 +1,51 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Package Overview + - - meta + - name: description + content: Overview of the packges available in the Account Kit SDK + - - meta + - property: og:description + content: Overview of the packges available in the Account Kit SDK +--- + +# Package Overview + +The Alchemy Account Kit SDK is comprised of a number of smaller packages that developers can leverage to interact with [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) infrastructure. For almost all cases, `aa-core` is sufficient with the subsequent packages offering various utilities for interacting with specific Account Abstraction Infrastructure or Smart Accounts. + +## [`aa-core`](/packages/aa-core/) + +This package contains the core interfaces and components for interacting with 4337 infrastructure. The primary interfaces that it exports are the `SmartAccountProvider` and `BaseSmartContractAccount`. + +The `SmartAccountProvider` is an [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) compliant Provider that wraps JSON RPC methods and some Wallet Methods (signing, sendTransaction, etc). With this Provider, you can submit User Operations to RPC providers, estimate gas, configure a Paymaster and more. It is not opinionated about which RPC provider you are using and is configurable to work with any RPC provider. Because it implements EIP-1193, it can be used with any web3 library. + +The `BaseSmartContractAccount` interface is used to define how you would interact with your Smart Contract Account. The methods exposed and implemented by a class the implements `BaseSmartContractAccount` allow the `SmartAccountProvider` to provide ergonic utilities for building and submitting User Operations. + +For more details on all the utilities exported by `aa-core` see the [aa-core documentation](/packages/aa-core/). + +## [`aa-alchemy`](/packages/aa-alchemy/) + +This package builds on `aa-core` by exporting an `AlchemyProvider` which extends `SmartAccountProvider` and adds some additional utilities for interacting with Alchemy's RPCs and Alchemy's `rundler`. The Provider also exports utilities for leveraging Alchemy's Gas Manager. + +**If you are using Alchemy's RPCs you have to use this package.** This is due to the specifics around how Alchemy's bundler does gas estimation. Not using this package and it's provider can result in incorrect gas estimations and failed transactions. + +For more details on all the utilities exported by `aa-alchemy` see the [aa-alchemy documentation](/packages/aa-alchemy/). + +## [`aa-accounts`](/packages/aa-accounts/) + +This packages provides various implementations of `BaseSmartContractAccount` for interacting with different Smart Accounts. This package is not required to use `aa-core` or `aa-alchemy`. If you want to use your own Smart Account implementation, you can do so by following the guide ["Using Your Own Account"](/smart-accounts/accounts/using-your-own). + +If you'd like to use a Smart Account that is not supported by this package, you can implement `BaseSmartContractAccount` yourself and use it with `aa-core` or `aa-alchemy` + +For details on contributing your own Smart Account implementation, see the [aa-accounts contribution guide](/packages/aa-accounts/contributing). + +To see all of the Smart Accounts that are supported by this package, see the [aa-accounts documentation](/packages/aa-accounts/). + +## [`aa-ethers`](/packages/aa-ethers/) + +This package provides an adapter that allows you to convert a `SmartAccountProvider` or `AlchemyProvider` into an ethers `JsonRpcProvider` and `Signer`. These are primarily for convenience if your codebase expects a `JsonRpcProvider` or `Signer` in places and you want to use `aa-core` or `aa-alchemy` with minimal lift. + +It is not required to use `aa-ethers` even if you are using `ethers` as your web3 library. Because the `SmartAccountProvider` is an EIP-1193 compliant provider, you can always wrap it in an ethers [`Web3Provider`](https://docs.ethers.org/v5/api/providers/other/#Web3Provider) and use it as a `Signer` or `JsonRpcProvider`. diff --git a/site/postcss.config.cjs b/site/postcss.config.cjs new file mode 100644 index 000000000..12a703d90 --- /dev/null +++ b/site/postcss.config.cjs @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/site/public/alchemy.svg b/site/public/alchemy.svg new file mode 100644 index 000000000..9fabdbc2f --- /dev/null +++ b/site/public/alchemy.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/site/public/arrow-right.svg b/site/public/arrow-right.svg new file mode 100644 index 000000000..58e253f24 --- /dev/null +++ b/site/public/arrow-right.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/site/public/favicon.ico b/site/public/favicon.ico new file mode 100644 index 000000000..ce604be09 Binary files /dev/null and b/site/public/favicon.ico differ diff --git a/site/public/fb.svg b/site/public/fb.svg new file mode 100644 index 000000000..72b4fb9ec --- /dev/null +++ b/site/public/fb.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/site/public/kit-logo.svg b/site/public/kit-logo.svg new file mode 100644 index 000000000..d09fe4165 --- /dev/null +++ b/site/public/kit-logo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/site/public/linkedin.svg b/site/public/linkedin.svg new file mode 100644 index 000000000..301603238 --- /dev/null +++ b/site/public/linkedin.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/site/public/twitter.svg b/site/public/twitter.svg new file mode 100644 index 000000000..c7164a29d --- /dev/null +++ b/site/public/twitter.svg @@ -0,0 +1,4 @@ + + + + diff --git a/site/smart-accounts/accounts/light-account.md b/site/smart-accounts/accounts/light-account.md new file mode 100644 index 000000000..ef35bd9e4 --- /dev/null +++ b/site/smart-accounts/accounts/light-account.md @@ -0,0 +1,49 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Using Light Account + - - meta + - name: description + content: Using Light Account with Account Kit + - - meta + - property: og:description + content: Using Light Account with Account Kit +--- + +# Light Account + +Light Account is a secure, gas-optimized, [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) smart account implementation. + +We started with the Ethereum Foundation’s canonical [SimpleAccount](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/samples/SimpleAccount.sol) and added key improvements for production app developers: + +- significantly reduced gas costs +- [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) signature support to ensure users can sign messages, such as on OpenSea +- ownership transfer so that users won’t get locked into a single `Signer` + +## Using Light Account + +The code snippet below demonstrates the usage of Light Account with Account Kit. It creates a Light Account and sends a `UserOperation` from it: + + + +## Benchmarks + +Here are some benchmarks for the Light Account and other smart account implementations you might consider: + +| Account | Creation | Native transfer | ERC20 transfer | Total Gas | +| -------------------- | -------- | --------------- | -------------- | --------- | +| Alchemy LightAccount | 279746 | 100844 | 90345 | 470935 | +| Kernel v2.1-lite | 230968 | 101002 | 90321 | 422291 | +| Kernel v2.1 | 265215 | 106460 | 96038 | 467713 | +| Biconomy | 270013 | 104408 | 93730 | 468151 | +| Etherspot | 279219 | 103719 | 93324 | 476262 | +| Kernel v2.0 | 339882 | 110018 | 99622 | 549522 | +| SimpleAccount | 383218 | 101319 | 90907 | 575444 | + +## Developer Links + +[LightAccount Github Repo](https://github.com/alchemyplatform/light-account) + +[Quantstamp Audit Report](https://github.com/alchemyplatform/light-account/blob/main/Quantstamp-Audit.pdf) diff --git a/site/smart-accounts/accounts/modular-account.md b/site/smart-accounts/accounts/modular-account.md new file mode 100644 index 000000000..6accd6596 --- /dev/null +++ b/site/smart-accounts/accounts/modular-account.md @@ -0,0 +1,19 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Using Modular Account + - - meta + - name: description + content: Using Modular Account with Account Kit + - - meta + - property: og:description + content: Using Modular Account with Account Kit +--- + +# Modular Account + +Support for Modular Accounts is coming soon. Modular Accounts are based on the `EIP-6900` spec and offer a lot more flexibility and extensibility. You can read more about EIP-6900 [here](/erc-6900). + +We’re currently collaborating with the community and plan to finalize the ERC soon. LightAccount is fully forward-compatible with ERC-6900. We’ll release an upgrade once the ERC is finalized. diff --git a/site/smart-accounts/accounts/overview.md b/site/smart-accounts/accounts/overview.md new file mode 100644 index 000000000..7b03c6259 --- /dev/null +++ b/site/smart-accounts/accounts/overview.md @@ -0,0 +1,50 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Choosing a Smart Account + - - meta + - name: description + content: Choosing a Smart Account Overview + - - meta + - property: og:description + content: Choosing a Smart Account Overview +next: + text: Choosing a Signer + link: /smart-accounts/signers/overview +--- + +# Choosing a Smart Account + +## What's a Smart Account? + +A smart account is a smart contract controlled by an account owner or other authorized entities. You can use it to manage assets, carry out transactions (known as `userOperations` or `userOps`), and more. There are many different implementations of a Smart Account, including Alchemy's [Light Account](/smart-accounts/accounts/light-account). + +## Kick Off with Light Account + +The Light Account offers a straightforward, secure, and cost-effective smart account implementation. It comes equipped with features like owner transfers, [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) message signing, and batched transactions. For most applications, we recommend using the Light Account. + +Here's a snippet that demonstrates how to work with the Light Account using Account Kit. This snippet sets up a Light Account and initiates a `UserOperation` from it: + + + +## Modular Account Implementation + +The Alchemy team is actively developing the Modular Account Implementation, which adheres to the [ERC-6900](https://eips.ethereum.org/EIPS/eip-6900) standard. It's set to include all the features of the Light Account and additional functionalities. Additionally, because ERC-6900 is [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) compliant, the Modular Account Implementation is well-suited to the ERC-4337 ecosystem. The `LightAccount` design is forward-compatible with `ModularAccount`. Until `ModularAccount` is available, it's a good practice to use `LightAccount`. + +If the Light Account doesn't fit your specific needs, you can always use your own smart account implementation with Account Kit. For detailed guidance on this, refer to the guide on [Using Your Own Account Implementation](/smart-accounts/accounts/using-your-own). + +## Benchmarks + +Here are some benchmarks for the Light Account vs other smart account implementations: + +| Account | Creation | Native transfer | ERC20 transfer | Total Gas | +| -------------------- | -------- | --------------- | -------------- | --------- | +| Alchemy LightAccount | 279710 | 100844 | 90345 | 470899 | +| Kernel v2.1-lite | 230968 | 101002 | 90321 | 422291 | +| Kernel v2.1 | 265215 | 106460 | 96038 | 467713 | +| Biconomy | 270013 | 104408 | 93730 | 468151 | +| Etherspot | 279219 | 103719 | 93324 | 476262 | +| Kernel v2.0 | 339882 | 110018 | 99622 | 549522 | +| SimpleAccount | 383218 | 101319 | 90907 | 575444 | diff --git a/site/smart-accounts/accounts/using-your-own.md b/site/smart-accounts/accounts/using-your-own.md new file mode 100644 index 000000000..b076c6a57 --- /dev/null +++ b/site/smart-accounts/accounts/using-your-own.md @@ -0,0 +1,60 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Using Your Own Account + - - meta + - name: description + content: How to extend Account Kit to work with your own account + - - meta + - property: og:description + content: How to extend Account Kit to work with your own account +--- + +# Using Your Own Account + +You are not limited to the accounts defined in `@alchemy/aa-accounts`. The `SmartAccountProvider` can be used with any Smart Contract Account because it only relies on the [`ISmartContractAccount`](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/core/src/account/types.ts#L8) interface. This means you can use your own Smart Contract Account implementation with the Account Kit. + +## Implementing `ISmartContractAccount` + +Let's take a look at [`BaseSmartContractAccount`](https://github.com/alchemyplatform/aa-sdk/blob/main/packages/core/src/account/base.ts) and walk through an example of implementing an interface to work with your own Smart Contract Account. + +### 1. Extend `BaseSmartContractAccount` + +In `aa-core`, we provide an abstract class that handles most of the `ISmartContractAccount` interface, making this very simple! + +The `BaseSmartContractAccount` class leaves four methods as abstract for you to implement in your own class: + +::: details Click to expand +<<< @/../packages/core/src/account/base.ts#abstract-methods +::: + +### 2. [Optional] Implement Additional methods from `BaseSmartContractAccount` + +The `BaseSmartContractAccount` class also exposes some additional implementations that by defaul will throw an error if not implemented. You can override these methods to provide your own implementation: + +::: details Click to expand +<<< @/../packages/core/src/account/base.ts#optional-methods +::: + +### 3. [Optional] Contribute to `aa-accounts`! + +See ["Contributing to `aa-accounts`"](/packages/aa-accounts/contributing) for more information on how to contribute your own Smart Contract Account implementation to `aa-accounts`. + +## `LightSmartContractAccount` as an Example + +The team at Alchemy has built an extension of the eth-infinitism `SimpleAccount` called [LightAccount.sol](https://github.com/alchemyplatform/light-account/blob/main/src/LightAccount.sol). You can learn more about the Light Account in the [Light Account documentation](/smart-accounts/accounts/light-account). + +We provide an implementation of `ISmartContractAccount` that works with `LightAccount.sol` which can be used as an example of how to implement your own Smart Contract Account: +::: details LightSmartContractAccount +<<< @/../packages/accounts/src/light-account/account.ts +::: + +## The `ISmartContractAccount` Interface + +For your reference, this is the definition of the `ISmartContractAccount` interface as pulled from the source code: + +::: details ISmartContractAccount +<<< @/../packages/core/src/account/types.ts +::: diff --git a/site/smart-accounts/batching-transactions.md b/site/smart-accounts/batching-transactions.md new file mode 100644 index 000000000..805e1d8d6 --- /dev/null +++ b/site/smart-accounts/batching-transactions.md @@ -0,0 +1,74 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Batching Transactions + - - meta + - name: description + content: Guide to submitting Transactions in batches with Account Kit + - - meta + - property: og:description + content: Guide to submitting Transactions in batches with Account Kit +--- + +# Batching Transactions + +One benefit of Smart Contract Accounts is that it's possible to batch transactions in one User Operation. Not all Smart Contract Accounts support batching. But, if the account you're using does, then implementations of `SmartAccountProvider` will allow you to make those calls. + +There are two ways you can batch transactions using `SmartAccountProvider`: + +1. via `sendUserOperation` +2. via `sendTransactions` + +## `sendUserOperation` + +The `SmartAccountProvider` supports passing either a single `UserOperation` or an array of `UserOperation`s to `sendUserOperation`. If you pass an array, the provider will batch the transactions into a single User Operation and submit it to the network. Let's see an example: + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +// the hash returned here is the hash of the User Operation +const { hash } = await provider.sendUserOperation([ + { + target: "0x...", + data: "0xcallDataTransacation1", + }, + { + target: "0x...", + data: "0xcallDataTransacation2", + }, +]); +``` + +<<< @/snippets/provider.ts + +::: + +## `sendTransactions` + +The `SmartAccountProvider` supports sending `UserOperation`s and waiting for them to be mined in a transaction via the `sendTransaction` and `sendTransactions` methods. The latter allows for batching in the same way `sendUserOperation`: + +::: code-group + +```ts [example.ts] +import { provider } from "./provider"; +// [!code focus:99] +// the hash returned here is the hash of the mined Tx that includes the UserOperation +const hash = await provider.sendTransactions([ + { + to: "0x...", + data: "0xcallDataTransacation1", + }, + { + to: "0x...", + data: "0xcallDataTransacation2", + }, +]); +``` + +<<< @/snippets/provider.ts + +::: diff --git a/site/smart-accounts/overview.md b/site/smart-accounts/overview.md new file mode 100644 index 000000000..187124f92 --- /dev/null +++ b/site/smart-accounts/overview.md @@ -0,0 +1,54 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Using Smart Accounts + - - meta + - name: description + content: The end-to-end process of integrating smart accounts in your applications with Account Kit. + - - meta + - property: og:description + content: The end-to-end process of integrating smart accounts in your applications with Account Kit. +--- + +# Overview + +In this guide we'll explain the end-to-end journey of integrating smart accounts in your applications with Account Kit. We'll cover the necessary steps such as creating an Alchemy account, selecting the right account and signer and sending a User Operation. Additionally, we'll touch upon advanced functionalities like sponsoring gas, batching transactions and transferring ownership. + +## 1. Setting Up an Alchemy Account + +Before diving into smart accounts, it's important to [set up your Alchemy account](https://auth.alchemy.com/signup). This will allow you to access the Alchemy API key which is required to initialize a provider and interact with the blockchain. Additionally, you'll get access to Alchemy's Gas Manager, which will enable you to sponsor gas for your users. + +## 2. Choosing a Smart Account + +The next step is to select the right smart account implementation for your application. We recommend using `LightAccount`, which is a simple, secure, and cost-effective solution for most use cases. It supports features such as owner transfers, [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) message signing, batched transactions and more. However, if you're looking for more advanced features, you can use your own account implementation. We'll cover both options in detail in the following sections: + +- [Using Light Account](accounts/light-account) +- [Using your own account implementation](accounts/using-your-own) + +::: tip Note +The `LightAccount` implementation is not [ERC-6900](/erc-6900) compliant. The `ModularAccount` implementation is launching later this year and will be EIP-6900 compatible. However, `LightAccount` is forward-compatible with `ModularAccount` and can be upgraded to it in the future. +::: + +## 3. Choosing a Signer + +A signer is the entity that signs transactions (User Operations) on behalf of the smart account. It can be an EOA, a custodial service, or a multi-party computation (MPC) service. We explain the different types of signers in detail in the [overview](signers/overview) section on choosing a signer. We'll also cover the common signer examples in detail in the following sections: + +- [Magic Link](signers/magic-link) +- [Web3Auth](signers/web3auth) +- [Externally Owned Account](signers/eoa) + +At this point you should be able to integrate smart accounts in your application. However, there are a few advanced features that can help you improve the user experience and save on gas costs. Information about these can be found in the subsequent sections. + +## 4. Sponsoring Gas + +Being able to sponsor gas for your users is one of the most powerful features enabled by smart accounts and can help you build a seamless user experience. We'll cover this in detail with code examples in the [Sponsoring Gas](sponsoring-gas) section. + +## 5. Batching Transactions + +Transaction batching allows you to bundle multiple transaction calls into a single User Operation and execute them in a single atomic transaction. This can help you save on gas costs and improve the user experience. We'll cover this in detail with code examples in the [Batching Transactions](batching-transactions) section. + +## 6. Transferring Ownership + +Ownership is an important aspect of smart accounts. The Light Account implementation allows you to transfer the ownership of a smart account to another entity. We'll cover this in detail with code examples in the [Transferring Ownership](transferring-ownership) section. diff --git a/site/smart-accounts/signers/capsule.md b/site/smart-accounts/signers/capsule.md new file mode 100644 index 000000000..e627634bd --- /dev/null +++ b/site/smart-accounts/signers/capsule.md @@ -0,0 +1,95 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Capsule + - - meta + - name: description + content: Guide to use Capsule as a signer + - - meta + - property: og:description + content: Guide to use Capsule as a signer +--- + +# Capsule + +[Capsule](https://usecapsule.com/) is a signing solution that you can use to create secure, embedded [MPC wallets](https://www.alchemy.com/overviews/mpc-wallet) with just an email or a social login. Capsule-enabled wallets are portable across applications, recoverable, and programmable, so your users do not need to create different signers or contract accounts for every application they use. + +Combining Capsule with Account Kit allows you to get the best of both on and off-chain programmability. You can use Capsule as a signer to create a wallet that works across apps, and then connect it to Account Kit to create expressive smart accounts for your users! + +## Integration + +Follow these steps to begin integrating Capsule: + +1. Obtain access to the Capsule SDK and an API key by completing [this form](https://form.typeform.com/to/hLaJeYJW). +2. For further assistance or if you wish to add more permissions or automation, consult the [complete Capsule developer documentation](https://docs.usecapsule.com) or contact hello@usecapsule.com. + +### Install the SDK + +Web +::: code-group + +```bash [npm] +npm i -s @usecapsule/web-sdk +``` + +```bash [yarn] +yarn add @usecapsule/web-sdk +``` + +::: + +React Native +::: code-group + +```bash [npm] +npm i -s @usecapsule/react-native-sdk +``` + +```bash [yarn] +yarn add @usecapsule/react-native-sdk +``` + +::: + +### Create a SmartAccountSigner + +Next, setup the Capsule SDK and create a `SmartAccountSigner` + +<<< @/snippets/capsule.ts + +### Use it with LightAccount + +Let's see it in action with `aa-alchemy` and `LightSmartContractAccount` from `aa-accounts`: +::: code-group + +```ts [alchemy.ts] +import { AlchemyProvider } from "@alchemy/aa-alchemy"; +import { + LightSmartContractAccount, + getDefaultLightAccountFactory, +} from "@alchemy/aa-accounts"; +import { sepolia } from "viem/chains"; +import { capsuleSigner } from "./capsule"; + +const chain = sepolia; +const provider = new AlchemyProvider({ + apiKey: "ALCHEMY_API_KEY", + chain, + entryPointAddress: "0x...", +}).connect( + (rpcClient) => + new LightSmartContractAccount({ + entryPointAddress: "0x...", + chain: rpcClient.chain, + owner: capsuleSigner, + factoryAddress: getDefaultLightAccountFactory(chain), + rpcClient, + }) +); +``` + +<<< @/snippets/capsule.ts + +::: diff --git a/site/smart-accounts/signers/contributing.md b/site/smart-accounts/signers/contributing.md new file mode 100644 index 000000000..6c8b3b486 --- /dev/null +++ b/site/smart-accounts/signers/contributing.md @@ -0,0 +1,44 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Contributing to Signer Docs + - - meta + - name: description + content: How to contribute a signer to the docs + - - meta + - property: og:description + content: How to contribute a signer to the docs +--- + +# Adding Your Signer to This List + +If you'd like to add your signer to this list, we welcome PRs! Here's how to do it: + +1. Fork this [repo](https://github.com/OMGWINNING/aa-sdk-staging) +2. In [`site/.vitepress/config.ts`](https://github.com/OMGWINNING/aa-sdk-private/blob/main/site/.vitepress/config.ts), there is a `sidebar` property. Find the `Choosing a Signer` item and add a new entry in `items`. The `text` property of the entry is what will be visible in the sidebar and the `link` property should be `kebab-case`. This should match the file name in the next step. Place it above the `Externally Owned Account` guide. eg: + +```ts{9} +{ + sidebar: [ + // ... other entries + { + text: "Choosing a Signer", + base: "/smart-accounts/signers", + items: [ + // ... other entries + { text: "My New Signer", link: "/my-new-signer" }, + { text: "Externally Owned Account", link: "/eoa" }, + { text: "Using Your Own", link: "/using-your-own" }, + { text: "Contributing", link: "/contributing" }, + ], + }, + ]; +} +``` + +3. Add your document to [`site/smart-accounts/signers/`](https://github.com/OMGWINNING/aa-sdk-staging/tree/main/site/smart-accounts/signers) and name it `your-signer-name.md` (the name should match the `link` property you added in the previous step) +4. Open a PR! + +If your `Signer` or library exports an `EIP-1193` compliant provider, you can use the `WalletClientSigner` from `aa-core` to easily integrate with Account Kit. See the ["Using Your Own Signer"](/smart-accounts/signers/using-your-own) guide for more details. diff --git a/site/smart-accounts/signers/eoa.md b/site/smart-accounts/signers/eoa.md new file mode 100644 index 000000000..7078157be --- /dev/null +++ b/site/smart-accounts/signers/eoa.md @@ -0,0 +1,70 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: EOA + - - meta + - name: description + content: Guide to using and EOA as a signer + - - meta + - property: og:description + content: Guide to using and EOA as a signer +--- + +# Externally Owned Accounts + +An Externally Owned Account (EOA) is a regular Ethereum account that is controlled by a private key. This is the most common type of account, and is what you are used to when using MetaMask or other wallets. The Account Kit supports EOAs as signers and the process for connecting an EOA is simple, but can depend on how you are connecting to the EOA in your dApp. + +## Integration + +In this example we'll use `viem` in two ways. The first way allows you to connect to an EOA over JSON RPC and the second allows you to connect to so called "Local Accounts". A Local Account is an EOA for which you have access to the private key on the client. + +### JSON RPC + +A JSON RPC based account is one where the key material is not available locally, but on some external client (eg. Metamask extension or hardware wallet). + +```ts +import { createWalletClient, custom } from "viem"; +import { mainnet } from "viem/chains"; +import { WalletClientSigner } from "@alchemy/core"; + +const client = createWalletClient({ + chain: mainnet, + transport: custom(window.ethereum), +}); + +// this can now be used as an owner for a Smart Contract Account +export const eoaSigner = new WalletClientSigner( + client, + "json-rpc" //signerType +); +``` + +### Local Account + +In this example we assume you have access to the private key locally. + +```ts +import { createWalletClient, http } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +// import { mnemonicToAccount } from "viem/accounts"; +import { mainnet } from "viem/chains"; +import { WalletClientSigner } from "@alchemy/core"; + +// if you have a mnemonic, viem also exports a mnemonicToAccount function (see above import) +const account = privateKeyToAccount("0x..."); + +// This client can now be used to do things like `eth_requestAccounts` +export const client = createWalletClient({ + account, + chain: mainnet, + transport: http(), +}); + +// this can now be used as an owner for a Smart Contract Account +export const eoaSigner = new WalletClientSigner( + client, + "local" // signerType +); +``` diff --git a/site/smart-accounts/signers/fireblocks.md b/site/smart-accounts/signers/fireblocks.md new file mode 100644 index 000000000..8fe7572c4 --- /dev/null +++ b/site/smart-accounts/signers/fireblocks.md @@ -0,0 +1,75 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Fireblocks + - - meta + - name: description + content: Guide for using Fireblocks as a signer + - - meta + - property: og:description + content: Guide for using Fireblocks as a signer +--- + +# Fireblocks + +[Fireblocks](https://www.fireblocks.com/) is an enterprise grade MPC wallet provider providing industry's most secure custodial and non-custodial wallets. Fireblocks has created a multi-layer security matrix that layers MPC, secure enclaves, our signature Policy Engine, and an asset transfer network to provide the strongest software and hardware defense available against evolving attack vectors. + +Fireblocks' security structure provides a truly secure environment for storing, transferring, and issuing digital assets. This ensures that your assets are protected from cyberattacks, internal colluders, and human errors. As a result, Fireblocks serves as the foundation for thousands of digital asset businesses and has securely transferred over $3T in digital assets. + +Fireblocks' MPC wallets are EOA accounts, which in any account abstraction enabled wallet is the root of their security & trust model. Using Fireblocks MPC based EOA wallets in combination with the Alchemy Account Kit will give you the best of both worlds; Enterprise grade security for securing your off-chain key material, and the utmost flexibility of your on-chain Smart Accounts + +# Integrataion + +### Install the Fireblocks Web3 Provider + +::: code-group + +```bash [npm] +npm i -s @fireblocks/fireblocks-web3-provider +``` + +```bash [yarn] +yarn add @fireblocks/fireblocks-web3-provider +``` + +::: + +### Create a SmartAccountSigner + +Next, setup the Fireblocks Web3 Provider and create a `SmartAccountSigner`: + +<<< @/snippets/fireblocks.ts + +### Use it with LightAccount + +Let's see it in action with `aa-alchemy` and `LightSmartContractAccount` from `aa-accounts`: +::: code-group + +```ts [example.ts] +import { AlchemyProvider } from "@alchemy/aa-alchemy"; +import { LightSmartContractAccount } from "@alchemy/aa-accounts"; +import { sepolia } from "viem/chains"; +import { fireblocksSigner } from "./fireblocks"; + +const chain = sepolia; +const provider = new AlchemyProvider({ + apiKey: "ALCHEMY_API_KEY", + chain, + entryPointAddress: "0x...", +}).connect( + (rpcClient) => + new LightSmartContractAccount({ + entryPointAddress: "0x...", + chain: rpcClient.chain, + owner: fireblocksSigner, + factoryAddress: "0x...", + rpcClient, + }) +); +``` + +<<< @/snippets/fireblocks.ts + +::: diff --git a/site/smart-accounts/signers/lit.md b/site/smart-accounts/signers/lit.md new file mode 100644 index 000000000..ed5d29bb2 --- /dev/null +++ b/site/smart-accounts/signers/lit.md @@ -0,0 +1,95 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Lit Protocol + - - meta + - name: description + content: Guide to use a PKP Wallet as a signer + - - meta + - property: og:description + content: Guide to use a PKP Wallet as a signer +--- + +# Lit Protocol + +[LitProtocol](https://litprotocol.com/) is distributed cryptography for signing, encryption, and compute. A generalizable key management network, Lit provides developers with a set of tools for managing sovereign identities on the open Web. + +Combining Lit Protocol's [pkp wallet](https://www.npmjs.com/package/@lit-protocol/pkp-ethers) with Account Kit allows you to use your Programmable Key Pairs (PKPs) as a smart account for your users. + +::: warning + +Lit Protocol's pkp network is still in testnet. Backwards compatibility, and data availability will not be guaranteed until mainnet. Do not use PKP wallets to store valuable assets. + +::: + +# Integration + +### Install the pkp ethers package + +::: code-group + +```bash [npm] +npm i @lit-protocol/pkp-ethers +``` + +```bash [yarn] +yarn add @lit-protocol/pkp-ethers +``` + +::: + +### Install the LitNodeClient + +::: code-group + +```bash [npm] +npm i @lit-protocol/lit-node-client +``` + +```bash [yarn] +yarn add @lit-protocol/lit-node-client +``` + +::: + +### Creating PKP + +See documentation [here](https://developer.litprotocol.com/v2/pkp/intro) for creating PKPs + +### Create a SmartAccountSigner + +Next, setup the `LitNodeClient` and `PKPEthersWallet` to create a `SmartAccountSigner`: + +<<< @/snippets/lit.ts + +### Use it with LightAccount + +We can link our `SmartAccountSigner` to a `LightSmartContractAccount` from `aa-accounts`: +::: code-group + +```ts [example.ts] +import { AlchemyProvider } from "@alchemy/aa-alchemy"; +import { LightSmartContractAccount } from "@alchemy/aa-accounts"; +import { litSigner } from "./lit"; + +const chain = sepolia; +const provider = new AlchemyProvider({ + apiKey: "ALCHEMY_API_KEY", + chain, + entryPointAddress: "0x...", +}).connect( + (rpcClient) => + new LightSmartContractAccount({ + entryPointAddress: "0x...", + chain: rpcClient.chain, + owner: litSigner, + factoryAddress: "0x...", + rpcClient, + }) +); +``` + +<<< @/snippets/lit.ts +::: diff --git a/site/smart-accounts/signers/magic-link.md b/site/smart-accounts/signers/magic-link.md new file mode 100644 index 000000000..18be0695b --- /dev/null +++ b/site/smart-accounts/signers/magic-link.md @@ -0,0 +1,76 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Magic Link + - - meta + - name: description + content: Guide to use Magic.Link as a signer + - - meta + - property: og:description + content: Guide to use Magic.Link as a signer +--- + +# Magic Link + +[Magic](https://magic.link) is an embedded wallet provider that allows users to generate wallets scoped to your application via Social Logins, Email OTP, or Webauthn. This is great for enabling a better experience for your users. But ultimately these wallets are not much different from EOA's, so you don't have the benefit of Account Abstraction (gas sponsorship, batching, etc). + +Combining Magic with Account Kit allows you to get the best of both worlds. You can use Magic to generate a wallet scoped to your application, and then use Account Kit to create Smart Contract Accounts for your users! + +## Integration + +### Install the SDK + +::: code-group + +```bash [npm] +npm i -s magic-sdk +``` + +```bash [yarn] +yarn add magic-sdk +``` + +::: + +### Create a SmartAccountSigner + +Next, setup the magic sdk and create a `SmartAccountSigner`: + +<<< @/snippets/magic.ts + +### Use it with LightAccount + +Let's see it in action with `aa-alchemy` and `LightSmartContractAccount` from `aa-accounts`: +::: code-group + +```ts [example.ts] +import { AlchemyProvider } from "@alchemy/aa-alchemy"; +import { + LightSmartContractAccount, + getDefaultLightAccountFactory, +} from "@alchemy/aa-accounts"; +import { sepolia } from "viem/chains"; +import { magicSigner } from "./magic"; + +const chain = sepolia; +const provider = new AlchemyProvider({ + apiKey: "ALCHEMY_API_KEY", + chain, + entryPointAddress: "0x...", +}).connect( + (rpcClient) => + new LightSmartContractAccount({ + entryPointAddress: "0x...", + chain: rpcClient.chain, + owner: magicSigner, + factoryAddress: getDefaultLightAccountFactory(chain), + rpcClient, + }) +); +``` + +<<< @/snippets/magic.ts + +::: diff --git a/site/smart-accounts/signers/overview.md b/site/smart-accounts/signers/overview.md new file mode 100644 index 000000000..aef167738 --- /dev/null +++ b/site/smart-accounts/signers/overview.md @@ -0,0 +1,129 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Choosing a Signer + - - meta + - name: description + content: An overview of Signers in Account Kit and how to choose one + - - meta + - property: og:description + content: An overview of Signers in Account Kit and how to choose one +prev: + text: Choosing a Smart Account + link: /smart-accounts/accounts/overview +next: + text: Sponsoring Gas + link: /smart-accounts/sponsoring-gas +--- + +# What is a Signer? + +A **signer** is a service (e.g. Magic or Turnkey) or application (e.g. Metamask) that manages the private key and signs operations. Most web3 users today use an [Externally Owned Account (EOA)](https://ethereum.org/en/developers/docs/accounts/#externally-owned-accounts-and-key-pairs) with a self-custodial signer such as Metamask to manage the private key. + +With Account Kit, you will deploy a **smart account** for each user instead of an EOA wallet. This smart account stores the user’s assets (e.g. tokens or NFTs). The default smart account in Account Kit is called [`LightAccount`](/smart-accounts/accounts/light-account) and it uses a typical single-owner architecture. + +The smart account is controlled by an **Owner** address. The smart account will only execute a transaction if it was signed by the owner’s private key. + +You can choose any signer service or application to manage the Owner private key for the user. Using services like Magic, Turnkey, or Web3auth, you can secure the user’s account with an email, social login, or passkeys. You can also use a self-custodial wallet like Metamask as the signer. + +This doc provides a basic introduction to signers and the criteria you should consider when choosing which signer to use with Account Kit in your application. + +## Role of a Signer + +The signer plays a crucial role in your app because it controls the user’s smart account. The signer is responsible for: + +- Securely storing the user’s private key which controls the user’s assets +- Authenticating the user +- Protecting the user’s account from phishing attacks +- Signing user operations requested by the user, if and only if the user has authenticated +- Optionally offering account recovery methods + +## Types of Signers + +### Non-custodial Wallets + +Non-custodial wallet providers store private keys such that they cannot access the private key without the user’s involvement. For example, the user must provide a password or passkey that only they know in order to decrypt the private key stored by the provider. Users benefit from heightened security, while remaining in control of their private keys at all times. This is similar to a safety deposit box vault: the provider secures the bank vault but only the user has access to the individual safety deposit boxes (e.g. wallets). + +**Example**: Turnkey, Magic + +### MPC Wallets (non-custodial) + +Multi-Party Computation (MPC) signers split the Owner Account private key into key shares that are then distributed to a number of share holders. Share holders only know the value of their key share and transaction holders can sign transactions without revealing their key shares to other holders. + +Valid signatures do not always require all shares to sign a transaction. MPC signers can set a threshold, requiring a certain number of shares for a signature to be valid. Common configurations are 2 of 2 shares or 2 of 3 shares. By requiring multiple shares, MPC models mitigate the risks associated with a single key being compromised. + +Some MPC signers provide recovery services in which key share(s) are backed up in the service provider’s cloud, on the end user’s device, or in the end user’s cloud (e.g. iCloud or Google Drive). When evaluating an MPC provider, it’s important to under where each key share is stored. + +**Example**: Web3Auth, Privy, Fireblocks MPC, Portal, Capsule + +::: details TSS vs SSSS + +There are two common approaches to MPC. + +Traditionally, MPC services leveraged SSSS (Shamir’s Secret Shard Sharing). This approach generates a private key in one location and then the shares are distributed to the parties involved. When a user wants to sign, they need to retrieve N of M shares and reconstruct the key locally. + +An improvement on SSSS is Threshold Signature Scheme (TSS). In this model, the key is never recreated during signing. Instead, each party is given the message to sign and then signs the payload locally before broadcasting the signature to the rest of the group. This allows for the key material to remain private and deconstructed. + +TSS is safer than SSSS because is possible to create the initial shares without ever constructing the original key on any one device. However, the tradeoff is that signing requires a Peer-to-Peer exchange which introduces latency. + +::: + +### Decentralized MPC network (non-custodial) + +A decentralized MPC network is an extension on the MPC approach outlined above. Instead of relying on a single, centralized service to store a key share and initiate signature requests, an MPC network distributes this responsibility across many nodes in a network. The user’s private key is split into many key shares with each share store by a different node. The user may request signatures from the network and a valid signature will be produced if and only if a threshold number of nodes agree to sign the request. + +Examples: Lit Protocol, Web3Auth (Torus Network) + +### Self-custodial wallet + +Self-custodial wallets store the private key locally where only the end user can access it. For example, the user may store their seed phrase in a browser extension, in a mobile app using their phone’s secure enclave, or in a hardware wallet. When using a self-custodial wallet, the user is the only one with the power to sign transactions. + +Self-custodial wallets require the user to maintain good security hygiene at all times. They also rely on the user to backup a copy of their private key in the event the wallet is lost or destroyed. If the user loses access to the device on which their private key is stored, they will have no way to recover the account unless they backed up the private key in another device or location. + +**Example**: Metamask, Ledger + +### Custodial Wallet + +Custodial wallet providers have full control over the user’s private key and sign transactions on behalf of the user. These services typically implement security measures to ensure that only the authorized user(s) can request a signature. These providers are also typically regulated entities (e.g., qualified custodians). The user must trust this service provider to securely store the private key and sign transactions if and only if the user wishes. + +**Example**: Coinbase Custody, Bitgo + +## Supported Signers + +Account Kit supports any signer and offers a bespoke integration guide for the most popular signers: + +- Magic +- Web3Auth +- Turnkey +- Privy +- Lit Protocol +- Fireblocks +- Portal +- Capsule +- Self-custodial wallets like Metamask or Ledger + +If you want to use another signer, you can integrate any other signer by wrapping it in an [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) provider or using [`SmartAccountSigner`](/smart-accounts/signers/using-your-own#implementing-smartaccountsigner) to adapt non-standard signers. + +## Criteria to consider + +Here are some important criteria to consider when choosing a signer. + +- **Custody model:** Who has access to the private key? + - Self-Custodial: the end user controls the private key and manually approves signature requests + - Non-Custodial: a third-party service manages the private key or a subset of the key shares, but cannot sign transactions without the user’s involvement + - Custodial: a third-party service manages the private key and can sign transactions without the user’s involvement +- **Security Model**: Assess the security model of the provider. Where is the private key stored? (on device? in the cloud? on what cloud provider?) Is the private key encrypted? What encryption algorithm is used? Who has access to the decryption keys? This is a non-exhaustive list and we recommend doing further research. +- **Authentication Methods:** What authentication method delivers the right balance of security, self-sovereignty, and ease-of-use for your target users? + - Email + Password: sign up for a smart account with an email and password. + - Social Logins: sign up for a smart account with Google, Facebook, or other social logins. + - Passkeys: sign up for a smart account secured by a passkey (e.g. fingerprint or Face ID) stored on-device. + - Self Custodial Wallet: sign up for a smart account using a self-custodial wallet such as Metamask or Ledger. +- **Availability:** If the signer service provider goes down, will users be able to sign transactions? +- **Key Export:** Does the signer allow the end user to export their private key? Can the user initiate an export even if the service provider has gone down? This is an important factor to ensure the user retains control of their assets no matter what happens to the service provider. +- **Key Recovery**: If the user forgets their password or loses their passkey, what recovery methods does the signer provide? If the provider stores a backup copy of the private key or MPC key shares, where are those backups stored and who has access to them? + +--- + +_Disclaimer: This page refers to third-party services, products software, technology, and content (collectively, “Third-Party Services”) that may be integrated or interact with Alchemy’s software and services. Alchemy is not responsible for any Third-Party Service, or for any compatibility issues, errors, or bugs caused in whole or in part by the Third-Party Service or any update or upgrade thereto. Your use of any Third-Party Service is at your own risk. You are responsible for obtaining any associated licenses and consents to the extent necessary for you to use the Third-Party Services. Your use of the Third-Party Services may be subject to separate terms and conditions set forth by the provider (including disclaimers or warnings), separate fees or charges, or a separate privacy notice. You are responsible for understanding and complying with any such terms or privacy notice._ diff --git a/site/smart-accounts/signers/portal.md b/site/smart-accounts/signers/portal.md new file mode 100644 index 000000000..ab2ea7ab0 --- /dev/null +++ b/site/smart-accounts/signers/portal.md @@ -0,0 +1,75 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Portal + - - meta + - name: description + content: Guide to use Portal as a signer + - - meta + - property: og:description + content: Guide to use Portal as a signer +--- + +# Portal + +[Portal](https://www.portalhq.io/) is an embedded blockchain infrastructure company that powers companies with an end to end platform for key management for self-custodial wallets (MPC and AA), security firewall, and web3 protocol connect kit. + +A combination of Portal and Alchemy's Account Kit allows you to have robust key management and security, while also exploring everything that web3 has to offer with smart contract accounts for your users. + +## Integration + +Check out Portal's developer [docs](https://docs.portalhq.io/) to learn more about Portal. If you want to get quick access to their SDKs, please reach out via [this](https://5g2cefp2j92.typeform.com/portal-labs?typeform-source=www.portalhq.io) form. + +### Install the Portal SDK + +::: code-group + +```bash [npm] +npm install --save @portal-hq/web +``` + +```bash [yarn] +yarn add @portal-hq/web +``` + +::: + +### Create a SmartAccountSigner + +Next, setup the Portal SDK and create a `SmartAccountSigner`: + +<<< @/snippets/portal.ts + +### Use it with LightAccount + +Let's see it in action with `aa-alchemy` and `LightSmartContractAccount` from `aa-accounts`: +::: code-group + +```ts [example.ts] +import { AlchemyProvider } from "@alchemy/aa-alchemy"; +import { LightSmartContractAccount } from "@alchemy/aa-accounts"; +import { polygonMumbai } from "viem/chains"; +import { portalSigner } from "./portalSigner"; + +const chain = polygonMumbai; +const provider = new AlchemyProvider({ + apiKey: process.env.ALCHEMY_API_KEY, + chain, + entryPointAddress: ENTRY_POINT_CONTRACT_ADDRESS, +}).connect( + (rpcClient) => + new LightSmartContractAccount({ + entryPointAddress: ENTRY_POINT_CONTRACT_ADDRESS, + chain: rpcClient.chain, + owner: portalSigner, + factoryAddress: FACTORY_CONTRACT_ADDRESS, + rpcClient, + }) +); +``` + +<<< @/snippets/portal.ts + +::: diff --git a/site/smart-accounts/signers/privy.md b/site/smart-accounts/signers/privy.md new file mode 100644 index 000000000..c912df04d --- /dev/null +++ b/site/smart-accounts/signers/privy.md @@ -0,0 +1,59 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Privy + - - meta + - name: description + content: Guide to use Privy as a signer + - - meta + - property: og:description + content: Guide to use Privy as a signer +--- + +# Privy + +[Privy](https://privy.io) is an easy way for web3 developers to onboard users across mobile and desktop, regardless of whether they already have a wallet or not. + +With Privy, you can easily provision **self-custodial embedded wallets** for your users when they login with email, SMS, or social logins, while also enabling web3-native users to use their existing wallets with your app, if they prefer. + +Combining Privy with Account Kit allows you to seamlessly generate embedded wallets for your users and supercharge these wallets with account abstraction – setting the foundation for an innovative & delightful onchain experience. + +## Integration + +### Install the SDK + +::: code-group + +```bash [npm] +npm i @privy-io/react-auth +``` + +```bash [yarn] +yarn add @privy-io/react-auth +``` + +::: + +### Create a SmartAccountSigner + +First, set up your React app with Privy following the [Privy Quickstart](https://docs.privy.io/guide/quickstart). You should also configure your `PrivyProvider` to create embedded wallets for your users when they login, like below: + +```ts [PrivyProvider] + + + +``` + +Then, when a user logs in to your app, Privy will create an embedded wallet for them. You can use this embedded wallet to create a `LightSmartContractAccount` from `aa-accounts`: + +<<< @/snippets/privy.ts diff --git a/site/smart-accounts/signers/turnkey.md b/site/smart-accounts/signers/turnkey.md new file mode 100644 index 000000000..a23b4c47d --- /dev/null +++ b/site/smart-accounts/signers/turnkey.md @@ -0,0 +1,85 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Turnkey + - - meta + - name: description + content: Guide to use Turnkey as a signer + - - meta + - property: og:description + content: Guide to use Turnkey as a signer +--- + +# Turnkey + +[Turnkey](https://turnkey.com) provides simple APIs to securely manage your private keys. With Turnkey, users are able to spin up thousands of wallets and sign millions of transactions, all without compromising on security. + +## Integration + +### Sign up for a Turnkey Account + +Signing up for a Turnkey Account is quick and easy. You can follow our [quickstart guide](https://docs.turnkey.com/getting-started/quickstart) to create a your first organization, API key and private key in minutes. + +### Install the SDK + +::: code-group + +```bash [npm] +npm i -s @turnkey/api-key-stamper +npm i -s @turnkey/http +npm i -s @turnkey/viem +``` + +```bash [yarn] +yarn add @turnkey/api-key-stamper +yarn add @turnkey/http +yarn add @turnkey/viem +``` + +::: + +### Create a SmartAccountSigner + +Next, setup the Turnkey sdk and create a `SmartAccountSigner`: + +<<< @/snippets/turnkey.ts + +### Use it with LightAccount + +Let's see it in action with `aa-alchemy` and `LightSmartContractAccount` from `aa-accounts`: +::: code-group + +```ts [example.ts] +import { AlchemyProvider } from "@alchemy/aa-alchemy"; +import { + LightSmartContractAccount, + getDefaultLightAccountFactory, +} from "@alchemy/aa-accounts"; +import { sepolia } from "viem/chains"; +import { newTurnkeySigner } from "./turnkey"; + +async function main() { + const owner = await newTurnkeySigner(); + const chain = sepolia; + const provider = new AlchemyProvider({ + apiKey: "ALCHEMY_API_KEY", + chain, + entryPointAddress: "0x...", + }).connect( + (rpcClient) => + new LightSmartContractAccount({ + entryPointAddress: "0x...", + chain: rpcClient.chain, + owner, + factoryAddress: getDefaultLightAccountFactory(sepolia), + rpcClient, + }) + ); +} +``` + +<<< @/snippets/turnkey.ts + +::: diff --git a/site/smart-accounts/signers/using-your-own.md b/site/smart-accounts/signers/using-your-own.md new file mode 100644 index 000000000..3b1e0c149 --- /dev/null +++ b/site/smart-accounts/signers/using-your-own.md @@ -0,0 +1,34 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Using Your Own Signer + - - meta + - name: description + content: How to use any Signer with Account Kit + - - meta + - property: og:description + content: How to use any Signer with Account Kit +--- + +# Using Your Own Signer + +Account Kit is designed to be flexible and allow you to use any signer you want. You have two options. You can either: + +1. If your signer is an [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) compliant provider, you can leverage `viem`'s `WalletClient` and the `WalletClientSigner` exported in `aa-core` +2. Implement `SmartAccountSigner` (exported in `aa-core`) + +## Using `WalletClientSigner` + +Viem allows you to create `WalletClient`s which can be used to wrap local or JSON RPC based wallets. You can see the complete docs for leveraging the `WalletClient` [here](https://viem.sh/docs/clients/wallet.html). + +We support a `SmartAccountSigner` implementation called `WalletClientSigner` that makes it really easy to use a viem `WalletClient` as an owner on your Smart Contract Account. If your signer is EIP-1193 compliant, it's really easy to use with `WalletClient`. Let's take a look at a simple example: + +<<< @/snippets/wallet-client-signer.ts + +## Implementing `SmartAccountSigner` + +The `SmartAccountSigner` interface is really straighforward: + +<<< @/../packages/core/src/signer/types.ts diff --git a/site/smart-accounts/signers/web3auth.md b/site/smart-accounts/signers/web3auth.md new file mode 100644 index 000000000..73d062ab7 --- /dev/null +++ b/site/smart-accounts/signers/web3auth.md @@ -0,0 +1,76 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Web3Auth + - - meta + - name: description + content: Guide to use Web3Auth as a signer + - - meta + - property: og:description + content: Guide to use Web3Auth as a signer +--- + +# Web3Auth + +[Web3Auth](https://web3auth.io/) is an embedded wallet provider that allows users to generate wallets scoped to your application via Social Logins, Email OTP, or Custom Authentication Methods. This is great for enabling a better experience for your users. But ultimately these wallets are not much different from EOA's, so you don't have the benefit of Account Abstraction (gas sponsorship, batching, etc). + +Combining Web3Auth with Account Kit allows you to get the best of both worlds. You can use Web3Auth to generate a wallet scoped to your application, and then use Account Kit to create Smart Contract Accounts for your users! + +# Integrataion + +### Install the SDK + +::: code-group + +```bash [npm] +npm i -s @web3auth/modal +``` + +```bash [yarn] +yarn add @web3auth/modal +``` + +::: + +### Create a SmartAccountSigner + +Next, setup the web3auth sdk and create a `SmartAccountSigner` + +<<< @/snippets/web3auth.ts + +### Use it with LightAccount + +Let's see it in action with `aa-alchemy` and `LightSmartContractAccount` from `aa-accounts`: +::: code-group + +```ts [example.ts] +import { AlchemyProvider } from "@alchemy/aa-alchemy"; +import { + LightSmartContractAccount, + getDefaultLightAccountFactory, +} from "@alchemy/aa-accounts"; +import { sepolia } from "viem/chains"; +import { web3authSigner } from "./web3auth"; + +const chain = sepolia; +const provider = new AlchemyProvider({ + apiKey: "ALCHEMY_API_KEY", + chain, + entryPointAddress: "0x...", +}).connect( + (rpcClient) => + new LightSmartContractAccount({ + entryPointAddress: "0x...", + chain: rpcClient.chain, + owner: web3authSigner, + factoryAddress: getDefaultLightAccountFactory(chain), + rpcClient, + }) +); +``` + +<<< @/snippets/web3auth.ts + +::: diff --git a/site/smart-accounts/sponsoring-gas.md b/site/smart-accounts/sponsoring-gas.md new file mode 100644 index 000000000..f7cccbc94 --- /dev/null +++ b/site/smart-accounts/sponsoring-gas.md @@ -0,0 +1,117 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Sponsoring Gas + - - meta + - name: description + content: Guide to Sponsor Gas of an account + - - meta + - property: og:description + content: Guide to Sponsor Gas of an account +prev: + text: Choosing a Signer + link: /smart-accounts/signers/overview +--- + +# Sponsoring Gas + +Gas fees are a significant barrier to entry for new user of your app. With Account Kit you can remove this barrier by sponsoring gas fees for transactions via the [Gas Manager](https://docs.alchemy.com/docs/gas-manager-services). This guide explains how to sponsor gas by creating a gas policy, linking it to your provider, and sending sponsored user operations from a smart account. + +## How to Sponsor Gas + +After [installing `aa-sdk`](/getting-started#install-the-packages) in your project, follow these steps to sponsor gas. + +### 1. Set Up the Provider + +First, create an `AlchemyProvider`. You'll use this to send user operations and interact with the blockchain. + +<<< @/snippets/provider.ts + +Remember to replace `ALCHEMY_API_KEY` with your Alchemy API key. If you don't have one yet, you can create an API key on the [Alchemy dashboard](https://dashboard.alchemy.com/). + +### 2. Create a Gas Manager Policy + +A gas manager policy is a set of rules that define which user operations are eligible for gas sponsorship. You can control which operations are eligible for sponsorship by defining rules: + +- **Spending rules**: limit the amount of money or the number of user ops that can be sponsored by this policy +- **Allowlist**: restrict wallet addresses that are eligible for sponsorship. The policy will only sponsor gas for user operations that were sent by addresses on this list. +- **Blocklist**: ban certain addresses from receiving sponsorship under this policy +- **Policy duration**: define the duration of your policy and the sponsorship expiry period. This is the period for which the Gas Manager signature (paymaster data) will remain valid once it is generated. + +To learn more about policy configuration, refer to the guide on [setting up a gas manager policy](https://docs.alchemy.com/docs/setup-a-gas-manager-policy). + +Once you've decided on policy rules for your app, [create a policy](https://dashboard.alchemy.com/gas-manager/policy/create) in the Gas Manager dashboard. + +### 3. Link the Policy to your Provider + +Next, you must link your gas policy to your provider. Find your Policy ID located at the top of the policy page in the Gas Manager dashboard. + +![Policy ID](../assets/images/policy-id.png) + +Copy it and then replace the `GAS_MANAGER_POLICY_ID` in the snippet below. + +::: code-group + +```ts [sponsor-gas.ts] +import { provider } from "./provider.ts"; + +// Find your Gas Manager policy id at: // [!code focus:10] +//dashboard.alchemy.com/gas-manager/policy/create +const GAS_MANAGER_POLICY_ID = "YourGasManagerPolicyId"; + +// Link the provider with the Gas Manager. This ensures user operations +// sent with this provider get sponsorship from the Gas Manager. +provider.withAlchemyGasManager({ + policyId: GAS_MANAGER_POLICY_ID, + entryPoint: entryPointAddress, +}); + +// Here's how to send a sponsored user operation from your smart account: +const { hash } = await provider.sendUserOperation({ + target: "0xTargetAddress", + data: "0xCallData", + value: 0n, // value in bigint or leave undefined +}); +``` + +<<< @/snippets/provider.ts + +::: + +You've created a gas manager policy and linked it to the provider. This guarantees that user operations sent with this provider receive sponsorship if and only the user operation satisfies the rules defined in your gas policy. + +### 4. Send the Sponsored UserOperation + +Now you're ready to send sponsored user operations! You can send a user operation by calling `sendUserOperation` on the provider. The Gas Manager will check if this user operation satisfies the policy rules defined above and sponsor the gas costs if the rules are met. If the user operation does not meet the policy rules, an error will be thrown. + +::: code-group + +```ts [sponsor-gas.ts] +import { provider } from "./provider.ts"; + +// Your Gas Manager policy id is available at: // +//dashboard.alchemy.com/gas-manager/policy/create +const GAS_MANAGER_POLICY_ID = "YourGasManagerPolicyId"; + +// Link the provider with the Gas Manager so the user operations +// sent with this provider get sponsorship from the Gas Manager. +provider.withAlchemyGasManager({ + policyId: GAS_MANAGER_POLICY_ID, + entryPoint: entryPointAddress, +}); + +// Send a sponsored user operation from your smart account like this: // [!code focus:6] +const { hash } = await provider.sendUserOperation({ + target: "0xTargetAddress", + data: "0xCallData", + value: 0n, // value in bigint or leave undefined +}); +``` + +<<< @/snippets/provider.ts + +::: + +Congratulations! You've successfully sponsored gas for a user operation by creating a Gas Manager Policy, defining policy rules, linking your policy to the provider, and submitting a user operation. diff --git a/site/smart-accounts/transferring-ownership.md b/site/smart-accounts/transferring-ownership.md new file mode 100644 index 000000000..99b448161 --- /dev/null +++ b/site/smart-accounts/transferring-ownership.md @@ -0,0 +1,93 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Transferring Ownership + - - meta + - name: description + content: Guide to transferring ownership of an account + - - meta + - property: og:description + content: Guide to transferring ownership of an account +--- + +# Transferring Ownership + +Not all Smart Contract Account implementations support transfering the owner (e.g. `SimpleAccount`). However, a number of the accounts in this guide and in the Account Kit do, including Alchemy's Light Account! Let's see a few different ways we can transfer ownership of an Account (using Light Account as an example). + +## Light Account + +Light Account exposes the following method which allows the existing owner to transfer ownership to a new address: + +```solidity +function transferOwnership(address newOwner) public virtual onlyOwner +``` + +There a number of ways you can call this method using the Account Kit. + +### 1. Using `LightSmartContractAccount` + +::: code-group + +```ts [example.ts] +import { LightSmartContractAccount } from "@alchemy/aa-accounts"; +import { provider } from "./provider"; + +// this will return the address of the smart contract account you want to transfer ownerhip of +const accountAddress = await provider.getAddress(); +const newOwner = "0x..."; // the address of the new owner + +const hash = LightSmartContractAccount.transferOwnership(provider, newOwner); // [!code focus:99] +``` + +<<< @/snippets/provider.ts + +::: + +Since `@alchemy/aa-accounts` exports a `LightAccount` ABI, the above approach makes it easy to transfer ownership. That said, you can also directly call `sendUserOperation` to execute the ownership transfer. As you'll see below, however, it is a bit verbose: + +### 2. Using `sendUserOperation` + +Assuming you have connected the `provider` to a `LightAccount` using `provider.connect`, you can call `sendUserOperation` on the provider and encoding the `transferOwnership` call data: + +::: code-group + +```ts [example.ts] +import { encodeFunctionData } from "viem"; +import { provider } from "./provider"; + +// this will return the address of the smart contract account you want to transfer ownerhip of +const accountAddress = await provider.getAddress(); +const newOwner = "0x..."; // the address of the new owner + +// [!code focus:99] +const { hash: userOperationHash } = provider.sendUserOperation({ + to: accountAddress, + data: encodeFunctionData({ + abi: [ + { + inputs: [ + { + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + ], + functionName: "transferOwnership", + args: [newOwner], + }), +}); +``` + +<<< @/snippets/provider.ts + +::: + +See the [`LightSmartContractAccount`](/packages/aa-accounts/light-account/introduction) docs for more details about the Alchemy Light Account implementation. diff --git a/site/snippets/account-alchemy.ts b/site/snippets/account-alchemy.ts new file mode 100644 index 000000000..827b8a614 --- /dev/null +++ b/site/snippets/account-alchemy.ts @@ -0,0 +1,48 @@ +/* via `aa-alchemy` */ + +import { AlchemyProvider } from "@alchemy/aa-alchemy"; +import { + LocalAccountSigner, + SimpleSmartContractAccount, + type SmartAccountSigner, +} from "@alchemy/aa-core"; +import { polygonMumbai } from "viem/chains"; + +const SIMPLE_ACCOUNT_FACTORY_ADDRESS = + "0x9406Cc6185a346906296840746125a0E44976454"; + +// 1. define the EOA owner of the Smart Account +// this uses a utility method for creating an account signer using mnemonic +// we also have a utility for creating an account signer from a private key +const owner: SmartAccountSigner = + LocalAccountSigner.mnemonicToAccountSigner(MNEMONIC); + +// 2. initialize the provider and connect it to the account +let provider = new AlchemyProvider({ + apiKey: API_KEY, + chain, + entryPointAddress: ENTRYPOINT_ADDRESS, +}).connect( + (rpcClient) => + new SimpleSmartContractAccount({ + entryPointAddress: ENTRYPOINT_ADDRESS, + chain: polygonMumbai, // ether a viem Chain or chainId that supports account abstraction at Alchemy + owner, + factoryAddress: SIMPLE_ACCOUNT_FACTORY_ADDRESS, + rpcClient, + }) +); + +// [OPTIONAL] Use Alchemy Gas Manager +provider.withAlchemyGasManager({ + policyId: PAYMASTER_POLICY_ID, + entryPoint: ENTRYPOINT_ADDRESS, +}); + +// 3. send a UserOperation +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const { hash } = await provider.sendUserOperation({ + target: "0xTargetAddress", + data: "0xcallData", + value: 0n, // value: bigint or undefined +}); diff --git a/site/snippets/account-core.ts b/site/snippets/account-core.ts new file mode 100644 index 000000000..1eb4e2035 --- /dev/null +++ b/site/snippets/account-core.ts @@ -0,0 +1,46 @@ +/* via `aa-core`*/ + +import { + LocalAccountSigner, + SimpleSmartContractAccount, + SmartAccountProvider, + type SmartAccountSigner, +} from "@alchemy/aa-core"; +import { polygonMumbai } from "viem/chains"; + +const SIMPLE_ACCOUNT_FACTORY_ADDRESS = + "0x9406Cc6185a346906296840746125a0E44976454"; + +// 1. define the EOA owner of the Smart Account +// this uses a utility method for creating an account signer using mnemonic +// we also have a utility for creating an account signer from a private key +const owner: SmartAccountSigner = + LocalAccountSigner.mnemonicToAccountSigner(MNEMONIC); + +// 2. initialize the provider and connect it to the account +const provider = new SmartAccountProvider({ + // the demo key below is public and rate-limited, it's better to create a new one + // you can get started with a free account @ https://www.alchemy.com/ + rpcProvider: "https://polygon-mumbai.g.alchemy.com/v2/demo", + entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", + chain: polygonMumbai, +}).connect( + (rpcClient) => + new SimpleSmartContractAccount({ + entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", + chain: polygonMumbai, + factoryAddress: SIMPLE_ACCOUNT_FACTORY_ADDRESS, + rpcClient, + owner, + // optionally if you already know the account's address + accountAddress: "0x000...000", + }) +); + +// 3. send a UserOperation +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const { hash } = await provider.sendUserOperation({ + target: "0xTargetAddress", + data: "0xcallData", + value: 0n, // value: bigint or undefined +}); diff --git a/site/snippets/account-ethers.ts b/site/snippets/account-ethers.ts new file mode 100644 index 000000000..fa8e2e615 --- /dev/null +++ b/site/snippets/account-ethers.ts @@ -0,0 +1,44 @@ +/* via `aa-ethers`*/ + +import { getChain, SimpleSmartContractAccount } from "@alchemy/aa-core"; +import { + convertWalletToAccountSigner, + EthersProviderAdapter, +} from "@alchemy/aa-ethers"; +import { Wallet } from "@ethersproject/wallet"; +import { Alchemy, Network } from "alchemy-sdk"; + +const SIMPLE_ACCOUNT_FACTORY_ADDRESS = + "0x9406Cc6185a346906296840746125a0E44976454"; + +// 1. connect to an RPC Provider and a Wallet +const alchemy = new Alchemy({ + apiKey: API_KEY, + network: Network.MATIC_MUMBAI, +}); +const alchemyProvider = await alchemy.config.getProvider(); +const owner = Wallet.fromMnemonic(MNEMONIC); + +// 2. Create the SimpleAccount signer +// signer is an ethers.js Signer +const signer = EthersProviderAdapter.fromEthersProvider( + alchemyProvider, + ENTRYPOINT_ADDRESS +).connectToAccount( + (rpcClient) => + new SimpleSmartContractAccount({ + entryPointAddress: ENTRYPOINT_ADDRESS, + chain: getChain(alchemyProvider.network.chainId), + owner: convertWalletToAccountSigner(owner), + factoryAddress: SIMPLE_ACCOUNT_FACTORY_ADDRESS, + rpcClient, + }) +); + +// 3. send a user op +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const { hash } = await signer.sendUserOperation({ + target: "0xTargetAddress", + data: "0xcallData", + value: 0n, // value: bigint or undefined +}); diff --git a/site/snippets/capsule.ts b/site/snippets/capsule.ts new file mode 100644 index 000000000..e0a63926d --- /dev/null +++ b/site/snippets/capsule.ts @@ -0,0 +1,31 @@ +import { WalletClientSigner, type SmartAccountSigner } from "@alchemy/aa-core"; +import Capsule, { + Environment, + createCapsuleViemClient, +} from "@usecapsule/web-sdk"; +import { http } from "viem"; +import { sepolia } from "viem/chains"; + +// get an API Key by filling out this form: https://form.typeform.com/to/hLaJeYJW +const CAPSULE_API_KEY = null; + +// Capsule's viem client supports custom chains and providers +// Remember to replace "ALCHEMY_API_KEY" with your own Alchemy API key, get one here: https://dashboard.alchemy.com/ +const CHAIN = sepolia; +const PROVIDER = "https://eth-sepolia.g.alchemy.com/v2/ALCHEMY_API_KEY"; + +// Instantiate the Capsule SDK +// Use the DEVELOPMENT environment for development and PRODUCTION for releases +const capsule = new Capsule(Environment.DEVELOPMENT, CAPSULE_API_KEY); + +// returns a viem client that wraps capsule for key methods +const capsuleClient = createCapsuleViemClient(capsule, { + chain: CHAIN, + transport: http(PROVIDER), +}); + +// a smart account signer you can use as an owner on ISmartContractAccount +export const capsuleSigner: SmartAccountSigner = new WalletClientSigner( + capsuleClient, + "capsule" // signerType +); diff --git a/site/snippets/client.ts b/site/snippets/client.ts new file mode 100644 index 000000000..86858eb15 --- /dev/null +++ b/site/snippets/client.ts @@ -0,0 +1,7 @@ +import { createPublicErc4337Client } from "@alchemy/aa-core"; +import { mainnet } from "viem/chains"; + +export const client = createPublicErc4337Client({ + chain: mainnet, + rpcUrl: "https://eth-mainnet.g.alchemy.com/v2/demo", +}); diff --git a/site/snippets/core-provider.ts b/site/snippets/core-provider.ts new file mode 100644 index 000000000..ce821a45f --- /dev/null +++ b/site/snippets/core-provider.ts @@ -0,0 +1,28 @@ +import { + LightSmartContractAccount, + getDefaultLightAccountFactory, +} from "@alchemy/aa-accounts"; +import { + LocalAccountSigner, + SmartAccountProvider, + SmartAccountSigner, +} from "@alchemy/aa-core"; +import { polygonMumbai } from "viem/chains"; + +const owner: SmartAccountSigner = + LocalAccountSigner.mnemonicToAccountSigner(YOUR_OWNER_MNEMONIC); + +export const provider = new SmartAccountProvider({ + rpcProvider: "https://polygon-mumbai.g.alchemy.com/v2/demo", + entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", + chain: polygonMumbai, +}).connect( + (rpcClient) => + new LightSmartContractAccount({ + entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", + chain: polygonMumbai, + factoryAddress: getDefaultLightAccountFactory(polygonMumbai), + rpcClient, + owner, + }) +); diff --git a/site/snippets/ethers-provider.ts b/site/snippets/ethers-provider.ts new file mode 100644 index 000000000..7f6eff8d2 --- /dev/null +++ b/site/snippets/ethers-provider.ts @@ -0,0 +1,15 @@ +import { EthersProviderAdapter } from "@alchemy/aa-ethers"; +import { Alchemy, Network } from "alchemy-sdk"; + +export const entryPointAddress = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"; + +const alchemy = new Alchemy({ + apiKey: process.env.API_KEY!, + network: Network.MATIC_MUMBAI, +}); +const ethersProvider = await alchemy.config.getProvider(); + +export const provider = EthersProviderAdapter.fromEthersProvider( + ethersProvider, + entryPointAddress +); diff --git a/site/snippets/ethers-signer.ts b/site/snippets/ethers-signer.ts new file mode 100644 index 000000000..2cc730a33 --- /dev/null +++ b/site/snippets/ethers-signer.ts @@ -0,0 +1,22 @@ +import { + LightSmartContractAccount, + getDefaultLightAccountFactory, +} from "@alchemy/aa-accounts"; +import { LocalAccountSigner, SmartAccountSigner } from "@alchemy/aa-core"; +import { polygonMumbai } from "viem/chains"; +import { entryPointAddress, provider } from "./ethers-provider.js"; + +const owner: SmartAccountSigner = LocalAccountSigner.mnemonicToAccountSigner( + process.env.YOUR_OWNER_MNEMONIC! +); + +export const signer = provider.connectToAccount( + (rpcClient) => + new LightSmartContractAccount({ + entryPointAddress: entryPointAddress, + chain: polygonMumbai, + factoryAddress: getDefaultLightAccountFactory(polygonMumbai), + rpcClient, + owner, + }) +); diff --git a/site/snippets/fireblocks.ts b/site/snippets/fireblocks.ts new file mode 100644 index 000000000..d31caf11e --- /dev/null +++ b/site/snippets/fireblocks.ts @@ -0,0 +1,23 @@ +import { WalletClientSigner, type SmartAccountSigner } from "@alchemy/aa-core"; +import { + ChainId, + FireblocksWeb3Provider, +} from "@fireblocks/fireblocks-web3-provider"; +import { createWalletClient, custom } from "viem"; +import { sepolia } from "viem/chains"; + +const externalProvider = new FireblocksWeb3Provider({ + // apiBaseUrl: ApiBaseUrl.Sandbox // If using a sandbox workspace + privateKey: process.env.FIREBLOCKS_API_PRIVATE_KEY_PATH, + apiKey: process.env.FIREBLOCKS_API_KEY, + vaultAccountIds: process.env.FIREBLOCKS_VAULT_ACCOUNT_IDS, + chainId: ChainId.SEPOLIA, + logTransactionStatusChanges: true, // Verbose logging +}); + +const walletClient = createWalletClient({ + chain: sepolia, // can provide a different chain here + transport: custom(externalProvider), +}); + +export const signer: SmartAccountSigner = new WalletClientSigner(walletClient); diff --git a/site/snippets/light-account.ts b/site/snippets/light-account.ts new file mode 100644 index 000000000..e3a2c4cca --- /dev/null +++ b/site/snippets/light-account.ts @@ -0,0 +1,42 @@ +// importing required dependencies +import { AlchemyProvider } from "@alchemy/aa-alchemy"; +import { + LightSmartContractAccount, + getDefaultLightAccountFactory, +} from "@alchemy/aa-accounts"; +import { LocalAccountSigner, type SmartAccountSigner } from "@alchemy/aa-core"; +import { sepolia } from "viem/chains"; + +const chain = sepolia; +const PRIVATE_KEY = "0xYourEOAPrivateKey"; // Replace with the private key of your EOA that will be the owner of Light Account + +const eoaSigner: SmartAccountSigner = + LocalAccountSigner.privateKeyToAccountSigner(PRIVATE_KEY); // Create a signer for your EOA + +// Create a provider with your EOA as the smart account owner, this provider is used to send user operations from your smart account and interact with the blockchain +const provider = new AlchemyProvider({ + apiKey: "ALCHEMY_API_KEY", // Replace with your Alchemy API key, you can get one at https://dashboard.alchemy.com/ + chain, + entryPointAddress: "0x...", +}).connect( + (rpcClient) => + new LightSmartContractAccount({ + entryPointAddress: "0x...", + chain: rpcClient.chain, + owner: eoaSigner, + factoryAddress: getDefaultLightAccountFactory(rpcClient.chain), // Default address for Light Account on Sepolia, you can replace it with your own. + rpcClient, + }) +); + +// Logging the smart account address -- please fund this address with some SepoliaETH in order for the user operations to be executed successfully +provider.getAddress().then((address: string) => console.log(address)); + +// Send a user operation from your smart contract account +const { hash } = await provider.sendUserOperation({ + target: "0xTargetAddress", // Replace with the desired target address + data: "0xCallData", // Replace with the desired call data + value: 0n, // value: bigint or undefined +}); + +console.log(hash); // Log the user operation hash diff --git a/site/snippets/lit.ts b/site/snippets/lit.ts new file mode 100644 index 000000000..37b9cfb84 --- /dev/null +++ b/site/snippets/lit.ts @@ -0,0 +1,65 @@ +import { WalletClientSigner, type SmartAccountSigner } from "@alchemy/aa-core"; +import { LitAbility, LitActionResource } from "@lit-protocol/auth-helpers"; +import { LitNodeClient } from "@lit-protocol/lit-node-client"; +import { PKPEthersWallet } from "@lit-protocol/pkp-ethers"; +import { AuthCallbackParams } from "@lit-protocol/types"; +import { createWalletClient, custom } from "viem"; +import { polygonMumbai } from "viem/chains"; + +const API_KEY = ""; +const POLYGON_MUMBAI_RPC_URL = `${polygonMumbai.rpcUrls.alchemy.http[0]}/${API_KEY}`; +const PKP_PUBLIC_KEY = ""; + +const litNodeClient = new LitNodeClient({ + litNetwork: "cayenne", + debug: false, +}); +await litNodeClient.connect(); + +const resourceAbilities = [ + { + resource: new LitActionResource("*"), + ability: LitAbility.PKPSigning, + }, +]; + +/** + * For provisioning keys and setting up authentication methods see documentation below + * https://developer.litprotocol.com/v2/pkp/minting + */ +const authNeededCallback = async (params: AuthCallbackParams) => { + const response = await litNodeClient.signSessionKey({ + sessionKey: params.sessionKeyPair, + statement: params.statement, + authMethods: [], + pkpPublicKey: PKP_PUBLIC_KEY, + expiration: params.expiration, + resources: params.resources, + chainId: 1, + }); + return response.authSig; +}; + +const sessionSigs = await litNodeClient + .getSessionSigs({ + chain: "ethereum", + expiration: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7).toISOString(), + resourceAbilityRequests: resourceAbilities, + authNeededCallback, + }) + .catch((err) => { + console.log("error while attempting to access session signatures: ", err); + throw err; + }); + +const pkpWallet = new PKPEthersWallet({ + pkpPubKey: PKP_PUBLIC_KEY, + rpc: POLYGON_MUMBAI_RPC_URL, + controllerSessionSigs: sessionSigs, +}); + +// a smart account signer you can use as an owner on ISmartContractAccount +export const litSigner: SmartAccountSigner = new WalletClientSigner( + createWalletClient({ transport: custom(pkpWallet.rpcProvider) }), // JsonRpcProvider instance, + "lit" // signerType +); diff --git a/site/snippets/magic.ts b/site/snippets/magic.ts new file mode 100644 index 000000000..31b94cb6d --- /dev/null +++ b/site/snippets/magic.ts @@ -0,0 +1,22 @@ +import { WalletClientSigner, type SmartAccountSigner } from "@alchemy/aa-core"; +import { Magic } from "magic-sdk"; +import { createWalletClient, custom } from "viem"; + +// this is generated from the [Magic Dashboard](https://dashboard.magic.link/) +const MAGIC_API_KEY = "pk_test_..."; + +// instantiate Magic SDK instance +export const magic = new Magic(MAGIC_API_KEY); + +// a viem wallet client that wraps magic for utility methods +// NOTE: this isn't necessary since you can just use the `magic.rpcProvider` +// directly, but this makes things much easier +export const magicClient = createWalletClient({ + transport: custom(magic.rpcProvider), +}); + +// a smart account signer you can use as an owner on ISmartContractAccount +export const magicSigner: SmartAccountSigner = new WalletClientSigner( + magicClient, + "magic" // signerType +); diff --git a/site/snippets/portal.ts b/site/snippets/portal.ts new file mode 100644 index 000000000..317623e6c --- /dev/null +++ b/site/snippets/portal.ts @@ -0,0 +1,21 @@ +import { WalletClientSigner, type SmartAccountSigner } from "@alchemy/aa-core"; +import Portal, { PortalOptions } from "@portal-hq/web"; +import { createWalletClient, custom } from "viem"; +import { polygonMumbai } from "viem/chains"; + +const portalOptions = { + autoApprove: true, + gatewayConfig: `${polygonMumbai.rpcUrls.alchemy.http}/${process.env.ALCHEMY_API_KEY}`, + chainId: 80001, + host: process.env.PORTAL_WEB_HOST, +} as PortalOptions; + +const portal = new Portal(portalOptions); + +const portalWalletClient = createWalletClient({ + transport: custom(portal.provider), +}); +export const portalSigner: SmartAccountSigner = new WalletClientSigner( + portalWalletClient, + "portal" // signerType +); diff --git a/site/snippets/privy.ts b/site/snippets/privy.ts new file mode 100644 index 000000000..18d010864 --- /dev/null +++ b/site/snippets/privy.ts @@ -0,0 +1,54 @@ +/** + * This example assumes your app is wrapped with the `PrivyProvider` and + * is configured to create embedded wallets for users upon login. + */ +import { LightSmartContractAccount } from "@alchemy/aa-accounts"; +import { AlchemyProvider } from "@alchemy/aa-alchemy"; +import { WalletClientSigner, type SmartAccountSigner } from "@alchemy/aa-core"; +import { useWallets } from "@privy-io/react-auth"; +import { createWalletClient, custom } from "viem"; +import { sepolia } from "viem/chains"; + +// The code below makes use of Privy's React hooks. You must paste +// or use it within a React Component or Context. + +// Find the user's embedded wallet +// eslint-disable-next-line react-hooks/rules-of-hooks +const { wallets } = useWallets(); +const embeddedWallet = wallets.find( + (wallet) => wallet.walletClientType === "privy" +); +if (!embeddedWallet) throw new Error("User does not have an embedded wallet"); + +// Switch the embedded wallet to your desired network +await embeddedWallet.switchChain(sepolia.id); + +// Get a viem client from the embedded wallet +const eip1193provider = await embeddedWallet.getEthereumProvider(); +const privyClient = createWalletClient({ + account: embeddedWallet.address, + chain: sepolia, + transport: custom(eip1193provider), +}); + +// Create a smart account signer from the embedded wallet's viem client +const privySigner: SmartAccountSigner = new WalletClientSigner( + privyClient, + "privy" // signerType +); + +// Create an Alchemy Provider with the smart account signer +export const provider = new AlchemyProvider({ + apiKey: "ALCHEMY_API_KEY", + chain: sepolia, + entryPointAddress: "0x...", +}).connect( + (rpcClient) => + new LightSmartContractAccount({ + entryPointAddress: "0x...", + chain: rpcClient.chain, + owner: privySigner, + factoryAddress: "0x...", + rpcClient, + }) +); diff --git a/site/snippets/provider.ts b/site/snippets/provider.ts new file mode 100644 index 000000000..e18a35d3a --- /dev/null +++ b/site/snippets/provider.ts @@ -0,0 +1,28 @@ +import { AlchemyProvider } from "@alchemy/aa-alchemy"; +import { + LightSmartContractAccount, + getDefaultLightAccountFactory, +} from "@alchemy/aa-accounts"; +import { LocalAccountSigner, type SmartAccountSigner } from "@alchemy/aa-core"; +import { sepolia } from "viem/chains"; + +const chain = sepolia; +const PRIVATE_KEY = "0xYourEOAPrivateKey"; +const eoaSigner: SmartAccountSigner = + LocalAccountSigner.privateKeyToAccountSigner(`0x${PRIVATE_KEY}`); +const entryPointAddress = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"; + +export const provider = new AlchemyProvider({ + apiKey: "ALCHEMY_API_KEY", // replace with your alchemy api key of the Alchemy app associated with the Gas Manager, get yours at https://dashboard.alchemy.com/ + chain, + entryPointAddress: entryPointAddress, +}).connect( + (rpcClient) => + new LightSmartContractAccount({ + entryPointAddress: entryPointAddress, + chain: rpcClient.chain, + owner: eoaSigner, + factoryAddress: getDefaultLightAccountFactory(rpcClient.chain), + rpcClient, + }) +); diff --git a/site/snippets/simple-account-abi.ts b/site/snippets/simple-account-abi.ts new file mode 100644 index 000000000..8585d2a47 --- /dev/null +++ b/site/snippets/simple-account-abi.ts @@ -0,0 +1,524 @@ +export const SimpleAccountAbi = [ + { + inputs: [ + { + internalType: "contract IEntryPoint", + name: "anEntryPoint", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "previousAdmin", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "newAdmin", + type: "address", + }, + ], + name: "AdminChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "beacon", + type: "address", + }, + ], + name: "BeaconUpgraded", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint8", + name: "version", + type: "uint8", + }, + ], + name: "Initialized", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "contract IEntryPoint", + name: "entryPoint", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + ], + name: "SimpleAccountInitialized", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "implementation", + type: "address", + }, + ], + name: "Upgraded", + type: "event", + }, + { + inputs: [], + name: "addDeposit", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "entryPoint", + outputs: [ + { + internalType: "contract IEntryPoint", + name: "", + type: "address", + }, + ], + stateMutability: "view", + 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: "bytes[]", + name: "func", + type: "bytes[]", + }, + ], + name: "executeBatch", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "getDeposit", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getNonce", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "anOwner", + type: "address", + }, + ], + name: "initialize", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "uint256[]", + name: "", + type: "uint256[]", + }, + { + internalType: "uint256[]", + name: "", + type: "uint256[]", + }, + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + name: "onERC1155BatchReceived", + outputs: [ + { + internalType: "bytes4", + name: "", + type: "bytes4", + }, + ], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "uint256", + name: "", + type: "uint256", + }, + { + internalType: "uint256", + name: "", + type: "uint256", + }, + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + name: "onERC1155Received", + outputs: [ + { + internalType: "bytes4", + name: "", + type: "bytes4", + }, + ], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "uint256", + name: "", + type: "uint256", + }, + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + name: "onERC721Received", + outputs: [ + { + internalType: "bytes4", + name: "", + type: "bytes4", + }, + ], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "proxiableUUID", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes4", + name: "interfaceId", + type: "bytes4", + }, + ], + name: "supportsInterface", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "address", + name: "", + type: "address", + }, + { + internalType: "uint256", + name: "", + type: "uint256", + }, + { + internalType: "bytes", + name: "", + type: "bytes", + }, + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + name: "tokensReceived", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "newImplementation", + type: "address", + }, + ], + name: "upgradeTo", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "newImplementation", + type: "address", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + ], + name: "upgradeToAndCall", + outputs: [], + stateMutability: "payable", + 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: "validationData", + type: "uint256", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address payable", + name: "withdrawAddress", + type: "address", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "withdrawDepositTo", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + stateMutability: "payable", + type: "receive", + }, +] as const; diff --git a/site/snippets/turnkey.ts b/site/snippets/turnkey.ts new file mode 100644 index 000000000..cd9857efe --- /dev/null +++ b/site/snippets/turnkey.ts @@ -0,0 +1,47 @@ +import { LocalAccountSigner, SmartAccountSigner } from "@alchemy/aa-core"; +import { ApiKeyStamper } from "@turnkey/api-key-stamper"; +import { TurnkeyClient } from "@turnkey/http"; +import { createAccount } from "@turnkey/viem"; + +export async function newTurnkeySigner() { + const turnkeyClient = new TurnkeyClient( + { + /* + Configurable, but you will likely use "https://api.turnkey.com". + */ + baseUrl: process.env.TURNKEY_BASE_URL!, + }, + new ApiKeyStamper({ + /* + You will generate these values as part of our quickstart guide: + + https://docs.turnkey.com/getting-started/quickstart + + They are what the signer will use to authenticate requests to the Turnkey.com. + */ + apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!, + apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY!, + }) + ); + + const turnkeyAccount = await createAccount({ + client: turnkeyClient, + /* + You can pull this from the top right widget in the Turnkey dashboard. + The quickstart guide details how to do this. + */ + organizationId: process.env.TURNKEY_ORGANIZATION_ID!, + /* + This value should be the ID of the private key that you want to own + the light smart contract. Similarly pulled from the dashboard and + documented in the quickstart guide. + */ + privateKeyId: process.env.TURNKEY_PRIVATE_KEY_ID!, + }); + + const turnkeySigner: SmartAccountSigner = new LocalAccountSigner( + turnkeyAccount + ); + + return turnkeySigner; +} diff --git a/site/snippets/wallet-client-signer.ts b/site/snippets/wallet-client-signer.ts new file mode 100644 index 000000000..5956e8b38 --- /dev/null +++ b/site/snippets/wallet-client-signer.ts @@ -0,0 +1,15 @@ +import { WalletClientSigner, type SmartAccountSigner } from "@alchemy/aa-core"; +import { createWalletClient, custom } from "viem"; +import { sepolia } from "viem/chains"; + +const externalProvider = window.ethereum; // or anyother EIP-1193 provider + +const walletClient = createWalletClient({ + chain: sepolia, // can provide a different chain here + transport: custom(externalProvider), +}); + +export const signer: SmartAccountSigner = new WalletClientSigner( + walletClient, + "json-rpc" // signerType +); diff --git a/site/snippets/web3auth.ts b/site/snippets/web3auth.ts new file mode 100644 index 000000000..c5070bb94 --- /dev/null +++ b/site/snippets/web3auth.ts @@ -0,0 +1,25 @@ +import { WalletClientSigner, type SmartAccountSigner } from "@alchemy/aa-core"; +import { Web3Auth } from "@web3auth/modal"; +import { createWalletClient, custom } from "viem"; + +// see https://web3auth.io/docs/quick-start for more info +const web3auth = new Web3Auth({ + // web3auth config... +}); + +await web3auth.initModal(); + +await web3auth.connect(); + +// a viem wallet client that wraps web3auth for utility methods +// NOTE: this isn't necessary since you can just use the `web3auth.rpcProvider` +// directly, but this makes things much easier +export const web3authClient = createWalletClient({ + transport: custom(web3auth.provider), +}); + +// a smart account signer you can use as an owner on ISmartContractAccount +export const web3authSigner: SmartAccountSigner = new WalletClientSigner( + web3authClient, + "web3auth" // signerType +); diff --git a/site/tailwind.config.cjs b/site/tailwind.config.cjs new file mode 100644 index 000000000..b02d0b815 --- /dev/null +++ b/site/tailwind.config.cjs @@ -0,0 +1,19 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ["./**/*.{js,ts,jsx,tsx,md}"], + darkMode: "class", + important: true, + theme: { + extend: { + backgroundImage: { + "gradient-1": "linear-gradient(125deg, #ff9c27 0%, #fd48ce 51.7%)", + "gradient-2": + "linear-gradient(120deg, #5498ff 26.44%, #a131f9 109.11%)", + "gradient-3": + "linear-gradient(126deg, #ff37dc 26.26%, #733ff1 108.32%)", + "gradient-4": "linear-gradient(123deg, #ff3a3a -4.89%, #fe8862 90.61%)", + }, + }, + }, + plugins: [], +}; diff --git a/site/tsconfig.json b/site/tsconfig.json new file mode 100644 index 000000000..52b48e99b --- /dev/null +++ b/site/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "module": "esnext", + "target": "esnext", + "lib": ["DOM", "ESNext"], + "strict": true, + "jsx": "preserve", + "esModuleInterop": true, + "skipLibCheck": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "noUnusedLocals": true, + "strictNullChecks": true, + "forceConsistentCasingInFileNames": true, + "types": ["vite/client", "vitepress"], + "paths": { + "~/*": ["src/*"] + } + }, + "exclude": ["dist", "node_modules"] +} diff --git a/site/why-account-kit.md b/site/why-account-kit.md new file mode 100644 index 000000000..9dd8c359f --- /dev/null +++ b/site/why-account-kit.md @@ -0,0 +1,104 @@ +--- +outline: deep +head: + - - meta + - property: og:title + content: Why Account Kit + - - meta + - name: description + content: Learn why we decided to build Account Kit and how it can help you provide seamless UX to your users + - - meta + - property: og:description + content: Learn why we decided to build Account Kit and how it can help you provide seamless UX to your users +--- + +# Why Account Kit + +## The Problem + +It’s way too hard for new users to start using web3 apps. Today, users have to jump through hoops before they submit their first transaction: + +1. Download a wallet app or extension +2. Back up the seed phrase on paper +3. Buy ETH for gas on an exchange +4. Sign a transaction in a wallet + +The user leaves your app at every step! This style of web3 onboarding is too complex and intimidating — most new users drop off before they ever reach the magic moment with your app. We need to make wallets, seed phrases, and gas costs disappear in order to onboard the next billion users. + +## The Solution: Smart Accounts + +Account Kit provides all the tools you need to onboard the next 1B users with a simple, familiar user experience. + +[ HERO GIF/VIDEO ] + +With Account Kit, you can create a **smart account** for every user. Smart accounts are smart contract wallets that leverage account abstraction to radically simplify every step of the onboarding experience. Now, a new user will: + +1. Create a smart account directly in your app without third-party downloads +2. Sign up with an email, social login, passkey, or self-custodial wallet +3. Submit transactions without needing ETH in their account for gas +4. Submit transactions in the background without leaving your app + +Account Kit makes it possible to build a web3 app that feels like web2: simple and familiar for mainstream users. + +It’s enabled by [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337), a new account abstraction standard developed by Vitalik and the Ethereum Foundation. This is the beginning of a major [transition](https://vitalik.ca/general/2023/06/09/three_transitions.html) from traditional EOA wallets to smart accounts. + +Account Kit was designed to make it easy for every developer to leverage account abstraction. It includes everything you need to ship smart accounts in minutes! + +## Your app, your UX + +With Account Kit, you can design every step of the user experience from signup to checkout natively in your app. Replace third-party wallet interactions with native confirmations and background transactions. Curate the right information to display and abstract away the details. Use Account Kit to streamline the experience and improve engagement with your app. + +## Familiar, secure login + +Streamline your sign up flow with simple web2 login options supported by Account Kit: + +- Email + password +- Email + passwordless link +- Social logins like Google, Facebook, or Twitter +- Passkeys secured by fingerprints or FaceID +- Self-custodial wallets like MetaMask or Ledger +- and more + +Account Kit integrates all the leading wallet Signers with bespoke integration guides for [Magic.link](/smart-accounts/signers/magic-link), [web3auth](/smart-accounts/signers/magic-link), [Turnkey](/smart-accounts/signers/turnkey), Privy, Lit Protocol, Fireblocks, Portal, and [Capsule](/smart-accounts/signers/capsule). Account Kit even supports self-custodial wallets like Metamask or Ledger. Users can even change their signer later via Account Kit’s [ownership transfer](/smart-accounts/transferring-ownership) functionality. + +Learn [how to choose the right signer for your use case in this doc](/smart-accounts/signers/overview). + +## Sponsor gas + +Account Kit removes the greatest barrier to entry of all: gas fees. + +Many newcomers give up on web3 before submitting their first transaction. It's daunting to buy crypto for the first time, especially before trying the app! Gas fees -- even cheap ones on L2 -- discourage newcomers from trying your app. + +With Account Kit you can remove this barrier by [sponsoring gas fees](/smart-accounts/sponsoring-gas) for transactions — especially the first one! Get the user to your app’s magic moment as quickly, and help them fall in love with your product before asking them to deposit money. + +The [Gas Manager API](https://dashboard.alchemy.com/gas-manager) included in Account Kit is a powerful tool to [sponsor gas](/smart-accounts/sponsoring-gas). Sponsorship rules are programmable, giving you precise control over spending limits, allowlisted/blocklisted wallet addresses, and more through a REST API or an intuitive management dashboard. + +In the future, Account Kit will support paying gas in stablecoins like USDC and other ERC20s. If you’re interested those features, [contact us](mailto:account-abstraction@alchemy.com) to chat. + +## Batch transactions + +Streamline multi-step actions into a single click. Using the Bundler API, you can effortlessly [batch multiple transactions](smart-accounts/batching-transactions) into a single operation. For example, imagine a normal user who wants to mint two NFTs as part of your giveaway. You can submit a single user operation that batches the following transactions together all with a single click and sponsored gas: + +1. Deploy a smart account contract for the user +2. Mint NFT #1 +3. Mint NFT #2 + +Here’s another great example: on DEXes you can batch the `Approve` transaction and `Swap` transaction into a single operation. The user never needs to know about token approvals! + +## Instant compatibility + +Account Kit is instantly [compatible with your dapp](https://docs.alchemy.com/docs/how-to-make-your-dapp-compatible-with-smart-contract-wallets) because it supports the EIP-1193 standard. Almost all dapps use this canonical interface to communicate with wallets and send requests. This means that Account Kit will plug into all the wallet connect libraries you know and love including RainbowKit, Wagmi, and Web3Modal to name a few. Account Kit is also built on viem with support for ethers.js, enabling engineers to get started in minutes! + +## The complete stack for account abstraction + +We built Account Kit from the ground up to be reliable, scalable, and developer-friendly. When you use Account Kit, you’re tapping into a vertically integrated stack designed to work together seamlessly from bottom to top: + +- **AA-SDK**: A lightweight library to integrate, deploy, and use smart accounts. The `aa-sdk` orchestrates operations like gas estimation, UserOp submission, and counterfactual address generation under the hood. We handled all the details so you don’t have to. +- **LightAccount:** Secure, gas-optimized, audited smart contract accounts. Purpose-built for [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) and forward compatible with [EIP-6900](https://eips.ethereum.org/EIPS/eip-6900). +- **Signer:** Bespoke developer guides to integrate the most popular wallet providers. Secure your accounts with email, social login, passkeys, or a self-custodial wallet signer. +- **Gas Manager API:** A programmable API to abstract away gas fees for UserOps that meet your criteria. We built [programmable policies](https://docs.alchemy.com/reference/gas-manager-admin-api-quickstart) to give you flexibility and control to decide which transactions should be sponsored. The Gas Manager works hand-in-hand with the Bundler. +- **Bundler API:** The most reliable ERC-4337 Bundler. Land your UserOps onchain at massive scale. We wrote our Bundler from scratch, in Rust, to handle the highest loads at production scale. Check out the open source code in our affectionately named [Rundler github repo](https://github.com/alchemyplatform/rundler). + +We have years of experience as the leading web3 developer platform powering customers from Opensea to Shopify, and brought all that expertise to bear in Account Kit. + +Get started with Account Kit on the next page: [Getting Started](/getting-started). diff --git a/yarn.lock b/yarn.lock index 1579b1821..2b77402ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,22 +12,165 @@ resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.9.4.tgz#aae21cb858bbb0411949d5b7b3051f4209043f62" integrity sha512-UK0bHA7hh9cR39V+4gl2/NnBBjoXIxkuWAPCaY4X7fbH4L/azIi7ilWOCjMUYfpJgraLUAqkRi2BqrjME8Rynw== +"@alchemy/aa-accounts@latest": + version "0.1.0-alpha.32" + resolved "https://registry.yarnpkg.com/@alchemy/aa-accounts/-/aa-accounts-0.1.0-alpha.32.tgz#848c7e64cecfbfe42d5643c9fba58b1974b13a95" + integrity sha512-aFVJrQnygXETE7U0knqPLKRxcNhfxV0cBRhlIAJc2AXMt/bC5bJv8O/kkS0BYP7A221lmafCDJXwozd9ssgoRA== + dependencies: + viem "^1.5.3" + "@alchemy/aa-alchemy@latest": - version "0.1.0-alpha.30" - resolved "https://registry.yarnpkg.com/@alchemy/aa-alchemy/-/aa-alchemy-0.1.0-alpha.30.tgz#9808843879acda6188efa8c90bafab10da334529" - integrity sha512-q99kqHHXaZK673OLy0PW9IPr8k+siSNcX/H0ONp0+S7fKTBZhY1F79P4LcItseGwM6cQI+RAc4ugLlTukxDjbg== + version "0.1.0-alpha.32" + resolved "https://registry.yarnpkg.com/@alchemy/aa-alchemy/-/aa-alchemy-0.1.0-alpha.32.tgz#33ce9b99796fe38950f6a6074adfb88595a354ed" + integrity sha512-ICkiCW064//5P2F8kgsoZDsN9mllLzTE1ZjdO8SMFkRODYAoc04Fz1Ko7hKYTcw+tbJP+XptdbzaYoGEd9PzXA== dependencies: viem "^1.10.9" "@alchemy/aa-core@latest": - version "0.1.0-alpha.30" - resolved "https://registry.yarnpkg.com/@alchemy/aa-core/-/aa-core-0.1.0-alpha.30.tgz#0aaab82aa7274c4832695777a73b35b507bbdbae" - integrity sha512-2cnt7bZ8c+PnLt3GP7FTGzkRNjbG2pjR2+lFHRC+wk3YU6xhdbcX8SQ2nv9Bj3uBA4XxHlCFUguvfLJ+Mdw4hg== + version "0.1.0-alpha.32" + resolved "https://registry.yarnpkg.com/@alchemy/aa-core/-/aa-core-0.1.0-alpha.32.tgz#c94e8eaab4ab2ad7400ae29b58d31018adf67c07" + integrity sha512-aS+0U2yqeEyNcoumPxd5fLBuSBA3bj3FTwMkmyYjWZSnUDjdqFb/a4yc5v4zhe1pBejCWzu6fJzNwIC4+xCFdg== dependencies: abitype "^0.8.3" eventemitter3 "^5.0.1" viem "^1.10.9" +"@algolia/autocomplete-core@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz#1d56482a768c33aae0868c8533049e02e8961be7" + integrity sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw== + dependencies: + "@algolia/autocomplete-plugin-algolia-insights" "1.9.3" + "@algolia/autocomplete-shared" "1.9.3" + +"@algolia/autocomplete-plugin-algolia-insights@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz#9b7f8641052c8ead6d66c1623d444cbe19dde587" + integrity sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg== + dependencies: + "@algolia/autocomplete-shared" "1.9.3" + +"@algolia/autocomplete-preset-algolia@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz#64cca4a4304cfcad2cf730e83067e0c1b2f485da" + integrity sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA== + dependencies: + "@algolia/autocomplete-shared" "1.9.3" + +"@algolia/autocomplete-shared@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz#2e22e830d36f0a9cf2c0ccd3c7f6d59435b77dfa" + integrity sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ== + +"@algolia/cache-browser-local-storage@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.20.0.tgz#357318242fc542ffce41d6eb5b4a9b402921b0bb" + integrity sha512-uujahcBt4DxduBTvYdwO3sBfHuJvJokiC3BP1+O70fglmE1ShkH8lpXqZBac1rrU3FnNYSUs4pL9lBdTKeRPOQ== + dependencies: + "@algolia/cache-common" "4.20.0" + +"@algolia/cache-common@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.20.0.tgz#ec52230509fce891091ffd0d890618bcdc2fa20d" + integrity sha512-vCfxauaZutL3NImzB2G9LjLt36vKAckc6DhMp05An14kVo8F1Yofb6SIl6U3SaEz8pG2QOB9ptwM5c+zGevwIQ== + +"@algolia/cache-in-memory@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.20.0.tgz#5f18d057bd6b3b075022df085c4f83bcca4e3e67" + integrity sha512-Wm9ak/IaacAZXS4mB3+qF/KCoVSBV6aLgIGFEtQtJwjv64g4ePMapORGmCyulCFwfePaRAtcaTbMcJF+voc/bg== + dependencies: + "@algolia/cache-common" "4.20.0" + +"@algolia/client-account@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.20.0.tgz#23ce0b4cffd63100fb7c1aa1c67a4494de5bd645" + integrity sha512-GGToLQvrwo7am4zVkZTnKa72pheQeez/16sURDWm7Seyz+HUxKi3BM6fthVVPUEBhtJ0reyVtuK9ArmnaKl10Q== + dependencies: + "@algolia/client-common" "4.20.0" + "@algolia/client-search" "4.20.0" + "@algolia/transporter" "4.20.0" + +"@algolia/client-analytics@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.20.0.tgz#0aa6bef35d3a41ac3991b3f46fcd0bf00d276fa9" + integrity sha512-EIr+PdFMOallRdBTHHdKI3CstslgLORQG7844Mq84ib5oVFRVASuuPmG4bXBgiDbcsMLUeOC6zRVJhv1KWI0ug== + dependencies: + "@algolia/client-common" "4.20.0" + "@algolia/client-search" "4.20.0" + "@algolia/requester-common" "4.20.0" + "@algolia/transporter" "4.20.0" + +"@algolia/client-common@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.20.0.tgz#ca60f04466515548651c4371a742fbb8971790ef" + integrity sha512-P3WgMdEss915p+knMMSd/fwiHRHKvDu4DYRrCRaBrsfFw7EQHon+EbRSm4QisS9NYdxbS04kcvNoavVGthyfqQ== + dependencies: + "@algolia/requester-common" "4.20.0" + "@algolia/transporter" "4.20.0" + +"@algolia/client-personalization@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.20.0.tgz#ca81308e8ad0db3b27458b78355f124f29657181" + integrity sha512-N9+zx0tWOQsLc3K4PVRDV8GUeOLAY0i445En79Pr3zWB+m67V+n/8w4Kw1C5LlbHDDJcyhMMIlqezh6BEk7xAQ== + dependencies: + "@algolia/client-common" "4.20.0" + "@algolia/requester-common" "4.20.0" + "@algolia/transporter" "4.20.0" + +"@algolia/client-search@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.20.0.tgz#3bcce817ca6caedc835e0eaf6f580e02ee7c3e15" + integrity sha512-zgwqnMvhWLdpzKTpd3sGmMlr4c+iS7eyyLGiaO51zDZWGMkpgoNVmltkzdBwxOVXz0RsFMznIxB9zuarUv4TZg== + dependencies: + "@algolia/client-common" "4.20.0" + "@algolia/requester-common" "4.20.0" + "@algolia/transporter" "4.20.0" + +"@algolia/logger-common@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.20.0.tgz#f148ddf67e5d733a06213bebf7117cb8a651ab36" + integrity sha512-xouigCMB5WJYEwvoWW5XDv7Z9f0A8VoXJc3VKwlHJw/je+3p2RcDXfksLI4G4lIVncFUYMZx30tP/rsdlvvzHQ== + +"@algolia/logger-console@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.20.0.tgz#ac443d27c4e94357f3063e675039cef0aa2de0a7" + integrity sha512-THlIGG1g/FS63z0StQqDhT6bprUczBI8wnLT3JWvfAQDZX5P6fCg7dG+pIrUBpDIHGszgkqYEqECaKKsdNKOUA== + dependencies: + "@algolia/logger-common" "4.20.0" + +"@algolia/requester-browser-xhr@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.20.0.tgz#db16d0bdef018b93b51681d3f1e134aca4f64814" + integrity sha512-HbzoSjcjuUmYOkcHECkVTwAelmvTlgs48N6Owt4FnTOQdwn0b8pdht9eMgishvk8+F8bal354nhx/xOoTfwiAw== + dependencies: + "@algolia/requester-common" "4.20.0" + +"@algolia/requester-common@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.20.0.tgz#65694b2263a8712b4360fef18680528ffd435b5c" + integrity sha512-9h6ye6RY/BkfmeJp7Z8gyyeMrmmWsMOCRBXQDs4mZKKsyVlfIVICpcSibbeYcuUdurLhIlrOUkH3rQEgZzonng== + +"@algolia/requester-node-http@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.20.0.tgz#b52b182b52b0b16dec4070832267d484a6b1d5bb" + integrity sha512-ocJ66L60ABSSTRFnCHIEZpNHv6qTxsBwJEPfYaSBsLQodm0F9ptvalFkHMpvj5DfE22oZrcrLbOYM2bdPJRHng== + dependencies: + "@algolia/requester-common" "4.20.0" + +"@algolia/transporter@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.20.0.tgz#7e5b24333d7cc9a926b2f6a249f87c2889b944a9" + integrity sha512-Lsii1pGWOAISbzeyuf+r/GPhvHMPHSPrTDWNcIzOE1SG1inlJHICaVe2ikuoRjcpgxZNU54Jl+if15SUCsaTUg== + dependencies: + "@algolia/cache-common" "4.20.0" + "@algolia/logger-common" "4.20.0" + "@algolia/requester-common" "4.20.0" + +"@alloc/quick-lru@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" + integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== + "@ampproject/remapping@^2.2.0": version "2.2.1" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" @@ -287,7 +430,7 @@ chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/parser@^7.22.15", "@babel/parser@^7.22.16": +"@babel/parser@^7.20.15", "@babel/parser@^7.21.3", "@babel/parser@^7.22.15", "@babel/parser@^7.22.16": version "7.22.16" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.16.tgz#180aead7f247305cce6551bea2720934e2fa2c95" integrity sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA== @@ -2132,6 +2275,29 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@docsearch/css@3.5.2", "@docsearch/css@^3.5.2": + version "3.5.2" + resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.5.2.tgz#610f47b48814ca94041df969d9fcc47b91fc5aac" + integrity sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA== + +"@docsearch/js@^3.5.2": + version "3.5.2" + resolved "https://registry.yarnpkg.com/@docsearch/js/-/js-3.5.2.tgz#a11cb2e7e62890e9e940283fed6972ecf632629d" + integrity sha512-p1YFTCDflk8ieHgFJYfmyHBki1D61+U9idwrLh+GQQMrBSP3DLGKpy0XUJtPjAOPltcVbqsTjiPFfH7JImjUNg== + dependencies: + "@docsearch/react" "3.5.2" + preact "^10.0.0" + +"@docsearch/react@3.5.2": + version "3.5.2" + resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.5.2.tgz#2e6bbee00eb67333b64906352734da6aef1232b9" + integrity sha512-9Ahcrs5z2jq/DcAvYtvlqEBHImbm4YJI8M9y0x6Tqg598P40HTEkX7hsMcIuThI+hTFxRGZ9hll0Wygm2yEjng== + dependencies: + "@algolia/autocomplete-core" "1.9.3" + "@algolia/autocomplete-preset-algolia" "1.9.3" + "@docsearch/css" "3.5.2" + algoliasearch "^4.19.1" + "@emotion/babel-plugin@^11.11.0": version "11.11.0" resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" @@ -4002,6 +4168,11 @@ resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.3.tgz#a136f83b0758698df454e328759dbd3d44555311" integrity sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g== +"@types/web-bluetooth@^0.0.17": + version "0.0.17" + resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.17.tgz#5c9f3c617f64a9735d7b72a7cc671e166d900c40" + integrity sha512-4p9vcSmxAayx72yn70joFoL44c9MO/0+iVEBIQXe3v2h2SiAsEIo/G5v6ObFWvNKRFjbrVadNf9LqEEZeQPzdA== + "@types/ws@^7.4.4": version "7.4.7" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" @@ -4230,6 +4401,132 @@ loupe "^2.3.6" pretty-format "^27.5.1" +"@vue/compiler-core@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.3.4.tgz#7fbf591c1c19e1acd28ffd284526e98b4f581128" + integrity sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g== + dependencies: + "@babel/parser" "^7.21.3" + "@vue/shared" "3.3.4" + estree-walker "^2.0.2" + source-map-js "^1.0.2" + +"@vue/compiler-dom@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz#f56e09b5f4d7dc350f981784de9713d823341151" + integrity sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w== + dependencies: + "@vue/compiler-core" "3.3.4" + "@vue/shared" "3.3.4" + +"@vue/compiler-sfc@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz#b19d942c71938893535b46226d602720593001df" + integrity sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ== + dependencies: + "@babel/parser" "^7.20.15" + "@vue/compiler-core" "3.3.4" + "@vue/compiler-dom" "3.3.4" + "@vue/compiler-ssr" "3.3.4" + "@vue/reactivity-transform" "3.3.4" + "@vue/shared" "3.3.4" + estree-walker "^2.0.2" + magic-string "^0.30.0" + postcss "^8.1.10" + source-map-js "^1.0.2" + +"@vue/compiler-ssr@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz#9d1379abffa4f2b0cd844174ceec4a9721138777" + integrity sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ== + dependencies: + "@vue/compiler-dom" "3.3.4" + "@vue/shared" "3.3.4" + +"@vue/devtools-api@^6.5.0": + version "6.5.0" + resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz#98b99425edee70b4c992692628fa1ea2c1e57d07" + integrity sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q== + +"@vue/reactivity-transform@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz#52908476e34d6a65c6c21cd2722d41ed8ae51929" + integrity sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw== + dependencies: + "@babel/parser" "^7.20.15" + "@vue/compiler-core" "3.3.4" + "@vue/shared" "3.3.4" + estree-walker "^2.0.2" + magic-string "^0.30.0" + +"@vue/reactivity@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.3.4.tgz#a27a29c6cd17faba5a0e99fbb86ee951653e2253" + integrity sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ== + dependencies: + "@vue/shared" "3.3.4" + +"@vue/runtime-core@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.3.4.tgz#4bb33872bbb583721b340f3088888394195967d1" + integrity sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA== + dependencies: + "@vue/reactivity" "3.3.4" + "@vue/shared" "3.3.4" + +"@vue/runtime-dom@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz#992f2579d0ed6ce961f47bbe9bfe4b6791251566" + integrity sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ== + dependencies: + "@vue/runtime-core" "3.3.4" + "@vue/shared" "3.3.4" + csstype "^3.1.1" + +"@vue/server-renderer@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.3.4.tgz#ea46594b795d1536f29bc592dd0f6655f7ea4c4c" + integrity sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ== + dependencies: + "@vue/compiler-ssr" "3.3.4" + "@vue/shared" "3.3.4" + +"@vue/shared@3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.3.4.tgz#06e83c5027f464eef861c329be81454bc8b70780" + integrity sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ== + +"@vueuse/core@10.4.1", "@vueuse/core@^10.4.1": + version "10.4.1" + resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-10.4.1.tgz#fc2c8a83a571c207aaedbe393b22daa6d35123f2" + integrity sha512-DkHIfMIoSIBjMgRRvdIvxsyboRZQmImofLyOHADqiVbQVilP8VVHDhBX2ZqoItOgu7dWa8oXiNnScOdPLhdEXg== + dependencies: + "@types/web-bluetooth" "^0.0.17" + "@vueuse/metadata" "10.4.1" + "@vueuse/shared" "10.4.1" + vue-demi ">=0.14.5" + +"@vueuse/integrations@^10.4.1": + version "10.4.1" + resolved "https://registry.yarnpkg.com/@vueuse/integrations/-/integrations-10.4.1.tgz#a48356a0bad49f5e724cd9acaebd73d2b66f22a9" + integrity sha512-uRBPyG5Lxoh1A/J+boiioPT3ELEAPEo4t8W6Mr4yTKIQBeW/FcbsotZNPr4k9uz+3QEksMmflWloS9wCnypM7g== + dependencies: + "@vueuse/core" "10.4.1" + "@vueuse/shared" "10.4.1" + vue-demi ">=0.14.5" + +"@vueuse/metadata@10.4.1": + version "10.4.1" + resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-10.4.1.tgz#9d2ff5c67abf17a8c07865c2413fbd0e92f7b7d7" + integrity sha512-2Sc8X+iVzeuMGHr6O2j4gv/zxvQGGOYETYXEc41h0iZXIRnRbJZGmY/QP8dvzqUelf8vg0p/yEA5VpCEu+WpZg== + +"@vueuse/shared@10.4.1": + version "10.4.1" + resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-10.4.1.tgz#d5ce33033c156efb60664b5d6034d6cd4e2f530c" + integrity sha512-vz5hbAM4qA0lDKmcr2y3pPdU+2EVw/yzfRsBdu+6+USGa4PxqSQRYIUC9/NcT06y+ZgaTsyURw2I9qOFaaXHAg== + dependencies: + vue-demi ">=0.14.5" + "@wagmi/connectors@3.1.1": version "3.1.1" resolved "https://registry.yarnpkg.com/@wagmi/connectors/-/connectors-3.1.1.tgz#3a7993e1e6865370aa9635b7a5d53f0faf0534f1" @@ -4781,6 +5078,26 @@ alchemy-sdk@^2.8.3: sturdy-websocket "^0.2.1" websocket "^1.0.34" +algoliasearch@^4.19.1: + version "4.20.0" + resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.20.0.tgz#700c2cb66e14f8a288460036c7b2a554d0d93cf4" + integrity sha512-y+UHEjnOItoNy0bYO+WWmLWBlPwDjKHW6mNHrPi0NkuhpQOOEbrkwQH/wgKFDLh7qlKjzoKeiRtlpewDPDG23g== + dependencies: + "@algolia/cache-browser-local-storage" "4.20.0" + "@algolia/cache-common" "4.20.0" + "@algolia/cache-in-memory" "4.20.0" + "@algolia/client-account" "4.20.0" + "@algolia/client-analytics" "4.20.0" + "@algolia/client-common" "4.20.0" + "@algolia/client-personalization" "4.20.0" + "@algolia/client-search" "4.20.0" + "@algolia/logger-common" "4.20.0" + "@algolia/logger-console" "4.20.0" + "@algolia/requester-browser-xhr" "4.20.0" + "@algolia/requester-common" "4.20.0" + "@algolia/requester-node-http" "4.20.0" + "@algolia/transporter" "4.20.0" + ansi-colors@^4.1.1: version "4.1.3" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" @@ -4810,6 +5127,11 @@ ansi-regex@^6.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== +ansi-sequence-parser@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz#e0aa1cdcbc8f8bb0b5bca625aac41f5f056973cf" + integrity sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg== + ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -4834,6 +5156,19 @@ ansi-styles@^6.0.0, ansi-styles@^6.1.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + "aproba@^1.0.3 || ^2.0.0", aproba@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" @@ -4860,6 +5195,11 @@ arg@^4.1.0: resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== +arg@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -5029,6 +5369,18 @@ atomic-sleep@^1.0.0: resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== +autoprefixer@^10.4.16: + version "10.4.16" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.16.tgz#fad1411024d8670880bdece3970aa72e3572feb8" + integrity sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ== + dependencies: + browserslist "^4.21.10" + caniuse-lite "^1.0.30001538" + fraction.js "^4.3.6" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" + available-typed-arrays@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" @@ -5166,6 +5518,11 @@ bin-links@^4.0.1: read-cmd-shim "^4.0.0" write-file-atomic "^5.0.0" +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + bind-decorator@^1.0.11: version "1.0.11" resolved "https://registry.yarnpkg.com/bind-decorator/-/bind-decorator-1.0.11.tgz#e41bc06a1f65dd9cec476c91c5daf3978488252f" @@ -5226,7 +5583,7 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.2: +braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -5367,6 +5724,11 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + camelcase-keys@^6.2.2: version "6.2.2" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" @@ -5386,6 +5748,11 @@ caniuse-lite@^1.0.30001406, caniuse-lite@^1.0.30001517: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001532.tgz#c6a4d5d2da6d2b967f0ee5e12e7f680db6ad2fca" integrity sha512-FbDFnNat3nMnrROzqrsg314zhqN5LGQ1kyyMk2opcrwGbVGpHRhgCWtAgD5YJUqNAiQ+dklreil/c3Qf1dfCTw== +caniuse-lite@^1.0.30001538: + version "1.0.30001543" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001543.tgz#478a3e9dddbb353c5ab214b0ecb0dbed529ed1d8" + integrity sha512-qxdO8KPWPQ+Zk6bvNpPeQIOH47qZSYdFZd6dXQzb2KzhnSXju4Kd7H1PkSJx6NICSMgo/IhRZRhhfPTHYpJUCA== + chai@^4.3.7: version "4.3.8" resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.8.tgz#40c59718ad6928da6629c70496fe990b2bb5b17c" @@ -5439,6 +5806,21 @@ check-error@^1.0.2: resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA== +chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + chownr@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" @@ -5628,6 +6010,11 @@ commander@^2.20.3: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commander@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + common-ancestor-path@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" @@ -5903,7 +6290,7 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -csstype@^3.0.11, csstype@^3.0.2, csstype@^3.0.7: +csstype@^3.0.11, csstype@^3.0.2, csstype@^3.0.7, csstype@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== @@ -6083,6 +6470,11 @@ detect-node-es@^1.1.0: resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== +didyoumean@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" + integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== + diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" @@ -6100,6 +6492,11 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +dlv@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" + integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== + doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -6685,6 +7082,11 @@ estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== +estree-walker@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + esutils@^2.0.2, esutils@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -6798,6 +7200,21 @@ execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" +execa@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" + integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^8.0.1" + human-signals "^5.0.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^4.1.0" + strip-final-newline "^3.0.0" + exponential-backoff@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" @@ -6845,7 +7262,7 @@ fast-glob@3.2.7: merge2 "^1.3.0" micromatch "^4.0.4" -fast-glob@^3.2.9, fast-glob@^3.3.1: +fast-glob@^3.2.12, fast-glob@^3.2.9, fast-glob@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== @@ -6993,6 +7410,13 @@ focus-lock@^0.11.6: dependencies: tslib "^2.0.3" +focus-trap@^7.5.2: + version "7.5.2" + resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-7.5.2.tgz#e5ee678d10a18651f2591ffb66c949fb098d57cf" + integrity sha512-p6vGNNWLDGwJCiEjkSK6oERj/hEyI9ITsSwIUICBoKLlWiTWXJRfQibCwcoi50rTZdbi87qDtUlMCmQwsGSgPw== + dependencies: + tabbable "^6.2.0" + follow-redirects@^1.14.8, follow-redirects@^1.15.0: version "1.15.2" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" @@ -7029,6 +7453,11 @@ formdata-polyfill@^4.0.10: dependencies: fetch-blob "^3.1.2" +fraction.js@^4.3.6: + version "4.3.6" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.6.tgz#e9e3acec6c9a28cf7bc36cbe35eea4ceb2c5c92d" + integrity sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg== + framer-motion@^10.12.12: version "10.16.4" resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-10.16.4.tgz#30279ef5499b8d85db3a298ee25c83429933e9f8" @@ -7196,6 +7625,11 @@ get-stream@^6.0.0, get-stream@^6.0.1: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +get-stream@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" + integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== + get-symbol-description@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" @@ -7260,7 +7694,7 @@ gitconfiglocal@^1.0.0: dependencies: ini "^1.3.2" -glob-parent@5.1.2, glob-parent@^5.1.2: +glob-parent@5.1.2, glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -7291,6 +7725,18 @@ glob@7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" +glob@7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@7.1.7: version "7.1.7" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" @@ -7569,6 +8015,11 @@ human-signals@^4.3.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== +human-signals@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" + integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== + humanize-ms@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" @@ -7780,6 +8231,13 @@ is-bigint@^1.0.1: dependencies: has-bigints "^1.0.1" +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + is-boolean-object@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" @@ -7848,7 +8306,7 @@ is-generator-function@^1.0.10, is-generator-function@^1.0.7: dependencies: has-tostringtag "^1.0.0" -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -8056,6 +8514,11 @@ isomorphic-ws@^4.0.1: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== +isows@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.3.tgz#93c1cf0575daf56e7120bab5c8c448b0809d0d74" + integrity sha512-2cKei4vlmg2cxEjm3wVSqn8pcoRF/LX/wpifuuNquFO4SQmPwarClT+SUCA2lt+l581tTeZIPIZuIDo2jWN1fg== + iterator.prototype@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.1.tgz#ab5b790e23ec00658f5974e032a2b05188bd3a5c" @@ -8103,6 +8566,11 @@ jayson@^4.1.0: uuid "^8.3.2" ws "^7.4.5" +jiti@^1.18.2: + version "1.20.0" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.20.0.tgz#2d823b5852ee8963585c8dd8b7992ffc1ae83b42" + integrity sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA== + js-sha3@0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" @@ -8404,7 +8872,7 @@ libnpmpublish@7.1.4: sigstore "^1.4.0" ssri "^10.0.1" -lilconfig@2.1.0: +lilconfig@2.1.0, lilconfig@^2.0.5, lilconfig@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== @@ -8726,6 +9194,11 @@ map-obj@^4.0.0: resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== +mark.js@8.11.1: + version "8.11.1" + resolved "https://registry.yarnpkg.com/mark.js/-/mark.js-8.11.1.tgz#180f1f9ebef8b0e638e4166ad52db879beb2ffc5" + integrity sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ== + md5-hex@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/md5-hex/-/md5-hex-3.0.1.tgz#be3741b510591434b2784d79e556eefc2c9a8e5c" @@ -8767,7 +9240,7 @@ merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -micromatch@4.0.5, micromatch@^4.0.4: +micromatch@4.0.5, micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== @@ -8948,6 +9421,11 @@ minipass@^5.0.0: resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.3.tgz#05ea638da44e475037ed94d1c7efcc76a25e1974" integrity sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg== +minisearch@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/minisearch/-/minisearch-6.1.0.tgz#6e74e743dbd0e88fa5ca52fef2391e0aa7055252" + integrity sha512-PNxA/X8pWk+TiqPbsoIYH0GQ5Di7m6326/lwU/S4mlo4wGQddIcf/V//1f9TB0V4j59b57b+HZxt8h3iMROGvg== + minizlib@^2.1.1, minizlib@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" @@ -9033,6 +9511,15 @@ mute-stream@0.0.8, mute-stream@~0.0.4: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + nanoid@^3.3.4, nanoid@^3.3.6: version "3.3.6" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" @@ -9206,6 +9693,16 @@ normalize-package-data@^5.0.0: semver "^7.3.5" validate-npm-package-license "^3.0.4" +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== + npm-bundled@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.2.tgz#944c78789bd739035b70baa2ca5cc32b8d860bc1" @@ -9422,11 +9919,16 @@ nx@15.9.6, "nx@>=15.5.2 < 16": "@nrwl/nx-win32-arm64-msvc" "15.9.6" "@nrwl/nx-win32-x64-msvc" "15.9.6" -object-assign@^4.1.1: +object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== +object-hash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + object-inspect@^1.12.3, object-inspect@^1.9.0: version "1.12.3" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" @@ -9833,7 +10335,7 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.3.1: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== @@ -9893,6 +10395,11 @@ pino@7.11.0: sonic-boom "^2.2.1" thread-stream "^0.15.1" +pirates@^4.0.1: + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== + pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" @@ -9914,7 +10421,38 @@ pngjs@^5.0.0: resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== -postcss-selector-parser@^6.0.10: +postcss-import@^15.1.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70" + integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew== + dependencies: + postcss-value-parser "^4.0.0" + read-cache "^1.0.0" + resolve "^1.1.7" + +postcss-js@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2" + integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw== + dependencies: + camelcase-css "^2.0.1" + +postcss-load-config@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-4.0.1.tgz#152383f481c2758274404e4962743191d73875bd" + integrity sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA== + dependencies: + lilconfig "^2.0.5" + yaml "^2.1.1" + +postcss-nested@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.0.1.tgz#f83dc9846ca16d2f4fa864f16e9d9f7d0961662c" + integrity sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ== + dependencies: + postcss-selector-parser "^6.0.11" + +postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.11: version "6.0.13" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b" integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ== @@ -9922,6 +10460,11 @@ postcss-selector-parser@^6.0.10: cssesc "^3.0.0" util-deprecate "^1.0.2" +postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + postcss@8.4.14: version "8.4.14" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf" @@ -9931,6 +10474,24 @@ postcss@8.4.14: picocolors "^1.0.0" source-map-js "^1.0.2" +postcss@^8.1.10: + version "8.4.30" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.30.tgz#0e0648d551a606ef2192a26da4cabafcc09c1aa7" + integrity sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g== + dependencies: + nanoid "^3.3.6" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +postcss@^8.4.23: + version "8.4.31" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" + integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== + dependencies: + nanoid "^3.3.6" + picocolors "^1.0.0" + source-map-js "^1.0.2" + postcss@^8.4.27: version "8.4.29" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.29.tgz#33bc121cf3b3688d4ddef50be869b2a54185a1dd" @@ -9940,7 +10501,7 @@ postcss@^8.4.27: picocolors "^1.0.0" source-map-js "^1.0.2" -preact@^10.12.0, preact@^10.5.9: +preact@^10.0.0, preact@^10.12.0, preact@^10.5.9: version "10.17.1" resolved "https://registry.yarnpkg.com/preact/-/preact-10.17.1.tgz#0a1b3c658c019e759326b9648c62912cf5c2dde1" integrity sha512-X9BODrvQ4Ekwv9GURm9AKAGaomqXmip7NQTZgY7gcNmr7XE83adOMJvd3N42id1tMFU7ojiynRsYnY6/BRFxLA== @@ -10229,6 +10790,13 @@ react@18.2.0: dependencies: loose-envify "^1.1.0" +read-cache@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" + integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== + dependencies: + pify "^2.3.0" + read-cmd-shim@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-3.0.0.tgz#62b8c638225c61e6cc607f8f4b779f3b8238f155" @@ -10361,6 +10929,13 @@ readable-stream@~2.3.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + real-require@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.1.0.tgz#736ac214caa20632847b7ca8c1056a0767df9381" @@ -10482,6 +11057,15 @@ resolve-pkg-maps@^1.0.0: resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== +resolve@^1.1.7, resolve@^1.22.2: + version "1.22.6" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.6.tgz#dd209739eca3aef739c626fea1b4f3c506195362" + integrity sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + resolve@^1.10.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.22.4: version "1.22.4" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34" @@ -10710,6 +11294,16 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +shiki@^0.14.4: + version "0.14.4" + resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.14.4.tgz#2454969b466a5f75067d0f2fa0d7426d32881b20" + integrity sha512-IXCRip2IQzKwxArNNq1S+On4KPML3Yyn8Zzs/xRgcgOWIr8ntIK3IKzjFPfjy/7kt9ZMjc+FItfqHRBg8b6tNQ== + dependencies: + ansi-sequence-parser "^1.1.0" + jsonc-parser "^3.2.0" + vscode-oniguruma "^1.7.0" + vscode-textmate "^8.0.0" + side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -10729,7 +11323,7 @@ signal-exit@3.0.7, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -signal-exit@^4.0.1: +signal-exit@^4.0.1, signal-exit@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== @@ -11078,6 +11672,19 @@ stylis@4.2.0: resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== +sucrase@^3.32.0: + version "3.34.0" + resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.34.0.tgz#1e0e2d8fcf07f8b9c3569067d92fbd8690fb576f" + integrity sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.2" + commander "^4.0.0" + glob "7.1.6" + lines-and-columns "^1.1.6" + mz "^2.7.0" + pirates "^4.0.1" + ts-interface-checker "^0.1.9" + superstruct@^0.14.2: version "0.14.2" resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.14.2.tgz#0dbcdf3d83676588828f1cf5ed35cda02f59025b" @@ -11107,6 +11714,39 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +tabbable@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" + integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== + +tailwindcss@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.3.tgz#90da807393a2859189e48e9e7000e6880a736daf" + integrity sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w== + dependencies: + "@alloc/quick-lru" "^5.2.0" + arg "^5.0.2" + chokidar "^3.5.3" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.2.12" + glob-parent "^6.0.2" + is-glob "^4.0.3" + jiti "^1.18.2" + lilconfig "^2.1.0" + micromatch "^4.0.5" + normalize-path "^3.0.0" + object-hash "^3.0.0" + picocolors "^1.0.0" + postcss "^8.4.23" + postcss-import "^15.1.0" + postcss-js "^4.0.1" + postcss-load-config "^4.0.1" + postcss-nested "^6.0.1" + postcss-selector-parser "^6.0.11" + resolve "^1.22.2" + sucrase "^3.32.0" + tapable@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" @@ -11183,6 +11823,20 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + thread-stream@^0.15.1: version "0.15.2" resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-0.15.2.tgz#fb95ad87d2f1e28f07116eb23d85aba3bc0425f4" @@ -11286,6 +11940,11 @@ ts-api-utils@^1.0.1: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg== +ts-interface-checker@^0.1.9: + version "0.1.13" + resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" + integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== + ts-node@^10.8.1, ts-node@^10.9.1: version "10.9.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" @@ -11695,7 +12354,7 @@ valtio@1.11.0: proxy-compare "2.5.1" use-sync-external-store "1.2.0" -viem@^1.0.0, viem@^1.10.9, viem@^1.5.3: +viem@^1.0.0, viem@^1.10.9: version "1.10.9" resolved "https://registry.yarnpkg.com/viem/-/viem-1.10.9.tgz#9b16ade429b234f7b33a79544badddbb5498b13c" integrity sha512-fhQxnMBFwGbyE/VOfcvcavxAPyqIHSgOB793gQcMPH1B9ahQvjMe3C3icqypC01ACaqpDPgYWGfvF/ExTeIPuA== @@ -11710,6 +12369,35 @@ viem@^1.0.0, viem@^1.10.9, viem@^1.5.3: isomorphic-ws "5.0.0" ws "8.13.0" +viem@^1.16.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/viem/-/viem-1.16.2.tgz#7e9719dd19e7464284b94d9c00f94f86f5858ccd" + integrity sha512-ZQ8kemNvRVwucwcsj4/SjKohK+wZv9Vxx/gXAlwqGMCW7r+niOeECtFku/1L7UPTmPgdmq4kic9R71t6XQDmGw== + dependencies: + "@adraffy/ens-normalize" "1.9.4" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@scure/bip32" "1.3.2" + "@scure/bip39" "1.2.1" + abitype "0.9.8" + isows "1.0.3" + ws "8.13.0" + +viem@^1.5.3: + version "1.15.4" + resolved "https://registry.yarnpkg.com/viem/-/viem-1.15.4.tgz#422b2a23bc2c9e240d91758ae29a464ca6faed5b" + integrity sha512-kQtJiYbZ86rzGdAXkvAxf6ovsabzyn41loNiSjQNFXwvn24cGP3IbQhQcK5OYorQY9Pz7Dm54EBV5XhFxo+15g== + dependencies: + "@adraffy/ens-normalize" "1.9.4" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@scure/bip32" "1.3.2" + "@scure/bip39" "1.2.1" + "@types/ws" "^8.5.5" + abitype "0.9.8" + isomorphic-ws "5.0.0" + ws "8.13.0" + vite-node@0.31.4: version "0.31.4" resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.31.4.tgz#0437f76c35fa83f0a868d3fb5896ca9e164291f5" @@ -11722,7 +12410,7 @@ vite-node@0.31.4: picocolors "^1.0.0" vite "^3.0.0 || ^4.0.0" -"vite@^3.0.0 || ^4.0.0": +"vite@^3.0.0 || ^4.0.0", vite@^4.4.9: version "4.4.9" resolved "https://registry.yarnpkg.com/vite/-/vite-4.4.9.tgz#1402423f1a2f8d66fd8d15e351127c7236d29d3d" integrity sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA== @@ -11733,6 +12421,23 @@ vite-node@0.31.4: optionalDependencies: fsevents "~2.3.2" +vitepress@^1.0.0-rc.14: + version "1.0.0-rc.14" + resolved "https://registry.yarnpkg.com/vitepress/-/vitepress-1.0.0-rc.14.tgz#d014df7890ca26d3ef73a5c05b2a73cc659e961c" + integrity sha512-yChIeXOAcNvVnSVjhziH1vte0uhKb00PuZf7KdIMfx3ixTMAz73Nn+6gREvCv0SdH+anteGUKz5eljv0ygcgGQ== + dependencies: + "@docsearch/css" "^3.5.2" + "@docsearch/js" "^3.5.2" + "@vue/devtools-api" "^6.5.0" + "@vueuse/core" "^10.4.1" + "@vueuse/integrations" "^10.4.1" + focus-trap "^7.5.2" + mark.js "8.11.1" + minisearch "^6.1.0" + shiki "^0.14.4" + vite "^4.4.9" + vue "^3.3.4" + vitest@^0.31.0: version "0.31.4" resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.31.4.tgz#5abe02562675262949c10e40811f348a80f6b2a6" @@ -11764,6 +12469,32 @@ vitest@^0.31.0: vite-node "0.31.4" why-is-node-running "^2.2.2" +vscode-oniguruma@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz#439bfad8fe71abd7798338d1cd3dc53a8beea94b" + integrity sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA== + +vscode-textmate@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-8.0.0.tgz#2c7a3b1163ef0441097e0b5d6389cd5504b59e5d" + integrity sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg== + +vue-demi@>=0.14.5: + version "0.14.6" + resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.6.tgz#dc706582851dc1cdc17a0054f4fec2eb6df74c92" + integrity sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w== + +vue@^3.2.45, vue@^3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.3.4.tgz#8ed945d3873667df1d0fcf3b2463ada028f88bd6" + integrity sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw== + dependencies: + "@vue/compiler-dom" "3.3.4" + "@vue/compiler-sfc" "3.3.4" + "@vue/runtime-dom" "3.3.4" + "@vue/server-renderer" "3.3.4" + "@vue/shared" "3.3.4" + wagmi@^1.3.11: version "1.4.1" resolved "https://registry.yarnpkg.com/wagmi/-/wagmi-1.4.1.tgz#32e5fda3e3a47559115118e7c8315fe25115ab11" @@ -12058,6 +12789,11 @@ yaml@^1.10.0: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yaml@^2.1.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.2.tgz#f522db4313c671a0ca963a75670f1c12ea909144" + integrity sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg== + yargs-parser@20.2.4: version "20.2.4" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54"