├── .changeset ├── README.md └── config.json ├── .editorconfig ├── .github ├── CODEOWNERS └── workflows │ ├── agw-client-coverage.yml │ ├── agw-client-test.yml │ ├── build-test.yml │ ├── publish-npm-packages-private.yml │ ├── publish-npm-packages.yml │ └── release.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierrc ├── .vscode └── settings.json ├── CONTRIBUTING.md ├── README.md ├── eslint.config.mjs ├── package.json ├── packages ├── agw-client │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── abis │ │ │ ├── AGWAccount.ts │ │ │ ├── AGWRegistryAbi.ts │ │ │ ├── AccountFactory.ts │ │ │ ├── BridgeHubAbi.ts │ │ │ ├── DelegateRegistry.ts │ │ │ ├── ExclusiveDelegateResolver.ts │ │ │ ├── FeatureFlagRegistryAbi.ts │ │ │ ├── SessionKeyPolicyRegistry.ts │ │ │ ├── SessionKeyValidator.ts │ │ │ └── ZkSyncAbi.ts │ │ ├── abstractClient.ts │ │ ├── actions │ │ │ ├── createSession.ts │ │ │ ├── deployAccount.ts │ │ │ ├── deployContract.ts │ │ │ ├── getLinkedAccounts.ts │ │ │ ├── getLinkedAgw.ts │ │ │ ├── getSessionStatus.ts │ │ │ ├── linkToAgw.ts │ │ │ ├── prepareTransaction.ts │ │ │ ├── revokeSessions.ts │ │ │ ├── sendPrivyTransaction.ts │ │ │ ├── sendTransaction.ts │ │ │ ├── sendTransactionBatch.ts │ │ │ ├── sendTransactionForSession.ts │ │ │ ├── sendTransactionInternal.ts │ │ │ ├── signMessage.ts │ │ │ ├── signTransaction.ts │ │ │ ├── signTransactionBatch.ts │ │ │ ├── signTransactionForSession.ts │ │ │ ├── signTypedData.ts │ │ │ ├── writeContract.ts │ │ │ └── writeContractForSession.ts │ │ ├── constants.ts │ │ ├── eip5792.ts │ │ ├── eip712.ts │ │ ├── errors │ │ │ ├── account.ts │ │ │ ├── eip712.ts │ │ │ └── insufficientBalance.ts │ │ ├── exports │ │ │ ├── actions.ts │ │ │ ├── constants.ts │ │ │ ├── index.ts │ │ │ └── sessions.ts │ │ ├── featureFlagRegistry.ts │ │ ├── getAgwTypedSignature.ts │ │ ├── replaceBigInts.ts │ │ ├── sessionClient.ts │ │ ├── sessionValidator.ts │ │ ├── sessions.ts │ │ ├── transformEIP1193Provider.ts │ │ ├── types │ │ │ ├── call.ts │ │ │ ├── customPaymaster.ts │ │ │ ├── sendTransactionBatch.ts │ │ │ └── signTransactionBatch.ts │ │ ├── utils.ts │ │ └── walletActions.ts │ ├── test │ │ ├── abstract.ts │ │ ├── anvil.ts │ │ ├── constants.ts │ │ ├── errors.ts │ │ ├── fixtures.ts │ │ ├── globalSetup.ts │ │ ├── src │ │ │ ├── abstractClient.test.ts │ │ │ ├── actions │ │ │ │ ├── createSession.test.ts │ │ │ │ ├── deployContract.test.ts │ │ │ │ ├── prepareTransaction.test.ts │ │ │ │ ├── revokeSessions.test.ts │ │ │ │ ├── sendTransaction │ │ │ │ │ ├── sendPrivyTransaction.test.ts │ │ │ │ │ ├── sendTransaction.test.ts │ │ │ │ │ ├── sendTransactionBatch.test.ts │ │ │ │ │ └── sendTransactionInternal.test.ts │ │ │ │ ├── sendTransactionForSession.test.ts │ │ │ │ ├── signMessage.test.ts │ │ │ │ ├── signTransaction.test.ts │ │ │ │ ├── signTransactionBatch.test.ts │ │ │ │ ├── signTypedData.test.ts │ │ │ │ ├── writeContract.test.ts │ │ │ │ └── writeContractForSession.test.ts │ │ │ ├── eip712.test.ts │ │ │ ├── sessionClient.test.ts │ │ │ ├── sessionValidator.test.ts │ │ │ ├── transformEIP1193Provider.test.ts │ │ │ ├── utils.test.ts │ │ │ └── walletActions.test.ts │ │ └── vitest.config.ts │ ├── tsconfig.cjs.json │ └── tsconfig.json ├── agw-react │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── abstractWallet.ts │ │ ├── abstractWalletConnector.ts │ │ ├── abstractWalletThirdweb.ts │ │ ├── agwProvider.tsx │ │ ├── constants.ts │ │ ├── exports │ │ │ ├── connectors.ts │ │ │ ├── index.ts │ │ │ ├── privy.ts │ │ │ └── thirdweb.ts │ │ ├── hooks │ │ │ ├── useAbstractClient.ts │ │ │ ├── useCreateSession.ts │ │ │ ├── useGlobalWalletSignerAccount.ts │ │ │ ├── useGlobalWalletSignerClient.ts │ │ │ ├── useLoginWithAbstract.ts │ │ │ ├── useRevokeSessions.ts │ │ │ └── useWriteContractSponsored.ts │ │ ├── privy │ │ │ ├── abstractPrivyProvider.tsx │ │ │ ├── injectWagmiConnector.tsx │ │ │ ├── useAbstractPrivyLogin.tsx │ │ │ └── usePrivyCrossAppProvider.ts │ │ └── query │ │ │ ├── createSession.ts │ │ │ └── writeContractSponsored.ts │ ├── tsconfig.cjs.json │ └── tsconfig.json ├── agw-web │ ├── package.json │ ├── src │ │ ├── abstract-icon.ts │ │ ├── eip6963emitter.ts │ │ ├── exports │ │ │ ├── index.ts │ │ │ ├── mainnet.ts │ │ │ └── testnet.ts │ │ └── types.ts │ ├── tsconfig.cjs.json │ └── tsconfig.json └── web3-react-agw │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ └── index.ts │ ├── tsconfig.cjs.json │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── tsconfig.base.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.5/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.{js,json,yml}] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @coffeexcoin @cygaar 2 | -------------------------------------------------------------------------------- /.github/workflows/agw-client-coverage.yml: -------------------------------------------------------------------------------- 1 | name: AGW Client Coverage 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | paths: 7 | - 'packages/agw-client/**' 8 | pull_request: 9 | paths: 10 | - 'packages/agw-client/**' 11 | 12 | jobs: 13 | test: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: '22' 23 | 24 | - name: Install pnpm 25 | uses: pnpm/action-setup@v4 26 | 27 | - name: Set up foundry 28 | uses: foundry-rs/foundry-toolchain@v1 29 | with: 30 | version: nightly-143abd6a768eeb52a5785240b763d72a56987b4a 31 | 32 | - name: Install dependencies 33 | run: pnpm install 34 | 35 | - name: Run coverage 36 | run: pnpm --filter @abstract-foundation/agw-client coverage 37 | 38 | - name: Upload results to Codecov 39 | uses: codecov/codecov-action@v4 40 | with: 41 | token: ${{ secrets.CODECOV_TOKEN }} 42 | -------------------------------------------------------------------------------- /.github/workflows/agw-client-test.yml: -------------------------------------------------------------------------------- 1 | name: AGW Client Tests 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - 'packages/agw-client/**' 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Setup Node.js 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: '22' 19 | 20 | - name: Install pnpm 21 | uses: pnpm/action-setup@v4 22 | 23 | - name: Set up foundry 24 | uses: foundry-rs/foundry-toolchain@v1 25 | with: 26 | version: nightly-143abd6a768eeb52a5785240b763d72a56987b4a 27 | 28 | - name: Install dependencies 29 | run: pnpm install 30 | 31 | - name: Run tests 32 | run: pnpm --filter @abstract-foundation/agw-client test 33 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages 3 | 4 | name: Package Build 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | pull_request: 11 | branches: 12 | - main 13 | 14 | jobs: 15 | publish: 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: read 19 | packages: write 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | 24 | - name: Set up pnpm 25 | uses: pnpm/action-setup@v4 26 | 27 | - name: Set up node 28 | uses: actions/setup-node@v4 29 | with: 30 | node-version: 20 31 | 32 | - run: pnpm install 33 | - run: pnpm lint:check 34 | - run: pnpm prettier-check 35 | - run: pnpm build 36 | - run: pnpm test:build 37 | -------------------------------------------------------------------------------- /.github/workflows/publish-npm-packages-private.yml: -------------------------------------------------------------------------------- 1 | name: Publish Packages (Private) 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | contents: read 11 | packages: write 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: Set up pnpm 17 | uses: pnpm/action-setup@v4 18 | 19 | - name: Set up node 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: 22 23 | registry-url: https://npm.pkg.github.com 24 | cache: 'pnpm' 25 | scope: '@abstract-foundation' 26 | 27 | - name: Install dependencies 28 | run: pnpm install 29 | 30 | - name: Build packages 31 | run: pnpm build 32 | 33 | - name: Publish packages 34 | run: pnpm -r publish --no-git-checks --access restricted --tag 'experimental' 35 | env: 36 | NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | -------------------------------------------------------------------------------- /.github/workflows/publish-npm-packages.yml: -------------------------------------------------------------------------------- 1 | name: Publish Packages 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | packages: write 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Set up pnpm 18 | uses: pnpm/action-setup@v4 19 | 20 | - name: Set up node 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: 22 24 | registry-url: https://registry.npmjs.org/ 25 | cache: 'pnpm' 26 | scope: '@abstract-foundation' 27 | 28 | - name: Install dependencies 29 | run: pnpm install 30 | 31 | - name: Build packages 32 | run: pnpm build 33 | 34 | - name: Publish packages 35 | run: pnpm -r publish --no-git-checks --access public ${{ github.event.release.prerelease == true && '--tag next' || '--tag latest' }} 36 | env: 37 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 38 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | permissions: 14 | pull-requests: write 15 | contents: write 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout Repo 19 | uses: actions/checkout@v4 20 | 21 | - name: Set up pnpm 22 | uses: pnpm/action-setup@v4 23 | 24 | - name: Set up node 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version: 22 28 | registry-url: https://registry.npmjs.org/ 29 | cache: 'pnpm' 30 | scope: '@abstract-foundation' 31 | 32 | - name: Install dependencies 33 | run: pnpm install 34 | 35 | - name: Create Release Pull Request 36 | uses: changesets/action@v1 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist 3 | node_modules 4 | tsconfig.tsbuildinfo 5 | 6 | # local env files 7 | .env 8 | .env.local 9 | .env.development.local 10 | .env.test.local 11 | .env.production.local 12 | .envrc 13 | 14 | # local packages 15 | *.tgz 16 | 17 | # coverage 18 | **coverage/ 19 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | pnpm lint-staged 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": true, 5 | "printWidth": 80 6 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnPaste": true, 3 | "editor.formatOnSave": true, 4 | "editor.defaultFormatter": "esbenp.prettier-vscode", 5 | "editor.codeActionsOnSave": { 6 | "source.fixAll.eslint": "always" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | We’re excited that you’re interested in contributing to the Abstract Global Wallet SDK! ❤️ Contributions, whether they're bug reports, feature suggestions, documentation updates, or code enhancements, are highly appreciated. 4 | 5 | To make the contribution process smooth and efficient, please follow these guidelines. 6 | 7 | ## How to Contribute 8 | 9 | ### 1. Fork the Repository 10 | 11 | Fork the repository to your GitHub account to get started. This will create your own copy where you can make changes. 12 | 13 | 1. Go to the main repository on GitHub. 14 | 2. Click the "Fork" button in the top right corner. 15 | 3. Once the repository is forked, clone it to your local machine. 16 | 17 | ### 2. Create a New Branch 18 | 19 | Before you start working on your changes, create a new branch for your work to keep the `main` branch clean: 20 | 21 | ```bash 22 | git checkout -b your-branch-name 23 | ``` 24 | 25 | - Use a descriptive name for your branch (e.g., `feature/add-new-component` or `bugfix/fix-issue-123`). 26 | 27 | ### 3. Make Your Changes 28 | 29 | Now that you have a branch set up, you can make changes to the codebase. When making changes: 30 | 31 | - Follow the existing code style and structure. 32 | - Write clear, concise commit messages that explain the "why" behind your changes. 33 | - Make sure your changes are well-tested. If applicable, add tests to validate the new functionality. 34 | 35 | ### 4. Commit Your Changes 36 | 37 | Once you're happy with your changes and everything works as expected, commit them to your branch: 38 | 39 | ```bash 40 | git add . 41 | git commit -m "description of your changes" 42 | ``` 43 | 44 | ### 5. Push to Your Fork 45 | 46 | Push the changes to your fork on GitHub: 47 | 48 | ```bash 49 | git push origin your-branch-name 50 | ``` 51 | 52 | ### 6. Submit a Pull Request (PR) 53 | 54 | - **Title**: Use a concise and informative title for your pull request. 55 | - **Description**: Provide a detailed description of the changes made, why you made them, and any relevant details about the implementation. 56 | - **Link to Issue**: If your PR addresses an issue, please link to it (e.g., `Fixes #123`). 57 | 58 | Submit the pull request and wait for the review process to begin. 59 | 60 | --- 61 | 62 | ## Reporting Bugs and Issues 63 | 64 | If you encounter a bug or want to suggest a feature: 65 | 66 | - Use the Github Issues page to report issues or request features. 67 | - Include as much detail as possible: steps to reproduce the issue, expected behavior, and screenshots if applicable. 68 | - If submitting a feature request, please explain the use case and benefits of the feature. 69 | 70 | ### **How Do I Submit a Good Enhancement Suggestion?** 71 | 72 | Enhancement suggestions are tracked as [GitHub issues](https://github.com/Abstract-Foundation/agw-sdk/issues). 73 | 74 | - Use a **clear and descriptive title** for the issue to identify the suggestion. 75 | - Provide a **step-by-step description of the suggested enhancement** in as many details as possible. 76 | - **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you. 77 | - You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://gitlab.gnome.org/Archive/byzanz) on Linux. 78 | - **Explain why this enhancement would be useful** to most agw-sdk users. You may also want to point out the other projects that solved it better and which could serve as inspiration. 79 | 80 | --- 81 | 82 | ## Coding Guidelines 83 | 84 | - Follow the existing structure and style of the codebase. 85 | - Ensure all functionality is thoroughly tested. 86 | - Write clear, self-documenting code and add comments when necessary. 87 | - Keep performance and security in mind when making changes. 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # agw-sdk 2 | The Abstract Global Wallet SDK is a toolkit for developers to integrate the Abstract Global Wallet into their applications. 3 | 4 | ## Abstract Global Wallet (AGW) 5 | 6 | [Abstract Global Wallet (AGW)](https://docs.abs.xyz/overview) is a cross-application [smart contract wallet](https://docs.abs.xyz/how-abstract-works/native-account-abstraction/smart-contract-wallets) that allows users to interact with any application built on Abstract. It is powered by Abstract's [native account abstraction](https://docs.abs.xyz/how-abstract-works/native-account-abstraction). 7 | 8 | AGW allows users to sign up once using familiar login methods (such as email, social accounts, passkeys, and more), and then use this account to interact with *any* application on Abstract. 9 | 10 | ![AGW SDK](https://pbs.twimg.com/media/GWF7DpqWkAAQJ2f?format=jpg&name=medium) 11 | 12 | 13 | ## Packages 14 | 15 | The SDK consists of two main packages: 16 | 17 | - [`@abstract-foundation/agw-client`](https://www.npmjs.com/package/@abstract-foundation/agw-client): The core client library for interacting with the Abstract Global Wallet. 18 | - [`@abstract-foundation/agw-react`](https://www.npmjs.com/package/@abstract-foundation/agw-react): React hooks and components for integrating the Abstract Global Wallet into React applications. 19 | 20 | 21 | ## Documentation 22 | 23 | For detailed documentation, please refer to the [Abstract Global Wallet Documentation](https://docs.abs.xyz/how-abstract-works/abstract-global-wallet/overview). 24 | 25 | ## Contributing 26 | 27 | We welcome contributions to the Abstract Global Wallet SDK! If you'd like to contribute, please follow these steps: 28 | 29 | 1. Fork the repository. 30 | 2. Create a new branch for your feature or bugfix. 31 | 3. Make your changes and commit with clear messages. 32 | 4. Submit a pull request to the main repository. 33 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import eslint from '@eslint/js'; 2 | import eslintPluginPrettier from 'eslint-plugin-prettier'; 3 | import eslintPluginSimpleImportSort from 'eslint-plugin-simple-import-sort'; 4 | import { createRequire } from 'module'; 5 | import tseslint from 'typescript-eslint'; 6 | const require = createRequire(import.meta.url); 7 | const requireExtensions = require('eslint-plugin-require-extensions'); 8 | 9 | export default tseslint.config( 10 | eslint.configs.recommended, 11 | ...tseslint.configs.stylistic, 12 | ...tseslint.configs.strict, 13 | { 14 | plugins: { 15 | 'simple-import-sort': eslintPluginSimpleImportSort, 16 | 'require-extensions': requireExtensions, 17 | prettier: eslintPluginPrettier, 18 | }, 19 | rules: { 20 | 'prettier/prettier': 'error', 21 | 'require-extensions/require-extensions': 'error', 22 | '@typescript-eslint/no-explicit-any': 'warn', 23 | '@typescript-eslint/no-non-null-assertion': 'warn', 24 | '@typescript-eslint/no-empty-object-type': 'warn', 25 | '@typescript-eslint/no-namespace': 'off', 26 | '@typescript-eslint/no-unused-vars': [ 27 | 'warn', // or "error" 28 | { 29 | argsIgnorePattern: '^_', 30 | varsIgnorePattern: '^_', 31 | caughtErrorsIgnorePattern: '^_', 32 | }, 33 | ], 34 | 'simple-import-sort/imports': 'error', 35 | 'simple-import-sort/exports': 'error', 36 | }, 37 | }, 38 | { 39 | ignores: ['node_modules/**', 'packages/**/dist/**'], 40 | }, 41 | ); 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "build": "pnpm run --r --filter \"./packages/**\" build", 5 | "test:build": "pnpm run --r --filter \"./packages/**\" test:build", 6 | "lint:check": "eslint", 7 | "lint:fix": "eslint --fix", 8 | "prettier-format": "prettier --config .prettierrc './**/*.ts' --write", 9 | "prettier-check": "prettier --config .prettierrc './**/*.ts' --check", 10 | "prepare": "husky", 11 | "lint": "pnpm run lint:fix && pnpm run prettier-format" 12 | }, 13 | "packageManager": "pnpm@9.4.0", 14 | "lint-staged": { 15 | "**/*.{js,jsx,ts,tsx,json,css,scss,md}": [ 16 | "prettier --write", 17 | "eslint --fix" 18 | ] 19 | }, 20 | "devDependencies": { 21 | "@changesets/cli": "^2.27.11", 22 | "@arethetypeswrong/cli": "^0.16.4", 23 | "@types/eslint__js": "^8.42.3", 24 | "eslint": "^9.10.0", 25 | "eslint-config-prettier": "^9.1.0", 26 | "eslint-plugin-prettier": "^5.2.1", 27 | "eslint-plugin-require-extensions": "^0.1.3", 28 | "eslint-plugin-simple-import-sort": "^12.1.1", 29 | "husky": "^9.1.6", 30 | "lint-staged": "^15.2.10", 31 | "prettier": "^3.3.3", 32 | "@eslint/plugin-kit": ">=0.2.3", 33 | "publint": "^0.2.12", 34 | "tsc-watch": "^6.2.0", 35 | "typescript-eslint": "^8.5.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/agw-client/README.md: -------------------------------------------------------------------------------- 1 | # @abstract-foundation/agw-client 2 | 3 | The `@abstract-foundation/agw-client` package provides the core client library for interacting with the [Abstract Global Wallet (AGW)](https://docs.abs.xyz/overview). 4 | 5 | ## Abstract Global Wallet (AGW) 6 | 7 | [Abstract Global Wallet (AGW)](https://docs.abs.xyz/overview) is a cross-application [smart contract wallet](https://docs.abs.xyz/how-abstract-works/native-account-abstraction/smart-contract-wallets) that users can be used to interact with any application built on Abstract, powered by Abstract's [native account abstraction](https://docs.abs.xyz/how-abstract-works/native-account-abstraction). 8 | 9 | 10 | ## Installation 11 | 12 | Install the core client library via NPM: 13 | 14 | ```bash 15 | npm install @abstract-foundation/agw-client 16 | ``` 17 | 18 | ## Quick Start 19 | 20 | ### Importing 21 | 22 | ```tsx 23 | import { createAbstractClient } from '@abstract-foundation/agw-client' 24 | ``` 25 | 26 | ### Creating an Abstract Client 27 | 28 | ```tsx 29 | import { createAbstractClient } from '@abstract-foundation/agw-client'; 30 | import { ChainEIP712, http } from 'viem'; 31 | import { Account } from 'viem/accounts'; 32 | 33 | // Assume you have a signer account and chain configuration 34 | const signer: Account = { 35 | address: '0xYourSignerAddress', 36 | // ...other account properties 37 | }; 38 | 39 | (async () => { 40 | const abstractClient = await createAbstractClient({ 41 | signer, 42 | chain, 43 | transport: http(), // optional, defaults to HTTP transport if omitted 44 | }); 45 | 46 | // Use the abstractClient instance 47 | })(); 48 | ``` 49 | 50 | ## API Reference 51 | 52 | ### `createAbstractClient` 53 | 54 | Asynchronously creates an `AbstractClient` instance, extending the standard `Client` with actions specific to the Abstract Global Wallet. 55 | 56 | ### Example 57 | 58 | ```tsx 59 | import { createAbstractClient } from '@abstract-foundation/agw-client'; 60 | 61 | (async () => { 62 | const abstractClient = await createAbstractClient({ 63 | signer: /* your signer account */, 64 | chain: /* your chain configuration */, 65 | }); 66 | 67 | // Use abstractClient to interact with the blockchain 68 | })(); 69 | ``` 70 | 71 | ## Examples 72 | 73 | ### Sending a Transaction 74 | 75 | ```tsx 76 | import { createAbstractClient } from '@abstract-foundation/agw-client'; 77 | 78 | (async () => { 79 | const abstractClient = await createAbstractClient({ 80 | signer: /* your signer account */, 81 | chain: /* your chain configuration */, 82 | }); 83 | 84 | try { 85 | const txHash = await abstractClient.sendTransaction({ 86 | to: '0xRecipientAddress', 87 | value: 1000000000000000000n, // 1 ETH in wei 88 | }); 89 | console.log('Transaction Hash:', txHash); 90 | } catch (error) { 91 | console.error('Error sending transaction:', error); 92 | } 93 | })(); 94 | ``` 95 | 96 | ### Sponsored Transactions via Paymasters 97 | 98 | ```tsx 99 | import { createAbstractClient } from '@abstract-foundation/agw-client'; 100 | import { ChainEIP712, http } from 'viem'; 101 | import { Account } from 'viem/accounts'; 102 | 103 | (async () => { 104 | // Create a signer account and chain configuration 105 | const signer: Account = { 106 | address: '0xYourSignerAddress', 107 | // ...other account properties 108 | }; 109 | 110 | // Create an instance of Abstract Client 111 | const abstractClient = await createAbstractClient({ 112 | signer, 113 | chain, 114 | transport: http(), // Optional, defaults to HTTP transport if omitted 115 | }); 116 | 117 | // Example of a sponsored transaction using a Paymaster 118 | try { 119 | const txHash = await abstractClient.sendTransaction({ 120 | to: '0xRecipientAddress', 121 | value: 1000000000000000000n, // 1 ETH in wei 122 | sponsor: { 123 | paymaster: '0xPaymasterAddress', // Address of the Paymaster contract 124 | }, 125 | }); 126 | 127 | console.log('Sponsored Transaction Hash:', txHash); 128 | } catch (error) { 129 | console.error('Error sending sponsored transaction:', error); 130 | } 131 | })(); 132 | 133 | ``` 134 | 135 | ### Explanation of Paymaster Usage: 136 | 137 | - **Paymaster**: The `paymaster` object is specified in the `sendTransaction` method, allowing the Paymaster contract to cover the gas fees for the transaction. 138 | - **Sponsored Transaction**: The transaction fee is covered by the Paymaster, so the user’s balance is unaffected by gas costs. 139 | 140 | ## Documentation 141 | 142 | For detailed documentation, please refer to the [Abstract Global Wallet Documentation](https://docs.abs.xyz/how-abstract-works/abstract-global-wallet/overview). -------------------------------------------------------------------------------- /packages/agw-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@abstract-foundation/agw-client", 3 | "description": "Abstract Global Wallet Client SDK", 4 | "version": "1.8.5", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/abstract-foundation/agw-sdk.git", 9 | "directory": "packages/agw-client" 10 | }, 11 | "scripts": { 12 | "test": "vitest -c ./test/vitest.config.ts --disable-console-intercept", 13 | "coverage": "vitest run --coverage -c ./test/vitest.config.ts", 14 | "build": "pnpm run clean && pnpm run build:esm+types && pnpm run build:cjs", 15 | "build:esm+types": "tsc --outDir ./dist/esm --declaration --declarationMap --declarationDir ./dist/types && printf '{\"type\":\"module\"}' > ./dist/esm/package.json", 16 | "build:cjs": "tsc -p tsconfig.cjs.json && printf '{\"type\":\"commonjs\"}' > ./dist/cjs/package.json", 17 | "clean": "rm -rf dist tsconfig.tsbuildinfo", 18 | "typecheck": "tsc --noEmit", 19 | "debug": "tsc-watch --sourceMap true --outDir ./dist/esm --declaration --declarationMap --declarationDir ./dist/types", 20 | "test:build": "publint && attw --pack --ignore-rules false-cjs" 21 | }, 22 | "main": "./dist/cjs/exports/index.js", 23 | "module": "./dist/esm/exports/index.js", 24 | "types": "./dist/types/exports/index.d.ts", 25 | "typings": "./dist/types/exports/index.d.ts", 26 | "exports": { 27 | ".": { 28 | "types": "./dist/types/exports/index.d.ts", 29 | "import": "./dist/esm/exports/index.js", 30 | "require": "./dist/cjs/exports/index.js" 31 | }, 32 | "./actions": { 33 | "types": "./dist/types/exports/actions.d.ts", 34 | "import": "./dist/esm/exports/actions.js", 35 | "require": "./dist/cjs/exports/actions.js" 36 | }, 37 | "./constants": { 38 | "types": "./dist/types/exports/constants.d.ts", 39 | "import": "./dist/esm/exports/constants.js", 40 | "require": "./dist/cjs/exports/constants.js" 41 | }, 42 | "./sessions": { 43 | "types": "./dist/types/exports/sessions.d.ts", 44 | "import": "./dist/esm/exports/sessions.js", 45 | "require": "./dist/cjs/exports/sessions.js" 46 | } 47 | }, 48 | "typesVersions": { 49 | "*": { 50 | "actions": [ 51 | "./dist/types/exports/actions.d.ts" 52 | ], 53 | "constants": [ 54 | "./dist/types/exports/constants.d.ts" 55 | ], 56 | "sessions": [ 57 | "./dist/types/exports/sessions.d.ts" 58 | ] 59 | } 60 | }, 61 | "files": [ 62 | "dist", 63 | "src", 64 | "package.json" 65 | ], 66 | "peerDependencies": { 67 | "abitype": "^1.0.0", 68 | "typescript": ">=5.0.4", 69 | "viem": "^2.22.23" 70 | }, 71 | "devDependencies": { 72 | "@types/node": "^22.5.5", 73 | "@vitest/coverage-v8": "^2.1.9", 74 | "prool": "^0.0.23", 75 | "viem": "^2.22.23", 76 | "vitest": "^2.1.9" 77 | }, 78 | "peerDependenciesMeta": { 79 | "typescript": { 80 | "optional": true 81 | } 82 | }, 83 | "keywords": [ 84 | "eth", 85 | "ethereum", 86 | "smart-account", 87 | "abstract", 88 | "account-abstraction", 89 | "global-wallet", 90 | "wallet", 91 | "web3" 92 | ] 93 | } 94 | -------------------------------------------------------------------------------- /packages/agw-client/src/abis/ExclusiveDelegateResolver.ts: -------------------------------------------------------------------------------- 1 | export const ExclusiveDelegateResolverAbi = [ 2 | { 3 | type: 'function', 4 | name: 'DELEGATE_REGISTRY', 5 | inputs: [], 6 | outputs: [ 7 | { 8 | name: '', 9 | type: 'address', 10 | internalType: 'address', 11 | }, 12 | ], 13 | stateMutability: 'view', 14 | }, 15 | { 16 | type: 'function', 17 | name: 'GLOBAL_DELEGATION', 18 | inputs: [], 19 | outputs: [ 20 | { 21 | name: '', 22 | type: 'bytes24', 23 | internalType: 'bytes24', 24 | }, 25 | ], 26 | stateMutability: 'view', 27 | }, 28 | { 29 | type: 'function', 30 | name: 'decodeRightsExpiration', 31 | inputs: [ 32 | { 33 | name: 'rights', 34 | type: 'bytes32', 35 | internalType: 'bytes32', 36 | }, 37 | ], 38 | outputs: [ 39 | { 40 | name: '', 41 | type: 'bytes24', 42 | internalType: 'bytes24', 43 | }, 44 | { 45 | name: '', 46 | type: 'uint40', 47 | internalType: 'uint40', 48 | }, 49 | ], 50 | stateMutability: 'pure', 51 | }, 52 | { 53 | type: 'function', 54 | name: 'delegatedWalletsByRights', 55 | inputs: [ 56 | { 57 | name: 'wallet', 58 | type: 'address', 59 | internalType: 'address', 60 | }, 61 | { 62 | name: 'rights', 63 | type: 'bytes24', 64 | internalType: 'bytes24', 65 | }, 66 | ], 67 | outputs: [ 68 | { 69 | name: '', 70 | type: 'address[]', 71 | internalType: 'address[]', 72 | }, 73 | ], 74 | stateMutability: 'view', 75 | }, 76 | { 77 | type: 'function', 78 | name: 'exclusiveOwnerByRights', 79 | inputs: [ 80 | { 81 | name: 'contractAddress', 82 | type: 'address', 83 | internalType: 'address', 84 | }, 85 | { 86 | name: 'tokenId', 87 | type: 'uint256', 88 | internalType: 'uint256', 89 | }, 90 | { 91 | name: 'rights', 92 | type: 'bytes24', 93 | internalType: 'bytes24', 94 | }, 95 | ], 96 | outputs: [ 97 | { 98 | name: 'owner', 99 | type: 'address', 100 | internalType: 'address', 101 | }, 102 | ], 103 | stateMutability: 'view', 104 | }, 105 | { 106 | type: 'function', 107 | name: 'exclusiveWalletByRights', 108 | inputs: [ 109 | { 110 | name: 'vault', 111 | type: 'address', 112 | internalType: 'address', 113 | }, 114 | { 115 | name: 'rights', 116 | type: 'bytes24', 117 | internalType: 'bytes24', 118 | }, 119 | ], 120 | outputs: [ 121 | { 122 | name: '', 123 | type: 'address', 124 | internalType: 'address', 125 | }, 126 | ], 127 | stateMutability: 'view', 128 | }, 129 | { 130 | type: 'function', 131 | name: 'generateRightsWithExpiration', 132 | inputs: [ 133 | { 134 | name: 'rightsIdentifier', 135 | type: 'bytes24', 136 | internalType: 'bytes24', 137 | }, 138 | { 139 | name: 'expiration', 140 | type: 'uint40', 141 | internalType: 'uint40', 142 | }, 143 | ], 144 | outputs: [ 145 | { 146 | name: '', 147 | type: 'bytes32', 148 | internalType: 'bytes32', 149 | }, 150 | ], 151 | stateMutability: 'pure', 152 | }, 153 | ] as const; 154 | -------------------------------------------------------------------------------- /packages/agw-client/src/abstractClient.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Account, 3 | type Address, 4 | type Client, 5 | createClient, 6 | createPublicClient, 7 | createWalletClient, 8 | http, 9 | type Transport, 10 | } from 'viem'; 11 | import { toAccount } from 'viem/accounts'; 12 | import { type ChainEIP712 } from 'viem/zksync'; 13 | 14 | import type { CustomPaymasterHandler } from './types/customPaymaster.js'; 15 | import { getSmartAccountAddressFromInitialSigner } from './utils.js'; 16 | import { 17 | type AbstractWalletActions, 18 | globalWalletActions, 19 | } from './walletActions.js'; 20 | 21 | /** 22 | * Parameters for creating an AbstractClient instance. 23 | * @interface CreateAbstractClientParameters 24 | */ 25 | interface CreateAbstractClientParameters { 26 | /** 27 | * The account used for signing AGW transactions. 28 | * @type {Account} 29 | */ 30 | signer: Account; 31 | 32 | /** 33 | * The chain configuration supporting EIP-712. 34 | * @type {ChainEIP712} 35 | */ 36 | chain: ChainEIP712; 37 | 38 | /** 39 | * Optional transport layer for network communication. 40 | * If not provided, a default HTTP transport will be used. 41 | * @type {Transport} 42 | * @optional 43 | */ 44 | transport?: Transport; 45 | 46 | /** 47 | * The address of the smart account. 48 | * @type {Address} 49 | * @optional 50 | */ 51 | address?: Address; 52 | 53 | /** 54 | * Whether the client is a Privy cross-app client. 55 | * @type {boolean} 56 | * @optional 57 | */ 58 | isPrivyCrossApp?: boolean; 59 | 60 | /** 61 | * The transport layer for the underlying public client. 62 | * @type {Transport} 63 | * @optional 64 | */ 65 | publicTransport?: Transport; 66 | 67 | /** 68 | * The custom paymaster handler. 69 | * @type {CustomPaymasterHandler} 70 | * @optional 71 | */ 72 | customPaymasterHandler?: CustomPaymasterHandler; 73 | } 74 | 75 | type AbstractClientActions = AbstractWalletActions; 76 | 77 | export type AbstractClient = Client & 78 | AbstractClientActions; 79 | 80 | export async function createAbstractClient({ 81 | signer, 82 | chain, 83 | transport, 84 | address, 85 | isPrivyCrossApp = false, 86 | publicTransport = http(), 87 | customPaymasterHandler, 88 | }: CreateAbstractClientParameters): Promise { 89 | if (!transport) { 90 | throw new Error('Transport is required'); 91 | } 92 | 93 | const publicClient = createPublicClient({ 94 | chain: chain, 95 | transport: publicTransport, 96 | }); 97 | 98 | const smartAccountAddress = 99 | address ?? 100 | (await getSmartAccountAddressFromInitialSigner( 101 | signer.address, 102 | publicClient, 103 | )); 104 | 105 | const baseClient = createClient({ 106 | account: toAccount(smartAccountAddress), 107 | chain: chain, 108 | transport, 109 | }); 110 | 111 | // Create a signer wallet client to handle actual signing 112 | const signerWalletClient = createWalletClient({ 113 | account: signer, 114 | chain: chain, 115 | transport, 116 | }); 117 | 118 | const abstractClient = baseClient.extend( 119 | globalWalletActions( 120 | signerWalletClient, 121 | publicClient, 122 | isPrivyCrossApp, 123 | customPaymasterHandler, 124 | ), 125 | ); 126 | return abstractClient as AbstractClient; 127 | } 128 | -------------------------------------------------------------------------------- /packages/agw-client/src/actions/createSession.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Account, 3 | type Address, 4 | type Client, 5 | concatHex, 6 | type Hash, 7 | type Hex, 8 | type PublicClient, 9 | type Transport, 10 | } from 'viem'; 11 | import { readContract, writeContract } from 'viem/actions'; 12 | import type { ChainEIP712 } from 'viem/chains'; 13 | import { getAction } from 'viem/utils'; 14 | 15 | import AGWAccountAbi from '../abis/AGWAccount.js'; 16 | import { SessionKeyValidatorAbi } from '../abis/SessionKeyValidator.js'; 17 | import { SESSION_KEY_VALIDATOR_ADDRESS } from '../constants.js'; 18 | import { encodeSession, type SessionConfig } from '../sessions.js'; 19 | import { isSmartAccountDeployed } from '../utils.js'; 20 | 21 | export interface CreateSessionParameters { 22 | session: SessionConfig; 23 | paymaster?: Address; 24 | paymasterInput?: Hex; 25 | } 26 | 27 | export interface CreateSessionReturnType { 28 | transactionHash: Hash | undefined; 29 | session: SessionConfig; 30 | } 31 | 32 | /** 33 | * Creates a session key for an Abstract Global Wallet. 34 | * 35 | * Session keys enable temporary, permissioned access to a wallet, allowing specific actions 36 | * to be performed without requiring the wallet owner's signature for each transaction. 37 | * 38 | * @param args - Parameters for creating the session 39 | * @param args.session - Session key configuration object 40 | * @param args.paymaster - Optional address of a paymaster to sponsor the transaction 41 | * @param args.paymasterInput - Optional data for the paymaster 42 | * @returns Object containing the transaction hash of the session key creation and the session config 43 | * 44 | * @example 45 | * ```ts 46 | * import { useAbstractClient } from "@abstract-foundation/agw-react"; 47 | * import { LimitType } from "@abstract-foundation/agw-client/sessions"; 48 | * import { toFunctionSelector, parseEther } from "viem"; 49 | * import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; 50 | * 51 | * // Generate a new session key pair 52 | * const sessionPrivateKey = generatePrivateKey(); 53 | * const sessionSigner = privateKeyToAccount(sessionPrivateKey); 54 | * 55 | * export default function CreateSession() { 56 | * const { data: agwClient } = useAbstractClient(); 57 | * 58 | * async function createSession() { 59 | * if (!agwClient) return; 60 | * 61 | * const { session } = await agwClient.createSession({ 62 | * session: { 63 | * signer: sessionSigner.address, 64 | * expiresAt: BigInt(Math.floor(Date.now() / 1000) + 60 * 60 * 24), 65 | * feeLimit: { 66 | * limitType: LimitType.Lifetime, 67 | * limit: parseEther("1"), 68 | * period: BigInt(0), 69 | * }, 70 | * callPolicies: [ 71 | * { 72 | * target: "0xC4822AbB9F05646A9Ce44EFa6dDcda0Bf45595AA", 73 | * selector: toFunctionSelector("mint(address,uint256)"), 74 | * valueLimit: { 75 | * limitType: LimitType.Unlimited, 76 | * limit: BigInt(0), 77 | * period: BigInt(0), 78 | * }, 79 | * maxValuePerUse: BigInt(0), 80 | * constraints: [], 81 | * } 82 | * ], 83 | * transferPolicies: [], 84 | * }, 85 | * }); 86 | * } 87 | * 88 | * return ; 89 | * } 90 | * ``` 91 | * 92 | * @see {@link SessionConfig} - The session configuration type 93 | * @see {@link encodeSession} - Function to encode a session configuration 94 | */ 95 | export async function createSession( 96 | client: Client, 97 | publicClient: PublicClient, 98 | args: CreateSessionParameters, 99 | ): Promise { 100 | const { session, ...rest } = args; 101 | 102 | const isDeployed = await isSmartAccountDeployed( 103 | publicClient, 104 | client.account.address, 105 | ); 106 | 107 | const hasModule = isDeployed ? await hasSessionModule(client) : false; 108 | 109 | let transactionHash: Hash | undefined = undefined; 110 | 111 | if (!hasModule) { 112 | const encodedSession = encodeSession(session); 113 | transactionHash = await getAction( 114 | client, 115 | writeContract, 116 | 'writeContract', 117 | )({ 118 | address: client.account.address, 119 | abi: AGWAccountAbi, 120 | functionName: 'addModule', 121 | args: [concatHex([SESSION_KEY_VALIDATOR_ADDRESS, encodedSession])], 122 | ...rest, 123 | } as any); 124 | } else { 125 | transactionHash = await getAction( 126 | client, 127 | writeContract, 128 | 'writeContract', 129 | )({ 130 | address: SESSION_KEY_VALIDATOR_ADDRESS, 131 | abi: SessionKeyValidatorAbi, 132 | functionName: 'createSession', 133 | args: [session as any], 134 | ...rest, 135 | } as any); 136 | } 137 | 138 | return { transactionHash, session }; 139 | } 140 | 141 | async function hasSessionModule( 142 | client: Client, 143 | ) { 144 | const validationHooks = await getAction( 145 | client, 146 | readContract, 147 | 'readContract', 148 | )({ 149 | address: client.account.address, 150 | abi: AGWAccountAbi, 151 | functionName: 'listHooks', 152 | args: [true], 153 | }); 154 | 155 | const hasSessionModule = validationHooks.some( 156 | (hook) => hook === SESSION_KEY_VALIDATOR_ADDRESS, 157 | ); 158 | 159 | return hasSessionModule; 160 | } 161 | -------------------------------------------------------------------------------- /packages/agw-client/src/actions/deployAccount.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Account, 3 | type Address, 4 | encodeFunctionData, 5 | type Hash, 6 | keccak256, 7 | type PublicClient, 8 | toBytes, 9 | type Transport, 10 | type WalletClient, 11 | zeroAddress, 12 | } from 'viem'; 13 | import type { ChainEIP712, TransactionRequestEIP712 } from 'viem/chains'; 14 | 15 | import AccountFactoryAbi from '../abis/AccountFactory.js'; 16 | import { 17 | EOA_VALIDATOR_ADDRESS, 18 | SMART_ACCOUNT_FACTORY_ADDRESS, 19 | } from '../constants.js'; 20 | import { 21 | getInitializerCalldata, 22 | getSmartAccountAddressFromInitialSigner, 23 | isSmartAccountDeployed, 24 | } from '../utils.js'; 25 | 26 | export type DeployAccountParameters = { 27 | walletClient: WalletClient; 28 | publicClient: PublicClient; 29 | initialSignerAddress?: Address; 30 | } & Omit; 31 | 32 | export interface DeployAccountReturnType { 33 | smartAccountAddress: Address; 34 | deploymentTransaction: Hash | undefined; 35 | } 36 | 37 | export async function deployAccount( 38 | params: DeployAccountParameters, 39 | ): Promise { 40 | const { initialSignerAddress, walletClient, publicClient, ...rest } = params; 41 | 42 | const initialSigner = initialSignerAddress ?? walletClient.account.address; 43 | 44 | const address = await getSmartAccountAddressFromInitialSigner( 45 | initialSigner, 46 | publicClient, 47 | ); 48 | 49 | let deploymentTransaction: Hash | undefined = undefined; 50 | 51 | const isDeployed = await isSmartAccountDeployed(publicClient, address); 52 | if (!isDeployed) { 53 | const initializerCallData = getInitializerCalldata( 54 | initialSigner, 55 | EOA_VALIDATOR_ADDRESS, 56 | { 57 | allowFailure: false, 58 | callData: '0x', 59 | value: 0n, 60 | target: zeroAddress, 61 | }, 62 | ); 63 | const addressBytes = toBytes(initialSigner); 64 | const salt = keccak256(addressBytes); 65 | const deploymentCalldata = encodeFunctionData({ 66 | abi: AccountFactoryAbi, 67 | functionName: 'deployAccount', 68 | args: [salt, initializerCallData], 69 | }); 70 | 71 | deploymentTransaction = await walletClient.sendTransaction({ 72 | account: walletClient.account, 73 | to: SMART_ACCOUNT_FACTORY_ADDRESS, 74 | data: deploymentCalldata, 75 | ...rest, 76 | }); 77 | } 78 | 79 | return { 80 | smartAccountAddress: address, 81 | deploymentTransaction, 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /packages/agw-client/src/actions/deployContract.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Abi, 3 | type Account, 4 | type Client, 5 | type ContractConstructorArgs, 6 | type PublicClient, 7 | type Transport, 8 | type WalletClient, 9 | } from 'viem'; 10 | import { 11 | type ChainEIP712, 12 | type DeployContractParameters, 13 | type DeployContractReturnType, 14 | encodeDeployData, 15 | } from 'viem/zksync'; 16 | 17 | import { CONTRACT_DEPLOYER_ADDRESS } from '../constants.js'; 18 | import { sendTransaction } from './sendTransaction.js'; 19 | 20 | /** 21 | * Function to deploy a smart contract from the connected Abstract Global Wallet. 22 | * 23 | * This extends the deployContract function from Viem to include options for contract deployment on Abstract. 24 | * 25 | * @example 26 | * ```tsx 27 | * import { useAbstractClient } from "@abstract-foundation/agw-react"; 28 | * import { erc20Abi } from "viem"; // example abi 29 | * import { abstractTestnet } from "viem/chains"; 30 | * 31 | * export default function DeployContract() { 32 | * const { data: agwClient } = useAbstractClient(); 33 | * 34 | * async function deployContract() { 35 | * if (!agwClient) return; 36 | * 37 | * const hash = await agwClient.deployContract({ 38 | * abi: erc20Abi, // Your smart contract ABI 39 | * account: agwClient.account, 40 | * bytecode: "0x...", // Your smart contract bytecode 41 | * chain: abstractTestnet, 42 | * args: [], // Constructor arguments 43 | * }); 44 | * } 45 | * } 46 | * ``` 47 | * 48 | * @param parameters - Contract deployment parameters 49 | * @param parameters.abi - The ABI of the contract to deploy (required) 50 | * @param parameters.bytecode - The bytecode of the contract to deploy (required) 51 | * @param parameters.account - The account to deploy the contract from (required) 52 | * @param parameters.chain - The chain to deploy the contract on, e.g. abstractTestnet / abstract (required) 53 | * @param parameters.args - Constructor arguments to call upon deployment 54 | * @param parameters.deploymentType - Specifies the type of contract deployment ('create', 'create2', 'createAccount', 'create2Account'). Defaults to 'create' 55 | * @param parameters.factoryDeps - An array of bytecodes of contracts that are dependencies for the contract being deployed 56 | * @param parameters.salt - Specifies a unique identifier for the contract deployment 57 | * @param parameters.gasPerPubdata - The amount of gas to pay per byte of data on Ethereum 58 | * @param parameters.paymaster - Address of the paymaster smart contract that will pay the gas fees (requires paymasterInput) 59 | * @param parameters.paymasterInput - Input data to the paymaster (requires paymaster) 60 | * @returns The hash of the transaction that deployed the contract 61 | */ 62 | export function deployContract< 63 | const abi extends Abi | readonly unknown[], 64 | chain extends ChainEIP712 | undefined = ChainEIP712, 65 | account extends Account | undefined = Account, 66 | chainOverride extends ChainEIP712 | undefined = ChainEIP712, 67 | allArgs = ContractConstructorArgs, 68 | >( 69 | walletClient: Client, 70 | signerClient: WalletClient, 71 | publicClient: PublicClient, 72 | parameters: DeployContractParameters< 73 | abi, 74 | chain, 75 | account, 76 | chainOverride, 77 | allArgs 78 | >, 79 | isPrivyCrossApp = false, 80 | ): Promise { 81 | const { abi, args, bytecode, deploymentType, salt, ...request } = 82 | parameters as DeployContractParameters; 83 | 84 | const data = encodeDeployData({ 85 | abi, 86 | args, 87 | bytecode, 88 | deploymentType, 89 | salt, 90 | }); 91 | 92 | // Add the bytecode to the factoryDeps if it's not already there 93 | request.factoryDeps = request.factoryDeps || []; 94 | if (!request.factoryDeps.includes(bytecode)) 95 | request.factoryDeps.push(bytecode); 96 | 97 | return sendTransaction( 98 | walletClient, 99 | signerClient, 100 | publicClient, 101 | { 102 | ...request, 103 | data, 104 | to: CONTRACT_DEPLOYER_ADDRESS, 105 | }, 106 | isPrivyCrossApp, 107 | ); 108 | } 109 | -------------------------------------------------------------------------------- /packages/agw-client/src/actions/getLinkedAccounts.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Account, 3 | type Address, 4 | checksumAddress, 5 | type Client, 6 | getAddress, 7 | InvalidAddressError, 8 | isAddress, 9 | type Transport, 10 | } from 'viem'; 11 | import { readContract } from 'viem/actions'; 12 | import { getAction, parseAccount } from 'viem/utils'; 13 | import { type ChainEIP712 } from 'viem/zksync'; 14 | 15 | import { ExclusiveDelegateResolverAbi } from '../abis/ExclusiveDelegateResolver.js'; 16 | import { 17 | AGW_LINK_DELEGATION_RIGHTS, 18 | CANONICAL_EXCLUSIVE_DELEGATE_RESOLVER_ADDRESS, 19 | } from '../constants.js'; 20 | import { AccountNotFoundError } from '../errors/account.js'; 21 | 22 | export interface GetLinkedAccountsReturnType { 23 | linkedAccounts: Address[]; 24 | } 25 | 26 | export interface GetLinkedAccountsParameters { 27 | agwAddress: Address; 28 | } 29 | 30 | export interface IsLinkedAccountParameters { 31 | address: Address; 32 | } 33 | 34 | /** 35 | * Get all accounts linked to an Abstract Global Wallet. 36 | * 37 | * @example 38 | * ```tsx 39 | * import { useAbstractClient } from "@abstract-foundation/agw-react"; 40 | * 41 | * export default function LinkedAccounts() { 42 | * const { data: agwClient } = useAbstractClient(); 43 | * 44 | * async function fetchLinkedAccounts() { 45 | * if (!agwClient) return; 46 | * 47 | * const { linkedAccounts } = await agwClient.getLinkedAccounts({ 48 | * agwAddress: agwClient.account.address 49 | * }); 50 | * 51 | * console.log(linkedAccounts); // Array of linked account addresses 52 | * } 53 | * } 54 | * ``` 55 | * 56 | * @param parameters - Parameters for getting linked accounts 57 | * @param parameters.agwAddress - Address of the Abstract Global Wallet to check for linked accounts (required) 58 | * @returns An object containing an array of linked account addresses 59 | */ 60 | export async function getLinkedAccounts< 61 | transport extends Transport = Transport, 62 | chain extends ChainEIP712 | undefined = ChainEIP712 | undefined, 63 | account extends Account | undefined = Account | undefined, 64 | >( 65 | client: Client, 66 | parameters: GetLinkedAccountsParameters, 67 | ): Promise { 68 | const { agwAddress } = parameters; 69 | 70 | if (!isAddress(agwAddress, { strict: false })) { 71 | throw new InvalidAddressError({ address: agwAddress }); 72 | } 73 | 74 | const checksummedAddress = getAddress(agwAddress); 75 | 76 | const result = await getAction( 77 | client, 78 | readContract, 79 | 'readContract', 80 | )({ 81 | abi: ExclusiveDelegateResolverAbi, 82 | address: CANONICAL_EXCLUSIVE_DELEGATE_RESOLVER_ADDRESS, 83 | functionName: 'delegatedWalletsByRights', 84 | args: [checksummedAddress, AGW_LINK_DELEGATION_RIGHTS], 85 | }); 86 | 87 | return { 88 | linkedAccounts: [...result], 89 | }; 90 | } 91 | 92 | /** 93 | * Check if an address is linked to the connected Abstract Global Wallet. 94 | * 95 | * @example 96 | * ```tsx 97 | * import { useAbstractClient } from "@abstract-foundation/agw-react"; 98 | * 99 | * export default function CheckLinkedAccount() { 100 | * const { data: agwClient } = useAbstractClient(); 101 | * const addressToCheck = "0x..."; 102 | * 103 | * async function checkIfLinked() { 104 | * if (!agwClient) return; 105 | * 106 | * const isLinked = await agwClient.isLinkedAccount({ 107 | * address: addressToCheck 108 | * }); 109 | * 110 | * console.log(isLinked); // true or false 111 | * } 112 | * } 113 | * ``` 114 | * 115 | * @param parameters - Parameters for checking linked account 116 | * @param parameters.address - Address to check if linked to the connected wallet (required) 117 | * @returns Boolean indicating if the address is linked to the connected wallet 118 | */ 119 | export async function isLinkedAccount( 120 | client: Client, 121 | parameters: IsLinkedAccountParameters, 122 | ): Promise { 123 | const { address } = parameters; 124 | if (!client.account) { 125 | throw new AccountNotFoundError({ 126 | docsPath: '/docs/contract/readContract', 127 | }); 128 | } 129 | const clientAccount = parseAccount(client.account); 130 | const { linkedAccounts } = await getLinkedAccounts(client, { 131 | agwAddress: clientAccount.address, 132 | }); 133 | 134 | return linkedAccounts.includes(checksumAddress(address)); 135 | } 136 | -------------------------------------------------------------------------------- /packages/agw-client/src/actions/getLinkedAgw.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Account, 3 | type Address, 4 | BaseError, 5 | type Chain, 6 | type Client, 7 | getAddress, 8 | InvalidAddressError, 9 | isAddress, 10 | type Transport, 11 | } from 'viem'; 12 | import { readContract } from 'viem/actions'; 13 | import { getAction, parseAccount } from 'viem/utils'; 14 | import { type ChainEIP712 } from 'viem/zksync'; 15 | 16 | import { ExclusiveDelegateResolverAbi } from '../abis/ExclusiveDelegateResolver.js'; 17 | import { 18 | AGW_LINK_DELEGATION_RIGHTS, 19 | CANONICAL_EXCLUSIVE_DELEGATE_RESOLVER_ADDRESS, 20 | } from '../constants.js'; 21 | import { AccountNotFoundError } from '../errors/account.js'; 22 | 23 | export interface GetLinkedAgwReturnType { 24 | agw: Address | undefined; 25 | } 26 | 27 | export interface GetLinkedAgwParameters { 28 | address?: Address | undefined; 29 | } 30 | 31 | export interface IsLinkedAccountParameters { 32 | address: Address; 33 | } 34 | 35 | /** 36 | * Get the linked Abstract Global Wallet for an Ethereum Mainnet address. 37 | * 38 | * @example 39 | * ```tsx 40 | * import { linkableWalletActions } from "@abstract-foundation/agw-client"; 41 | * import { createWalletClient, custom } from "viem"; 42 | * import { sepolia } from "viem/chains"; 43 | * 44 | * export default function CheckLinkedWallet() { 45 | * async function checkLinkedWallet() { 46 | * // Initialize a Viem Wallet client and extend it with linkableWalletActions 47 | * const client = createWalletClient({ 48 | * chain: sepolia, 49 | * transport: custom(window.ethereum!), 50 | * }).extend(linkableWalletActions()); 51 | * 52 | * // Check if an address has a linked AGW 53 | * const { agw } = await client.getLinkedAgw(); 54 | * 55 | * if (agw) { 56 | * console.log("Linked AGW:", agw); 57 | * } else { 58 | * console.log("No linked AGW found"); 59 | * } 60 | * } 61 | * 62 | * return ; 63 | * } 64 | * ``` 65 | * 66 | * @param parameters - Parameters for getting the linked AGW 67 | * @param parameters.address - The Ethereum Mainnet address to check for a linked AGW. If not provided, defaults to the connected account's address 68 | * @returns Object containing the address of the linked AGW, or undefined if no AGW is linked 69 | */ 70 | export async function getLinkedAgw< 71 | chain extends Chain | undefined = Chain | undefined, 72 | account extends Account | undefined = Account | undefined, 73 | >( 74 | client: Client, 75 | parameters: GetLinkedAgwParameters, 76 | ): Promise { 77 | const { address = client.account?.address } = parameters; 78 | 79 | if (address === undefined) { 80 | throw new BaseError('No address provided'); 81 | } 82 | 83 | if (!isAddress(address, { strict: false })) { 84 | throw new InvalidAddressError({ address }); 85 | } 86 | 87 | const checksummedAddress = getAddress(address); 88 | 89 | const result = await getAction( 90 | client, 91 | readContract, 92 | 'readContract', 93 | )({ 94 | abi: ExclusiveDelegateResolverAbi, 95 | address: CANONICAL_EXCLUSIVE_DELEGATE_RESOLVER_ADDRESS, 96 | functionName: 'exclusiveWalletByRights', 97 | args: [checksummedAddress, AGW_LINK_DELEGATION_RIGHTS], 98 | }); 99 | 100 | if (result === checksummedAddress) { 101 | return { 102 | agw: undefined, 103 | }; 104 | } 105 | 106 | return { 107 | agw: result, 108 | }; 109 | } 110 | 111 | export async function isLinkedAccount( 112 | client: Client, 113 | parameters: IsLinkedAccountParameters, 114 | ): Promise { 115 | const { address } = parameters; 116 | 117 | if (client.account === undefined) { 118 | throw new AccountNotFoundError({ 119 | docsPath: '/docs/contract/readContract', 120 | }); 121 | } 122 | 123 | const clientAccount = parseAccount(client.account); 124 | 125 | const { agw } = await getLinkedAgw(client, { address }); 126 | return agw === clientAccount.address; 127 | } 128 | -------------------------------------------------------------------------------- /packages/agw-client/src/actions/getSessionStatus.ts: -------------------------------------------------------------------------------- 1 | import type { Address, Hash, PublicClient, Transport } from 'viem'; 2 | 3 | import { SessionKeyValidatorAbi } from '../abis/SessionKeyValidator.js'; 4 | import { SESSION_KEY_VALIDATOR_ADDRESS } from '../constants.js'; 5 | import { 6 | getSessionHash, 7 | type SessionConfig, 8 | SessionStatus, 9 | } from '../sessions.js'; 10 | 11 | /** 12 | * Function to check the current status of a session key from the validator contract. 13 | * 14 | * Allows you to determine if a session is active, expired, closed, or not initialized. 15 | * 16 | * @example 17 | * ```tsx 18 | * import { useAbstractClient } from "@abstract-foundation/agw-react"; 19 | * import { SessionStatus } from "@abstract-foundation/agw-client/sessions"; 20 | * import { useAccount } from "wagmi"; 21 | * 22 | * export default function CheckSessionStatus() { 23 | * const { address } = useAccount(); 24 | * const { data: agwClient } = useAbstractClient(); 25 | * 26 | * async function checkStatus() { 27 | * if (!address || !agwClient) return; 28 | * 29 | * // Provide either a session hash or session config object 30 | * const sessionHashOrConfig = "..."; // or { ... } 31 | * const status = await agwClient.getSessionStatus(sessionHashOrConfig); 32 | * 33 | * // Handle the different status cases 34 | * switch (status) { 35 | * case 0: // Not initialized 36 | * console.log("Session does not exist"); 37 | * case 1: // Active 38 | * console.log("Session is active and can be used"); 39 | * case 2: // Closed 40 | * console.log("Session has been revoked"); 41 | * case 3: // Expired 42 | * console.log("Session has expired"); 43 | * } 44 | * } 45 | * } 46 | * ``` 47 | * 48 | * @param sessionHashOrConfig - Either the hash of the session configuration or the session configuration object itself 49 | * @returns The current status of the session: 50 | * - `SessionStatus.NotInitialized` (0): The session has not been created 51 | * - `SessionStatus.Active` (1): The session is active and can be used 52 | * - `SessionStatus.Closed` (2): The session has been revoked 53 | * - `SessionStatus.Expired` (3): The session has expired 54 | */ 55 | export async function getSessionStatus( 56 | publicClient: PublicClient, 57 | address: Address, 58 | sessionHashOrConfig: Hash | SessionConfig, 59 | ): Promise { 60 | const sessionHash = 61 | typeof sessionHashOrConfig === 'string' 62 | ? sessionHashOrConfig 63 | : getSessionHash(sessionHashOrConfig); 64 | 65 | return await publicClient.readContract({ 66 | address: SESSION_KEY_VALIDATOR_ADDRESS as Address, 67 | abi: SessionKeyValidatorAbi, 68 | functionName: 'sessionStatus', 69 | args: [address, sessionHash], 70 | }); 71 | } 72 | -------------------------------------------------------------------------------- /packages/agw-client/src/actions/revokeSessions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Account, 3 | type Address, 4 | type Client, 5 | type Hash, 6 | type Hex, 7 | type Transport, 8 | } from 'viem'; 9 | import { writeContract } from 'viem/actions'; 10 | import type { ChainEIP712 } from 'viem/chains'; 11 | import { getAction } from 'viem/utils'; 12 | 13 | import { SessionKeyValidatorAbi } from '../abis/SessionKeyValidator.js'; 14 | import { SESSION_KEY_VALIDATOR_ADDRESS } from '../constants.js'; 15 | import { getSessionHash, type SessionConfig } from '../sessions.js'; 16 | 17 | export interface RevokeSessionsParameters { 18 | session: SessionConfig | Hash | (SessionConfig | Hash)[]; 19 | paymaster?: Address; 20 | paymasterInput?: Hex; 21 | } 22 | export interface RevokeSessionsReturnType { 23 | transactionHash: Hash | undefined; 24 | } 25 | 26 | /** 27 | * Function to revoke session keys from the connected Abstract Global Wallet. 28 | * 29 | * This allows you to invalidate existing session keys, preventing them from being used for future transactions. 30 | * 31 | * @example 32 | * ```tsx 33 | * import { useAbstractClient } from "@abstract-foundation/agw-react"; 34 | * 35 | * export default function RevokeSessions() { 36 | * const { data: agwClient } = useAbstractClient(); 37 | * 38 | * async function revokeSessions() { 39 | * if (!agwClient) return; 40 | * 41 | * // Revoke a single session by passing the session configuration 42 | * const { transactionHash } = await agwClient.revokeSessions({ 43 | * session: existingSession, 44 | * }); 45 | * 46 | * // Or - revoke multiple sessions at once 47 | * const { transactionHash } = await agwClient.revokeSessions({ 48 | * session: [existingSession1, existingSession2], 49 | * }); 50 | * 51 | * // Or - revoke sessions using their creation transaction hashes 52 | * const { transactionHash } = await agwClient.revokeSessions({ 53 | * session: "0x1234...", 54 | * }); 55 | * 56 | * // Or - revoke multiple sessions using their creation transaction hashes 57 | * const { transactionHash } = await agwClient.revokeSessions({ 58 | * session: ["0x1234...", "0x5678..."], 59 | * }); 60 | * 61 | * // Or - revoke multiple sessions using both session configuration and creation transaction hashes 62 | * const { transactionHash } = await agwClient.revokeSessions({ 63 | * session: [existingSession, "0x1234..."], 64 | * }); 65 | * } 66 | * } 67 | * ``` 68 | * 69 | * @param parameters - Parameters for revoking sessions 70 | * @param parameters.session - The session(s) to revoke (required). Can be provided in three formats: 71 | * - A single SessionConfig object 72 | * - A single session key creation transaction hash from createSession 73 | * - An array of SessionConfig objects and/or session key creation transaction hashes 74 | * @param parameters.paymaster - Optional paymaster address to sponsor the transaction 75 | * @param parameters.paymasterInput - Optional paymaster input data 76 | * @returns Object containing the transaction hash of the revocation transaction 77 | */ 78 | export async function revokeSessions( 79 | client: Client, 80 | args: RevokeSessionsParameters, 81 | ): Promise { 82 | const { session, ...rest } = args; 83 | 84 | const sessionHashes = 85 | typeof session === 'string' 86 | ? [session as Hash] 87 | : Array.isArray(session) 88 | ? session.map(sessionHash) 89 | : [getSessionHash(session)]; 90 | 91 | const transactionHash = await getAction( 92 | client, 93 | writeContract, 94 | 'writeContract', 95 | )({ 96 | address: SESSION_KEY_VALIDATOR_ADDRESS, 97 | abi: SessionKeyValidatorAbi, 98 | functionName: 'revokeKeys', 99 | args: [sessionHashes], 100 | ...rest, 101 | } as any); 102 | 103 | return { transactionHash }; 104 | } 105 | 106 | function sessionHash(session: SessionConfig | Hash): Hash { 107 | if (typeof session === 'string') { 108 | return session; 109 | } 110 | return getSessionHash(session); 111 | } 112 | -------------------------------------------------------------------------------- /packages/agw-client/src/actions/sendPrivyTransaction.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Account, 3 | type Client, 4 | type Hex, 5 | type SendTransactionRequest, 6 | type SignMessageParameters, 7 | type SignTypedDataParameters, 8 | toHex, 9 | type Transport, 10 | } from 'viem'; 11 | import { 12 | type ChainEIP712, 13 | type SendEip712TransactionParameters, 14 | type SignEip712TransactionParameters, 15 | type SignEip712TransactionReturnType, 16 | } from 'viem/zksync'; 17 | 18 | import { replaceBigInts } from '../replaceBigInts.js'; 19 | import type { SendTransactionBatchParameters } from '../types/sendTransactionBatch.js'; 20 | import type { SignTransactionBatchParameters } from '../types/signTransactionBatch.js'; 21 | export async function sendPrivyTransaction< 22 | chain extends ChainEIP712 | undefined = ChainEIP712 | undefined, 23 | account extends Account | undefined = Account | undefined, 24 | chainOverride extends ChainEIP712 | undefined = ChainEIP712 | undefined, 25 | const request extends SendTransactionRequest< 26 | chain, 27 | chainOverride 28 | > = SendTransactionRequest, 29 | >( 30 | client: Client, 31 | parameters: 32 | | SendEip712TransactionParameters 33 | | SendTransactionBatchParameters, 34 | ): Promise { 35 | const result = (await client.request( 36 | { 37 | method: 'privy_sendSmartWalletTx', 38 | params: [replaceBigInts(parameters, toHex)], 39 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 40 | } as any, 41 | { retryCount: 0 }, 42 | )) as SignEip712TransactionReturnType; 43 | return result; 44 | } 45 | 46 | export async function sendPrivySignMessage( 47 | client: Client, 48 | parameters: Omit, 49 | ): Promise { 50 | const result = (await client.request( 51 | { 52 | method: 'privy_signSmartWalletMessage', 53 | params: [parameters.message], 54 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 55 | } as any, 56 | { retryCount: 0 }, 57 | )) as Hex; 58 | return result; 59 | } 60 | 61 | export async function sendPrivySignTypedData( 62 | client: Client, 63 | parameters: Omit, 64 | ): Promise { 65 | const result = (await client.request( 66 | { 67 | method: 'privy_signSmartWalletTypedData', 68 | params: [client.account.address, parameters], 69 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 70 | } as any, 71 | { retryCount: 0 }, 72 | )) as Hex; 73 | return result; 74 | } 75 | 76 | export async function signPrivyTransaction< 77 | chain extends ChainEIP712 | undefined = ChainEIP712 | undefined, 78 | account extends Account | undefined = Account | undefined, 79 | chainOverride extends ChainEIP712 | undefined = ChainEIP712 | undefined, 80 | >( 81 | client: Client, 82 | parameters: 83 | | SignEip712TransactionParameters 84 | | SignTransactionBatchParameters, 85 | ): Promise { 86 | const { chain: _chain, account: _account, ...request } = parameters; 87 | 88 | const result = (await client.request( 89 | { 90 | method: 'privy_signSmartWalletTx', 91 | params: [replaceBigInts(request, toHex)], 92 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 93 | } as any, 94 | { retryCount: 0 }, 95 | )) as SignEip712TransactionReturnType; 96 | return result; 97 | } 98 | -------------------------------------------------------------------------------- /packages/agw-client/src/actions/sendTransactionForSession.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Account, 3 | BaseError, 4 | type Client, 5 | type Hex, 6 | type PublicClient, 7 | type SendTransactionRequest, 8 | type Transport, 9 | type WalletClient, 10 | } from 'viem'; 11 | import { 12 | type ChainEIP712, 13 | type SendEip712TransactionParameters, 14 | type SendEip712TransactionReturnType, 15 | } from 'viem/zksync'; 16 | 17 | import { SESSION_KEY_VALIDATOR_ADDRESS } from '../constants.js'; 18 | import { 19 | encodeSessionWithPeriodIds, 20 | getPeriodIdsForTransaction, 21 | type SessionConfig, 22 | } from '../sessions.js'; 23 | import type { CustomPaymasterHandler } from '../types/customPaymaster.js'; 24 | import { sendTransactionInternal } from './sendTransactionInternal.js'; 25 | 26 | export interface SendTransactionForSessionParameters< 27 | chain extends ChainEIP712 | undefined = ChainEIP712 | undefined, 28 | account extends Account | undefined = Account | undefined, 29 | chainOverride extends ChainEIP712 | undefined = ChainEIP712 | undefined, 30 | request extends SendTransactionRequest< 31 | chain, 32 | chainOverride 33 | > = SendTransactionRequest, 34 | > { 35 | parameters: SendEip712TransactionParameters< 36 | chain, 37 | account, 38 | chainOverride, 39 | request 40 | >; 41 | session: SessionConfig; 42 | } 43 | 44 | export async function sendTransactionForSession< 45 | chain extends ChainEIP712 | undefined = ChainEIP712 | undefined, 46 | account extends Account | undefined = Account | undefined, 47 | chainOverride extends ChainEIP712 | undefined = ChainEIP712 | undefined, 48 | const request extends SendTransactionRequest< 49 | chain, 50 | chainOverride 51 | > = SendTransactionRequest, 52 | >( 53 | client: Client, 54 | signerClient: WalletClient, 55 | publicClient: PublicClient, 56 | parameters: SendEip712TransactionParameters< 57 | chain, 58 | account, 59 | chainOverride, 60 | request 61 | >, 62 | session: SessionConfig, 63 | customPaymasterHandler: CustomPaymasterHandler | undefined = undefined, 64 | ): Promise { 65 | const selector: Hex | undefined = parameters.data 66 | ? `0x${parameters.data.slice(2, 10)}` 67 | : undefined; 68 | 69 | if (!parameters.to) { 70 | throw new BaseError('Transaction to field is not specified'); 71 | } 72 | return sendTransactionInternal( 73 | client, 74 | signerClient, 75 | publicClient, 76 | parameters, 77 | SESSION_KEY_VALIDATOR_ADDRESS, 78 | { 79 | [SESSION_KEY_VALIDATOR_ADDRESS]: encodeSessionWithPeriodIds( 80 | session, 81 | getPeriodIdsForTransaction({ 82 | sessionConfig: session, 83 | target: parameters.to, 84 | selector, 85 | timestamp: BigInt(Math.floor(Date.now() / 1000)), 86 | }), 87 | ), 88 | }, 89 | customPaymasterHandler, 90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /packages/agw-client/src/actions/sendTransactionInternal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Account, 3 | type Address, 4 | BaseError, 5 | type Chain, 6 | type Client, 7 | type Hex, 8 | type PublicClient, 9 | type SendTransactionRequest, 10 | type Transport, 11 | type WalletClient, 12 | } from 'viem'; 13 | import { getChainId, sendRawTransaction } from 'viem/actions'; 14 | import { 15 | assertCurrentChain, 16 | getAction, 17 | getTransactionError, 18 | type GetTransactionErrorParameters, 19 | parseAccount, 20 | } from 'viem/utils'; 21 | import { 22 | type ChainEIP712, 23 | type SendEip712TransactionParameters, 24 | type SendEip712TransactionReturnType, 25 | } from 'viem/zksync'; 26 | 27 | import { INSUFFICIENT_BALANCE_SELECTOR } from '../constants.js'; 28 | import { AccountNotFoundError } from '../errors/account.js'; 29 | import { InsufficientBalanceError } from '../errors/insufficientBalance.js'; 30 | import type { CustomPaymasterHandler } from '../types/customPaymaster.js'; 31 | import { prepareTransactionRequest } from './prepareTransaction.js'; 32 | import { signTransaction } from './signTransaction.js'; 33 | 34 | export async function sendTransactionInternal< 35 | const request extends SendTransactionRequest, 36 | chain extends ChainEIP712 | undefined = ChainEIP712 | undefined, 37 | account extends Account | undefined = Account | undefined, 38 | chainOverride extends ChainEIP712 | undefined = ChainEIP712 | undefined, 39 | >( 40 | client: Client, 41 | signerClient: WalletClient, 42 | publicClient: PublicClient, 43 | parameters: SendEip712TransactionParameters< 44 | chain, 45 | account, 46 | chainOverride, 47 | request 48 | >, 49 | validator: Address, 50 | validationHookData: Record = {}, 51 | customPaymasterHandler: CustomPaymasterHandler | undefined = undefined, 52 | ): Promise { 53 | const { chain = client.chain } = parameters; 54 | 55 | if (!signerClient.account) 56 | throw new AccountNotFoundError({ 57 | docsPath: '/docs/actions/wallet/sendTransaction', 58 | }); 59 | const account = parseAccount(signerClient.account); 60 | 61 | try { 62 | // assertEip712Request(parameters) 63 | 64 | // Prepare the request for signing (assign appropriate fees, etc.) 65 | const request = await prepareTransactionRequest( 66 | client, 67 | signerClient, 68 | publicClient, 69 | { 70 | ...parameters, 71 | parameters: ['gas', 'nonce', 'fees'], 72 | isSponsored: 73 | customPaymasterHandler !== undefined || 74 | (parameters as any).paymaster !== undefined, 75 | } as any, 76 | ); 77 | 78 | let chainId: number | undefined; 79 | if (chain !== null) { 80 | chainId = await getAction(signerClient, getChainId, 'getChainId')({}); 81 | assertCurrentChain({ 82 | currentChainId: chainId, 83 | chain, 84 | }); 85 | } 86 | 87 | const serializedTransaction = await signTransaction( 88 | client, 89 | signerClient, 90 | publicClient, 91 | { 92 | ...request, 93 | chainId, 94 | } as any, 95 | validator, 96 | validationHookData, 97 | customPaymasterHandler, 98 | ); 99 | return await getAction( 100 | client, 101 | sendRawTransaction, 102 | 'sendRawTransaction', 103 | )({ 104 | serializedTransaction, 105 | }); 106 | } catch (err) { 107 | if ( 108 | err instanceof Error && 109 | err.message.includes(INSUFFICIENT_BALANCE_SELECTOR) 110 | ) { 111 | throw new InsufficientBalanceError(); 112 | } 113 | throw getTransactionError(err as BaseError, { 114 | ...(parameters as GetTransactionErrorParameters), 115 | account, 116 | chain: chain as Chain, 117 | }); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /packages/agw-client/src/actions/signMessage.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Account, 3 | bytesToString, 4 | type Client, 5 | fromHex, 6 | hashMessage, 7 | type Hex, 8 | type SignMessageParameters, 9 | type Transport, 10 | type WalletClient, 11 | } from 'viem'; 12 | import type { ChainEIP712 } from 'viem/chains'; 13 | 14 | import { getAgwTypedSignature } from '../getAgwTypedSignature.js'; 15 | import { sendPrivySignMessage } from './sendPrivyTransaction.js'; 16 | 17 | /** 18 | * Function to sign messages using the connected Abstract Global Wallet. 19 | * 20 | * This method follows the EIP-1271 standard for contract signature verification. 21 | * 22 | * @example 23 | * ```tsx 24 | * import { useAbstractClient } from "@abstract-foundation/agw-react"; 25 | * 26 | * export default function SignMessage() { 27 | * const { data: agwClient } = useAbstractClient(); 28 | * 29 | * async function signMessage() { 30 | * if (!agwClient) return; 31 | * 32 | * const signature = await agwClient.signMessage({ 33 | * message: "Hello, Abstract!", 34 | * }); 35 | * 36 | * console.log("Signature:", signature); 37 | * } 38 | * } 39 | * ``` 40 | * 41 | * @param parameters - Parameters for signing a message 42 | * @param parameters.message - The message to sign. Can be a string or a hex value (required) 43 | * @returns A Promise containing the signature of the message 44 | */ 45 | export async function signMessage( 46 | client: Client, 47 | signerClient: WalletClient, 48 | parameters: Omit, 49 | isPrivyCrossApp = false, 50 | ): Promise { 51 | if (isPrivyCrossApp) { 52 | // We handle {message: {raw}} here because the message is expected to be a string 53 | if (typeof parameters.message === 'object') { 54 | if (parameters.message.raw instanceof Uint8Array) { 55 | parameters.message = bytesToString(parameters.message.raw); 56 | } else { 57 | parameters.message = fromHex(parameters.message.raw, 'string'); 58 | } 59 | } 60 | return await sendPrivySignMessage(client, parameters); 61 | } 62 | 63 | return await getAgwTypedSignature({ 64 | client, 65 | signer: signerClient, 66 | messageHash: hashMessage(parameters.message), 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /packages/agw-client/src/actions/signTransactionBatch.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Account, 3 | type Address, 4 | type Client, 5 | type Hex, 6 | type PublicClient, 7 | type Transport, 8 | type WalletClient, 9 | } from 'viem'; 10 | import { 11 | type ChainEIP712, 12 | type SignEip712TransactionReturnType, 13 | } from 'viem/zksync'; 14 | 15 | import type { CustomPaymasterHandler } from '../types/customPaymaster.js'; 16 | import type { SignTransactionBatchParameters } from '../types/signTransactionBatch.js'; 17 | import { signPrivyTransaction } from './sendPrivyTransaction.js'; 18 | import { getBatchTransactionObject } from './sendTransactionBatch.js'; 19 | import { signTransaction } from './signTransaction.js'; 20 | 21 | /** 22 | * Function to sign a batch of transactions in a single call using the connected Abstract Global Wallet. 23 | * 24 | * @example 25 | * ```tsx 26 | * import { useAbstractClient } from "@abstract-foundation/agw-react"; 27 | * import { encodeFunctionData, parseUnits } from "viem"; 28 | * 29 | * export default function SignTransactionBatch() { 30 | * const { data: agwClient } = useAbstractClient(); 31 | * 32 | * async function signTransactionBatch() { 33 | * if (!agwClient) return; 34 | * 35 | * // Sign a batch of multiple transactions in a single call 36 | * const rawTransaction = await agwClient.signTransactionBatch({ 37 | * calls: [ 38 | * // 1. Simple ETH transfer 39 | * { 40 | * to: "0x1234567890123456789012345678901234567890", 41 | * value: parseUnits("0.1", 18), // 0.1 ETH 42 | * }, 43 | * // 2. Contract interaction 44 | * { 45 | * to: "0xabcdef0123456789abcdef0123456789abcdef01", 46 | * data: encodeFunctionData({ 47 | * abi: [ 48 | * { 49 | * name: "transfer", 50 | * type: "function", 51 | * inputs: [ 52 | * { name: "to", type: "address" }, 53 | * { name: "amount", type: "uint256" } 54 | * ], 55 | * outputs: [{ type: "bool" }], 56 | * stateMutability: "nonpayable" 57 | * } 58 | * ], 59 | * functionName: "transfer", 60 | * args: ["0x9876543210987654321098765432109876543210", parseUnits("10", 18)] 61 | * }) 62 | * } 63 | * ] 64 | * }); 65 | * 66 | * console.log("Serialized transaction:", rawTransaction); 67 | * } 68 | * } 69 | * ``` 70 | * 71 | * @param parameters - Parameters for signing a batch of transactions 72 | * @param parameters.calls - An array of transaction requests. Each transaction can include: 73 | * - to: The recipient address (required) 74 | * - data: Contract code or method call with encoded args 75 | * - value: Amount in wei to send 76 | * @param parameters.paymaster - Address of the paymaster smart contract that will pay the gas fees 77 | * @param parameters.paymasterInput - Input data to the paymaster 78 | * @returns The transaction hash of the submitted transaction batch 79 | */ 80 | export async function signTransactionBatch< 81 | chain extends ChainEIP712 | undefined = ChainEIP712 | undefined, 82 | account extends Account | undefined = Account | undefined, 83 | chainOverride extends ChainEIP712 | undefined = ChainEIP712 | undefined, 84 | >( 85 | client: Client, 86 | signerClient: WalletClient, 87 | publicClient: PublicClient, 88 | parameters: SignTransactionBatchParameters, 89 | validator: Address, 90 | validationHookData: Record = {}, 91 | customPaymasterHandler: CustomPaymasterHandler | undefined = undefined, 92 | isPrivyCrossApp = false, 93 | ): Promise { 94 | const { calls, ...rest } = parameters; 95 | if (calls.length === 0) { 96 | throw new Error('No calls provided'); 97 | } 98 | if (isPrivyCrossApp) { 99 | return await signPrivyTransaction(client, parameters); 100 | } 101 | 102 | const batchTransaction = getBatchTransactionObject( 103 | client.account.address, 104 | parameters, 105 | ); 106 | 107 | return signTransaction( 108 | client, 109 | signerClient, 110 | publicClient, 111 | { 112 | ...batchTransaction, 113 | ...rest, 114 | }, 115 | validator, 116 | validationHookData, 117 | customPaymasterHandler, 118 | ); 119 | } 120 | -------------------------------------------------------------------------------- /packages/agw-client/src/actions/signTransactionForSession.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Account, 3 | BaseError, 4 | type Client, 5 | type Hex, 6 | type PublicClient, 7 | type SendTransactionRequest, 8 | type Transport, 9 | type WalletClient, 10 | } from 'viem'; 11 | import { 12 | type ChainEIP712, 13 | type SendEip712TransactionParameters, 14 | type SignEip712TransactionParameters, 15 | type SignTransactionReturnType, 16 | } from 'viem/zksync'; 17 | 18 | import { SESSION_KEY_VALIDATOR_ADDRESS } from '../constants.js'; 19 | import { 20 | encodeSessionWithPeriodIds, 21 | getPeriodIdsForTransaction, 22 | type SessionConfig, 23 | } from '../sessions.js'; 24 | import type { CustomPaymasterHandler } from '../types/customPaymaster.js'; 25 | import { isSmartAccountDeployed } from '../utils.js'; 26 | import { signTransaction } from './signTransaction.js'; 27 | 28 | export interface SendTransactionForSessionParameters< 29 | chain extends ChainEIP712 | undefined = ChainEIP712 | undefined, 30 | account extends Account | undefined = Account | undefined, 31 | chainOverride extends ChainEIP712 | undefined = ChainEIP712 | undefined, 32 | request extends SendTransactionRequest< 33 | chain, 34 | chainOverride 35 | > = SendTransactionRequest, 36 | > { 37 | parameters: SendEip712TransactionParameters< 38 | chain, 39 | account, 40 | chainOverride, 41 | request 42 | >; 43 | session: SessionConfig; 44 | } 45 | 46 | export async function signTransactionForSession< 47 | chain extends ChainEIP712 | undefined = ChainEIP712 | undefined, 48 | account extends Account | undefined = Account | undefined, 49 | chainOverride extends ChainEIP712 | undefined = ChainEIP712 | undefined, 50 | >( 51 | client: Client, 52 | signerClient: WalletClient, 53 | publicClient: PublicClient, 54 | parameters: SignEip712TransactionParameters, 55 | session: SessionConfig, 56 | customPaymasterHandler: CustomPaymasterHandler | undefined = undefined, 57 | ): Promise { 58 | const isDeployed = await isSmartAccountDeployed( 59 | publicClient, 60 | client.account.address, 61 | ); 62 | if (!isDeployed) { 63 | throw new BaseError('Smart account not deployed'); 64 | } 65 | 66 | const selector: Hex | undefined = parameters.data 67 | ? `0x${parameters.data.slice(2, 10)}` 68 | : undefined; 69 | 70 | if (!parameters.to) { 71 | throw new BaseError('Transaction to field is not specified'); 72 | } 73 | 74 | return await signTransaction( 75 | client, 76 | signerClient, 77 | publicClient, 78 | parameters, 79 | SESSION_KEY_VALIDATOR_ADDRESS, 80 | { 81 | [SESSION_KEY_VALIDATOR_ADDRESS]: encodeSessionWithPeriodIds( 82 | session, 83 | getPeriodIdsForTransaction({ 84 | sessionConfig: session, 85 | target: parameters.to, 86 | selector, 87 | timestamp: BigInt(Math.floor(Date.now() / 1000)), 88 | }), 89 | ), 90 | }, 91 | customPaymasterHandler, 92 | ); 93 | } 94 | -------------------------------------------------------------------------------- /packages/agw-client/src/actions/signTypedData.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Account, 3 | BaseError, 4 | type Client, 5 | fromRlp, 6 | hashTypedData, 7 | type Hex, 8 | type PublicClient, 9 | type Transport, 10 | type TypedData, 11 | type TypedDataDefinition, 12 | type WalletClient, 13 | } from 'viem'; 14 | import { type SignTypedDataParameters } from 'viem/accounts'; 15 | import type { ChainEIP712 } from 'viem/chains'; 16 | 17 | import { 18 | EOA_VALIDATOR_ADDRESS, 19 | SESSION_KEY_VALIDATOR_ADDRESS, 20 | } from '../constants.js'; 21 | import { getAgwTypedSignature } from '../getAgwTypedSignature.js'; 22 | import { 23 | encodeSessionWithPeriodIds, 24 | getPeriodIdsForTransaction, 25 | type SessionConfig, 26 | } from '../sessions.js'; 27 | import type { CustomPaymasterHandler } from '../types/customPaymaster.js'; 28 | import { isEip712TypedData, transformEip712TypedData } from '../utils.js'; 29 | import { sendPrivySignTypedData } from './sendPrivyTransaction.js'; 30 | import { 31 | signEip712TransactionInternal, 32 | signTransaction, 33 | } from './signTransaction.js'; 34 | 35 | export async function signTypedData( 36 | client: Client, 37 | signerClient: WalletClient, 38 | publicClient: PublicClient, 39 | parameters: Omit, 40 | isPrivyCrossApp = false, 41 | ): Promise { 42 | // if the typed data is already a zkSync EIP712 transaction, don't try to transform it 43 | // to an AGW typed signature, just pass it through to the signer. 44 | if (isEip712TypedData(parameters)) { 45 | const transformedTypedData = transformEip712TypedData(parameters); 46 | 47 | if (transformedTypedData.chainId !== client.chain.id) { 48 | throw new BaseError('Chain ID mismatch in AGW typed signature'); 49 | } 50 | 51 | const signedTransaction = await signTransaction( 52 | client, 53 | signerClient, 54 | publicClient, 55 | { 56 | ...transformedTypedData, 57 | chain: client.chain, 58 | }, 59 | EOA_VALIDATOR_ADDRESS, 60 | {}, 61 | undefined, 62 | isPrivyCrossApp, 63 | ); 64 | 65 | if (!signedTransaction.startsWith('0x71')) { 66 | throw new BaseError( 67 | 'Expected RLP encoded EIP-712 transaction as signature', 68 | ); 69 | } 70 | 71 | const rlpSignature: Hex = `0x${signedTransaction.slice(4)}`; 72 | 73 | const signatureParts = fromRlp(rlpSignature, 'hex'); 74 | if (signatureParts.length < 15) { 75 | throw new BaseError( 76 | 'Expected RLP encoded EIP-712 transaction with at least 15 fields', 77 | ); 78 | } 79 | // This is somewhat not type safe as it assumes that the signature from signTransaction is an 80 | // RLP encoded 712 transaction and that the customSignature field is the 15th field in the transaction. 81 | // That being said, it's a safe assumption for the current use case. 82 | return signatureParts[14] as Hex; 83 | } else if (isPrivyCrossApp) { 84 | return await sendPrivySignTypedData(client, parameters); 85 | } 86 | 87 | return await getAgwTypedSignature({ 88 | client, 89 | signer: signerClient, 90 | messageHash: hashTypedData(parameters), 91 | }); 92 | } 93 | 94 | export async function signTypedDataForSession< 95 | const typedData extends TypedData | Record, 96 | primaryType extends string, 97 | >( 98 | client: Client, 99 | signerClient: WalletClient, 100 | publicClient: PublicClient, 101 | parameters: TypedDataDefinition, 102 | session: SessionConfig, 103 | paymasterHandler?: CustomPaymasterHandler, 104 | ): Promise { 105 | // if the typed data is already a zkSync EIP712 transaction, don't try to transform it 106 | // to an AGW typed signature, just pass it through to the signer. 107 | if (!isEip712TypedData(parameters as any)) { 108 | throw new BaseError( 109 | 'Session client can only sign EIP712 transactions as typed data', 110 | ); 111 | } 112 | 113 | const transactionRequest = transformEip712TypedData(parameters as any); 114 | 115 | if (!transactionRequest.to) { 116 | throw new BaseError('Transaction must have a to address'); 117 | } 118 | 119 | // Match the expect signature format of the AGW smart account so the result can be 120 | // directly used in eth_sendRawTransaction as the customSignature field 121 | const validationHookData = { 122 | [SESSION_KEY_VALIDATOR_ADDRESS]: encodeSessionWithPeriodIds( 123 | session, 124 | getPeriodIdsForTransaction({ 125 | sessionConfig: session, 126 | target: transactionRequest.to, 127 | selector: (transactionRequest.data?.slice(0, 10) ?? '0x') as Hex, 128 | timestamp: BigInt(Math.floor(Date.now() / 1000)), 129 | }), 130 | ), 131 | }; 132 | 133 | const { customSignature } = await signEip712TransactionInternal( 134 | client, 135 | signerClient, 136 | publicClient, 137 | { 138 | chain: client.chain, 139 | ...transactionRequest, 140 | }, 141 | SESSION_KEY_VALIDATOR_ADDRESS, 142 | validationHookData, 143 | paymasterHandler, 144 | ); 145 | 146 | return customSignature; 147 | } 148 | -------------------------------------------------------------------------------- /packages/agw-client/src/actions/writeContract.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Abi, 3 | type Account, 4 | BaseError, 5 | type Client, 6 | type ContractFunctionArgs, 7 | type ContractFunctionName, 8 | encodeFunctionData, 9 | type EncodeFunctionDataParameters, 10 | type PublicClient, 11 | type Transport, 12 | type WalletClient, 13 | type WriteContractParameters, 14 | type WriteContractReturnType, 15 | } from 'viem'; 16 | import { getContractError, parseAccount } from 'viem/utils'; 17 | import { type ChainEIP712 } from 'viem/zksync'; 18 | 19 | import { AccountNotFoundError } from '../errors/account.js'; 20 | import { sendTransaction } from './sendTransaction.js'; 21 | 22 | /** 23 | * Function to call functions on a smart contract using the connected Abstract Global Wallet. 24 | * 25 | * @example 26 | * ```tsx 27 | * import { useAbstractClient } from "@abstract-foundation/agw-react"; 28 | * import { parseAbi } from "viem"; 29 | * 30 | * export default function WriteContract() { 31 | * const { data: agwClient } = useAbstractClient(); 32 | * 33 | * async function writeContract() { 34 | * if (!agwClient) return; 35 | * 36 | * const transactionHash = await agwClient.writeContract({ 37 | * abi: parseAbi(["function mint(address,uint256) external"]), // Your contract ABI 38 | * address: "0xC4822AbB9F05646A9Ce44EFa6dDcda0Bf45595AA", 39 | * functionName: "mint", 40 | * args: ["0x273B3527BF5b607dE86F504fED49e1582dD2a1C6", BigInt(1)], 41 | * }); 42 | * 43 | * console.log("Transaction hash:", transactionHash); 44 | * } 45 | * } 46 | * ``` 47 | * 48 | * @param parameters - Parameters for writing to a contract 49 | * @param parameters.address - The address of the contract to write to (required) 50 | * @param parameters.abi - The ABI of the contract to write to (required) 51 | * @param parameters.functionName - The name of the function to call on the contract (required) 52 | * @param parameters.args - The arguments to pass to the function 53 | * @param parameters.account - The account to use for the transaction (defaults to the AGW's account) 54 | * @param parameters.chain - The chain to use for the transaction (defaults to the chain in the AbstractClient) 55 | * @param parameters.value - The amount of native token to send with the transaction (in wei) 56 | * @param parameters.dataSuffix - Data to append to the end of the calldata 57 | * @param parameters.gasPerPubdata - The amount of gas to pay per byte of data on Ethereum 58 | * @param parameters.paymaster - Address of the paymaster smart contract that will pay the gas fees 59 | * @param parameters.paymasterInput - Input data to the paymaster (required if paymaster is provided) 60 | * @returns The transaction hash of the contract write operation 61 | */ 62 | export async function writeContract< 63 | chain extends ChainEIP712 | undefined, 64 | account extends Account | undefined, 65 | const abi extends Abi | readonly unknown[], 66 | functionName extends ContractFunctionName, 67 | args extends ContractFunctionArgs< 68 | abi, 69 | 'nonpayable' | 'payable', 70 | functionName 71 | >, 72 | chainOverride extends ChainEIP712 | undefined, 73 | >( 74 | client: Client, 75 | signerClient: WalletClient, 76 | publicClient: PublicClient, 77 | parameters: WriteContractParameters< 78 | abi, 79 | functionName, 80 | args, 81 | chain, 82 | account, 83 | chainOverride 84 | >, 85 | isPrivyCrossApp = false, 86 | ): Promise { 87 | const { 88 | abi, 89 | account: account_ = client.account, 90 | address, 91 | args, 92 | dataSuffix, 93 | functionName, 94 | ...request 95 | } = parameters as WriteContractParameters; 96 | 97 | if (!account_) 98 | throw new AccountNotFoundError({ 99 | docsPath: '/docs/contract/writeContract', 100 | }); 101 | const account = parseAccount(account_); 102 | 103 | const data = encodeFunctionData({ 104 | abi, 105 | args, 106 | functionName, 107 | } as EncodeFunctionDataParameters); 108 | 109 | try { 110 | return await sendTransaction( 111 | client, 112 | signerClient, 113 | publicClient, 114 | { 115 | data: `${data}${dataSuffix ? dataSuffix.replace('0x', '') : ''}`, 116 | to: address, 117 | account, 118 | ...request, 119 | }, 120 | isPrivyCrossApp, 121 | ); 122 | } catch (error) { 123 | throw getContractError(error as BaseError, { 124 | abi, 125 | address, 126 | args, 127 | docsPath: '/docs/contract/writeContract', 128 | functionName, 129 | sender: account.address, 130 | }); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /packages/agw-client/src/actions/writeContractForSession.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Abi, 3 | type Account, 4 | BaseError, 5 | type Client, 6 | type ContractFunctionArgs, 7 | type ContractFunctionName, 8 | encodeFunctionData, 9 | type EncodeFunctionDataParameters, 10 | type PublicClient, 11 | type Transport, 12 | type WalletClient, 13 | type WriteContractParameters, 14 | type WriteContractReturnType, 15 | } from 'viem'; 16 | import { getContractError, parseAccount } from 'viem/utils'; 17 | import { type ChainEIP712 } from 'viem/zksync'; 18 | 19 | import { AccountNotFoundError } from '../errors/account.js'; 20 | import type { SessionConfig } from '../sessions.js'; 21 | import type { CustomPaymasterHandler } from '../types/customPaymaster.js'; 22 | import { sendTransactionForSession } from './sendTransactionForSession.js'; 23 | 24 | export async function writeContractForSession< 25 | chain extends ChainEIP712 | undefined, 26 | account extends Account | undefined, 27 | const abi extends Abi | readonly unknown[], 28 | functionName extends ContractFunctionName, 29 | args extends ContractFunctionArgs< 30 | abi, 31 | 'nonpayable' | 'payable', 32 | functionName 33 | >, 34 | chainOverride extends ChainEIP712 | undefined, 35 | >( 36 | client: Client, 37 | signerClient: WalletClient, 38 | publicClient: PublicClient, 39 | parameters: WriteContractParameters< 40 | abi, 41 | functionName, 42 | args, 43 | chain, 44 | account, 45 | chainOverride 46 | >, 47 | session: SessionConfig, 48 | customPaymasterHandler: CustomPaymasterHandler | undefined = undefined, 49 | ): Promise { 50 | const { 51 | abi, 52 | account: account_ = client.account, 53 | address, 54 | args, 55 | dataSuffix, 56 | functionName, 57 | ...request 58 | } = parameters as WriteContractParameters; 59 | 60 | if (!account_) 61 | throw new AccountNotFoundError({ 62 | docsPath: '/docs/contract/writeContract', 63 | }); 64 | const account = parseAccount(account_); 65 | 66 | const data = encodeFunctionData({ 67 | abi, 68 | args, 69 | functionName, 70 | } as EncodeFunctionDataParameters); 71 | 72 | try { 73 | return await sendTransactionForSession( 74 | client, 75 | signerClient, 76 | publicClient, 77 | { 78 | data: `${data}${dataSuffix ? dataSuffix.replace('0x', '') : ''}`, 79 | to: address, 80 | account, 81 | ...request, 82 | }, 83 | session, 84 | customPaymasterHandler, 85 | ); 86 | } catch (error) { 87 | throw getContractError(error as BaseError, { 88 | abi, 89 | address, 90 | args, 91 | docsPath: '/docs/contract/writeContract', 92 | functionName, 93 | sender: account.address, 94 | }); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /packages/agw-client/src/constants.ts: -------------------------------------------------------------------------------- 1 | import type { Address } from 'viem'; 2 | import { abstract, abstractTestnet } from 'viem/chains'; 3 | 4 | // AA smart contract deployment 5 | const SMART_ACCOUNT_FACTORY_ADDRESS = 6 | '0x9B947df68D35281C972511B3E7BC875926f26C1A' as const; 7 | 8 | // AA wallet validator contract deployment 9 | const EOA_VALIDATOR_ADDRESS = '0x74b9ae28EC45E3FA11533c7954752597C3De3e7A'; 10 | 11 | const SESSION_KEY_VALIDATOR_ADDRESS = 12 | '0x34ca1501FAE231cC2ebc995CE013Dbe882d7d081'; 13 | 14 | const CONTRACT_DEPLOYER_ADDRESS = 15 | '0x0000000000000000000000000000000000008006' as const; 16 | 17 | const AGW_REGISTRY_ADDRESS = 18 | '0xd5E3efDA6bB5aB545cc2358796E96D9033496Dda' as const; 19 | 20 | /** `function addModule(bytes moduleAndData) external` */ 21 | const ADD_MODULE_SELECTOR = '0xd3bdf4b5' as const; 22 | 23 | /** `function createSession(SessionLib.SessionSpec memory sessionSpec) external` */ 24 | const CREATE_SESSION_SELECTOR = '0x5a0694d2' as const; 25 | 26 | /** `function batchCall((address,bool,uint256,bytes)[]) external` */ 27 | const BATCH_CALL_SELECTOR = '0x8f0273a9' as const; 28 | 29 | /** `error INSUFFICIENT_FUNDS()` */ 30 | const INSUFFICIENT_BALANCE_SELECTOR = '0xe7931438' as const; 31 | 32 | const CANONICAL_DELEGATE_REGISTRY_ADDRESS = 33 | '0x0000000059A24EB229eED07Ac44229DB56C5d797'; 34 | 35 | const CANONICAL_EXCLUSIVE_DELEGATE_RESOLVER_ADDRESS = 36 | '0x0000000078CC4Cc1C14E27c0fa35ED6E5E58825D'; 37 | 38 | const AGW_LINK_DELEGATION_RIGHTS = 39 | '0xc10dcfe266c1f71ef476efbd3223555750dc271e4115626b'; 40 | 41 | const NON_EXPIRING_DELEGATION_RIGHTS = `${AGW_LINK_DELEGATION_RIGHTS}000000ffffffffff`; 42 | 43 | const BASE_GAS_PER_PUBDATA_BYTE = 800n; 44 | 45 | const BRIDGEHUB_ADDRESS: Record = { 46 | [abstractTestnet.id]: '0x35A54c8C757806eB6820629bc82d90E056394C92', 47 | [abstract.id]: '0x303a465b659cbb0ab36ee643ea362c509eeb5213', 48 | }; 49 | 50 | const SESSION_KEY_POLICY_REGISTRY_ADDRESS: Address = 51 | '0xfD20b9d7A406e2C4f5D6Df71ABE3Ee48B2EccC9F'; 52 | 53 | const FEATURE_FLAG_REGISTRY_ADDRESS: Address = 54 | '0xb5023a9F3e948e3A4f9DBA97118EEE801fA4e265'; 55 | 56 | export { 57 | ADD_MODULE_SELECTOR, 58 | AGW_LINK_DELEGATION_RIGHTS, 59 | AGW_REGISTRY_ADDRESS, 60 | BASE_GAS_PER_PUBDATA_BYTE, 61 | BATCH_CALL_SELECTOR, 62 | BRIDGEHUB_ADDRESS, 63 | CANONICAL_DELEGATE_REGISTRY_ADDRESS, 64 | CANONICAL_EXCLUSIVE_DELEGATE_RESOLVER_ADDRESS, 65 | CONTRACT_DEPLOYER_ADDRESS, 66 | CREATE_SESSION_SELECTOR, 67 | EOA_VALIDATOR_ADDRESS, 68 | FEATURE_FLAG_REGISTRY_ADDRESS, 69 | INSUFFICIENT_BALANCE_SELECTOR, 70 | NON_EXPIRING_DELEGATION_RIGHTS, 71 | SESSION_KEY_POLICY_REGISTRY_ADDRESS, 72 | SESSION_KEY_VALIDATOR_ADDRESS, 73 | SMART_ACCOUNT_FACTORY_ADDRESS, 74 | }; 75 | -------------------------------------------------------------------------------- /packages/agw-client/src/eip5792.ts: -------------------------------------------------------------------------------- 1 | import type { Address, Hex, RpcTransactionReceipt } from 'viem'; 2 | 3 | enum CallStatus { 4 | Pending = 100, 5 | Confirmed = 200, 6 | OffchainFailure = 400, 7 | Reverted = 500, 8 | PartiallyReverted = 600, 9 | } 10 | 11 | interface PaymasterServiceCapability { 12 | url: string; 13 | } 14 | 15 | interface AtomicCapabilityV1 { 16 | supported: boolean; 17 | } 18 | 19 | interface AtomicCapabilityV2 { 20 | status: 'supported' | 'ready' | 'unsupported'; 21 | } 22 | 23 | interface ChainCapabilitiesV1 { 24 | paymasterService?: PaymasterServiceCapability; 25 | atomicBatch?: AtomicCapabilityV1; 26 | } 27 | 28 | interface ChainCapabilitiesV2 { 29 | paymasterService?: PaymasterServiceCapability; 30 | atomic?: AtomicCapabilityV2; 31 | } 32 | 33 | type WalletCapabilitiesV1 = Record<`0x${string}`, ChainCapabilitiesV1>; 34 | 35 | export type WalletCapabilitiesV2 = Record<`0x${string}`, ChainCapabilitiesV2>; 36 | 37 | export type SendCallsParams = SendCallsParamsV1 | SendCallsParamsV2; 38 | 39 | export interface SendCallsParamsV1 { 40 | version: '1.0'; 41 | from: Address; 42 | calls: { 43 | to?: Address | undefined; 44 | data?: Hex | undefined; 45 | value?: Hex | undefined; 46 | chainId?: Hex | undefined; 47 | }[]; 48 | capabilities?: WalletCapabilitiesV1 | undefined; 49 | } 50 | 51 | export interface SendCallsParamsV2 { 52 | version: '2.0.0'; 53 | id: string; 54 | from: Address; 55 | chainId: Hex; 56 | atomicRequired: boolean; 57 | calls: { 58 | to?: Address | undefined; 59 | data?: Hex | undefined; 60 | value?: Hex | undefined; 61 | }[]; 62 | capabilities: Record; 63 | } 64 | 65 | export const agwCapabilitiesV2: WalletCapabilitiesV2 = { 66 | '0xab5': { 67 | atomic: { 68 | status: 'supported', 69 | }, 70 | }, 71 | '0x2b74': { 72 | atomic: { 73 | status: 'supported', 74 | }, 75 | }, 76 | }; 77 | 78 | export function getReceiptStatus( 79 | receipt: RpcTransactionReceipt | undefined, 80 | ): CallStatus { 81 | switch (receipt?.status) { 82 | case undefined: 83 | return CallStatus.Pending; 84 | case '0x1': 85 | return CallStatus.Confirmed; 86 | case '0x0': 87 | return CallStatus.Reverted; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /packages/agw-client/src/eip712.ts: -------------------------------------------------------------------------------- 1 | import { type ExactPartial, type OneOf } from 'viem'; 2 | import { assertRequest } from 'viem/utils'; 3 | import { 4 | type ChainEIP712, 5 | type SendEip712TransactionParameters, 6 | type ZksyncTransactionRequest, 7 | type ZksyncTransactionSerializable, 8 | } from 'viem/zksync'; 9 | 10 | import { InvalidEip712TransactionError } from './errors/eip712.js'; 11 | 12 | export type AssertEip712RequestParameters = ExactPartial< 13 | SendEip712TransactionParameters 14 | >; 15 | 16 | export function isEIP712Transaction( 17 | transaction: ExactPartial< 18 | OneOf 19 | >, 20 | ) { 21 | if (transaction.type === 'eip712') return true; 22 | if ( 23 | ('customSignature' in transaction && transaction.customSignature) || 24 | ('paymaster' in transaction && transaction.paymaster) || 25 | ('paymasterInput' in transaction && transaction.paymasterInput) || 26 | ('gasPerPubdata' in transaction && 27 | typeof transaction.gasPerPubdata === 'bigint') || 28 | ('factoryDeps' in transaction && transaction.factoryDeps) 29 | ) 30 | return true; 31 | return false; 32 | } 33 | 34 | export function assertEip712Request(args: AssertEip712RequestParameters) { 35 | if (!isEIP712Transaction(args as any)) 36 | throw new InvalidEip712TransactionError(); 37 | assertRequest(args as any); 38 | } 39 | -------------------------------------------------------------------------------- /packages/agw-client/src/errors/account.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from 'viem'; 2 | 3 | export class AccountNotFoundError extends BaseError { 4 | constructor({ docsPath }: { docsPath?: string | undefined } = {}) { 5 | super( 6 | [ 7 | 'Could not find an Account to execute with this Action.', 8 | 'Please provide an Account with the `account` argument on the Action, or by supplying an `account` to the Client.', 9 | ].join('\n'), 10 | { 11 | docsPath, 12 | docsSlug: 'account', 13 | name: 'AccountNotFoundError', 14 | }, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/agw-client/src/errors/eip712.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from 'viem'; 2 | 3 | export class InvalidEip712TransactionError extends BaseError { 4 | constructor() { 5 | super( 6 | [ 7 | 'Transaction is not an EIP712 transaction.', 8 | '', 9 | 'Transaction must:', 10 | ' - include `type: "eip712"`', 11 | ' - include one of the following: `customSignature`, `paymaster`, `paymasterInput`, `gasPerPubdata`, `factoryDeps`', 12 | ].join('\n'), 13 | { name: 'InvalidEip712TransactionError' }, 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/agw-client/src/errors/insufficientBalance.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from 'viem'; 2 | 3 | export class InsufficientBalanceError extends BaseError { 4 | constructor() { 5 | super(['Insufficient balance for transaction.'].join('\n'), { 6 | name: 'InsufficientBalanceError', 7 | }); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/agw-client/src/exports/actions.ts: -------------------------------------------------------------------------------- 1 | export { deployAccount } from '../actions/deployAccount.js'; 2 | export { getLinkedAccounts } from '../actions/getLinkedAccounts.js'; 3 | export { getLinkedAgw } from '../actions/getLinkedAgw.js'; 4 | export { getSessionStatus } from '../actions/getSessionStatus.js'; 5 | export { linkToAgw } from '../actions/linkToAgw.js'; 6 | export { getBatchTransactionObject } from '../actions/sendTransactionBatch.js'; 7 | -------------------------------------------------------------------------------- /packages/agw-client/src/exports/constants.ts: -------------------------------------------------------------------------------- 1 | import AccountFactoryAbi from '../abis/AccountFactory.js'; 2 | import AGWAccountAbi from '../abis/AGWAccount.js'; 3 | import { AGWRegistryAbi } from '../abis/AGWRegistryAbi.js'; 4 | import { BridgeHubAbi } from '../abis/BridgeHubAbi.js'; 5 | import { DelegateRegistryAbi } from '../abis/DelegateRegistry.js'; 6 | import { ExclusiveDelegateResolverAbi } from '../abis/ExclusiveDelegateResolver.js'; 7 | import { ZkSyncAbi } from '../abis/ZkSyncAbi.js'; 8 | import { 9 | AGW_REGISTRY_ADDRESS, 10 | BRIDGEHUB_ADDRESS, 11 | CANONICAL_DELEGATE_REGISTRY_ADDRESS, 12 | CANONICAL_EXCLUSIVE_DELEGATE_RESOLVER_ADDRESS, 13 | EOA_VALIDATOR_ADDRESS, 14 | SESSION_KEY_VALIDATOR_ADDRESS, 15 | SMART_ACCOUNT_FACTORY_ADDRESS, 16 | } from '../constants.js'; 17 | import { SessionKeyValidatorAbi } from './sessions.js'; 18 | 19 | export { 20 | AccountFactoryAbi, 21 | AGWAccountAbi, 22 | AGWRegistryAbi, 23 | AGW_REGISTRY_ADDRESS as agwRegistryAddress, 24 | BridgeHubAbi, 25 | BRIDGEHUB_ADDRESS as bridgehubAddress, 26 | DelegateRegistryAbi, 27 | CANONICAL_DELEGATE_REGISTRY_ADDRESS as delegateRegistryAddress, 28 | ExclusiveDelegateResolverAbi, 29 | CANONICAL_EXCLUSIVE_DELEGATE_RESOLVER_ADDRESS as exclusiveDelegateResolverAddress, 30 | SessionKeyValidatorAbi, 31 | SESSION_KEY_VALIDATOR_ADDRESS as sessionKeyValidatorAddress, 32 | SMART_ACCOUNT_FACTORY_ADDRESS as smartAccountFactoryAddress, 33 | EOA_VALIDATOR_ADDRESS as validatorAddress, 34 | ZkSyncAbi, 35 | }; 36 | -------------------------------------------------------------------------------- /packages/agw-client/src/exports/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | type AbstractClient, 3 | createAbstractClient, 4 | } from '../abstractClient.js'; 5 | export { deployAccount } from '../actions/deployAccount.js'; 6 | export { transformEIP1193Provider } from '../transformEIP1193Provider.js'; 7 | export type { 8 | CustomPaymasterHandler, 9 | CustomPaymasterParameters, 10 | } from '../types/customPaymaster.js'; 11 | export { 12 | getSmartAccountAddressFromInitialSigner, 13 | isAGWAccount, 14 | VALID_CHAINS as validChains, 15 | } from '../utils.js'; 16 | export { 17 | linkablePublicActions, 18 | linkableWalletActions, 19 | } from '../walletActions.js'; 20 | -------------------------------------------------------------------------------- /packages/agw-client/src/exports/sessions.ts: -------------------------------------------------------------------------------- 1 | import { SessionKeyValidatorAbi } from '../abis/SessionKeyValidator.js'; 2 | import { 3 | createSessionClient, 4 | type SessionClient, 5 | toSessionClient, 6 | } from '../sessionClient.js'; 7 | import type { 8 | CallPolicy, 9 | Constraint, 10 | Limit, 11 | SessionConfig, 12 | SessionState, 13 | SessionStatus, 14 | TransferPolicy, 15 | } from '../sessions.js'; 16 | import { 17 | ConstraintCondition, 18 | encodeSession, 19 | getSessionHash, 20 | LimitType, 21 | LimitUnlimited, 22 | LimitZero, 23 | } from '../sessions.js'; 24 | 25 | export { 26 | type CallPolicy, 27 | type Constraint, 28 | ConstraintCondition, 29 | createSessionClient, 30 | encodeSession, 31 | getSessionHash, 32 | type Limit, 33 | LimitType, 34 | LimitUnlimited, 35 | LimitZero, 36 | type SessionClient, 37 | type SessionConfig, 38 | SessionKeyValidatorAbi, 39 | type SessionState, 40 | type SessionStatus, 41 | toSessionClient, 42 | type TransferPolicy, 43 | }; 44 | -------------------------------------------------------------------------------- /packages/agw-client/src/featureFlagRegistry.ts: -------------------------------------------------------------------------------- 1 | import type { Address, PublicClient, Transport } from 'viem'; 2 | import type { ChainEIP712 } from 'viem/chains'; 3 | 4 | import { FeatureFlagRegistryAbi } from './abis/FeatureFlagRegistryAbi.js'; 5 | import { FEATURE_FLAG_REGISTRY_ADDRESS } from './constants.js'; 6 | 7 | export async function isFeatureFlagEnabled( 8 | client: PublicClient, 9 | account: Address, 10 | featureFlag: string, 11 | ) { 12 | try { 13 | const enabled = await client.readContract({ 14 | address: FEATURE_FLAG_REGISTRY_ADDRESS, 15 | abi: FeatureFlagRegistryAbi, 16 | functionName: 'isFeatureFlagEnabled', 17 | args: [featureFlag, account], 18 | }); 19 | 20 | return enabled; 21 | } catch (error) { 22 | // if flag status can not be determined, default to disabled to 23 | // ensure the flow is not blocked 24 | console.error(error); 25 | return false; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/agw-client/src/getAgwTypedSignature.ts: -------------------------------------------------------------------------------- 1 | import type { WalletClient } from 'viem'; 2 | import { 3 | type Account, 4 | type Client, 5 | encodeAbiParameters, 6 | encodeFunctionData, 7 | type Hash, 8 | type Hex, 9 | keccak256, 10 | parseAbiParameters, 11 | serializeErc6492Signature, 12 | toBytes, 13 | type Transport, 14 | zeroAddress, 15 | } from 'viem'; 16 | import { getCode, signTypedData } from 'viem/actions'; 17 | import type { ChainEIP712 } from 'viem/chains'; 18 | 19 | import AccountFactoryAbi from './abis/AccountFactory.js'; 20 | import { 21 | EOA_VALIDATOR_ADDRESS, 22 | SMART_ACCOUNT_FACTORY_ADDRESS, 23 | } from './constants.js'; 24 | import { getInitializerCalldata } from './utils.js'; 25 | 26 | export interface GetAgwTypedSignatureParams { 27 | client: Client; 28 | signer: WalletClient; 29 | messageHash: Hash; 30 | } 31 | 32 | export async function getAgwTypedSignature( 33 | args: GetAgwTypedSignatureParams, 34 | ): Promise { 35 | const { client, signer, messageHash } = args; 36 | const chainId = client.chain.id; 37 | const account = client.account; 38 | 39 | const rawSignature = await signTypedData(signer, { 40 | domain: { 41 | name: 'AbstractGlobalWallet', 42 | version: '1.0.0', 43 | chainId: BigInt(chainId), 44 | verifyingContract: account.address, 45 | }, 46 | types: { 47 | EIP712Domain: [ 48 | { name: 'name', type: 'string' }, 49 | { name: 'version', type: 'string' }, 50 | { name: 'chainId', type: 'uint256' }, 51 | { name: 'verifyingContract', type: 'address' }, 52 | ], 53 | AGWMessage: [{ name: 'signedHash', type: 'bytes32' }], 54 | }, 55 | message: { 56 | signedHash: messageHash, 57 | }, 58 | primaryType: 'AGWMessage', 59 | }); 60 | 61 | const signature = encodeAbiParameters( 62 | parseAbiParameters(['bytes', 'address']), 63 | [rawSignature, EOA_VALIDATOR_ADDRESS], 64 | ); 65 | 66 | const code = await getCode(client, { 67 | address: account.address, 68 | }); 69 | 70 | // if the account is already deployed, we can use signature directly 71 | // otherwise, we provide an ERC-6492 compatible signature 72 | if (code !== undefined) { 73 | return signature; 74 | } 75 | 76 | // Generate the ERC-6492 compatible signature 77 | // https://eips.ethereum.org/EIPS/eip-6492 78 | 79 | // 1. Generate the salt for account deployment 80 | const addressBytes = toBytes(signer.account.address); 81 | const salt = keccak256(addressBytes); 82 | 83 | // 2. Generate the ERC-6492 compatible signature with deploy parameters 84 | return serializeErc6492Signature({ 85 | address: SMART_ACCOUNT_FACTORY_ADDRESS, 86 | data: encodeFunctionData({ 87 | abi: AccountFactoryAbi, 88 | functionName: 'deployAccount', 89 | args: [ 90 | salt, 91 | getInitializerCalldata(signer.account.address, EOA_VALIDATOR_ADDRESS, { 92 | target: zeroAddress, 93 | allowFailure: false, 94 | callData: '0x', 95 | value: 0n, 96 | }), 97 | ], 98 | }), 99 | signature, 100 | }); 101 | } 102 | -------------------------------------------------------------------------------- /packages/agw-client/src/replaceBigInts.ts: -------------------------------------------------------------------------------- 1 | // replaceBigInts courtesy of ponder.sh: 2 | // https://github.com/ponder-sh/ponder/blob/bc65b865898b6145e87031314192c59f9e8b621f/packages/utils/src/replaceBigInts.ts 3 | type _ReplaceBigInts< 4 | arr extends readonly unknown[], 5 | type, 6 | result extends readonly unknown[] = [], 7 | > = arr extends [infer first, ...infer rest] 8 | ? _ReplaceBigInts< 9 | rest, 10 | type, 11 | readonly [...result, first extends bigint ? type : first] 12 | > 13 | : result; 14 | 15 | export type ReplaceBigInts = obj extends bigint 16 | ? type 17 | : obj extends unknown[] 18 | ? _ReplaceBigInts, type> 19 | : obj extends readonly [] 20 | ? _ReplaceBigInts 21 | : obj extends object 22 | ? { [key in keyof obj]: ReplaceBigInts } 23 | : obj; 24 | 25 | export const replaceBigInts = ( 26 | obj: T, 27 | replacer: (x: bigint) => type, 28 | ): ReplaceBigInts => { 29 | if (typeof obj === 'bigint') return replacer(obj) as ReplaceBigInts; 30 | if (Array.isArray(obj)) 31 | return obj.map((x) => replaceBigInts(x, replacer)) as ReplaceBigInts< 32 | T, 33 | type 34 | >; 35 | if (obj && typeof obj === 'object') 36 | return Object.fromEntries( 37 | Object.entries(obj).map(([k, v]) => [k, replaceBigInts(v, replacer)]), 38 | ) as ReplaceBigInts; 39 | return obj as ReplaceBigInts; 40 | }; 41 | -------------------------------------------------------------------------------- /packages/agw-client/src/types/call.ts: -------------------------------------------------------------------------------- 1 | import { type Address, type Hex } from 'viem'; 2 | 3 | export interface Call { 4 | target: Address; 5 | allowFailure: boolean; 6 | value: bigint; 7 | callData: Hex; 8 | } 9 | -------------------------------------------------------------------------------- /packages/agw-client/src/types/customPaymaster.ts: -------------------------------------------------------------------------------- 1 | import { type Address, type Hex } from 'viem'; 2 | 3 | export interface CustomPaymasterParameters { 4 | from: Address; 5 | chainId: number; 6 | nonce?: number | undefined; 7 | to?: Address | undefined; 8 | gas?: bigint | undefined; 9 | gasPrice?: bigint | undefined; 10 | gasPerPubdata?: bigint | undefined; 11 | value?: bigint | undefined; 12 | data?: Hex | undefined; 13 | maxFeePerGas?: bigint | undefined; 14 | maxPriorityFeePerGas?: bigint | undefined; 15 | } 16 | 17 | export interface PaymasterArgs { 18 | paymaster: Address; 19 | paymasterInput: Hex; 20 | } 21 | 22 | export type CustomPaymasterHandler = ( 23 | args: CustomPaymasterParameters, 24 | ) => Promise; 25 | -------------------------------------------------------------------------------- /packages/agw-client/src/types/sendTransactionBatch.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Account, 3 | Address, 4 | Chain, 5 | Hex, 6 | SendTransactionRequest, 7 | } from 'viem'; 8 | import type { SendEip712TransactionParameters } from 'viem/zksync'; 9 | 10 | export interface SendTransactionBatchParameters< 11 | request extends SendTransactionRequest = SendTransactionRequest, 12 | > { 13 | // TODO: figure out if more fields need to be lifted up 14 | calls: SendEip712TransactionParameters[]; 15 | paymaster?: Address | undefined; 16 | paymasterInput?: Hex | undefined; 17 | } 18 | -------------------------------------------------------------------------------- /packages/agw-client/src/types/signTransactionBatch.ts: -------------------------------------------------------------------------------- 1 | import type { GetChainParameter } from 'viem'; 2 | import type { Account, Address, Hex } from 'viem'; 3 | import type { GetAccountParameter } from 'viem/_types/types/account'; 4 | import type { ChainEIP712, SignEip712TransactionParameters } from 'viem/zksync'; 5 | 6 | export type SignTransactionBatchParameters< 7 | chain extends ChainEIP712 | undefined = ChainEIP712 | undefined, 8 | account extends Account | undefined = Account | undefined, 9 | chainOverride extends ChainEIP712 | undefined = ChainEIP712 | undefined, 10 | > = { 11 | calls: SignEip712TransactionParameters[]; 12 | paymaster?: Address | undefined; 13 | paymasterInput?: Hex | undefined; 14 | } & GetAccountParameter & 15 | GetChainParameter; 16 | -------------------------------------------------------------------------------- /packages/agw-client/test/constants.ts: -------------------------------------------------------------------------------- 1 | export const accounts = [ 2 | { 3 | address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', 4 | balance: 10000000000000000000000n, 5 | privateKey: 6 | '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80', 7 | }, 8 | { 9 | address: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', 10 | balance: 10000000000000000000000n, 11 | privateKey: 12 | '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d', 13 | }, 14 | { 15 | address: '0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc', 16 | balance: 10000000000000000000000n, 17 | }, 18 | { 19 | address: '0x90f79bf6eb2c4f870365e785982e1f101e93b906', 20 | balance: 10000000000000000000000n, 21 | }, 22 | { 23 | address: '0x15d34aaf54267db7d7c367839aaf71a00a2c6a65', 24 | balance: 10000000000000000000000n, 25 | }, 26 | { 27 | address: '0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc', 28 | balance: 10000000000000000000000n, 29 | }, 30 | { 31 | address: '0x976ea74026e726554db657fa54763abd0c3a0aa9', 32 | balance: 10000000000000000000000n, 33 | }, 34 | { 35 | address: '0x14dc79964da2c08b23698b3d3cc7ca32193d9955', 36 | balance: 10000000000000000000000n, 37 | }, 38 | { 39 | address: '0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f', 40 | balance: 10000000000000000000000n, 41 | }, 42 | { 43 | address: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720', 44 | balance: 10000000000000000000000n, 45 | privateKey: 46 | '0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6', 47 | }, 48 | ] as const; 49 | 50 | export const address = { 51 | smartAccountAddress: '0x0000000000000000000000000000000000012345', 52 | signerAddress: '0x8888888888888888888888888888888888888888', 53 | sessionSignerAddress: '0x9999999999999999999999999999999999999999', 54 | validatorAddress: '0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E', 55 | randomAddress: '0x2c59721AAdcC643a3C1Ac3bB2dB0b305b30067f2', 56 | } as const; 57 | 58 | export const poolId = 59 | Number(process.env.VITEST_POOL_ID ?? 1) * 60 | Number(process.env.VITEST_SHARD_ID ?? 1) + 61 | (process.env.VITE_NETWORK_TRANSPORT_MODE === 'webSocket' ? 100 : 0); 62 | 63 | export const typedData = { 64 | basic: { 65 | domain: { 66 | name: 'Ether Mail', 67 | version: '1', 68 | chainId: 1, 69 | verifyingContract: '0x0000000000000000000000000000000000000000', 70 | }, 71 | types: { 72 | Person: [ 73 | { name: 'name', type: 'string' }, 74 | { name: 'wallet', type: 'address' }, 75 | ], 76 | Mail: [ 77 | { name: 'from', type: 'Person' }, 78 | { name: 'to', type: 'Person' }, 79 | { name: 'contents', type: 'string' }, 80 | ], 81 | }, 82 | message: { 83 | from: { 84 | name: 'Cow', 85 | wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', 86 | }, 87 | to: { 88 | name: 'Bob', 89 | wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', 90 | }, 91 | contents: 'Hello, Bob!', 92 | }, 93 | }, 94 | complex: { 95 | domain: { 96 | name: 'Ether Mail 🥵', 97 | version: '1.1.1', 98 | chainId: 1, 99 | verifyingContract: '0x0000000000000000000000000000000000000000', 100 | }, 101 | types: { 102 | Name: [ 103 | { name: 'first', type: 'string' }, 104 | { name: 'last', type: 'string' }, 105 | ], 106 | Person: [ 107 | { name: 'name', type: 'Name' }, 108 | { name: 'wallet', type: 'address' }, 109 | { name: 'favoriteColors', type: 'string[3]' }, 110 | { name: 'foo', type: 'uint256' }, 111 | { name: 'age', type: 'uint8' }, 112 | { name: 'isCool', type: 'bool' }, 113 | ], 114 | Mail: [ 115 | { name: 'timestamp', type: 'uint256' }, 116 | { name: 'from', type: 'Person' }, 117 | { name: 'to', type: 'Person' }, 118 | { name: 'contents', type: 'string' }, 119 | { name: 'hash', type: 'bytes' }, 120 | ], 121 | }, 122 | message: { 123 | timestamp: 1234567890n, 124 | contents: 'Hello, Bob! 🖤', 125 | hash: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', 126 | from: { 127 | name: { 128 | first: 'Cow', 129 | last: 'Burns', 130 | }, 131 | wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', 132 | age: 69, 133 | foo: 123123123123123123n, 134 | favoriteColors: ['red', 'green', 'blue'], 135 | isCool: false, 136 | }, 137 | to: { 138 | name: { first: 'Bob', last: 'Builder' }, 139 | wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', 140 | age: 70, 141 | foo: 123123123123123123n, 142 | favoriteColors: ['orange', 'yellow', 'green'], 143 | isCool: true, 144 | }, 145 | }, 146 | }, 147 | } as const; 148 | 149 | export const simpleAccountFactoryAddress = 150 | '0x91E60e0613810449d098b0b5Ec8b51A0FE8c8985' as const; 151 | -------------------------------------------------------------------------------- /packages/agw-client/test/errors.ts: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////// 2 | // Errors 3 | 4 | export type ProviderRpcErrorType = ProviderRpcError & { 5 | name: 'ProviderRpcError'; 6 | }; 7 | export class ProviderRpcError extends Error { 8 | code: number; 9 | details: string; 10 | 11 | constructor(code: number, message: string) { 12 | super(message); 13 | this.code = code; 14 | this.details = message; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/agw-client/test/globalSetup.ts: -------------------------------------------------------------------------------- 1 | import * as executionInstances from './anvil.js'; 2 | 3 | export default async function () { 4 | if (process.env.SKIP_GLOBAL_SETUP) return; 5 | 6 | // Using this proxy, we can parallelize our test suite by spawning multiple "on demand" anvil 7 | // instances and proxying requests to them. Especially for local development, this is much faster 8 | // than running the tests serially. 9 | // 10 | // In vitest, each thread is assigned a unique, numerical id (`process.env.VITEST_POOL_ID`). We 11 | // append this id to the local rpc url (e.g. `http://127.0.0.1:8545/`). 12 | // 13 | // Whenever a request hits the proxy server at this url, it spawns (or reuses) an anvil instance 14 | // at a randomly assigned port and proxies the request to it. The anvil instance is added to a 15 | // [id:port] mapping for future request and is kept alive until the test suite finishes. 16 | // 17 | // Since each thread processes one test file after the other, we don't have to worry about 18 | // non-deterministic behavior caused by multiple tests hitting the same anvil instance concurrently 19 | // as long as we avoid `test.concurrent()`. 20 | // 21 | // We still need to remember to reset the anvil instance between test files. This is generally 22 | // handled in `setup.ts` but may require additional resetting (e.g. via `afterAll`), in case of 23 | // any custom per-test adjustments that persist beyond `anvil_reset`. 24 | const shutdown = await Promise.all([ 25 | ...Object.values(executionInstances).map((instance) => instance.start()), 26 | ]); 27 | return () => Promise.all(shutdown.map((fn) => fn())); 28 | } 29 | -------------------------------------------------------------------------------- /packages/agw-client/test/src/abstractClient.test.ts: -------------------------------------------------------------------------------- 1 | import { toAccount } from 'viem/accounts'; 2 | import { ChainEIP712 } from 'viem/zksync'; 3 | import { beforeEach, describe, expect, it, vi } from 'vitest'; 4 | 5 | import { createAbstractClient } from '../../src/abstractClient.js'; 6 | import { anvilAbstractTestnet } from '../anvil.js'; 7 | import { address } from '../constants.js'; 8 | 9 | const MOCK_SMART_ACCOUNT_ADDRESS = address.smartAccountAddress; 10 | 11 | // Mock the entire viem module 12 | vi.mock('viem', async () => { 13 | const actual = await vi.importActual('viem'); 14 | return { 15 | ...actual, 16 | createClient: vi.fn().mockReturnValue({ 17 | extend: vi.fn().mockReturnValue({ 18 | sendTransaction: vi.fn(), 19 | sendTransactionBatch: vi.fn(), 20 | signTransaction: vi.fn(), 21 | deployContract: vi.fn(), 22 | writeContract: vi.fn(), 23 | prepareAbstractTransactionRequest: vi.fn(), 24 | }), 25 | }), 26 | createPublicClient: vi.fn(), 27 | createWalletClient: vi.fn(), 28 | }; 29 | }); 30 | 31 | import { createClient, createPublicClient, createWalletClient } from 'viem'; 32 | 33 | vi.mock('../../src/utils', () => ({ 34 | getSmartAccountAddressFromInitialSigner: vi 35 | .fn() 36 | .mockResolvedValue('0x0000000000000000000000000000000000012345'), 37 | })); 38 | 39 | import { getSmartAccountAddressFromInitialSigner } from '../../src/utils.js'; 40 | 41 | describe('createAbstractClient', () => { 42 | const signer = toAccount(address.signerAddress); 43 | const mockWalletClient = vi.fn(); 44 | const mockPublicClient = vi.fn(); 45 | 46 | beforeEach(() => { 47 | vi.mocked(createWalletClient).mockReturnValue(mockWalletClient as any); 48 | vi.mocked(createPublicClient).mockReturnValue(mockPublicClient as any); 49 | }); 50 | 51 | const testAbstractClient = (abstractClient: any, expectedTransport: any) => { 52 | expect(createClient).toHaveBeenCalledWith({ 53 | account: expect.objectContaining({ 54 | address: MOCK_SMART_ACCOUNT_ADDRESS, 55 | }), 56 | chain: anvilAbstractTestnet.chain as ChainEIP712, 57 | transport: expectedTransport, 58 | }); 59 | 60 | [ 61 | 'sendTransaction', 62 | 'sendTransactionBatch', 63 | 'signTransaction', 64 | 'deployContract', 65 | 'writeContract', 66 | 'prepareAbstractTransactionRequest', 67 | ].forEach((prop) => { 68 | expect(abstractClient).toHaveProperty(prop); 69 | }); 70 | 71 | expect(getSmartAccountAddressFromInitialSigner).toHaveBeenCalledWith( 72 | address.signerAddress, 73 | mockPublicClient, 74 | ); 75 | }; 76 | 77 | it('throws if no transport is provided', () => { 78 | expect( 79 | createAbstractClient({ 80 | signer, 81 | chain: anvilAbstractTestnet.chain as ChainEIP712, 82 | }), 83 | ).rejects.toThrow(); 84 | }); 85 | 86 | it('creates client with default public transport', async () => { 87 | const mockTransport = vi.fn(); 88 | const abstractClient = await createAbstractClient({ 89 | signer, 90 | chain: anvilAbstractTestnet.chain as ChainEIP712, 91 | transport: mockTransport, 92 | }); 93 | 94 | testAbstractClient(abstractClient, mockTransport); 95 | }); 96 | 97 | it('creates client with custom public transport', async () => { 98 | const mockTransport = vi.fn(); 99 | const mockPublicTransport = vi.fn(); 100 | const abstractClient = await createAbstractClient({ 101 | signer, 102 | chain: anvilAbstractTestnet.chain as ChainEIP712, 103 | transport: mockTransport, 104 | publicTransport: mockPublicTransport, 105 | }); 106 | 107 | testAbstractClient(abstractClient, mockTransport); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /packages/agw-client/test/src/actions/createSession.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | concatHex, 3 | createClient, 4 | createPublicClient, 5 | createWalletClient, 6 | encodeFunctionData, 7 | http, 8 | parseEther, 9 | } from 'viem'; 10 | import { 11 | generatePrivateKey, 12 | privateKeyToAccount, 13 | toAccount, 14 | } from 'viem/accounts'; 15 | import { ChainEIP712 } from 'viem/zksync'; 16 | import { beforeEach, expect, test, vi } from 'vitest'; 17 | 18 | import { SESSION_KEY_VALIDATOR_ADDRESS } from '../../../src/constants.js'; 19 | import { isSmartAccountDeployed } from '../../../src/utils.js'; 20 | import { anvilAbstractTestnet } from '../../anvil.js'; 21 | import { address } from '../../constants.js'; 22 | vi.mock('../../../src/utils.js'); 23 | 24 | import { readContract, writeContract } from 'viem/actions'; 25 | 26 | vi.mock('viem/actions', () => ({ 27 | readContract: vi.fn(), 28 | writeContract: vi.fn(), 29 | })); 30 | 31 | import AGWAccountAbi from '../../../src/abis/AGWAccount.js'; 32 | import { createSession } from '../../../src/actions/createSession.js'; 33 | import { sendTransaction } from '../../../src/actions/sendTransaction.js'; 34 | import { 35 | encodeSession, 36 | LimitType, 37 | SessionConfig, 38 | } from '../../../src/sessions.js'; 39 | 40 | const sessionSigner = privateKeyToAccount(generatePrivateKey()); 41 | 42 | const baseClient = createClient({ 43 | account: address.smartAccountAddress, 44 | chain: anvilAbstractTestnet.chain as ChainEIP712, 45 | transport: anvilAbstractTestnet.clientConfig.transport, 46 | }); 47 | 48 | const signerClient = createWalletClient({ 49 | account: toAccount(address.signerAddress), 50 | chain: anvilAbstractTestnet.chain as ChainEIP712, 51 | transport: http(baseClient.transport.url), 52 | }); 53 | 54 | const publicClient = createPublicClient({ 55 | chain: anvilAbstractTestnet.chain as ChainEIP712, 56 | transport: anvilAbstractTestnet.clientConfig.transport, 57 | }); 58 | 59 | beforeEach(() => { 60 | vi.resetAllMocks(); 61 | }); 62 | 63 | test('should create a session with module already installed', async () => { 64 | vi.mocked(isSmartAccountDeployed).mockResolvedValue(true); 65 | vi.mocked(writeContract).mockResolvedValue('0xmockedTransactionHash'); 66 | vi.mocked(readContract).mockResolvedValue([SESSION_KEY_VALIDATOR_ADDRESS]); 67 | 68 | const session: SessionConfig = { 69 | signer: sessionSigner.address, 70 | expiresAt: 1099511627775n, 71 | callPolicies: [], 72 | transferPolicies: [], 73 | feeLimit: { 74 | limit: parseEther('1'), 75 | limitType: LimitType.Lifetime, 76 | period: 0n, 77 | }, 78 | }; 79 | 80 | const { transactionHash } = await createSession(baseClient, publicClient, { 81 | session, 82 | }); 83 | 84 | expect(transactionHash).toBe('0xmockedTransactionHash'); 85 | }); 86 | 87 | test('should add module and create a session with contract not deployed', async () => { 88 | vi.mocked(isSmartAccountDeployed).mockResolvedValue(false); 89 | vi.mocked(writeContract).mockResolvedValue('0xmockedTransactionHash'); 90 | vi.mocked(readContract).mockResolvedValue([]); 91 | 92 | const session: SessionConfig = { 93 | signer: sessionSigner.address, 94 | expiresAt: 1099511627775n, 95 | callPolicies: [], 96 | transferPolicies: [], 97 | feeLimit: { 98 | limit: parseEther('1'), 99 | limitType: LimitType.Lifetime, 100 | period: 0n, 101 | }, 102 | }; 103 | 104 | const { transactionHash } = await createSession(baseClient, publicClient, { 105 | session, 106 | }); 107 | 108 | expect(transactionHash).toBe('0xmockedTransactionHash'); 109 | 110 | expect(writeContract).toHaveBeenCalledWith(baseClient, { 111 | address: address.smartAccountAddress, 112 | abi: AGWAccountAbi, 113 | functionName: 'addModule', 114 | args: [concatHex([SESSION_KEY_VALIDATOR_ADDRESS, encodeSession(session)])], 115 | }); 116 | }); 117 | 118 | test('should add module and create a session with module not installed', async () => { 119 | vi.mocked(isSmartAccountDeployed).mockResolvedValue(true); 120 | vi.mocked(writeContract).mockResolvedValue('0xmockedTransactionHash'); 121 | vi.mocked(readContract).mockResolvedValue([]); 122 | 123 | const session: SessionConfig = { 124 | signer: sessionSigner.address, 125 | expiresAt: 1099511627775n, 126 | callPolicies: [], 127 | transferPolicies: [], 128 | feeLimit: { 129 | limit: parseEther('1'), 130 | limitType: LimitType.Lifetime, 131 | period: 0n, 132 | }, 133 | }; 134 | 135 | const { transactionHash } = await createSession(baseClient, publicClient, { 136 | session, 137 | }); 138 | 139 | expect(transactionHash).toBe('0xmockedTransactionHash'); 140 | 141 | expect(writeContract).toHaveBeenCalledWith(baseClient, { 142 | address: address.smartAccountAddress, 143 | abi: AGWAccountAbi, 144 | functionName: 'addModule', 145 | 146 | args: [concatHex([SESSION_KEY_VALIDATOR_ADDRESS, encodeSession(session)])], 147 | }); 148 | }); 149 | -------------------------------------------------------------------------------- /packages/agw-client/test/src/actions/deployContract.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createClient, 3 | createPublicClient, 4 | createWalletClient, 5 | http, 6 | } from 'viem'; 7 | import { toAccount } from 'viem/accounts'; 8 | import { ChainEIP712, encodeDeployData } from 'viem/zksync'; 9 | import { expect, test, vi } from 'vitest'; 10 | 11 | import { deployContract } from '../../../src/actions/deployContract.js'; 12 | import { CONTRACT_DEPLOYER_ADDRESS } from '../../../src/constants.js'; 13 | import { anvilAbstractTestnet } from '../../anvil.js'; 14 | import { address } from '../../constants.js'; 15 | 16 | vi.mock('../../../src/actions/sendTransaction', () => ({ 17 | sendTransaction: vi.fn().mockResolvedValue('0xmockedTransactionHash'), 18 | })); 19 | 20 | import { sendTransaction } from '../../../src/actions/sendTransaction.js'; 21 | 22 | const baseClient = createClient({ 23 | account: address.smartAccountAddress, 24 | chain: anvilAbstractTestnet.chain as ChainEIP712, 25 | transport: anvilAbstractTestnet.clientConfig.transport, 26 | }); 27 | 28 | const signerClient = createWalletClient({ 29 | account: toAccount(address.signerAddress), 30 | chain: anvilAbstractTestnet.chain as ChainEIP712, 31 | transport: http(baseClient.transport.url), 32 | }); 33 | 34 | const publicClient = createPublicClient({ 35 | chain: anvilAbstractTestnet.chain as ChainEIP712, 36 | transport: anvilAbstractTestnet.clientConfig.transport, 37 | }); 38 | 39 | const TestTokenABI = [ 40 | { 41 | inputs: [ 42 | { 43 | internalType: 'uint256', 44 | name: 'newVal', 45 | type: 'uint256', 46 | }, 47 | ], 48 | name: 'setValue', 49 | outputs: [], 50 | stateMutability: 'nonpayable', 51 | type: 'function', 52 | }, 53 | ]; 54 | 55 | const MOCK_SALT = 56 | '0x7f1e3b5a9c2d8f4e6a0c2d4f6a8b0c2d4e6f8a0b2c4d6e8f0a2c4e6f8a0b2c4d'; 57 | 58 | const contractBytecode = 59 | '0x0000000100200190000000150000c13d000000000201001900000060022002700000000902200197000000040020008c0000001f0000413d000000000301043b0000000b033001970000000c0030009c0000001f0000c13d000000240020008c0000001f0000413d0000000002000416000000000002004b0000001f0000c13d0000000401100370000000000101043b000000000010041b0000000001000019000000220001042e0000008001000039000000400010043f0000000001000416000000000001004b0000001f0000c13d0000002001000039000001000010044300000120000004430000000a01000041000000220001042e000000000100001900000023000104300000002100000432000000220001042e000000230001043000000000000000000000000000000000000000000000000000000000ffffffff0000000200000000000000000000000000000040000001000000000000000000ffffffff0000000000000000000000000000000000000000000000000000000055241077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bb15b285040315edf518d7c864e5d2c87378a8f1c65f45218de9cd10f3f559ed'; 60 | 61 | test('create2 with factoryDeps', async () => { 62 | vi.mocked(sendTransaction).mockResolvedValue('0xmockedTransactionHash'); 63 | 64 | const expectedData = encodeDeployData({ 65 | abi: TestTokenABI, 66 | args: [], 67 | bytecode: contractBytecode, 68 | deploymentType: 'create2', 69 | salt: MOCK_SALT, 70 | }); 71 | 72 | const transactionHash = await deployContract( 73 | baseClient, 74 | signerClient, 75 | publicClient, 76 | { 77 | abi: TestTokenABI, 78 | account: baseClient.account, 79 | chain: anvilAbstractTestnet.chain as ChainEIP712, 80 | bytecode: contractBytecode, 81 | args: [], 82 | deploymentType: 'create2', 83 | salt: MOCK_SALT, 84 | }, 85 | ); 86 | 87 | expect(transactionHash).toBe('0xmockedTransactionHash'); 88 | 89 | expect(sendTransaction).toHaveBeenCalledWith( 90 | baseClient, 91 | signerClient, 92 | publicClient, 93 | { 94 | account: baseClient.account, 95 | chain: anvilAbstractTestnet.chain as ChainEIP712, 96 | data: expectedData, 97 | factoryDeps: [contractBytecode], 98 | to: CONTRACT_DEPLOYER_ADDRESS, 99 | }, 100 | false, 101 | ); 102 | }); 103 | 104 | test('create2 with no chain and no account', async () => { 105 | vi.mocked(sendTransaction).mockResolvedValue('0xmockedTransactionHash'); 106 | 107 | const expectedData = encodeDeployData({ 108 | abi: TestTokenABI, 109 | args: [], 110 | bytecode: contractBytecode, 111 | deploymentType: 'create2', 112 | salt: MOCK_SALT, 113 | }); 114 | 115 | const transactionHash = await deployContract( 116 | baseClient, 117 | signerClient, 118 | publicClient, 119 | { 120 | abi: TestTokenABI, 121 | bytecode: contractBytecode, 122 | args: [], 123 | deploymentType: 'create2', 124 | salt: MOCK_SALT, 125 | }, 126 | ); 127 | 128 | expect(transactionHash).toBe('0xmockedTransactionHash'); 129 | 130 | expect(sendTransaction).toHaveBeenCalledWith( 131 | baseClient, 132 | signerClient, 133 | publicClient, 134 | { 135 | account: baseClient.account, 136 | chain: anvilAbstractTestnet.chain as ChainEIP712, 137 | data: expectedData, 138 | factoryDeps: [contractBytecode], 139 | to: CONTRACT_DEPLOYER_ADDRESS, 140 | }, 141 | false, 142 | ); 143 | }); 144 | -------------------------------------------------------------------------------- /packages/agw-client/test/src/actions/sendTransaction/sendPrivyTransaction.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createClient, 3 | createPublicClient, 4 | EIP1193RequestFn, 5 | toHex, 6 | } from 'viem'; 7 | import { ChainEIP712 } from 'viem/zksync'; 8 | import { describe, expect, test, vi } from 'vitest'; 9 | 10 | import { sendPrivyTransaction } from '../../../../src/actions/sendPrivyTransaction.js'; 11 | import { anvilAbstractTestnet } from '../../../anvil.js'; 12 | import { address } from '../../../constants.js'; 13 | 14 | const MOCK_TRANSACTION_HASH = 15 | '0x9afe47f3d95eccfc9210851ba5f877f76d372514a26b48bad848a07f77c33b87'; 16 | 17 | const baseClientRequestSpy = vi.fn(async ({ method, params }) => { 18 | if (method === 'privy_sendSmartWalletTx') { 19 | return MOCK_TRANSACTION_HASH; 20 | } 21 | return anvilAbstractTestnet.getClient().request({ method, params } as any); 22 | }); 23 | 24 | const baseClient = createClient({ 25 | account: address.smartAccountAddress, 26 | chain: anvilAbstractTestnet.chain as ChainEIP712, 27 | transport: anvilAbstractTestnet.clientConfig.transport, 28 | }); 29 | 30 | baseClient.request = baseClientRequestSpy as unknown as EIP1193RequestFn; 31 | 32 | const publicClient = createPublicClient({ 33 | chain: anvilAbstractTestnet.chain as ChainEIP712, 34 | transport: anvilAbstractTestnet.clientConfig.transport, 35 | }); 36 | 37 | const transaction = { 38 | to: '0x5432100000000000000000000000000000000000', 39 | from: '0x0000000000000000000000000000000000000000', 40 | data: '0x1234', 41 | paymaster: '0x5407B5040dec3D339A9247f3654E59EEccbb6391', 42 | paymasterInput: '0x', 43 | value: 10000, 44 | gasLimit: 700n, 45 | maxFeePerGas: 900n, 46 | }; 47 | 48 | publicClient.request = (async ({ method, params }) => { 49 | if (method === 'zks_estimateFee') { 50 | return { 51 | gas_limit: '0x156c00', 52 | gas_per_pubdata_limit: '0x143b', 53 | max_fee_per_gas: '0xee6b280', 54 | max_priority_fee_per_gas: '0x0', 55 | }; 56 | } 57 | return anvilAbstractTestnet.getClient().request({ method, params } as any); 58 | }) as EIP1193RequestFn; 59 | 60 | describe('sendPrivyTransaction', () => { 61 | test('sends a transaction correctly', async () => { 62 | const transactionHash = await sendPrivyTransaction(baseClient, { 63 | ...transaction, 64 | type: 'eip712', 65 | account: baseClient.account, 66 | chain: anvilAbstractTestnet.chain as ChainEIP712, 67 | } as any); 68 | 69 | expect(transactionHash).toBe(MOCK_TRANSACTION_HASH); 70 | 71 | expect(baseClientRequestSpy).toHaveBeenCalledWith( 72 | { 73 | method: 'privy_sendSmartWalletTx', 74 | params: [ 75 | { 76 | to: transaction.to, 77 | from: '0x0000000000000000000000000000000000000000', 78 | value: transaction.value, 79 | data: transaction.data, 80 | type: 'eip712', 81 | account: baseClient.account, 82 | chain: anvilAbstractTestnet.chain as ChainEIP712, 83 | gasLimit: toHex(700n), 84 | maxFeePerGas: toHex(900n), 85 | paymaster: '0x5407B5040dec3D339A9247f3654E59EEccbb6391', 86 | paymasterInput: '0x', 87 | }, 88 | ], 89 | }, 90 | { retryCount: 0 }, 91 | ); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /packages/agw-client/test/src/actions/sendTransaction/sendTransaction.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createClient, 3 | createPublicClient, 4 | createWalletClient, 5 | http, 6 | zeroAddress, 7 | } from 'viem'; 8 | import { toAccount } from 'viem/accounts'; 9 | import { ChainEIP712, ZksyncTransactionRequestEIP712 } from 'viem/zksync'; 10 | import { describe, expect, test, vi } from 'vitest'; 11 | 12 | import { sendTransaction } from '../../../../src/actions/sendTransaction.js'; 13 | import { EOA_VALIDATOR_ADDRESS } from '../../../../src/constants.js'; 14 | import { anvilAbstractTestnet } from '../../../anvil.js'; 15 | import { address } from '../../../constants.js'; 16 | 17 | vi.mock('../../../../src/actions/sendTransactionInternal'); 18 | vi.mock('../../../../src/actions/sendPrivyTransaction'); 19 | 20 | import { signPrivyTransaction } from '../../../../src/actions/sendPrivyTransaction.js'; 21 | import { sendTransactionInternal } from '../../../../src/actions/sendTransactionInternal.js'; 22 | 23 | // Client setup 24 | const baseClient = createClient({ 25 | account: address.smartAccountAddress, 26 | chain: anvilAbstractTestnet.chain as ChainEIP712, 27 | transport: anvilAbstractTestnet.clientConfig.transport, 28 | }); 29 | 30 | const signerClient = createWalletClient({ 31 | account: toAccount(address.signerAddress), 32 | chain: anvilAbstractTestnet.chain as ChainEIP712, 33 | transport: http(baseClient.transport.url), 34 | }); 35 | 36 | const publicClient = createPublicClient({ 37 | chain: anvilAbstractTestnet.chain as ChainEIP712, 38 | transport: anvilAbstractTestnet.clientConfig.transport, 39 | }); 40 | 41 | const transaction: ZksyncTransactionRequestEIP712 = { 42 | to: '0x5432100000000000000000000000000000000000', 43 | from: '0x0000000000000000000000000000000000000000', 44 | data: '0x1234', 45 | paymaster: '0x5407B5040dec3D339A9247f3654E59EEccbb6391', 46 | paymasterInput: '0xabc', 47 | }; 48 | 49 | describe('sendTransaction', () => { 50 | test('sendTransaction calls sendTransactionInternal correctly', async () => { 51 | vi.mocked(sendTransactionInternal).mockResolvedValue( 52 | '0xmockedTransactionHash', 53 | ); 54 | const transactionHash = await sendTransaction( 55 | baseClient, 56 | signerClient, 57 | publicClient, 58 | { 59 | ...transaction, 60 | type: 'eip712', 61 | account: baseClient.account, 62 | chain: anvilAbstractTestnet.chain as ChainEIP712, 63 | } as any, 64 | false, 65 | ); 66 | 67 | expect(sendTransactionInternal).toHaveBeenCalledWith( 68 | baseClient, 69 | signerClient, 70 | publicClient, 71 | expect.objectContaining({ 72 | from: zeroAddress, 73 | to: transaction.to, 74 | data: transaction.data, 75 | type: 'eip712', 76 | paymaster: '0x5407B5040dec3D339A9247f3654E59EEccbb6391', 77 | paymasterInput: '0xabc', 78 | account: baseClient.account, 79 | chain: anvilAbstractTestnet.chain as ChainEIP712, 80 | }), 81 | EOA_VALIDATOR_ADDRESS, 82 | {}, 83 | undefined, 84 | ); 85 | expect(transactionHash).toBe('0xmockedTransactionHash'); 86 | }); 87 | 88 | test('sendTransaction calls sendPrivyTransaction correctly', async () => { 89 | vi.mocked(signPrivyTransaction).mockResolvedValue('0x01abab'); 90 | vi.spyOn(publicClient, 'sendRawTransaction').mockResolvedValue('0x01234'); 91 | 92 | const transactionHash = await sendTransaction( 93 | baseClient, 94 | signerClient, 95 | publicClient, 96 | { 97 | ...transaction, 98 | type: 'eip712', 99 | } as any, 100 | true, 101 | ); 102 | 103 | expect(signPrivyTransaction).toHaveBeenCalledWith( 104 | baseClient, 105 | expect.objectContaining(transaction), 106 | ); 107 | expect(publicClient.sendRawTransaction).toHaveBeenCalledWith({ 108 | serializedTransaction: '0x01abab', 109 | }); 110 | expect(transactionHash).toBe('0x01234'); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /packages/agw-client/test/src/actions/signTransactionBatch.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Address, 3 | createClient, 4 | createPublicClient, 5 | createWalletClient, 6 | EIP1193RequestFn, 7 | encodeAbiParameters, 8 | Hex, 9 | http, 10 | parseAbiParameters, 11 | toFunctionSelector, 12 | } from 'viem'; 13 | import { toAccount } from 'viem/accounts'; 14 | import { 15 | ChainEIP712, 16 | parseEip712Transaction, 17 | ZksyncTransactionRequestEIP712, 18 | } from 'viem/zksync'; 19 | import { describe, expect, test, vi } from 'vitest'; 20 | 21 | import { signTransactionBatch } from '../../../src/actions/signTransactionBatch.js'; 22 | import { EOA_VALIDATOR_ADDRESS } from '../../../src/constants.js'; 23 | import { anvilAbstractTestnet } from '../../anvil.js'; 24 | import { address } from '../../constants.js'; 25 | const RAW_SIGNATURE = 26 | '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; 27 | 28 | const publicClient = createPublicClient({ 29 | chain: anvilAbstractTestnet.chain as ChainEIP712, 30 | transport: anvilAbstractTestnet.clientConfig.transport, 31 | }); 32 | 33 | const baseClient = createClient({ 34 | account: address.smartAccountAddress, 35 | chain: anvilAbstractTestnet.chain as ChainEIP712, 36 | transport: anvilAbstractTestnet.clientConfig.transport, 37 | }); 38 | 39 | baseClient.request = (async ({ method, params }) => { 40 | if (method === 'eth_chainId') { 41 | return anvilAbstractTestnet.chain.id; 42 | } else if (method === 'eth_call') { 43 | const callParams = params as { 44 | to: Address; 45 | data: Hex; 46 | }; 47 | if ( 48 | callParams.to === address.smartAccountAddress && 49 | callParams.data.startsWith(toFunctionSelector('function listHooks(bool)')) 50 | ) { 51 | return encodeAbiParameters(parseAbiParameters(['address[]']), [[]]); 52 | } 53 | return encodeAbiParameters(parseAbiParameters(['address[]']), [[]]); 54 | } 55 | return anvilAbstractTestnet.getClient().request({ method, params } as any); 56 | }) as EIP1193RequestFn; 57 | 58 | const signerClient = createWalletClient({ 59 | account: toAccount(address.signerAddress), 60 | chain: anvilAbstractTestnet.chain as ChainEIP712, 61 | transport: http(baseClient.transport.url), 62 | }); 63 | 64 | signerClient.request = (async ({ method, params }) => { 65 | if (method === 'eth_signTypedData_v4') { 66 | return RAW_SIGNATURE; 67 | } 68 | return anvilAbstractTestnet.getClient().request({ method, params } as any); 69 | }) as EIP1193RequestFn; 70 | 71 | describe('signTransactionBatch', () => { 72 | test('signs a batch of EIP-712 transactions correctly', async () => { 73 | const transactions = [ 74 | { 75 | to: '0xAbc1230000000000000000000000000000000000', 76 | value: 1n, 77 | }, 78 | { 79 | to: '0xDEF4560000000000000000000000000000000000', 80 | value: 2n, 81 | data: '0xabababab', 82 | }, 83 | ]; 84 | 85 | const signedBatch = await signTransactionBatch( 86 | baseClient, 87 | signerClient, 88 | publicClient, 89 | { 90 | calls: transactions, 91 | }, 92 | EOA_VALIDATOR_ADDRESS, 93 | ); 94 | 95 | const parsedBatch = parseEip712Transaction(signedBatch); 96 | 97 | expect(parsedBatch.type).toBe('eip712'); 98 | expect(parsedBatch.to).toBe(baseClient.account.address); 99 | expect(parsedBatch.data?.slice(0, 10)).toBe( 100 | toFunctionSelector('function batchCall((address,bool,uint256,bytes)[])'), 101 | ); 102 | expect(parsedBatch.value).toBe(3n); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /packages/agw-client/test/src/actions/writeContractForSession.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createClient, 3 | createPublicClient, 4 | createWalletClient, 5 | encodeFunctionData, 6 | http, 7 | parseEther, 8 | toFunctionSelector, 9 | } from 'viem'; 10 | import { toAccount } from 'viem/accounts'; 11 | import { ChainEIP712 } from 'viem/zksync'; 12 | import { beforeEach, expect, test, vi } from 'vitest'; 13 | 14 | import { anvilAbstractTestnet } from '../../anvil.js'; 15 | import { address } from '../../constants.js'; 16 | 17 | vi.mock('../../../src/utils.js'); 18 | vi.mock('../../../src/actions/sendTransactionForSession', () => ({ 19 | sendTransactionForSession: vi 20 | .fn() 21 | .mockResolvedValue('0xmockedTransactionHash'), 22 | })); 23 | 24 | vi.mock('../../../src/actions/sendTransactionForSession', () => ({ 25 | sendTransactionForSession: vi 26 | .fn() 27 | .mockResolvedValue('0xmockedTransactionHash'), 28 | })); 29 | 30 | import { sendTransactionForSession } from '../../../src/actions/sendTransactionForSession.js'; 31 | import { writeContractForSession } from '../../../src/actions/writeContractForSession.js'; 32 | import { LimitType, SessionConfig } from '../../../src/sessions.js'; 33 | import { LimitZero } from '../../../src/sessions.js'; 34 | import { isSmartAccountDeployed } from '../../../src/utils.js'; 35 | 36 | const baseClient = createClient({ 37 | account: address.smartAccountAddress, 38 | chain: anvilAbstractTestnet.chain as ChainEIP712, 39 | transport: anvilAbstractTestnet.clientConfig.transport, 40 | }); 41 | 42 | const signerClient = createWalletClient({ 43 | account: toAccount(address.sessionSignerAddress), 44 | chain: anvilAbstractTestnet.chain as ChainEIP712, 45 | transport: http(baseClient.transport.url), 46 | }); 47 | 48 | const publicClient = createPublicClient({ 49 | chain: anvilAbstractTestnet.chain as ChainEIP712, 50 | transport: anvilAbstractTestnet.clientConfig.transport, 51 | }); 52 | 53 | const TestTokenABI = [ 54 | { 55 | inputs: [ 56 | { 57 | internalType: 'address', 58 | name: 'to', 59 | type: 'address', 60 | }, 61 | { 62 | internalType: 'uint256', 63 | name: 'qty', 64 | type: 'uint256', 65 | }, 66 | ], 67 | name: 'mint', 68 | outputs: [], 69 | stateMutability: 'nonpayable', 70 | type: 'function', 71 | }, 72 | ]; 73 | 74 | const MOCK_CONTRACT_ADDRESS = '0x742d35Cc6634C0532925a3b844Bc454e4438f44e'; 75 | 76 | const session: SessionConfig = { 77 | signer: signerClient.account.address, 78 | expiresAt: BigInt(Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7), 79 | feeLimit: { 80 | limit: parseEther('1'), 81 | limitType: LimitType.Lifetime, 82 | period: 0n, 83 | }, 84 | callPolicies: [ 85 | { 86 | selector: toFunctionSelector('function mint(address,uint256)'), 87 | target: MOCK_CONTRACT_ADDRESS, 88 | constraints: [], 89 | maxValuePerUse: 0n, 90 | valueLimit: LimitZero, 91 | }, 92 | ], 93 | transferPolicies: [], 94 | }; 95 | 96 | beforeEach(() => { 97 | vi.resetAllMocks(); 98 | }); 99 | 100 | test('writeContractForSession', async () => { 101 | vi.mocked(isSmartAccountDeployed).mockResolvedValue(true); 102 | vi.mocked(sendTransactionForSession).mockResolvedValue( 103 | '0xmockedTransactionHash', 104 | ); 105 | 106 | const expectedData = encodeFunctionData({ 107 | abi: TestTokenABI, 108 | args: [address.signerAddress, 1n], 109 | functionName: 'mint', 110 | }); 111 | 112 | const transactionHash = await writeContractForSession( 113 | baseClient, 114 | signerClient, 115 | publicClient, 116 | { 117 | abi: TestTokenABI, 118 | account: baseClient.account, 119 | address: MOCK_CONTRACT_ADDRESS, 120 | chain: anvilAbstractTestnet.chain as ChainEIP712, 121 | functionName: 'mint', 122 | args: [address.signerAddress, 1n], 123 | }, 124 | session, 125 | ); 126 | 127 | expect(transactionHash).toBe('0xmockedTransactionHash'); 128 | 129 | expect(sendTransactionForSession).toHaveBeenCalledWith( 130 | baseClient, 131 | signerClient, 132 | publicClient, 133 | { 134 | account: baseClient.account, 135 | chain: anvilAbstractTestnet.chain as ChainEIP712, 136 | data: expectedData, 137 | to: MOCK_CONTRACT_ADDRESS, 138 | }, 139 | session, 140 | undefined, 141 | ); 142 | }); 143 | -------------------------------------------------------------------------------- /packages/agw-client/test/src/eip712.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import { assertEip712Request, isEIP712Transaction } from '../../src/eip712.js'; 4 | import { InvalidEip712TransactionError } from '../../src/errors/eip712.js'; 5 | 6 | describe('isEIP712Transaction', () => { 7 | const testCases = [ 8 | { desc: 'type "eip712"', input: { type: 'eip712' }, expected: true }, 9 | { desc: 'type not "eip712"', input: { type: 'eip1559' }, expected: false }, 10 | { 11 | desc: 'customSignature', 12 | input: { customSignature: '0x123' }, 13 | expected: true, 14 | }, 15 | { desc: 'paymaster', input: { paymaster: '0x123' }, expected: true }, 16 | { 17 | desc: 'paymasterInput', 18 | input: { paymasterInput: '0x123' }, 19 | expected: true, 20 | }, 21 | { 22 | desc: 'valid gasPerPubdata', 23 | input: { gasPerPubdata: 123n }, 24 | expected: true, 25 | }, 26 | { 27 | desc: 'invalid gasPerPubdata', 28 | input: { gasPerPubdata: 123 }, 29 | expected: false, 30 | }, 31 | { desc: 'factoryDeps', input: { factoryDeps: [] }, expected: true }, 32 | ]; 33 | 34 | testCases.forEach(({ desc, input, expected }) => { 35 | it(`should return ${expected} for transaction with ${desc}`, () => { 36 | expect(isEIP712Transaction(input as any)).toBe(expected); 37 | }); 38 | }); 39 | }); 40 | 41 | describe('assertEip712Request', () => { 42 | it('should throw if transaction is not EIP712', () => { 43 | expect(() => assertEip712Request({ type: 'eip1559' } as any)).toThrow( 44 | InvalidEip712TransactionError, 45 | ); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/agw-client/test/src/sessionClient.test.ts: -------------------------------------------------------------------------------- 1 | import { toAccount } from 'viem/accounts'; 2 | import { ChainEIP712 } from 'viem/zksync'; 3 | import { beforeEach, describe, expect, it, vi } from 'vitest'; 4 | 5 | import { createAbstractClient } from '../../src/abstractClient.js'; 6 | import { anvilAbstractTestnet } from '../anvil.js'; 7 | import { address } from '../constants.js'; 8 | 9 | // Mock the entire viem module 10 | vi.mock('viem', async () => { 11 | const actual = await vi.importActual('viem'); 12 | return { 13 | ...actual, 14 | createClient: vi.fn().mockReturnValue({ 15 | extend: vi.fn().mockReturnValue({ 16 | sendTransaction: vi.fn(), 17 | sendTransactionBatch: vi.fn(), 18 | signTransaction: vi.fn(), 19 | deployContract: vi.fn(), 20 | writeContract: vi.fn(), 21 | prepareAbstractTransactionRequest: vi.fn(), 22 | }), 23 | }), 24 | createPublicClient: vi.fn(), 25 | createWalletClient: vi.fn(), 26 | }; 27 | }); 28 | 29 | import { 30 | createClient, 31 | createPublicClient, 32 | createWalletClient, 33 | parseEther, 34 | toFunctionSelector, 35 | } from 'viem'; 36 | 37 | vi.mock('../../src/utils', () => ({ 38 | getSmartAccountAddressFromInitialSigner: vi 39 | .fn() 40 | .mockResolvedValue('0x0000000000000000000000000000000000012345'), 41 | })); 42 | 43 | import { toSessionClient } from '../../src/sessionClient.js'; 44 | import { LimitType, LimitZero } from '../../src/sessions.js'; 45 | import { SessionConfig } from '../../src/sessions.js'; 46 | import { getSmartAccountAddressFromInitialSigner } from '../../src/utils.js'; 47 | 48 | const MOCK_CONTRACT_ADDRESS = '0x742d35Cc6634C0532925a3b844Bc454e4438f44e'; 49 | 50 | const session: SessionConfig = { 51 | signer: address.sessionSignerAddress, 52 | expiresAt: BigInt(Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7), 53 | feeLimit: { 54 | limit: parseEther('1'), 55 | limitType: LimitType.Lifetime, 56 | period: 0n, 57 | }, 58 | callPolicies: [ 59 | { 60 | selector: toFunctionSelector('function mint(address,uint256)'), 61 | target: MOCK_CONTRACT_ADDRESS, 62 | constraints: [], 63 | maxValuePerUse: 0n, 64 | valueLimit: LimitZero, 65 | }, 66 | ], 67 | transferPolicies: [], 68 | }; 69 | 70 | describe('createSessionClient', () => { 71 | const signer = toAccount(address.signerAddress); 72 | const sessionSigner = toAccount(address.sessionSignerAddress); 73 | const mockWalletClient = vi.fn(); 74 | const mockPublicClient = vi.fn(); 75 | 76 | beforeEach(() => { 77 | vi.mocked(createWalletClient).mockReturnValue(mockWalletClient as any); 78 | vi.mocked(createPublicClient).mockReturnValue(mockPublicClient as any); 79 | }); 80 | 81 | const testSessionClient = (sessionClient: any, expectedTransport: any) => { 82 | expect(createClient).toHaveBeenCalledWith({ 83 | account: toAccount(address.smartAccountAddress), 84 | chain: anvilAbstractTestnet.chain as ChainEIP712, 85 | transport: expectedTransport, 86 | }); 87 | 88 | ['sendTransaction', 'writeContract'].forEach((prop) => { 89 | expect(sessionClient).toHaveProperty(prop); 90 | }); 91 | 92 | expect(getSmartAccountAddressFromInitialSigner).not.toHaveBeenCalled(); 93 | }; 94 | 95 | it('creates client with custom transport', async () => { 96 | const mockTransport = expect.any(Function); 97 | const mockReadTransport = vi.fn(); 98 | const abstractClient = await createAbstractClient({ 99 | signer, 100 | chain: anvilAbstractTestnet.chain as ChainEIP712, 101 | address: address.smartAccountAddress, 102 | transport: mockTransport, 103 | publicTransport: mockReadTransport, 104 | }); 105 | 106 | const sessionClient = toSessionClient({ 107 | client: abstractClient, 108 | signer: toAccount(address.sessionSignerAddress), 109 | session, 110 | }); 111 | 112 | testSessionClient(sessionClient, mockTransport); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /packages/agw-client/test/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'node:path'; 2 | 3 | import { defineConfig } from 'vitest/config'; 4 | 5 | export default defineConfig({ 6 | test: { 7 | environment: 'node', 8 | include: [join(__dirname, './src/**/*.test.ts')], 9 | globalSetup: [join(__dirname, './globalSetup.ts')], 10 | testTimeout: 60_000, 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /packages/agw-client/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "verbatimModuleSyntax": false, 5 | "allowSyntheticDefaultImports": true, 6 | "outDir": "./dist/cjs", 7 | "module": "CommonJS", 8 | "moduleResolution": "Node", 9 | "removeComments": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/agw-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["src/**/*.ts"], 4 | "exclude": ["src/**/*.test.ts"], 5 | "compilerOptions": { 6 | "sourceMap": true, 7 | "resolveJsonModule": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/agw-react/README.md: -------------------------------------------------------------------------------- 1 | # @abstract-foundation/agw-react 2 | 3 | The `@abstract-foundation/agw-react` package provides React hooks and components to integrate the [Abstract Global Wallet (AGW)](https://docs.abs.xyz/overview) into your React applications. 4 | 5 | ## Abstract Global Wallet (AGW) 6 | 7 | [Abstract Global Wallet (AGW)](https://docs.abs.xyz/overview) is a cross-application [smart contract wallet](https://docs.abs.xyz/how-abstract-works/native-account-abstraction/smart-contract-wallets) that users can use to interact with any application built on Abstract, powered by Abstract's [native account abstraction](https://docs.abs.xyz/how-abstract-works/native-account-abstraction). 8 | 9 | 10 | ## Installation 11 | 12 | Install the React integration package via NPM: 13 | 14 | ```bash 15 | npm install @abstract-foundation/agw-react 16 | ``` 17 | 18 | ## Quick Start 19 | 20 | ### Importing 21 | 22 | ```tsx 23 | import { useAbstractClient, useLoginWithAbstract } from '@abstract-foundation/agw-react'; 24 | ``` 25 | 26 | ### Using `useLoginWithAbstract` Hook 27 | 28 | ```tsx 29 | import React from 'react'; 30 | import { useLoginWithAbstract } from '@abstract-foundation/agw-react'; 31 | 32 | export default function App() { 33 | const { login, logout } = useLoginWithAbstract(); 34 | 35 | return ( 36 |
37 | 38 | 39 |
40 | ); 41 | } 42 | ``` 43 | 44 | ### Using `useAbstractClient` Hook 45 | 46 | ```tsx 47 | import React from 'react'; 48 | import { useAbstractClient } from '@abstract-foundation/agw-react'; 49 | 50 | export default function App() { 51 | const { data: abstractClient, error, isLoading } = useAbstractClient(); 52 | 53 | if (isLoading) return
Loading Abstract Client...
; 54 | if (error) return
Error: {error.message}
; 55 | 56 | // Use the abstractClient instance here 57 | return
Abstract Client is ready!
; 58 | } 59 | ``` 60 | 61 | ## API Reference 62 | 63 | - **`useAbstractClient`**: Hook to create and access an instance of the Abstract Client within your React application. 64 | - **`useLoginWithAbstract`**: Hook for signing in and signing out users with the Abstract Global Wallet. 65 | - **`useGlobalWalletSignerAccount`**: Hook to retrieve the Global Wallet Signer's account information. 66 | - **`useGlobalWalletSignerClient`**: Hook to access the wallet client associated with the Global Wallet Signer's account. 67 | - **`useWriteContractSponsored`**: Hook to perform sponsored contract write operations. 68 | 69 | ## Examples 70 | 71 | ### Sending a Transaction 72 | 73 | Using `@abstract-foundation/agw-react` to send a transaction: 74 | 75 | ```tsx 76 | import React from 'react'; 77 | import { useAbstractClient } from '@abstract-foundation/agw-react'; 78 | 79 | export default function SendTransactionButton() { 80 | const { data: abstractClient } = useAbstractClient(); 81 | 82 | const handleSendTransaction = async () => { 83 | if (!abstractClient) return; 84 | 85 | try { 86 | const txHash = await abstractClient.sendTransaction({ 87 | to: '0xRecipientAddress', 88 | value: 1000000000000000000n, // 1 ETH in wei 89 | }); 90 | console.log('Transaction Hash:', txHash); 91 | } catch (error) { 92 | console.error('Error sending transaction:', error); 93 | } 94 | }; 95 | 96 | return ( 97 | 100 | ); 101 | } 102 | ``` 103 | 104 | ### Using `useWriteContractSponsored` 105 | 106 | ```tsx 107 | import React from 'react'; 108 | import { useWriteContractSponsored } from '@abstract-foundation/agw-react'; 109 | import { Abi } from 'viem'; 110 | 111 | const contractAbi: Abi = [/* Your contract ABI here */]; 112 | 113 | export default function SponsoredContractWrite() { 114 | const { 115 | writeContractSponsored, 116 | data, 117 | error, 118 | isLoading, 119 | isSuccess, 120 | } = useWriteContractSponsored(); 121 | 122 | const handleWriteContract = () => { 123 | writeContractSponsored({ 124 | address: '0xYourContractAddress', 125 | abi: contractAbi, 126 | functionName: 'yourFunctionName', 127 | args: ['arg1', 'arg2'], 128 | }); 129 | }; 130 | 131 | return ( 132 |
133 | 136 | {isSuccess &&
Transaction Hash: {data?.hash}
} 137 | {error &&
Error: {error.message}
} 138 |
139 | ); 140 | } 141 | ``` 142 | 143 | ## Documentation 144 | 145 | For detailed documentation, please refer to the [Abstract Global Wallet Documentation](https://docs.abs.xyz/how-abstract-works/abstract-global-wallet/overview). 146 | -------------------------------------------------------------------------------- /packages/agw-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@abstract-foundation/agw-react", 3 | "description": "Abstract Global Wallet React Components", 4 | "version": "1.8.5", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/abstract-foundation/agw-sdk.git", 9 | "directory": "packages/agw-react" 10 | }, 11 | "scripts": { 12 | "build": "pnpm run clean && pnpm run build:esm+types && pnpm run build:cjs", 13 | "build:esm+types": "tsc --outDir ./dist/esm --declaration --declarationMap --declarationDir ./dist/types && printf '{\"type\":\"module\"}' > ./dist/esm/package.json", 14 | "build:cjs": "tsc -p tsconfig.cjs.json && printf '{\"type\":\"commonjs\"}' > ./dist/cjs/package.json", 15 | "clean": "rm -rf dist tsconfig.tsbuildinfo", 16 | "typecheck": "tsc --noEmit", 17 | "debug": "tsc-watch --sourceMap true --outDir ./dist/esm --declaration --declarationMap --declarationDir ./dist/types", 18 | "test:build": "publint && attw --pack --ignore-rules false-cjs" 19 | }, 20 | "main": "./dist/cjs/exports/index.js", 21 | "module": "./dist/esm/exports/index.js", 22 | "types": "./dist/types/exports/index.d.ts", 23 | "typings": "./dist/types/exports/index.d.ts", 24 | "exports": { 25 | ".": { 26 | "types": "./dist/types/exports/index.d.ts", 27 | "import": "./dist/esm/exports/index.js", 28 | "require": "./dist/cjs/exports/index.js" 29 | }, 30 | "./connectors": { 31 | "types": "./dist/types/exports/connectors.d.ts", 32 | "import": "./dist/esm/exports/connectors.js", 33 | "require": "./dist/cjs/exports/connectors.js" 34 | }, 35 | "./privy": { 36 | "types": "./dist/types/exports/privy.d.ts", 37 | "import": "./dist/esm/exports/privy.js", 38 | "require": "./dist/cjs/exports/privy.js" 39 | }, 40 | "./thirdweb": { 41 | "types": "./dist/types/exports/thirdweb.d.ts", 42 | "import": "./dist/esm/exports/thirdweb.js", 43 | "require": "./dist/cjs/exports/thirdweb.js" 44 | }, 45 | "./package.json": "./package.json" 46 | }, 47 | "typesVersions": { 48 | "*": { 49 | "connectors": [ 50 | "dist/types/exports/connectors.d.ts" 51 | ], 52 | "privy": [ 53 | "dist/types/exports/privy.d.ts" 54 | ], 55 | "thirdweb": [ 56 | "dist/types/exports/thirdweb.d.ts" 57 | ] 58 | } 59 | }, 60 | "files": [ 61 | "dist", 62 | "src", 63 | "package.json" 64 | ], 65 | "dependencies": { 66 | "@abstract-foundation/agw-client": "workspace:*" 67 | }, 68 | "peerDependencies": { 69 | "@privy-io/cross-app-connect": "^0.2.1", 70 | "@privy-io/react-auth": "^2.13.8", 71 | "@tanstack/react-query": "^5", 72 | "react": ">=18", 73 | "secp256k1": ">=5.0.1", 74 | "typescript": ">=5.0.4", 75 | "thirdweb": "^5.68.0", 76 | "viem": "^2.22.23", 77 | "wagmi": "^2.14.11" 78 | }, 79 | "devDependencies": { 80 | "@abstract-foundation/agw-client": "workspace:*", 81 | "@babel/core": "^7.27.4", 82 | "@babel/preset-env": "^7.27.2", 83 | "@privy-io/cross-app-connect": "^0.2.1", 84 | "@privy-io/react-auth": "^2.13.8", 85 | "@rainbow-me/rainbowkit": "^2.2.5", 86 | "@tanstack/query-core": "^5.56.2", 87 | "@types/react": ">=18.3.1", 88 | "@types/react-dom": ">=18.3.0", 89 | "@wagmi/core": "^2.16.4", 90 | "react": ">=18.3.1", 91 | "react-dom": ">=18.3.1", 92 | "thirdweb": "^5.68.0", 93 | "viem": "^2.22.23" 94 | }, 95 | "peerDependenciesMeta": { 96 | "typescript": { 97 | "optional": true 98 | }, 99 | "@rainbow-me/rainbowkit": { 100 | "optional": true 101 | }, 102 | "thirdweb": { 103 | "optional": true 104 | } 105 | }, 106 | "keywords": [ 107 | "eth", 108 | "ethereum", 109 | "smart-account", 110 | "abstract", 111 | "account-abstraction", 112 | "global-wallet", 113 | "wallet", 114 | "web3" 115 | ] 116 | } 117 | -------------------------------------------------------------------------------- /packages/agw-react/src/abstractWallet.ts: -------------------------------------------------------------------------------- 1 | import { type Wallet } from '@rainbow-me/rainbowkit'; 2 | 3 | import { abstractWalletConnector } from './abstractWalletConnector.js'; 4 | import { ICON_URL } from './constants.js'; 5 | 6 | /** 7 | * Create a RainbowKit wallet for Abstract Global Wallet 8 | * 9 | * @example 10 | * import { connectorsForWallets } from "@rainbow-me/rainbowkit"; 11 | * import { abstractWallet } from "@abstract-foundation/agw-react/connectors" 12 | * 13 | * const connectors = connectorsForWallets( 14 | * [ 15 | * { 16 | * groupName: "Abstract", 17 | * wallets: [abstractWallet], 18 | * }, 19 | * ]); 20 | */ 21 | const abstractWallet = (): Wallet => { 22 | return { 23 | id: 'abstract', 24 | name: 'Abstract', 25 | iconUrl: ICON_URL, 26 | iconBackground: '#ffffff', 27 | installed: true, 28 | shortName: 'Abstract', 29 | createConnector: (rkDetails) => 30 | abstractWalletConnector({ 31 | rkDetails, 32 | }), 33 | }; 34 | }; 35 | 36 | export { abstractWallet }; 37 | -------------------------------------------------------------------------------- /packages/agw-react/src/abstractWalletConnector.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type CustomPaymasterHandler, 3 | transformEIP1193Provider, 4 | validChains, 5 | } from '@abstract-foundation/agw-client'; 6 | import { toPrivyWalletConnector } from '@privy-io/cross-app-connect/rainbow-kit'; 7 | import type { WalletDetailsParams } from '@rainbow-me/rainbowkit/dist/wallets/Wallet.js'; 8 | import { type CreateConnectorFn } from '@wagmi/core'; 9 | import { 10 | type EIP1193EventMap, 11 | type EIP1193RequestFn, 12 | type EIP1474Methods, 13 | http, 14 | } from 'viem'; 15 | 16 | import { AGW_APP_ID, ICON_URL } from './constants.js'; 17 | 18 | interface AbstractWalletConnectorOptions { 19 | /** RainbowKit connector details */ 20 | rkDetails: WalletDetailsParams; 21 | /** Optional custom paymaster handler */ 22 | customPaymasterHandler: CustomPaymasterHandler; 23 | } 24 | 25 | /** 26 | * Create a wagmi connector for the Abstract Global Wallet. 27 | * 28 | * Adapted from wagmi injected connector as a reference implementation: 29 | * https://github.com/wevm/wagmi/blob/main/packages/core/src/connectors/injected.ts#L94 30 | * 31 | * @example 32 | * import { createConfig, http } from "wagmi"; 33 | * import { abstract } from "wagmi/chains"; 34 | * import { abstractWalletConnector } from "@abstract-foundation/agw-react/connectors" 35 | * 36 | * export const wagmiConfig = createConfig({ 37 | * chains: [abstract], 38 | * transports: { 39 | * [abstract.id]: http(), 40 | * }, 41 | * connectors: [abstractWalletConnector()], 42 | * ssr: true, 43 | * }); 44 | */ 45 | function abstractWalletConnector( 46 | options: Partial = {}, 47 | ): CreateConnectorFn< 48 | { 49 | on: ( 50 | event: event, 51 | listener: EIP1193EventMap[event], 52 | ) => void; 53 | removeListener: ( 54 | event: event, 55 | listener: EIP1193EventMap[event], 56 | ) => void; 57 | request: EIP1193RequestFn; 58 | }, 59 | Record, 60 | Record 61 | > { 62 | const { rkDetails, customPaymasterHandler } = options; 63 | return (params) => { 64 | const chains = [...params.chains]; 65 | let defaultChain = params.chains[0]; 66 | const validChainIds = Object.keys(validChains) 67 | .map(Number) 68 | .sort(function (a, b) { 69 | return a - b; 70 | }); 71 | for (const chainId of validChainIds) { 72 | const chainIndex = chains.findIndex((chain) => chain.id === chainId); 73 | const hasChain = chainIndex !== -1; 74 | if (hasChain) { 75 | const removedChains = chains.splice(chainIndex, 1); 76 | defaultChain = removedChains[0] ?? defaultChain; 77 | break; 78 | } 79 | } 80 | 81 | const connector = toPrivyWalletConnector({ 82 | iconUrl: ICON_URL, 83 | id: AGW_APP_ID, 84 | name: 'Abstract', 85 | })({ 86 | ...params, 87 | chains: [defaultChain, ...chains], 88 | }); 89 | 90 | const getAbstractProvider = async ( 91 | parameters?: { chainId?: number | undefined } | undefined, 92 | ) => { 93 | const chainId = parameters?.chainId ?? defaultChain.id; 94 | if (!validChains[chainId]) { 95 | throw new Error('Unsupported chain'); 96 | } 97 | const chain = 98 | params.chains.find((c) => c.id === chainId) ?? validChains[chainId]; 99 | 100 | const provider = await connector.getProvider({ 101 | chainId, 102 | }); 103 | 104 | const transport = params.transports?.[chainId] ?? http(); 105 | 106 | return transformEIP1193Provider({ 107 | provider, 108 | chain, 109 | transport, 110 | isPrivyCrossApp: true, 111 | customPaymasterHandler, 112 | }); 113 | }; 114 | 115 | const abstractConnector = { 116 | ...connector, 117 | ...rkDetails, 118 | getProvider: getAbstractProvider, 119 | type: 'injected', 120 | id: 'xyz.abs.privy', 121 | }; 122 | return abstractConnector; 123 | }; 124 | } 125 | 126 | export { abstractWalletConnector }; 127 | -------------------------------------------------------------------------------- /packages/agw-react/src/abstractWalletThirdweb.ts: -------------------------------------------------------------------------------- 1 | import { validChains } from '@abstract-foundation/agw-client'; 2 | import { createEmitter } from '@wagmi/core/internal'; 3 | import { EIP1193, type Wallet } from 'thirdweb/wallets'; 4 | import type { Chain } from 'viem/chains'; 5 | 6 | import { abstractWalletConnector } from './abstractWalletConnector.js'; 7 | 8 | /** 9 | * Create a thirdweb wallet for Abstract Global Wallet 10 | * 11 | * @returns A wallet instance wrapping Abstract Global Wallet to be used with the thirdweb Connect SDK 12 | * 13 | * @example 14 | * ```tsx 15 | * import { createThirdwebClient } from "thirdweb"; 16 | * import { abstractWallet } from "@abstract-foundation/agw-react/thirdweb" 17 | * 18 | * const client = createThirdwebClient({ clientId }); 19 | * 20 | * 21 | * ``` 22 | */ 23 | const abstractWallet = (): Wallet => { 24 | const connector = abstractWalletConnector()({ 25 | chains: Object.values(validChains) as [Chain, ...Chain[]], 26 | emitter: createEmitter('xyz.abs'), 27 | }); 28 | return EIP1193.fromProvider({ 29 | provider: connector.getProvider, 30 | walletId: 'xyz.abs', 31 | }); 32 | }; 33 | 34 | export { abstractWallet }; 35 | -------------------------------------------------------------------------------- /packages/agw-react/src/agwProvider.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | type CustomPaymasterHandler, 3 | validChains, 4 | } from '@abstract-foundation/agw-client'; 5 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; 6 | import React, { useMemo } from 'react'; 7 | import { type Chain, http, type Transport } from 'viem'; 8 | import { createConfig, WagmiProvider } from 'wagmi'; 9 | 10 | import { abstractWalletConnector } from './abstractWalletConnector.js'; 11 | 12 | interface AbstractWalletConfig { 13 | /** 14 | * Determines whether to use abstract testnet 15 | * @type {boolean} 16 | * @default false 17 | */ 18 | chain: Chain; 19 | /** 20 | * Optional transport for the client. 21 | * @type {Transport} 22 | * @default http() 23 | */ 24 | transport?: Transport; 25 | /** 26 | * Optional query client. 27 | * @type {QueryClient} 28 | * @default new QueryClient() 29 | */ 30 | queryClient?: QueryClient; 31 | /** 32 | * Optional custom paymaster handler. 33 | * @type {CustomPaymasterHandler} 34 | */ 35 | customPaymasterHandler?: CustomPaymasterHandler; 36 | } 37 | 38 | /** 39 | * AbstractWalletProvider is a React provider that wraps the WagmiProvider and QueryClientProvider. 40 | * It provides the AbstractWalletContext to its children. 41 | * @example 42 | * ```tsx 43 | * import { AbstractWalletProvider } from '@abstractwallet/agw-react'; 44 | * 45 | * const App = () => { 46 | * // optional configuration overrides 47 | * const transport = http("https://your.abstract.node.example.com/rpc") 48 | * const queryClient = new QueryClient() 49 | * return ( 50 | * 51 | * 52 | * 53 | * ); 54 | * }; 55 | * ``` 56 | * @param {AbstractWalletConfig} config - The configuration for the AbstractWalletProvider. 57 | */ 58 | export const AbstractWalletProvider = ({ 59 | chain, 60 | transport, 61 | queryClient = new QueryClient(), 62 | customPaymasterHandler, 63 | children, 64 | }: React.PropsWithChildren) => { 65 | if (!validChains[chain.id]) { 66 | throw new Error(`Chain ${chain.id} is not supported`); 67 | } 68 | 69 | const wagmiConfig = useMemo(() => { 70 | return createConfig({ 71 | chains: [chain], 72 | ssr: true, 73 | connectors: [ 74 | abstractWalletConnector({ 75 | customPaymasterHandler, 76 | }), 77 | ], 78 | transports: { 79 | [chain.id]: transport ?? http(), 80 | }, 81 | multiInjectedProviderDiscovery: false, 82 | }); 83 | }, [chain, transport, customPaymasterHandler]); 84 | 85 | return ( 86 | 87 | {children} 88 | 89 | ); 90 | }; 91 | -------------------------------------------------------------------------------- /packages/agw-react/src/constants.ts: -------------------------------------------------------------------------------- 1 | const AGW_APP_ID = 'cm04asygd041fmry9zmcyn5o5'; 2 | const ICON_URL = 'https://abstract-assets.abs.xyz/icons/light.png'; 3 | 4 | export { AGW_APP_ID, ICON_URL }; 5 | -------------------------------------------------------------------------------- /packages/agw-react/src/exports/connectors.ts: -------------------------------------------------------------------------------- 1 | export { abstractWallet } from '../abstractWallet.js'; 2 | export { abstractWalletConnector } from '../abstractWalletConnector.js'; 3 | -------------------------------------------------------------------------------- /packages/agw-react/src/exports/index.ts: -------------------------------------------------------------------------------- 1 | export { AbstractWalletProvider } from '../agwProvider.js'; 2 | export { useAbstractClient } from '../hooks/useAbstractClient.js'; 3 | export { useCreateSession } from '../hooks/useCreateSession.js'; 4 | export { useGlobalWalletSignerAccount } from '../hooks/useGlobalWalletSignerAccount.js'; 5 | export { useGlobalWalletSignerClient } from '../hooks/useGlobalWalletSignerClient.js'; 6 | export { useLoginWithAbstract } from '../hooks/useLoginWithAbstract.js'; 7 | export { useRevokeSessions } from '../hooks/useRevokeSessions.js'; 8 | export { useWriteContractSponsored } from '../hooks/useWriteContractSponsored.js'; 9 | -------------------------------------------------------------------------------- /packages/agw-react/src/exports/privy.ts: -------------------------------------------------------------------------------- 1 | export { 2 | AbstractPrivyProvider, 3 | agwAppLoginMethod, 4 | } from '../privy/abstractPrivyProvider.js'; 5 | export { InjectWagmiConnector } from '../privy/injectWagmiConnector.js'; 6 | export { useAbstractPrivyLogin } from '../privy/useAbstractPrivyLogin.js'; 7 | export { usePrivyCrossAppProvider } from '../privy/usePrivyCrossAppProvider.js'; 8 | -------------------------------------------------------------------------------- /packages/agw-react/src/exports/thirdweb.ts: -------------------------------------------------------------------------------- 1 | export { abstractWallet } from '../abstractWalletThirdweb.js'; 2 | -------------------------------------------------------------------------------- /packages/agw-react/src/hooks/useAbstractClient.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createAbstractClient, 3 | type CustomPaymasterHandler, 4 | } from '@abstract-foundation/agw-client'; 5 | import { useQuery } from '@tanstack/react-query'; 6 | import { custom, useChains, useConfig } from 'wagmi'; 7 | 8 | import { useGlobalWalletSignerClient } from './useGlobalWalletSignerClient.js'; 9 | 10 | export interface UseAbstractClientOptions { 11 | customPaymasterHandler: CustomPaymasterHandler; 12 | } 13 | 14 | /** 15 | * React hook that provides access to the Abstract Global Wallet client 16 | * used by the `AbstractWalletProvider` component. 17 | * 18 | * Use this client to perform operations such as sending transactions, deploying contracts, 19 | * and interacting with smart contracts from the connected Abstract Global Wallet. 20 | * 21 | * @param options - Optional client configuration 22 | * @param options.customPaymasterHandler - Optional paymaster handler for custom gas sponsorship logic 23 | * @returns Query result containing the Abstract client when successfully created 24 | * 25 | * @example 26 | * ```tsx 27 | * import { useAbstractClient } from "@abstract-foundation/agw-react"; 28 | * 29 | * function SendTransactionComponent() { 30 | * const { data: client, isLoading } = useAbstractClient(); 31 | * 32 | * const handleSendTransaction = async () => { 33 | * if (!client) return; 34 | * 35 | * const hash = await client.sendTransaction({ 36 | * to: '0x8e729E23CDc8bC21c37a73DA4bA9ebdddA3C8B6d', 37 | * data: '0x69', 38 | * }); 39 | * console.log('Transaction sent:', hash); 40 | * }; 41 | * 42 | * return ( 43 | * 46 | * ); 47 | * } 48 | * ``` 49 | * 50 | * Read more: [Abstract docs: useAbstractClient](https://docs.abs.xyz/abstract-global-wallet/agw-react/hooks/useAbstractClient) 51 | * 52 | * @see {@link createAbstractClient} - The underlying client creation function 53 | */ 54 | export const useAbstractClient = ({ 55 | customPaymasterHandler, 56 | }: Partial = {}) => { 57 | const { data: signer, status, error } = useGlobalWalletSignerClient(); 58 | const [chain] = useChains(); 59 | const config = useConfig(); 60 | 61 | return useQuery({ 62 | gcTime: 0, 63 | queryKey: ['abstractClient'], 64 | queryFn: async () => { 65 | if (error) { 66 | throw error; 67 | } 68 | if (!signer) { 69 | throw new Error('No signer found'); 70 | } 71 | 72 | const client = createAbstractClient({ 73 | signer: signer.account, 74 | chain, 75 | transport: custom(signer.transport), 76 | isPrivyCrossApp: true, 77 | publicTransport: config?._internal.transports[chain.id], 78 | customPaymasterHandler, 79 | }); 80 | 81 | return client; 82 | }, 83 | enabled: status !== 'pending', 84 | }); 85 | }; 86 | -------------------------------------------------------------------------------- /packages/agw-react/src/hooks/useGlobalWalletSignerAccount.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Config, 3 | type ResolvedRegister, 4 | useAccount, 5 | type UseAccountParameters, 6 | type UseAccountReturnType, 7 | } from 'wagmi'; 8 | 9 | /** 10 | * React hook to get the approved signer of the connected Abstract Global Wallet. 11 | * 12 | * This hook retrieves the account (EOA) that is approved to sign transactions 13 | * for the Abstract Global Wallet smart contract. It's useful when you need to access 14 | * the underlying EOA that signs transactions for the Abstract wallet, i.e. the Privy signer. 15 | * 16 | * Under the hood, this hook uses wagmi's `useAccount` and extracts the second address 17 | * (index 1) from the addresses array, which corresponds to the approved signer account. 18 | * 19 | * Note: If you need to get the address of the AGW smart contract itself (not the underlying approved signer), 20 | * you should use the standard `useAccount` hook from wagmi instead. 21 | * 22 | * @param parameters - Parameters to pass to the underlying wagmi useAccount hook 23 | * @returns Standard wagmi account object with the address set to the approved signer address 24 | * 25 | * @example 26 | * ```tsx 27 | * import { useGlobalWalletSignerAccount } from "@abstract-foundation/agw-react"; 28 | * 29 | * export default function App() { 30 | * const { address, status, isConnected } = useGlobalWalletSignerAccount(); 31 | * 32 | * if (status === "disconnected") { 33 | * return
Disconnected
; 34 | * } 35 | * 36 | * if (status === "connecting" || status === "reconnecting") { 37 | * return
Connecting...
; 38 | * } 39 | * 40 | * return ( 41 | *
42 | *

Connected Signer EOA: {address}

43 | *

Connection Status: {status}

44 | *
45 | * ); 46 | * } 47 | * ``` 48 | * 49 | * Read more: [Abstract docs: useGlobalWalletSignerAccount](https://docs.abs.xyz/abstract-global-wallet/agw-react/hooks/useGlobalWalletSignerAccount) 50 | * 51 | * @see {@link useAccount} - The underlying wagmi hook 52 | */ 53 | export function useGlobalWalletSignerAccount< 54 | config extends Config = ResolvedRegister['config'], 55 | >(parameters: UseAccountParameters = {}): UseAccountReturnType { 56 | const account = useAccount(parameters); 57 | 58 | if (!account.addresses?.[1]) { 59 | return { 60 | address: undefined, 61 | addresses: undefined, 62 | chain: undefined, 63 | chainId: undefined, 64 | connector: undefined, 65 | isConnected: false, 66 | isReconnecting: false, 67 | isConnecting: false, 68 | isDisconnected: true, 69 | status: 'disconnected', 70 | }; 71 | } 72 | 73 | return { 74 | ...account, 75 | address: account.addresses[1], 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /packages/agw-react/src/hooks/useGlobalWalletSignerClient.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Config, 3 | type ResolvedRegister, 4 | useWalletClient, 5 | type UseWalletClientParameters, 6 | type UseWalletClientReturnType, 7 | } from 'wagmi'; 8 | import type { GetWalletClientData } from 'wagmi/query'; 9 | 10 | import { useGlobalWalletSignerAccount } from './useGlobalWalletSignerAccount.js'; 11 | 12 | /** 13 | * React hook to get a wallet client instance of the approved signer of the connected Abstract Global Wallet. 14 | * 15 | * This hook returns a wallet client instance that can perform actions from the underlying EOA 16 | * (externally owned account) approved to sign transactions for the Abstract Global Wallet smart contract. 17 | * 18 | * Important: This hook is different from `useAbstractClient`, which performs actions from the 19 | * Abstract Global Wallet smart contract itself. This hook accesses the underlying EOA signer. 20 | * 21 | * Under the hood, it uses wagmi's `useWalletClient` hook, setting the account to the EOA 22 | * retrieved from `useGlobalWalletSignerAccount`. 23 | * 24 | * @param parameters - Optional parameters to pass to the underlying wagmi useWalletClient hook 25 | * @returns A query result containing the wallet client when successfully created 26 | * 27 | * @example 28 | * ```tsx 29 | * import { useGlobalWalletSignerClient } from "@abstract-foundation/agw-react"; 30 | * 31 | * function SignerComponent() { 32 | * const { data: client, isLoading, error } = useGlobalWalletSignerClient(); 33 | * 34 | * // Send a transaction directly from the EOA signer 35 | * const handleSendTransaction = async () => { 36 | * if (!client) return; 37 | * 38 | * try { 39 | * const hash = await client.sendTransaction({ 40 | * to: '0x8e729E23CDc8bC21c37a73DA4bA9ebdddA3C8B6d', 41 | * data: '0x69', 42 | * value: BigInt(0) 43 | * }); 44 | * console.log('Transaction sent:', hash); 45 | * } catch (err) { 46 | * console.error('Transaction failed:', err); 47 | * } 48 | * }; 49 | * 50 | * if (isLoading) return
Loading signer...
; 51 | * if (error) return
Error: {error.message}
; 52 | * if (!client) return
No signer available
; 53 | * 54 | * return ( 55 | *
56 | * 57 | *
58 | * ); 59 | * } 60 | * ``` 61 | * 62 | * Read more: [Abstract docs: useGlobalWalletSignerClient](https://docs.abs.xyz/abstract-global-wallet/agw-react/hooks/useGlobalWalletSignerClient) 63 | * 64 | * @see {@link useWalletClient} - The underlying wagmi hook 65 | * @see {@link useGlobalWalletSignerAccount} - Hook to get the approved signer account 66 | * @see {@link useAbstractClient} - Hook to get a client for the Abstract Global Wallet smart contract 67 | */ 68 | export function useGlobalWalletSignerClient< 69 | config extends Config = ResolvedRegister['config'], 70 | chainId extends 71 | config['chains'][number]['id'] = config['chains'][number]['id'], 72 | selectData = GetWalletClientData, 73 | >( 74 | parameters: UseWalletClientParameters = {}, 75 | ): UseWalletClientReturnType { 76 | const { address } = useGlobalWalletSignerAccount(); 77 | 78 | const walletClient = useWalletClient({ 79 | ...parameters, 80 | account: address, 81 | }); 82 | 83 | return walletClient; 84 | } 85 | -------------------------------------------------------------------------------- /packages/agw-react/src/hooks/useLoginWithAbstract.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | import { useConnect, useDisconnect } from 'wagmi'; 3 | 4 | interface AbstractLogin { 5 | login: () => void; 6 | logout: () => void; 7 | } 8 | 9 | /** 10 | * React hook for signing in and signing out users with Abstract Global Wallet. 11 | * 12 | * This hook provides utility functions to prompt users to sign up or sign into your 13 | * application using Abstract Global Wallet, and to sign out once connected. 14 | * 15 | * Under the hood, it uses the following wagmi hooks: 16 | * - `login`: Uses wagmi's `useConnect` hook to connect to the Abstract connector 17 | * - `logout`: Uses wagmi's `useDisconnect` hook to disconnect the user 18 | * 19 | * @returns An object containing login and logout functions 20 | * @returns {function} login - Opens the signup/login modal for Abstract Global Wallet 21 | * @returns {function} logout - Disconnects the user's wallet from the application 22 | * 23 | * @example 24 | * ```tsx 25 | * import { useLoginWithAbstract } from "@abstract-foundation/agw-react"; 26 | * import { useAccount } from "wagmi"; 27 | * 28 | * export default function LoginComponent() { 29 | * const { login, logout } = useLoginWithAbstract(); 30 | * const { isConnected } = useAccount(); 31 | * 32 | * return ( 33 | *
34 | * {isConnected ? ( 35 | * 38 | * ) : ( 39 | * 42 | * )} 43 | *
44 | * ); 45 | * } 46 | * ``` 47 | * 48 | * If the user doesn't have an Abstract Global Wallet, they will be prompted to create one. 49 | * If they already have a wallet, they'll be prompted to use it to sign in. 50 | * 51 | * Read more: [Abstract docs: useLoginWithAbstract](https://docs.abs.xyz/abstract-global-wallet/agw-react/hooks/useLoginWithAbstract) 52 | * 53 | * @see {@link useConnect} - The underlying wagmi connect hook 54 | * @see {@link useDisconnect} - The underlying wagmi disconnect hook 55 | */ 56 | export const useLoginWithAbstract = (): AbstractLogin => { 57 | const { connect, connectors } = useConnect(); 58 | const { disconnect } = useDisconnect(); 59 | 60 | const login = useCallback(() => { 61 | const connector = connectors.find((c) => c.id === 'xyz.abs.privy'); 62 | if (!connector) { 63 | throw new Error('Abstract connector not found'); 64 | } 65 | connect({ connector }); 66 | }, [connect, connectors]); 67 | 68 | const logout = useCallback(() => { 69 | disconnect(); 70 | }, [disconnect]); 71 | 72 | return { 73 | login, 74 | logout, 75 | }; 76 | }; 77 | -------------------------------------------------------------------------------- /packages/agw-react/src/hooks/useRevokeSessions.ts: -------------------------------------------------------------------------------- 1 | import { sessionKeyValidatorAddress } from '@abstract-foundation/agw-client/constants'; 2 | import { 3 | getSessionHash, 4 | type SessionConfig, 5 | SessionKeyValidatorAbi, 6 | } from '@abstract-foundation/agw-client/sessions'; 7 | import type { WriteContractParameters } from '@wagmi/core'; 8 | import type { Address, Hash, Hex } from 'viem'; 9 | import { useWriteContract } from 'wagmi'; 10 | 11 | export type RevokeSessionsArgs = { 12 | sessions: SessionConfig | Hash | (SessionConfig | Hash)[]; 13 | paymaster?: Address; 14 | paymasterData?: Hex; 15 | } & Omit; 16 | 17 | /** 18 | * React hook for revoking session keys from an Abstract Global Wallet. 19 | * 20 | * Use this hook to revoke session keys, preventing them from being able to execute 21 | * any further transactions on behalf of the wallet. 22 | * 23 | * Under the hood, it uses wagmi's `useWriteContract` hook to call the `revokeKeys` 24 | * function on the SessionKeyValidator contract. 25 | * 26 | * @returns An object containing functions and state for revoking sessions: 27 | * - `revokeSessions`: Function to revoke session keys 28 | * - `revokeSessionsAsync`: Promise-based function to revoke session keys 29 | * - `isPending`: Boolean indicating if a revocation is in progress 30 | * - `isError`: Boolean indicating if the revocation resulted in an error 31 | * - `error`: Error object if the revocation failed 32 | * - Other standard wagmi useWriteContract properties 33 | * 34 | * @example 35 | * ```tsx 36 | * import { useRevokeSessions } from "@abstract-foundation/agw-react"; 37 | * import { getSessionHash } from "@abstract-foundation/agw-client/sessions"; 38 | * import type { SessionConfig } from "@abstract-foundation/agw-client/sessions"; 39 | * 40 | * export default function RevokeSessionExample() { 41 | * const { 42 | * revokeSessionsAsync, 43 | * revokeSessions, 44 | * isPending, 45 | * isError, 46 | * error 47 | * } = useRevokeSessions(); 48 | * 49 | * // A session configuration stored in your application 50 | * const mySessionConfig: SessionConfig = { 51 | * // Your session configuration 52 | * }; 53 | * 54 | * async function handleRevokeSession() { 55 | * try { 56 | * // Revoke a single session using its configuration 57 | * await revokeSessionsAsync({ 58 | * sessions: mySessionConfig, 59 | * }); 60 | * 61 | * // Or revoke a session using its hash 62 | * await revokeSessionsAsync({ 63 | * sessions: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", 64 | * }); 65 | * 66 | * // Or revoke multiple sessions at once 67 | * await revokeSessionsAsync({ 68 | * sessions: [ 69 | * mySessionConfig, 70 | * "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", 71 | * anotherSessionConfig 72 | * ], 73 | * }); 74 | * 75 | * console.log("Sessions revoked successfully"); 76 | * } catch (err) { 77 | * console.error("Failed to revoke sessions:", err); 78 | * } 79 | * } 80 | * 81 | * return ( 82 | *
83 | * 89 | * 90 | * {isError && ( 91 | *
92 | * Error: {error?.message} 93 | *
94 | * )} 95 | *
96 | * ); 97 | * } 98 | * ``` 99 | * 100 | * Read more: [Abstract docs: useRevokeSessions](https://docs.abs.xyz/abstract-global-wallet/agw-react/hooks/useRevokeSessions) 101 | * 102 | * @see {@link useWriteContract} - The underlying wagmi hook 103 | * @see {@link SessionKeyValidatorAbi} - The ABI for the session validator contract 104 | * @see {@link getSessionHash} - Function to compute the hash of a session configuration 105 | */ 106 | export const useRevokeSessions = () => { 107 | const { writeContract, writeContractAsync, ...writeContractRest } = 108 | useWriteContract(); 109 | const getSessionHashes = ( 110 | sessions: SessionConfig | Hash | (SessionConfig | Hash)[], 111 | ): Hash[] => { 112 | return typeof sessions === 'string' 113 | ? [sessions as Hash] 114 | : Array.isArray(sessions) 115 | ? sessions.map((session) => 116 | typeof session === 'string' ? session : getSessionHash(session), 117 | ) 118 | : [getSessionHash(sessions)]; 119 | }; 120 | 121 | return { 122 | revokeSessions: (params: RevokeSessionsArgs) => { 123 | const { sessions, ...rest } = params; 124 | const sessionHashes = getSessionHashes(sessions); 125 | writeContract({ 126 | address: sessionKeyValidatorAddress, 127 | abi: SessionKeyValidatorAbi, 128 | functionName: 'revokeKeys', 129 | args: [sessionHashes], 130 | ...(rest as any), 131 | }); 132 | }, 133 | revokeSessionsAsync: async (params: RevokeSessionsArgs) => { 134 | const { sessions, ...rest } = params; 135 | const sessionHashes = getSessionHashes(sessions); 136 | await writeContractAsync({ 137 | address: sessionKeyValidatorAddress, 138 | abi: SessionKeyValidatorAbi, 139 | functionName: 'revokeKeys', 140 | args: [sessionHashes], 141 | ...(rest as any), 142 | }); 143 | }, 144 | ...writeContractRest, 145 | }; 146 | }; 147 | -------------------------------------------------------------------------------- /packages/agw-react/src/privy/abstractPrivyProvider.tsx: -------------------------------------------------------------------------------- 1 | import { validChains } from '@abstract-foundation/agw-client'; 2 | import { 3 | type LoginMethodOrderOption, 4 | PrivyProvider, 5 | type PrivyProviderProps, 6 | } from '@privy-io/react-auth'; 7 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; 8 | import React from 'react'; 9 | import { type Chain, type Transport } from 'viem'; 10 | import { createConfig, http, WagmiProvider } from 'wagmi'; 11 | 12 | import { AGW_APP_ID } from '../constants.js'; 13 | import { InjectWagmiConnector } from './injectWagmiConnector.js'; 14 | 15 | export const agwAppLoginMethod: LoginMethodOrderOption = `privy:${AGW_APP_ID}`; 16 | 17 | /** 18 | * Configuration options for the AbstractPrivyProvider. 19 | * @interface AgwPrivyProviderProps 20 | * @extends PrivyProviderProps 21 | * @property {boolean} testnet - Whether to use abstract testnet, defaults to false. 22 | * @property {Transport} transport - Optional transport to use, defaults to standard http. 23 | * @property {QueryClient} queryClient - Optional query client to use, defaults to a standard query client. 24 | */ 25 | interface AgwPrivyProviderProps extends PrivyProviderProps { 26 | chain: Chain; 27 | transport?: Transport; 28 | queryClient?: QueryClient; 29 | } 30 | 31 | /** 32 | * Provider component that integrates Abstract Global Wallet with Privy authentication. 33 | * 34 | * This component wraps your application with the necessary providers to use Abstract Global Wallet 35 | * with Privy authentication, including: 36 | * - PrivyProvider: Handles user authentication and EOA creation 37 | * - WagmiProvider: Provides web3 functionality 38 | * - QueryClientProvider: Manages data fetching with TanStack Query 39 | * - InjectWagmiConnector: Injects the Abstract wallet into Wagmi 40 | * 41 | * @param props - Props for the AbstractPrivyProvider component 42 | * @param props.chain - The blockchain network to connect to (must be supported by Abstract) 43 | * @param props.transport - Optional transport for network requests (defaults to http) 44 | * @param props.queryClient - Optional TanStack Query client (defaults to a new QueryClient) 45 | * @param props.appId - Your Privy app ID (required) 46 | * @param props.config - Optional Privy configuration (defaults to using Abstract as primary login) 47 | * @returns A provider component that wraps your application 48 | * 49 | * @example 50 | * ```tsx 51 | * import { AbstractPrivyProvider } from "@abstract-foundation/agw-react/privy"; 52 | * import { abstract } from "viem/chains"; 53 | * 54 | * function App() { 55 | * return ( 56 | * 60 | * 61 | * 62 | * ); 63 | * } 64 | * ``` 65 | * 66 | * Once your app is wrapped with this provider, you can use all the Abstract and Wagmi hooks 67 | * throughout your application to interact with blockchain and manage user authentication. 68 | * 69 | * @see {@link useAbstractPrivyLogin} - Hook to login users with Abstract Global Wallet via Privy 70 | * @see {@link useAbstractClient} - Hook to get an Abstract client for blockchain interactions 71 | */ 72 | export const AbstractPrivyProvider = ({ 73 | chain, 74 | transport, 75 | queryClient = new QueryClient(), 76 | ...props 77 | }: AgwPrivyProviderProps) => { 78 | if (!validChains[chain.id]) { 79 | throw new Error(`Chain ${chain.id} is not supported`); 80 | } 81 | 82 | const wagmiConfig = createConfig({ 83 | chains: [chain], 84 | ssr: true, 85 | connectors: [], 86 | transports: { 87 | [chain.id]: transport ?? http(), 88 | }, 89 | multiInjectedProviderDiscovery: false, 90 | }); 91 | 92 | // if no login methods and order are provided, set the default login method to the privy app login method 93 | if (!props.config) { 94 | props.config = { 95 | loginMethodsAndOrder: { 96 | primary: [agwAppLoginMethod], 97 | }, 98 | }; 99 | } else if (!props.config.loginMethodsAndOrder) { 100 | props.config.loginMethodsAndOrder = { 101 | primary: [agwAppLoginMethod], 102 | }; 103 | } 104 | return ( 105 | 106 | 107 | 108 | 109 | {props.children} 110 | 111 | 112 | 113 | 114 | ); 115 | }; 116 | -------------------------------------------------------------------------------- /packages/agw-react/src/privy/injectWagmiConnector.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, useEffect, useState } from 'react'; 2 | import React from 'react'; 3 | import type { Chain, EIP1193Provider, Transport } from 'viem'; 4 | import { useConfig, useReconnect } from 'wagmi'; 5 | import { injected } from 'wagmi/connectors'; 6 | 7 | import { usePrivyCrossAppProvider } from './usePrivyCrossAppProvider.js'; 8 | 9 | interface InjectWagmiConnectorProps extends React.PropsWithChildren { 10 | /** 11 | * The chain to connect to. 12 | * @type {Chain} 13 | */ 14 | chain: Chain; 15 | /** 16 | * Optional transport configuration for the provider. 17 | * @type {Transport} 18 | * @optional 19 | */ 20 | transport?: Transport; 21 | } 22 | 23 | /** 24 | * InjectWagmiConnector is a React component that injects the Abstract Wallet provider into Wagmi's connector system. 25 | * It handles the setup and reconnection of the wallet provider when ready. 26 | * 27 | * @example 28 | * ```tsx 29 | * import { InjectWagmiConnector } from '@abstractwallet/agw-react'; 30 | * 31 | * const App = () => { 32 | * return ( 33 | * 34 | * 35 | * 36 | * ); 37 | * }; 38 | * ``` 39 | * 40 | * @param {InjectWagmiConnectorProps} props - The component props 41 | * @param {Chain} props.chain - The blockchain network to connect to 42 | * @param {Transport} [props.transport] - Optional transport configuration for the provider 43 | * @param {React.ReactNode} props.children - Child components to render 44 | */ 45 | 46 | export const InjectWagmiConnector = (props: InjectWagmiConnectorProps) => { 47 | const { chain, transport, children } = props; 48 | 49 | const config = useConfig(); 50 | const { reconnect } = useReconnect(); 51 | const { provider, ready } = usePrivyCrossAppProvider({ chain, transport }); 52 | const [isSetup, setIsSetup] = useState(false); 53 | 54 | useEffect(() => { 55 | const setup = async (provider: EIP1193Provider) => { 56 | config.storage?.removeItem('xyz.abs.privy.disconnected'); 57 | const wagmiConnector = injected({ 58 | target: { 59 | provider, 60 | id: 'xyz.abs.privy', 61 | name: 'Abstract Global Wallet', 62 | icon: '', 63 | }, 64 | }); 65 | 66 | const connector = config._internal.connectors.setup(wagmiConnector); 67 | await config.storage?.setItem('recentConnectorId', 'xyz.abs.privy'); 68 | config._internal.connectors.setState([connector]); 69 | 70 | return connector; 71 | }; 72 | 73 | if (ready && (!isSetup || config.connectors.length === 0)) { 74 | setup(provider).then((connector) => { 75 | if (connector) { 76 | reconnect({ connectors: [connector] }); 77 | setIsSetup(true); 78 | } 79 | }); 80 | } 81 | }, [provider, ready, isSetup, config, reconnect]); 82 | 83 | return {children}; 84 | }; 85 | -------------------------------------------------------------------------------- /packages/agw-react/src/privy/useAbstractPrivyLogin.tsx: -------------------------------------------------------------------------------- 1 | import { useCrossAppAccounts } from '@privy-io/react-auth'; 2 | 3 | import { AGW_APP_ID } from '../constants.js'; 4 | 5 | /** 6 | * React hook for authenticating users with Abstract Global Wallet via Privy. 7 | * 8 | * This hook provides utility functions to prompt users to sign in with Abstract Global Wallet 9 | * or link their existing authenticated Privy account to an Abstract Global Wallet. 10 | * 11 | * Under the hood, it uses Privy's `useCrossAppAccounts` hook to connect to Abstract Global Wallet's 12 | * authentication system. 13 | * 14 | * @returns An object containing login and linking functions 15 | * @returns {function} login - Prompts users to authenticate with their Abstract Global Wallet 16 | * @returns {function} link - Allows authenticated users to link their account to an Abstract Global Wallet 17 | * 18 | * @example 19 | * ```tsx 20 | * import { useAbstractPrivyLogin } from "@abstract-foundation/agw-react/privy"; 21 | * 22 | * function LoginButton() { 23 | * const { login, link } = useAbstractPrivyLogin(); 24 | * 25 | * return ( 26 | *
27 | * 30 | * 31 | * 34 | *
35 | * ); 36 | * } 37 | * ``` 38 | * 39 | * The `login` function uses Privy's `loginWithCrossAppAccount` to authenticate users with 40 | * their Abstract Global Wallet account. 41 | * 42 | * The `link` function uses Privy's `linkCrossAppAccount` to allow already authenticated 43 | * users to link their existing account to an Abstract Global Wallet. 44 | * 45 | * @see {@link useCrossAppAccounts} - The underlying Privy hook 46 | * @see {@link AbstractPrivyProvider} - Provider component required to use this hook 47 | */ 48 | export const useAbstractPrivyLogin = () => { 49 | const { loginWithCrossAppAccount, linkCrossAppAccount } = 50 | useCrossAppAccounts(); 51 | 52 | return { 53 | login: () => loginWithCrossAppAccount({ appId: AGW_APP_ID }), 54 | link: () => linkCrossAppAccount({ appId: AGW_APP_ID }), 55 | }; 56 | }; 57 | -------------------------------------------------------------------------------- /packages/agw-react/src/query/writeContractSponsored.ts: -------------------------------------------------------------------------------- 1 | import type { MutateOptions, MutationOptions } from '@tanstack/query-core'; 2 | import { writeContract } from '@wagmi/core'; 3 | import type { 4 | Abi, 5 | Address, 6 | ContractFunctionArgs, 7 | ContractFunctionName, 8 | Hex, 9 | WriteContractErrorType, 10 | } from 'viem'; 11 | import { type Config } from 'wagmi'; 12 | import { 13 | type WriteContractData, 14 | type WriteContractVariables, 15 | } from 'wagmi/query'; 16 | 17 | export function writeContractSponsoredMutationOptions( 18 | config: config, 19 | ) { 20 | return { 21 | mutationFn(variables) { 22 | return writeContract(config, variables); 23 | }, 24 | mutationKey: ['writeContract'], 25 | } as const satisfies MutationOptions< 26 | WriteContractData, 27 | WriteContractErrorType, 28 | WriteContractSponsoredVariables< 29 | Abi, 30 | string, 31 | readonly unknown[], 32 | config, 33 | config['chains'][number]['id'] 34 | > 35 | >; 36 | } 37 | 38 | export type WriteContractSponsoredVariables< 39 | abi extends Abi | readonly unknown[], 40 | functionName extends ContractFunctionName, 41 | args extends ContractFunctionArgs< 42 | abi, 43 | 'nonpayable' | 'payable', 44 | functionName 45 | >, 46 | config extends Config, 47 | chainId extends config['chains'][number]['id'], 48 | /// 49 | allFunctionNames = ContractFunctionName, 50 | > = WriteContractVariables< 51 | abi, 52 | functionName, 53 | args, 54 | config, 55 | chainId, 56 | allFunctionNames 57 | > & { 58 | paymaster: Address; 59 | paymasterInput: Hex; 60 | }; 61 | 62 | export type WriteContractSponsoredMutate< 63 | config extends Config, 64 | context = unknown, 65 | > = < 66 | const abi extends Abi | readonly unknown[], 67 | functionName extends ContractFunctionName, 68 | args extends ContractFunctionArgs< 69 | abi, 70 | 'nonpayable' | 'payable', 71 | functionName 72 | >, 73 | chainId extends config['chains'][number]['id'], 74 | >( 75 | variables: WriteContractSponsoredVariables< 76 | abi, 77 | functionName, 78 | args, 79 | config, 80 | chainId 81 | >, 82 | options?: 83 | | MutateOptions< 84 | WriteContractData, 85 | WriteContractErrorType, 86 | WriteContractSponsoredVariables< 87 | abi, 88 | functionName, 89 | args, 90 | config, 91 | chainId, 92 | // use `functionName` to make sure it's not union of all possible function names 93 | functionName 94 | > & { 95 | paymaster: Address; 96 | paymasterInput: Hex; 97 | }, 98 | context 99 | > 100 | | undefined, 101 | ) => void; 102 | 103 | export type WriteContractSponsoredMutateAsync< 104 | config extends Config, 105 | context = unknown, 106 | > = < 107 | const abi extends Abi | readonly unknown[], 108 | functionName extends ContractFunctionName, 109 | args extends ContractFunctionArgs< 110 | abi, 111 | 'nonpayable' | 'payable', 112 | functionName 113 | >, 114 | chainId extends config['chains'][number]['id'], 115 | >( 116 | variables: WriteContractSponsoredVariables< 117 | abi, 118 | functionName, 119 | args, 120 | config, 121 | chainId 122 | >, 123 | options?: 124 | | MutateOptions< 125 | WriteContractData, 126 | WriteContractErrorType, 127 | WriteContractSponsoredVariables< 128 | Abi, 129 | string, 130 | readonly unknown[], 131 | config, 132 | config['chains'][number]['id'] 133 | >, 134 | context 135 | > 136 | | undefined, 137 | ) => Promise; 138 | -------------------------------------------------------------------------------- /packages/agw-react/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "verbatimModuleSyntax": false, 5 | "allowSyntheticDefaultImports": true, 6 | "outDir": "./dist/cjs", 7 | "module": "CommonJS", 8 | "moduleResolution": "Node", 9 | "removeComments": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/agw-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["src/**/*.ts", "src/**/*.tsx"], 4 | "exclude": ["src/**/*.test.ts"], 5 | "compilerOptions": { 6 | "sourceMap": true, 7 | "allowSyntheticDefaultImports": true, 8 | "resolveJsonModule": true, 9 | "jsx": "react" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/agw-web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@abstract-foundation/agw-web", 3 | "description": "Abstract Global Wallet for the web (generic EIP-6963 provider)", 4 | "version": "1.8.5", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/abstract-foundation/agw-sdk.git", 9 | "directory": "packages/agw-web" 10 | }, 11 | "scripts": { 12 | "build": "pnpm run clean && pnpm run build:esm+types && pnpm run build:cjs", 13 | "build:esm+types": "tsc --outDir ./dist/esm --declaration --declarationMap --declarationDir ./dist/types && printf '{\"type\":\"module\"}' > ./dist/esm/package.json", 14 | "build:cjs": "tsc -p tsconfig.cjs.json && printf '{\"type\":\"commonjs\"}' > ./dist/cjs/package.json", 15 | "clean": "rm -rf dist tsconfig.tsbuildinfo", 16 | "typecheck": "tsc --noEmit", 17 | "debug": "tsc-watch --sourceMap true --outDir ./dist/esm --declaration --declarationMap --declarationDir ./dist/types", 18 | "test:build": "publint && attw --pack --ignore-rules false-cjs" 19 | }, 20 | "main": "./dist/cjs/exports/index.js", 21 | "module": "./dist/esm/exports/index.js", 22 | "types": "./dist/types/exports/index.d.ts", 23 | "typings": "./dist/types/exports/index.d.ts", 24 | "exports": { 25 | ".": { 26 | "types": "./dist/types/exports/index.d.ts", 27 | "import": "./dist/esm/exports/index.js", 28 | "require": "./dist/cjs/exports/index.js" 29 | }, 30 | "./mainnet": { 31 | "types": "./dist/types/exports/mainnet.d.ts", 32 | "import": "./dist/esm/exports/mainnet.js", 33 | "require": "./dist/cjs/exports/mainnet.js" 34 | }, 35 | "./testnet": { 36 | "types": "./dist/types/exports/testnet.d.ts", 37 | "import": "./dist/esm/exports/testnet.js", 38 | "require": "./dist/cjs/exports/testnet.js" 39 | } 40 | }, 41 | "typesVersions": { 42 | "*": { 43 | "mainnet": [ 44 | "./dist/types/exports/mainnet.d.ts" 45 | ], 46 | "testnet": [ 47 | "./dist/types/exports/testnet.d.ts" 48 | ] 49 | } 50 | }, 51 | "files": [ 52 | "dist", 53 | "src", 54 | "package.json" 55 | ], 56 | "dependencies": { 57 | "@abstract-foundation/agw-client": "workspace:*" 58 | }, 59 | "peerDependencies": { 60 | "@privy-io/cross-app-connect": "^0.2.1", 61 | "viem": "^2.22.23" 62 | }, 63 | "devDependencies": { 64 | "@abstract-foundation/agw-client": "workspace:*", 65 | "@privy-io/cross-app-connect": "^0.2.1", 66 | "viem": "^2.22.23" 67 | }, 68 | "peerDependenciesMeta": { 69 | "typescript": { 70 | "optional": true 71 | }, 72 | "@rainbow-me/rainbowkit": { 73 | "optional": true 74 | } 75 | }, 76 | "keywords": [ 77 | "eth", 78 | "ethereum", 79 | "smart-account", 80 | "abstract", 81 | "account-abstraction", 82 | "global-wallet", 83 | "wallet", 84 | "web3" 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /packages/agw-web/src/eip6963emitter.ts: -------------------------------------------------------------------------------- 1 | import { transformEIP1193Provider } from '@abstract-foundation/agw-client'; 2 | import { toPrivyWalletProvider } from '@privy-io/cross-app-connect'; 3 | import { type EIP1193Provider, http } from 'viem'; 4 | 5 | import abstractIcon from './abstract-icon.js'; 6 | import type { 7 | AnnounceProviderParameters, 8 | AnnounceProviderReturnType, 9 | EIP6963ProviderDetail, 10 | EIP6963ProviderInfo, 11 | } from './types.js'; 12 | 13 | class EIP6963AnnounceProviderEvent extends CustomEvent { 14 | constructor(detail: EIP6963ProviderDetail) { 15 | super('eip6963:announceProvider', { detail }); 16 | } 17 | } 18 | 19 | const eip6963info: EIP6963ProviderInfo = { 20 | uuid: '2306fd26-fcfb-4f9e-87da-0d1e237e917c', 21 | name: 'Abstract Global Wallet', 22 | icon: abstractIcon, 23 | rdns: 'xyz.abs.privy', 24 | }; 25 | 26 | export function announceProvider({ 27 | chain, 28 | transport, 29 | customPaymasterHandler, 30 | }: AnnounceProviderParameters): AnnounceProviderReturnType { 31 | if (typeof window === 'undefined') { 32 | return () => void 0; 33 | } 34 | 35 | const privyProvider = toPrivyWalletProvider({ 36 | chainId: chain.id, 37 | providerAppId: 'cm04asygd041fmry9zmcyn5o5', 38 | chains: [chain], 39 | transports: { 40 | [chain.id]: transport ?? http(), 41 | }, 42 | }) as EIP1193Provider; 43 | 44 | const abstractProvider = transformEIP1193Provider({ 45 | provider: privyProvider, 46 | chain, 47 | transport: transport ?? http(), 48 | isPrivyCrossApp: true, 49 | customPaymasterHandler, 50 | }); 51 | 52 | const event = new EIP6963AnnounceProviderEvent({ 53 | info: eip6963info, 54 | provider: abstractProvider, 55 | }); 56 | 57 | window.dispatchEvent(event); 58 | 59 | const handler = () => window.dispatchEvent(event); 60 | window.addEventListener('eip6963:requestProvider', handler); 61 | return () => window.removeEventListener('eip6963:requestProvider', handler); 62 | } 63 | -------------------------------------------------------------------------------- /packages/agw-web/src/exports/index.ts: -------------------------------------------------------------------------------- 1 | import { announceProvider } from '../eip6963emitter.js'; 2 | 3 | export { announceProvider as initAbstractGlobalWallet }; 4 | export type { 5 | AnnounceProviderParameters, 6 | AnnounceProviderReturnType, 7 | } from '../types.js'; 8 | -------------------------------------------------------------------------------- /packages/agw-web/src/exports/mainnet.ts: -------------------------------------------------------------------------------- 1 | import { abstract } from 'viem/chains'; 2 | 3 | import { announceProvider } from '../eip6963emitter.js'; 4 | 5 | announceProvider({ 6 | chain: abstract, 7 | }); 8 | -------------------------------------------------------------------------------- /packages/agw-web/src/exports/testnet.ts: -------------------------------------------------------------------------------- 1 | import { abstractTestnet } from 'viem/chains'; 2 | 3 | import { announceProvider } from '../eip6963emitter.js'; 4 | 5 | announceProvider({ 6 | chain: abstractTestnet, 7 | }); 8 | -------------------------------------------------------------------------------- /packages/agw-web/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { CustomPaymasterHandler } from '@abstract-foundation/agw-client'; 2 | import type { Chain, EIP1193Provider, Transport } from 'viem'; 3 | 4 | export interface EIP6963ProviderInfo { 5 | uuid: string; 6 | name: string; 7 | icon: string; 8 | rdns: string; 9 | } 10 | 11 | export interface EIP6963ProviderDetail { 12 | info: EIP6963ProviderInfo; 13 | provider: EIP1193Provider; 14 | } 15 | 16 | export interface AnnounceProviderParameters { 17 | chain: Chain; 18 | transport?: Transport; 19 | customPaymasterHandler?: CustomPaymasterHandler; 20 | } 21 | 22 | export type AnnounceProviderReturnType = () => void; 23 | -------------------------------------------------------------------------------- /packages/agw-web/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "verbatimModuleSyntax": false, 5 | "allowSyntheticDefaultImports": true, 6 | "outDir": "./dist/cjs", 7 | "module": "CommonJS", 8 | "moduleResolution": "Node", 9 | "removeComments": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/agw-web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["src/**/*.ts", "src/**/*.tsx"], 4 | "exclude": ["src/**/*.test.ts"], 5 | "compilerOptions": { 6 | "sourceMap": true, 7 | "allowSyntheticDefaultImports": true, 8 | "resolveJsonModule": true, 9 | "jsx": "react" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/web3-react-agw/README.md: -------------------------------------------------------------------------------- 1 | # @abstract-foundation/web3-react-agw 2 | 3 | The `@abstract-foundation/web3-react-agw` package implements a [web3-react](https://github.com/Uniswap/web3-react) connector for [Abstract Global Wallet (AGW)](https://docs.abs.xyz/overview). 4 | 5 | ## Abstract Global Wallet (AGW) 6 | 7 | [Abstract Global Wallet (AGW)](https://docs.abs.xyz/overview) is a cross-application [smart contract wallet](https://docs.abs.xyz/how-abstract-works/native-account-abstraction/smart-contract-wallets) that users can use to interact with any application built on Abstract, powered by Abstract's [native account abstraction](https://docs.abs.xyz/how-abstract-works/native-account-abstraction). 8 | 9 | ## Installation 10 | 11 | Install the connector via NPM: 12 | 13 | ```bash 14 | npm install @abstract-foundation/web3-react-agw 15 | ``` 16 | 17 | ## Quick Start 18 | 19 | ### Importing 20 | 21 | ```tsx 22 | import { AbstractGlobalWallet } from '@abstract-foundation/web3-react-agw'; 23 | ``` 24 | 25 | ### Initializing the connector 26 | 27 | ```tsx 28 | // connector.tsx 29 | import { initializeConnector } from '@web3-react/core'; 30 | import { AbstractGlobalWallet } from '@abstract-foundation/web3-react-agw'; 31 | 32 | export const [agw, hooks] = initializeConnector( 33 | (actions) => new AbstractGlobalWallet({ actions }), 34 | ); 35 | ``` 36 | 37 | ### Using the connector 38 | 39 | ```tsx 40 | import React from 'react'; 41 | import { agw, hooks } from './connector'; 42 | 43 | const { useIsActive } = hooks; 44 | 45 | export default function App() { 46 | const isActive = useIsActive(); 47 | useEffect(() => { 48 | void agw.connectEagerly().catch(() => { 49 | console.debug('Failed to connect eagerly to agw'); 50 | }); 51 | }, []); 52 | 53 | const login = () => { 54 | void agw.activate(); 55 | }; 56 | 57 | const logout = () => { 58 | void agw.deactivate(); 59 | }; 60 | 61 | return ( 62 |
63 | {isActive ? ( 64 | 65 | ) : ( 66 | 67 | )} 68 |
69 | ); 70 | } 71 | ``` 72 | 73 | ## API Reference 74 | 75 | ### `AbstractGlobalWallet` 76 | 77 | Creates an `AbstractGlobalWallet` connector, extending the web3-react `Connector` to support the Abstract Global Wallet. 78 | 79 | ## Documentation 80 | 81 | For detailed documentation, please refer to the [Abstract Global Wallet Documentation](https://docs.abs.xyz/how-abstract-works/abstract-global-wallet/overview). 82 | -------------------------------------------------------------------------------- /packages/web3-react-agw/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@abstract-foundation/web3-react-agw", 3 | "description": "Abstract Global Wallet for web3-react", 4 | "version": "1.7.5", 5 | "scripts": { 6 | "build": "pnpm run clean && pnpm run build:esm+types && pnpm run build:cjs", 7 | "build:esm+types": "tsc --outDir ./dist/esm --declaration --declarationMap --declarationDir ./dist/types && printf '{\"type\":\"module\"}' > ./dist/esm/package.json", 8 | "build:cjs": "tsc -p tsconfig.cjs.json && printf '{\"type\":\"commonjs\"}' > ./dist/cjs/package.json", 9 | "clean": "rm -rf dist tsconfig.tsbuildinfo", 10 | "typecheck": "tsc --noEmit", 11 | "debug": "tsc-watch --sourceMap true --outDir ./dist/esm --declaration --declarationMap --declarationDir ./dist/types" 12 | }, 13 | "keywords": [ 14 | "eth", 15 | "ethereum", 16 | "smart-account", 17 | "abstract", 18 | "account-abstraction", 19 | "global-wallet", 20 | "wallet", 21 | "web3", 22 | "web3-react" 23 | ], 24 | "license": "MIT", 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/abstract-foundation/agw-sdk.git", 28 | "directory": "packages/web3-react-agw" 29 | }, 30 | "main": "./dist/cjs/index.js", 31 | "module": "./dist/esm/index.js", 32 | "types": "./dist/types/index.d.ts", 33 | "typings": "./dist/types/index.d.ts", 34 | "exports": { 35 | ".": { 36 | "types": "./dist/types/index.d.ts", 37 | "import": "./dist/esm/index.js", 38 | "require": "./dist/cjs/index.js" 39 | } 40 | }, 41 | "files": [ 42 | "dist", 43 | "src", 44 | "package.json" 45 | ], 46 | "dependencies": { 47 | "@abstract-foundation/agw-client": "workspace:^", 48 | "@privy-io/cross-app-connect": "^0.2.1", 49 | "@web3-react/types": "^8.2.3" 50 | }, 51 | "devDependencies": { 52 | "@tanstack/react-query": "^5.80.3", 53 | "react-dom": "^18", 54 | "@types/node": "^22.5.5", 55 | "@web3-react/core": "^8.2.3", 56 | "@web3-react/store": "^8.2.3", 57 | "viem": "^2.22.23", 58 | "wagmi": "^2.14.11" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/web3-react-agw/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "verbatimModuleSyntax": false, 5 | "allowSyntheticDefaultImports": true, 6 | "outDir": "./dist/cjs", 7 | "module": "CommonJS", 8 | "moduleResolution": "Node", 9 | "removeComments": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/web3-react-agw/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["src/**/*.ts", "src/**/*.tsx"], 4 | "exclude": ["src/**/*.test.ts"], 5 | "compilerOptions": { 6 | "sourceMap": true, 7 | "allowSyntheticDefaultImports": true, 8 | "resolveJsonModule": true, 9 | "jsx": "react" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | // This tsconfig file contains the shared config for the build (tsconfig.build.json) and type checking (tsconfig.json) config. 3 | "include": [], 4 | "compilerOptions": { 5 | // Incremental builds 6 | // NOTE: Enabling incremental builds speeds up `tsc`. Keep in mind though that it does not reliably bust the cache when the `tsconfig.json` file changes. 7 | "incremental": true, 8 | 9 | // Type checking 10 | "strict": true, 11 | "noFallthroughCasesInSwitch": true, // Not enabled by default in `strict` mode. 12 | "noImplicitOverride": true, // Not enabled by default in `strict` mode. 13 | "noImplicitReturns": true, // Not enabled by default in `strict` mode. 14 | "noUncheckedIndexedAccess": true, 15 | "noUnusedLocals": true, // Not enabled by default in `strict` mode. 16 | "noUnusedParameters": true, // Not enabled by default in `strict` mode. 17 | "useDefineForClassFields": true, // Not enabled by default in `strict` mode unless we bump `target` to ES2022. 18 | "useUnknownInCatchVariables": true, 19 | 20 | // JavaScript support 21 | "allowJs": false, 22 | "checkJs": false, 23 | 24 | // Interop constraints 25 | "forceConsistentCasingInFileNames": true, 26 | "verbatimModuleSyntax": true, 27 | 28 | // Language and environment 29 | "moduleResolution": "Node", 30 | "module": "ES2020", 31 | "target": "ES2021", // Setting this to `ES2021` enables native support for `Node v16+`: https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping. 32 | "lib": [ 33 | "ES2022", 34 | "DOM" // We are adding `DOM` here to get the `fetch`, etc. types. This should be removed once these types are available via DefinitelyTyped. 35 | ], 36 | 37 | // Skip type checking for node modules 38 | "skipLibCheck": true, 39 | "noErrorTruncation": true 40 | } 41 | } 42 | --------------------------------------------------------------------------------