├── .github ├── CODEOWNERS ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_report.md ├── pull_request_template.md └── workflows │ ├── lints.yml │ ├── secret_scanner.yml │ └── tests.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── cross-chain ├── .gitignore ├── L1-governance │ ├── .env.example │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── contracts │ │ └── Governance.sol │ ├── hardhat.config.ts │ ├── ignition │ │ └── modules │ │ │ ├── Governance.ts │ │ │ └── Lock.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json ├── L2-counter │ ├── .env.example │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── contracts │ │ └── Counter.sol │ ├── deploy │ │ └── deploy.ts │ ├── hardhat.config.ts │ ├── package-lock.json │ ├── package.json │ └── scripts │ │ ├── counter.json │ │ ├── display-value.ts │ │ ├── governance.json │ │ └── increment-counter.ts └── README.md ├── custom-aa ├── .env.example ├── .gitignore ├── LICENSE ├── README.md ├── contracts │ ├── AAFactory.sol │ └── TwoUserMultisig.sol ├── deploy │ ├── deploy-factory.ts │ └── deploy-multisig.ts ├── hardhat.config.ts ├── package.json └── test │ ├── main.test.ts │ └── utils │ └── utils.ts ├── custom-paymaster ├── .env.example ├── .gitignore ├── .prettierignore ├── .prettierrc ├── README.md ├── contracts │ ├── MyERC20.sol │ └── MyPaymaster.sol ├── deploy │ ├── deploy-paymaster.ts │ ├── use-paymaster.ts │ └── utils.ts ├── deployments │ ├── zkSyncEraTestNode │ │ ├── .chainId │ │ ├── MyERC20.json │ │ └── MyPaymaster.json │ └── zkSyncSepoliaTestnet │ │ ├── .chainId │ │ ├── MyERC20.json │ │ └── MyPaymaster.json ├── hardhat.config.ts ├── package.json └── test │ ├── MyERC20.test.ts │ ├── MyPaymaster.test.ts │ └── utils.ts ├── gated-nft ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── frontend │ ├── .eslintrc.json │ ├── .gitignore │ ├── README.md │ ├── app │ │ ├── assets │ │ │ └── zkSync_logo.png │ │ ├── components │ │ │ ├── Checkout.tsx │ │ │ ├── GreeterMessage.tsx │ │ │ ├── Input.tsx │ │ │ ├── Modal.tsx │ │ │ ├── Text.tsx │ │ │ └── WalletButton.tsx │ │ ├── constants │ │ │ └── consts.tsx │ │ ├── context │ │ │ └── Web3Context.tsx │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── hooks │ │ │ └── usePaymaster.tsx │ │ ├── layout.tsx │ │ ├── page.tsx │ │ └── types │ │ │ └── types.d.ts │ ├── next.config.js │ ├── package.json │ ├── postcss.config.js │ ├── tailwind.config.js │ └── tsconfig.json ├── package.json ├── yarn.lock └── zksync │ ├── .env.example │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── contracts │ ├── ERC721.sol │ ├── ERC721GatedPaymaster.sol │ ├── Greeter.sol │ └── metadata │ │ ├── 0 │ │ ├── 1 │ │ ├── 2 │ │ ├── 3 │ │ ├── 4 │ │ └── 5 │ ├── deploy │ ├── deploy-ERC721.ts │ ├── deploy-ERC721GatedPaymaster.ts │ └── deploy-greeter.ts │ ├── hardhat.config.ts │ ├── package.json │ ├── test │ ├── main.test.ts │ └── utils │ │ ├── deploy-ERC721GatedPaymaster.ts │ │ ├── deploy-greeter.ts │ │ └── utils.ts │ └── yarn.lock ├── hello-world-docker ├── .env.example ├── .gitignore ├── LICENSE ├── README.md ├── contracts │ └── Greeter.sol ├── deploy │ ├── deploy-greeter.ts │ └── use-greeter.ts ├── hardhat.config.ts ├── package.json └── test │ └── main.test.ts ├── hello-world ├── .env.example ├── .gitignore ├── LICENSE ├── README.md ├── contracts │ └── Greeter.sol ├── deploy │ ├── deploy-greeter.ts │ └── use-greeter.ts ├── frontend │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── package.json │ ├── public │ │ └── vite.svg │ ├── src │ │ ├── App.vue │ │ ├── abi.json │ │ ├── assets │ │ │ └── vue.svg │ │ ├── erc20.json │ │ ├── eth.json │ │ ├── main.ts │ │ ├── metamask.d.ts │ │ ├── style.css │ │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite.config.ts │ └── yarn.lock ├── hardhat.config.ts ├── package.json └── test │ ├── main.test.ts │ └── utils │ └── utils.ts ├── package.json ├── spend-limit ├── .env.example ├── .gitignore ├── LICENSE ├── README.md ├── TUTORIAL.md ├── contracts │ ├── AAFactory.sol │ ├── Account.sol │ ├── SpendLimit.sol │ └── test │ │ ├── TestAccount.sol │ │ └── TestSpendLimit.sol ├── deploy │ ├── deployFactoryAccount.ts │ ├── setLimit.ts │ └── transferETH.ts ├── hardhat.config.ts ├── package.json ├── test │ ├── spend-limit.test.ts │ └── utils │ │ ├── deploy.ts │ │ ├── helper.ts │ │ ├── rich-wallets.ts │ │ └── sendtx.ts └── tsconfig.json ├── tests ├── README.md ├── helper.ts ├── testConfig.ts └── testData.ts └── yarn.lock /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This CODEOWNERS file sets the individuals responsible for code in the tutorials repository. 2 | 3 | # These users are the default owners for everything in the repo. 4 | # They will be requested for review when someone opens a pull request. 5 | * @matter-labs/devxp 6 | 7 | # You can also specify code owners for specific directories or files. 8 | # For example: 9 | # /src/ @developer1 @developer2 10 | # /docs/ @documenter -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Welcome! 👋 4 | 5 | Hello there, contributor! We're thrilled that you're interested in contributing to the Paymaster Examples Repository. This document aims to provide you with all the information you need to make meaningful contributions to this project. 6 | 7 | Please take a moment to review this document in order to make the contribution process easy and effective for everyone involved. 8 | 9 | ## Getting Started 10 | 11 | - **Fork the repository.** Start by forking the main Paymaster Examples Repository to your own GitHub account. 12 | 13 | - **Clone the repository.** Once forked, clone the repository to your local machine. 14 | 15 | ```bash 16 | git clone https://github.com//tutorials.git 17 | ``` 18 | 19 | - **Create a new branch.** Branches should have descriptive names to help track the feature, bugfix, or enhancement you're working on. Use the following format: 20 | 21 | ```bash 22 | git checkout -b feature/description-of-your-feature 23 | ``` 24 | 25 | ## Making Changes 26 | 27 | - **Make your changes.** Be sure to thoroughly test your code and ensure it works as expected. We encourage comments and clear, readable code. 28 | 29 | - **Compile and test contracts.** Remember to compile your contracts and test them before committing. You will need to write unit tests for each contract contributed. Use the following commands: 30 | 31 | ```bash 32 | yarn compile:contracts 33 | yarn test:contracts 34 | ``` 35 | 36 | - **Commit your changes.** Follow the [Conventional Commits](https://www.conventionalcommits.org/) standard for commit messages. 37 | 38 | - **Push your changes.** Push your changes to your forked repository. 39 | 40 | ```bash 41 | git push origin feature/description-of-your-feature 42 | ``` 43 | 44 | ## Submitting a Pull Request 45 | 46 | - **Create a pull request (PR).** Navigate to the main [Tutorials repository](https://github.com/matter-labs/tutorials), and you should see your recently pushed branch highlighted with a "Compare & pull request" button. Fill in the PR title and provide a clear, detailed description of your changes. 47 | 48 | - **Wait for a review.** Your PR will be reviewed by our maintainers. They may ask for changes or clarifications, so be prepared to address any feedback. 49 | 50 | ## Code Style Guide 51 | 52 | We use `Prettier` for code formatting. Ensure to run the formatter before committing your changes: 53 | 54 | ```bash 55 | yarn format 56 | ``` 57 | 58 | ## Where Can I Ask for Help? 59 | 60 | If you need help with contributing or have any questions or concerns, feel free to open an issue or start a discussion in our [zkSync Community Hub](https://github.com/zkSync-Community-Hub/zkync-developers/discussions). We are here to help you. 61 | 62 | ## What's Next? 63 | 64 | Once your PR is approved and merged, your contribution will be part of the Paymaster Examples Repository. Congratulations! We appreciate your efforts and look forward to more of your contributions in the future. 65 | 66 | Remember, the best way to contribute is to have fun, be respectful, and keep learning. Thank you for contributing! 67 | 68 | --- 69 | 70 | _Last updated: August 10, 2023_ 71 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Use this template for reporting issues 4 | title: "" 5 | labels: bug 6 | assignees: "" 7 | --- 8 | 9 | ### 🐛 Bug Report for zkSync Era In-Memory Node 10 | 11 | #### 📝 Description 12 | 13 | Provide a clear and concise description of the bug. 14 | 15 | #### 🔄 Reproduction Steps 16 | 17 | 1. Step 1 18 | 2. Step 2 19 | 3. ... 20 | 21 | #### 🤔 Expected Behavior 22 | 23 | Describe what you expected to happen. 24 | 25 | #### 😯 Current Behavior 26 | 27 | Describe what actually happened. 28 | 29 | #### 🖥️ Environment 30 | 31 | - **Rust version**: [e.g., rustc 1.52.0] 32 | - **Operating System & Version**: [e.g., Ubuntu 20.04] 33 | - **Other relevant environment details**: 34 | 35 | #### 📋 Additional Context 36 | 37 | Add any other context about the problem here. If applicable, add screenshots to help explain. 38 | 39 | #### 📎 Log Output 40 | 41 | ``` 42 | Paste any relevant log output here. 43 | ``` 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: zksync-developers Discussion 4 | url: https://github.com/zkSync-Community-Hub/zkync-developers/discussions 5 | about: Please provide feedback, and ask questions here. 6 | - name: Tutorials Documentation 7 | url: https://era.zksync.io/docs/dev/tutorials/ 8 | about: Please refer to the documentation for immediate answers. 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Use this template for requesting features 4 | title: "" 5 | labels: feat 6 | assignees: "" 7 | --- 8 | 9 | ### 🌟 Feature Request 10 | 11 | #### 📝 Description 12 | 13 | Provide a clear and concise description of the feature you'd like to see. 14 | 15 | #### 🤔 Rationale 16 | 17 | Explain why this feature is important and how it benefits the project. 18 | 19 | #### 🖼️ Mockups/Examples 20 | 21 | If applicable, provide mockups or examples of how the feature would work. 22 | 23 | #### 📋 Additional Context 24 | 25 | Add any other context or information about the feature request here. 26 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # What :computer: 2 | 3 | - First thing updated with this PR 4 | - Second thing updated with this PR 5 | - Third thing updated with this PR 6 | 7 | # Why :hand: 8 | 9 | - Reason why first thing was added to PR 10 | - Reason why second thing was added to PR 11 | - Reason why third thing was added to PR 12 | 13 | # Evidence :camera: 14 | 15 | Include screenshots, screen recordings, or `console` output here demonstrating that your changes work as intended 16 | 17 | 18 | 19 | # Notes :memo: 20 | 21 | - Any notes/thoughts that the reviewers should know prior to reviewing the code? 22 | -------------------------------------------------------------------------------- /.github/workflows/lints.yml: -------------------------------------------------------------------------------- 1 | name: Links and Linting 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | 7 | workflow_dispatch: 8 | 9 | jobs: 10 | links: 11 | name: links 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Link Checker 16 | id: lychee 17 | uses: lycheeverse/lychee-action@v1.8.0 18 | 19 | format: 20 | name: format 21 | runs-on: ubuntu-latest 22 | strategy: 23 | matrix: 24 | node-version: [18.x] 25 | steps: 26 | - uses: actions/checkout@v3 27 | - name: Use Node.js ${{ matrix.node-version }} 28 | uses: actions/setup-node@v3 29 | with: 30 | node-version: ${{ matrix.node-version }} 31 | - name: prettier 32 | working-directory: . 33 | run: | 34 | yarn install 35 | yarn lint:fmt 36 | -------------------------------------------------------------------------------- /.github/workflows/secret_scanner.yml: -------------------------------------------------------------------------------- 1 | name: Leaked Secrets Scan 2 | on: [pull_request] 3 | jobs: 4 | TruffleHog: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Checkout code 8 | uses: actions/checkout@v2 9 | with: 10 | fetch-depth: 0 11 | - name: TruffleHog OSS 12 | uses: trufflesecurity/trufflehog@0c66d30c1f4075cee1aada2e1ab46dabb1b0071a 13 | with: 14 | path: ./ 15 | base: ${{ github.event.repository.default_branch }} 16 | head: HEAD 17 | extra_args: --debug --only-verified 18 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | pull_request: 4 | types: 5 | - opened 6 | - edited 7 | - reopened 8 | - synchronize 9 | push: 10 | branches: 11 | - main 12 | workflow_dispatch: 13 | 14 | jobs: 15 | docker-hello-world-tests: 16 | name: Docker Hello World Tests 17 | strategy: 18 | matrix: 19 | node-version: ["18.15.0"] 20 | runs-on: "ubuntu-latest" 21 | timeout-minutes: 30 22 | steps: 23 | - name: Checkout branch 24 | uses: actions/checkout@v3 25 | 26 | - name: Install Node 27 | uses: actions/setup-node@v3 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | 31 | - name: Install Docker 32 | uses: docker/setup-buildx-action@v3 33 | 34 | - name: Git Clone Local Repo 35 | run: git clone https://github.com/matter-labs/local-setup 36 | 37 | - name: Install dependencies 38 | run: | 39 | yarn install --frozen-lockfile 40 | 41 | - name: Run Docker Compose Daemon 42 | run: cd local-setup && mkdir -p ./volumes && mkdir -p ./volumes/postgres ./volumes/geth ./volumes/zksync/env/dev ./volumes/zksync/data && touch ./volumes/zksync/env.env && docker compose up -d && cd .. 43 | 44 | - name: Wait until node is ready 45 | run: | 46 | while ! curl -s -X POST -d '{"jsonrpc":"2.0","method":"net_version","id":1}' -H 'Content-Type: application/json' 0.0.0.0:3050; do sleep 1; done 47 | 48 | - name: Print Docker logs 49 | run: cd local-setup && docker compose logs && cd .. 50 | 51 | - name: Hello World Docker tests 52 | run: cd hello-world-docker && docker ps && yarn test 53 | 54 | im-node-tests: 55 | name: IM Node Tests 56 | timeout-minutes: 30 57 | strategy: 58 | matrix: 59 | os: [ubuntu-latest] 60 | node-version: ["18.15.0"] 61 | runs-on: ${{ matrix.os }} 62 | steps: 63 | - name: Checkout code 64 | uses: actions/checkout@v3 65 | with: 66 | fetch-depth: 0 67 | 68 | - name: Run Era Test Node 69 | uses: dutterbutter/era-test-node-action@v1 70 | 71 | - name: Setup Node.js 72 | uses: actions/setup-node@v3 73 | with: 74 | node-version: ${{ matrix.node-version }} 75 | 76 | - name: Install dependencies 77 | run: | 78 | yarn install --frozen-lockfile 79 | cd gated-nft/zksync && yarn install --frozen-lockfile 80 | 81 | - name: Custom AA tests 82 | run: | 83 | cd custom-aa && yarn test 84 | 85 | - name: Hello World tests 86 | run: | 87 | cd hello-world && yarn test 88 | 89 | - name: Gated NFT tests 90 | run: | 91 | cd gated-nft/zksync && yarn test 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Disregard all sub directory node_modules 46 | **/node_modules 47 | 48 | # Snowpack dependency directory (https://snowpack.dev/) 49 | web_modules/ 50 | 51 | # TypeScript cache 52 | *.tsbuildinfo 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Optional stylelint cache 61 | .stylelintcache 62 | 63 | # Microbundle cache 64 | .rpt2_cache/ 65 | .rts2_cache_cjs/ 66 | .rts2_cache_es/ 67 | .rts2_cache_umd/ 68 | 69 | # Optional REPL history 70 | .node_repl_history 71 | 72 | # Output of 'npm pack' 73 | *.tgz 74 | 75 | # Yarn Integrity file 76 | .yarn-integrity 77 | 78 | # dotenv environment variable files 79 | .env 80 | .env.development.local 81 | .env.test.local 82 | .env.production.local 83 | .env.local 84 | 85 | # parcel-bundler cache (https://parceljs.org/) 86 | .cache 87 | .parcel-cache 88 | 89 | # Next.js build output 90 | .next 91 | out 92 | 93 | # Nuxt.js build / generate output 94 | .nuxt 95 | dist 96 | 97 | # Gatsby files 98 | .cache/ 99 | # Comment in the public line in if your project uses Gatsby and not Next.js 100 | # https://nextjs.org/blog/next-9-1#public-directory-support 101 | # public 102 | 103 | # vuepress build output 104 | .vuepress/dist 105 | 106 | # vuepress v2.x temp and cache directory 107 | .temp 108 | .cache 109 | 110 | # Docusaurus cache and generated files 111 | .docusaurus 112 | 113 | # Serverless directories 114 | .serverless/ 115 | 116 | # FuseBox cache 117 | .fusebox/ 118 | 119 | # DynamoDB Local files 120 | .dynamodb/ 121 | 122 | # TernJS port file 123 | .tern-port 124 | 125 | # Stores VSCode versions used for testing VSCode extensions 126 | .vscode-test 127 | 128 | # Stores WebStorm configs 129 | .idea 130 | 131 | # yarn v2 132 | .yarn/cache 133 | .yarn/unplugged 134 | .yarn/build-state.yml 135 | .yarn/install-state.gz 136 | .pnp.* 137 | .DS_Store -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .tmp 2 | .cache/ 3 | coverage/ 4 | .nyc_output/ 5 | **/.yarn/** 6 | **/.pnp.* 7 | /dist*/ 8 | node_modules/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": false, 5 | "printWidth": 80, 6 | "tabWidth": 2 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Matter Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Gradient Banner](https://github.com/matter-labs/tutorials/assets/10233439/8efffb9b-ad1f-4bf2-8f73-9cab8f7ccd22) 2 | 3 | # zkSync Tutorials 4 | 5 | This repository contains tutorials for developing on zkSync Era. 6 | 7 | ## Tutorials 8 | 9 | | Name | Time-to-complete | Difficulty | 10 | | --------------------------------------------------- | ---------------- | ---------- | 11 | | [Cross Chain Governance](./cross-chain/README.md) | 3-4 Hours | Moderate | 12 | | [Custom Account Abstraction](./custom-aa/README.md) | 3-5 Hours | Hard | 13 | | [Gated NFT](./gated-nft/README.md) | 3-5 Hours | Hard | 14 | | [Hello World](./hello-world/README.md) | 3-4 Hours | Easy | 15 | | [Daily Spend Limit](./spend-limit/README.md) | 2-4 Hours | Moderate | 16 | 17 | ## Contributing 18 | 19 | If you want to contribute to this repository, please read the [contribution guidelines](./CONTRIBUTING.md). 20 | 21 | ## License 22 | 23 | This repository is licensed under the [MIT License](./LICENSE). 24 | 25 | ## Acknowledgements 26 | 27 | Thanks to the contributors that helped us create these tutorials: 28 | 29 | - [vanshwassan](https://github.com/vanshwassan) 30 | - [porco-rosso-j](https://github.com/porco-rosso-j) 31 | 32 | ## Support 33 | 34 | If you have any questions, feel free to ask them in our channels: 35 | 36 | - [zkSync's Documentation](https://era.zksync.io/docs/) 37 | - [GitHub](https://github.com/matter-labs) 38 | - [Twitter @zkSync](https://twitter.com/zksync) 39 | - [Join our Discord Community](https://join.zksync.dev) 40 | -------------------------------------------------------------------------------- /cross-chain/.gitignore: -------------------------------------------------------------------------------- 1 | /**/artifacts 2 | /**/artifacts-zk 3 | /**/node_modules 4 | /**/tmp 5 | /**/cache 6 | /**/cache-zk 7 | /**/typechain-types 8 | -------------------------------------------------------------------------------- /cross-chain/L1-governance/.env.example: -------------------------------------------------------------------------------- 1 | NODE_RPC_URL= 2 | PRIVATE_KEY= 3 | -------------------------------------------------------------------------------- /cross-chain/L1-governance/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | 4 | # Hardhat files 5 | /cache 6 | /artifacts 7 | /ignition/deployments/* 8 | 9 | # TypeChain files 10 | /typechain 11 | /typechain-types 12 | 13 | # solidity-coverage files 14 | /coverage 15 | /coverage.json 16 | 17 | # Hardhat Ignition default folder for deployments against a local node 18 | ignition/deployments/chain-31337 19 | -------------------------------------------------------------------------------- /cross-chain/L1-governance/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Matter Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cross-chain/L1-governance/README.md: -------------------------------------------------------------------------------- 1 | # Sample Hardhat Project 2 | 3 | This project demonstrates a basic Hardhat use case. It comes with a sample contract, a test for that contract, and a Hardhat Ignition module that deploys that contract. 4 | 5 | Try running some of the following tasks: 6 | 7 | ```shell 8 | npx hardhat help 9 | npx hardhat test 10 | REPORT_GAS=true npx hardhat test 11 | npx hardhat node 12 | npx hardhat ignition deploy ./ignition/modules/Lock.ts 13 | ``` 14 | -------------------------------------------------------------------------------- /cross-chain/L1-governance/contracts/Governance.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.8.13; 3 | 4 | import "@matterlabs/zksync-contracts/l1/contracts/zksync/interfaces/IZkSync.sol"; 5 | 6 | contract Governance { 7 | address public governor; 8 | 9 | constructor() { 10 | governor = msg.sender; 11 | } 12 | 13 | function callZkSync( 14 | address zkSyncAddress, 15 | address contractAddr, 16 | bytes memory data, 17 | uint256 gasLimit, 18 | uint256 gasPerPubdataByteLimit 19 | ) external payable { 20 | require(msg.sender == governor, "Only governor is allowed"); 21 | 22 | IZkSync zksync = IZkSync(zkSyncAddress); 23 | zksync.requestL2Transaction{value: msg.value}( 24 | contractAddr, 25 | 0, 26 | data, 27 | gasLimit, 28 | gasPerPubdataByteLimit, 29 | new bytes[](0), 30 | msg.sender 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cross-chain/L1-governance/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig } from "hardhat/config"; 2 | import "@nomicfoundation/hardhat-toolbox"; 3 | 4 | import dotenv from "dotenv"; 5 | dotenv.config(); 6 | 7 | const config: HardhatUserConfig = { 8 | solidity: "0.8.24", 9 | networks: { 10 | sepolia: { 11 | url: process.env.NODE_RPC_URL, 12 | accounts: [process.env.PRIVATE_KEY as any], 13 | }, 14 | }, 15 | }; 16 | 17 | export default config; 18 | -------------------------------------------------------------------------------- /cross-chain/L1-governance/ignition/modules/Governance.ts: -------------------------------------------------------------------------------- 1 | import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; 2 | 3 | const GovernanceModule = buildModule("GovernanceModule", (m) => { 4 | const governance = m.contract("Governance", [], {}); 5 | 6 | return { governance }; 7 | }); 8 | 9 | export default GovernanceModule; 10 | -------------------------------------------------------------------------------- /cross-chain/L1-governance/ignition/modules/Lock.ts: -------------------------------------------------------------------------------- 1 | import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; 2 | 3 | const JAN_1ST_2030 = 1893456000; 4 | const ONE_GWEI: bigint = 1_000_000_000n; 5 | 6 | const LockModule = buildModule("LockModule", (m) => { 7 | const unlockTime = m.getParameter("unlockTime", JAN_1ST_2030); 8 | const lockedAmount = m.getParameter("lockedAmount", ONE_GWEI); 9 | 10 | const lock = m.contract("Lock", [unlockTime], { 11 | value: lockedAmount, 12 | }); 13 | 14 | return { lock }; 15 | }); 16 | 17 | export default LockModule; 18 | -------------------------------------------------------------------------------- /cross-chain/L1-governance/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hardhat-project", 3 | "devDependencies": { 4 | "@matterlabs/zksync-contracts": "^0.6.1", 5 | "@nomicfoundation/hardhat-ethers": "^3.0.6", 6 | "@nomicfoundation/hardhat-toolbox": "^5.0.0", 7 | "@openzeppelin/contracts": "^5.0.2", 8 | "@typechain/ethers-v6": "^0.5.1", 9 | "@typechain/hardhat": "^9.1.0", 10 | "dotenv": "^16.4.5", 11 | "ethers": "^6.13.1", 12 | "hardhat": "^2.22.6", 13 | "ts-node": "^10.9.2", 14 | "typechain": "^8.3.2", 15 | "typescript": "^5.5.3" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /cross-chain/L1-governance/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /cross-chain/L2-counter/.env.example: -------------------------------------------------------------------------------- 1 | WALLET_PRIVATE_KEY= 2 | -------------------------------------------------------------------------------- /cross-chain/L2-counter/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .vscode 9 | 10 | # hardhat artifacts 11 | artifacts 12 | cache 13 | 14 | # zksync artifacts 15 | artifacts-zk 16 | cache-zk 17 | 18 | # Diagnostic reports (https://nodejs.org/api/report.html) 19 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 20 | 21 | # Runtime data 22 | pids 23 | *.pid 24 | *.seed 25 | *.pid.lock 26 | 27 | # Directory for instrumented libs generated by jscoverage/JSCover 28 | lib-cov 29 | 30 | # Coverage directory used by tools like istanbul 31 | coverage 32 | *.lcov 33 | 34 | # nyc test coverage 35 | .nyc_output 36 | 37 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 38 | .grunt 39 | 40 | # Bower dependency directory (https://bower.io/) 41 | bower_components 42 | 43 | # node-waf configuration 44 | .lock-wscript 45 | 46 | # Compiled binary addons (https://nodejs.org/api/addons.html) 47 | build/Release 48 | 49 | # Dependency directories 50 | node_modules/ 51 | jspm_packages/ 52 | 53 | # TypeScript v1 declaration files 54 | typings/ 55 | 56 | # TypeScript cache 57 | *.tsbuildinfo 58 | 59 | # Optional npm cache directory 60 | .npm 61 | 62 | # Optional eslint cache 63 | .eslintcache 64 | 65 | # Microbundle cache 66 | .rpt2_cache/ 67 | .rts2_cache_cjs/ 68 | .rts2_cache_es/ 69 | .rts2_cache_umd/ 70 | 71 | # Optional REPL history 72 | .node_repl_history 73 | 74 | # Output of 'npm pack' 75 | *.tgz 76 | 77 | # Yarn Integrity file 78 | .yarn-integrity 79 | 80 | # dotenv environment variables file 81 | .env 82 | .env.test 83 | 84 | # parcel-bundler cache (https://parceljs.org/) 85 | .cache 86 | 87 | # Next.js build output 88 | .next 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # Serverless directories 104 | .serverless/ 105 | 106 | # FuseBox cache 107 | .fusebox/ 108 | 109 | # DynamoDB Local files 110 | .dynamodb/ 111 | 112 | # TernJS port file 113 | .tern-port 114 | -------------------------------------------------------------------------------- /cross-chain/L2-counter/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Matter Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cross-chain/L2-counter/README.md: -------------------------------------------------------------------------------- 1 | # zkSync Hardhat project template 2 | 3 | This project was scaffolded with [zksync-cli](https://github.com/matter-labs/zksync-cli). 4 | 5 | ## Project Layout 6 | 7 | - `/contracts`: Contains solidity smart contracts. 8 | - `/deploy`: Scripts for contract deployment and interaction. 9 | - `/test`: Test files. 10 | - `hardhat.config.ts`: Configuration settings. 11 | 12 | ## How to Use 13 | 14 | - `npm run compile`: Compiles contracts. 15 | - `npm run deploy`: Deploys using script `/deploy/deploy.ts`. 16 | - `npm run interact`: Interacts with the deployed contract using `/deploy/interact.ts`. 17 | - `npm run test`: Tests the contracts. 18 | 19 | Note: Both `npm run deploy` and `npm run interact` are set in the `package.json`. You can also run your files directly, for example: `npx hardhat deploy-zksync --script deploy.ts` 20 | 21 | ### Environment Settings 22 | 23 | To keep private keys safe, this project pulls in environment variables from `.env` files. Primarily, it fetches the wallet's private key. 24 | 25 | Rename `.env.example` to `.env` and fill in your private key: 26 | 27 | ``` 28 | WALLET_PRIVATE_KEY=your_private_key_here... 29 | ``` 30 | 31 | ### Network Support 32 | 33 | `hardhat.config.ts` comes with a list of networks to deploy and test contracts. Add more by adjusting the `networks` section in the `hardhat.config.ts`. To make a network the default, set the `defaultNetwork` to its name. You can also override the default using the `--network` option, like: `hardhat test --network dockerizedNode`. 34 | 35 | ### Local Tests 36 | 37 | Running `npm run test` by default runs the [zkSync In-memory Node](https://era.zksync.io/docs/tools/testing/era-test-node.html) provided by the [@matterlabs/hardhat-zksync-node](https://era.zksync.io/docs/tools/hardhat/hardhat-zksync-node.html) tool. 38 | 39 | Important: zkSync In-memory Node currently supports only the L2 node. If contracts also need L1, use another testing environment like Dockerized Node. Refer to [test documentation](https://era.zksync.io/docs/tools/testing/) for details. 40 | 41 | ## Useful Links 42 | 43 | - [Docs](https://era.zksync.io/docs/dev/) 44 | - [Official Site](https://zksync.io/) 45 | - [GitHub](https://github.com/matter-labs) 46 | - [Twitter](https://twitter.com/zksync) 47 | - [Discord](https://join.zksync.dev/) 48 | 49 | ## License 50 | 51 | This project is under the [MIT](./LICENSE) license. 52 | -------------------------------------------------------------------------------- /cross-chain/L2-counter/contracts/Counter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.8.17; 3 | 4 | contract Counter { 5 | uint256 public value = 0; 6 | address public governance; 7 | 8 | constructor(address newGovernance) { 9 | governance = newGovernance; 10 | } 11 | 12 | function increment() public { 13 | require(msg.sender == governance, "Only governance is allowed"); 14 | 15 | value += 1; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /cross-chain/L2-counter/deploy/deploy.ts: -------------------------------------------------------------------------------- 1 | import { utils, Wallet } from "zksync-ethers"; 2 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 3 | import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; 4 | // load env file 5 | import dotenv from "dotenv"; 6 | dotenv.config(); 7 | 8 | // Insert the address of the governance contract 9 | const GOVERNANCE_ADDRESS = "0xA7d27A1202bE1237919Cf2cb60970141100725b4"; 10 | 11 | // An example of a deploy script that will deploy and call a simple contract. 12 | export default async function (hre: HardhatRuntimeEnvironment) { 13 | console.log(`Running deploy script for the Counter contract`); 14 | 15 | const PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY || ""; 16 | if (!PRIVATE_KEY) 17 | throw "⛔️ Private key not detected! Add it to the .env file!"; 18 | // Initialize the wallet. 19 | const wallet = new Wallet(PRIVATE_KEY); 20 | 21 | // Create deployer object and load the artifact of the contract you want to deploy. 22 | const deployer = new Deployer(hre, wallet); 23 | const artifact = await deployer.loadArtifact("Counter"); 24 | 25 | // Deploy this contract. The returned object will be of a `Contract` type, similar to the ones in `ethers`. 26 | // The address of the governance is an argument for contract constructor. 27 | const counterContract = await deployer.deploy(artifact, [ 28 | utils.applyL1ToL2Alias(GOVERNANCE_ADDRESS), 29 | ]); 30 | 31 | const receipt = await counterContract.deploymentTransaction()?.wait(); 32 | 33 | // Show the contract info. 34 | const contractAddress = receipt?.contractAddress; 35 | console.log(`${artifact.contractName} was deployed to ${contractAddress}`); 36 | } 37 | -------------------------------------------------------------------------------- /cross-chain/L2-counter/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig } from "hardhat/config"; 2 | 3 | import "@matterlabs/hardhat-zksync"; 4 | 5 | const config: HardhatUserConfig = { 6 | defaultNetwork: "zkSyncSepoliaTestnet", 7 | networks: { 8 | zkSyncSepoliaTestnet: { 9 | url: "https://sepolia.era.zksync.dev", 10 | ethNetwork: "sepolia", 11 | zksync: true, 12 | verifyURL: 13 | "https://explorer.sepolia.era.zksync.dev/contract_verification", 14 | }, 15 | zkSyncMainnet: { 16 | url: "https://mainnet.era.zksync.io", 17 | ethNetwork: "mainnet", 18 | zksync: true, 19 | verifyURL: 20 | "https://zksync2-mainnet-explorer.zksync.io/contract_verification", 21 | }, 22 | zkSyncGoerliTestnet: { 23 | // deprecated network 24 | url: "https://testnet.era.zksync.dev", 25 | ethNetwork: "goerli", 26 | zksync: true, 27 | verifyURL: 28 | "https://zksync2-testnet-explorer.zksync.dev/contract_verification", 29 | }, 30 | dockerizedNode: { 31 | url: "http://localhost:3050", 32 | ethNetwork: "http://localhost:8545", 33 | zksync: true, 34 | }, 35 | inMemoryNode: { 36 | url: "http://127.0.0.1:8011", 37 | ethNetwork: "localhost", // in-memory node doesn't support eth node; removing this line will cause an error 38 | zksync: true, 39 | }, 40 | hardhat: { 41 | zksync: true, 42 | }, 43 | }, 44 | zksolc: { 45 | version: "latest", 46 | settings: { 47 | // find all available options in the official documentation 48 | // https://era.zksync.io/docs/tools/hardhat/hardhat-zksync-solc.html#configuration 49 | }, 50 | }, 51 | solidity: { 52 | version: "0.8.17", 53 | }, 54 | }; 55 | 56 | export default config; 57 | -------------------------------------------------------------------------------- /cross-chain/L2-counter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zksync-hardhat-template", 3 | "description": "A template for zkSync smart contracts development with Hardhat", 4 | "private": true, 5 | "author": "Matter Labs", 6 | "license": "MIT", 7 | "repository": "https://github.com/matter-labs/zksync-hardhat-template.git", 8 | "scripts": { 9 | "deploy": "hardhat deploy-zksync --script deploy.ts", 10 | "interact": "hardhat deploy-zksync --script interact.ts", 11 | "compile": "hardhat compile", 12 | "clean": "hardhat clean", 13 | "test": "hardhat test --network hardhat" 14 | }, 15 | "devDependencies": { 16 | "@matterlabs/hardhat-zksync": "^1.0.0", 17 | "@matterlabs/zksync-contracts": "^0.6.1", 18 | "@nomiclabs/hardhat-etherscan": "^3.1.7", 19 | "@openzeppelin/contracts": "^4.6.0", 20 | "@types/chai": "^4.3.4", 21 | "@types/mocha": "^10.0.1", 22 | "chai": "^4.3.7", 23 | "dotenv": "^16.0.3", 24 | "ethers": "^6.9.2", 25 | "hardhat": "^2.12.4", 26 | "mocha": "^10.2.0", 27 | "ts-node": "^10.9.1", 28 | "typescript": "^4.9.5", 29 | "zksync-ethers": "^6.7.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cross-chain/L2-counter/scripts/counter.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "address", 6 | "name": "newGovernance", 7 | "type": "address" 8 | } 9 | ], 10 | "stateMutability": "nonpayable", 11 | "type": "constructor" 12 | }, 13 | { 14 | "inputs": [], 15 | "name": "governance", 16 | "outputs": [ 17 | { 18 | "internalType": "address", 19 | "name": "", 20 | "type": "address" 21 | } 22 | ], 23 | "stateMutability": "view", 24 | "type": "function" 25 | }, 26 | { 27 | "inputs": [], 28 | "name": "increment", 29 | "outputs": [], 30 | "stateMutability": "nonpayable", 31 | "type": "function" 32 | }, 33 | { 34 | "inputs": [], 35 | "name": "value", 36 | "outputs": [ 37 | { 38 | "internalType": "uint256", 39 | "name": "", 40 | "type": "uint256" 41 | } 42 | ], 43 | "stateMutability": "view", 44 | "type": "function" 45 | } 46 | ] 47 | -------------------------------------------------------------------------------- /cross-chain/L2-counter/scripts/display-value.ts: -------------------------------------------------------------------------------- 1 | import { Contract, Provider } from "zksync-ethers"; 2 | 3 | const COUNTER_ADDRESS = "COUNTER_CONTRACT_ADDRESS"; 4 | const COUNTER_ABI = require("./counter.json"); 5 | 6 | async function main() { 7 | // Initialize the provider 8 | const l2Provider = new Provider("RPC_NODE_URL"); 9 | 10 | const counterContract = new Contract( 11 | COUNTER_ADDRESS, 12 | COUNTER_ABI, 13 | l2Provider, 14 | ); 15 | 16 | const value = (await counterContract.value()).toString(); 17 | 18 | console.log(`The counter value is ${value}`); 19 | } 20 | 21 | main().catch((error) => { 22 | console.error(error); 23 | process.exitCode = 1; 24 | }); 25 | -------------------------------------------------------------------------------- /cross-chain/L2-counter/scripts/governance.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [], 4 | "stateMutability": "nonpayable", 5 | "type": "constructor" 6 | }, 7 | { 8 | "inputs": [ 9 | { 10 | "internalType": "address", 11 | "name": "zkSyncAddress", 12 | "type": "address" 13 | }, 14 | { 15 | "internalType": "address", 16 | "name": "contractAddr", 17 | "type": "address" 18 | }, 19 | { 20 | "internalType": "bytes", 21 | "name": "data", 22 | "type": "bytes" 23 | }, 24 | { 25 | "internalType": "uint256", 26 | "name": "gasLimit", 27 | "type": "uint256" 28 | }, 29 | { 30 | "internalType": "uint256", 31 | "name": "gasPerPubdataByteLimit", 32 | "type": "uint256" 33 | } 34 | ], 35 | "name": "callZkSync", 36 | "outputs": [], 37 | "stateMutability": "payable", 38 | "type": "function" 39 | }, 40 | { 41 | "inputs": [], 42 | "name": "governor", 43 | "outputs": [ 44 | { 45 | "internalType": "address", 46 | "name": "", 47 | "type": "address" 48 | } 49 | ], 50 | "stateMutability": "view", 51 | "type": "function" 52 | } 53 | ] 54 | -------------------------------------------------------------------------------- /cross-chain/L2-counter/scripts/increment-counter.ts: -------------------------------------------------------------------------------- 1 | import { Contract, Wallet, Interface } from "ethers"; 2 | import { Provider, utils } from "zksync-ethers"; 3 | // load env file 4 | import dotenv from "dotenv"; 5 | dotenv.config(); 6 | 7 | const GOVERNANCE_ABI = require("./governance.json"); 8 | const GOVERNANCE_ADDRESS = "GOVERNANCE_CONTRACT_ADDRESS"; 9 | const COUNTER_ABI = require("./counter.json"); 10 | const COUNTER_ADDRESS = "COUNTER_CONTRACT_ADDRESS"; 11 | 12 | const PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY || ""; 13 | if (!PRIVATE_KEY) 14 | throw "⛔️ Private key not detected! Add it to the .env file!"; 15 | // Initialize the wallet. 16 | 17 | async function main() { 18 | // Enter your Ethereum L1 provider RPC URL. 19 | const l1Provider = new Provider("RPC_NODE_URL"); 20 | // Set up the Governor wallet to be the same as the one that deployed the governance contract. 21 | const wallet = new Wallet(PRIVATE_KEY, l1Provider); 22 | // Set a constant that accesses the Layer 1 contract. 23 | const govcontract = new Contract(GOVERNANCE_ADDRESS, GOVERNANCE_ABI, wallet); 24 | 25 | // Initialize the L2 provider. 26 | const l2Provider = new Provider("https://sepolia.era.zksync.dev"); 27 | // Get the current address of the zkSync L1 bridge. 28 | const zkSyncAddress = await l2Provider.getMainContractAddress(); 29 | // Get the `Contract` object of the zkSync bridge. 30 | const zkSyncContract = new Contract( 31 | zkSyncAddress, 32 | utils.ZKSYNC_MAIN_ABI, 33 | wallet, 34 | ); 35 | 36 | // Encoding the L1 transaction is done in the same way as it is done on Ethereum. 37 | // Use an Interface which gives access to the contract functions. 38 | const counterInterface = new Interface(COUNTER_ABI); 39 | const data = counterInterface.encodeFunctionData("increment", []); 40 | 41 | // The price of an L1 transaction depends on the gas price used. 42 | // You should explicitly fetch the gas price before making the call. 43 | const gasPrice = await l1Provider.getGasPrice(); 44 | 45 | // Define a constant for gas limit which estimates the limit for the L1 to L2 transaction. 46 | const gasLimit = await l2Provider.estimateL1ToL2Execute({ 47 | contractAddress: COUNTER_ADDRESS, 48 | calldata: data, 49 | caller: utils.applyL1ToL2Alias(GOVERNANCE_ADDRESS), 50 | }); 51 | // baseCost takes the price and limit and formats the total in wei. 52 | // For more information on `REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT` see the [fee model documentation](../developer-guides/transactions/fee-model.md). 53 | const baseCost = await zkSyncContract.l2TransactionBaseCost( 54 | gasPrice, 55 | gasLimit, 56 | utils.REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, 57 | ); 58 | 59 | // !! If you don't include the gasPrice and baseCost in the transaction, a re-estimation of fee may generate errors. 60 | const tx = await govcontract.callZkSync( 61 | zkSyncAddress, 62 | COUNTER_ADDRESS, 63 | data, 64 | gasLimit, 65 | utils.REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, 66 | { 67 | // Pass the necessary ETH `value` to cover the fee for the operation 68 | value: baseCost, 69 | gasPrice, 70 | }, 71 | ); 72 | 73 | // Wait until the L1 tx is complete. 74 | await tx.wait(); 75 | 76 | // Get the TransactionResponse object for the L2 transaction corresponding to the execution call. 77 | const l2Response = await l2Provider.getL2TransactionFromPriorityOp(tx); 78 | 79 | // Output the receipt of the L2 transaction corresponding to the call to the counter contract. 80 | const l2Receipt = await l2Response.wait(); 81 | console.log(l2Receipt); 82 | } 83 | 84 | // We recommend always using this async/await pattern to properly handle errors. 85 | main().catch((error) => { 86 | console.error(error); 87 | process.exitCode = 1; 88 | }); 89 | -------------------------------------------------------------------------------- /cross-chain/README.md: -------------------------------------------------------------------------------- 1 | # Cross-chain governance full example 2 | 3 | This is the full example for the [zkSync "Cross-chain governance" tutorial](https://era.zksync.io/docs/dev/tutorials/cross-chain-tutorial.html). 4 | 5 | It consists of two folders: 6 | 7 | - `L1-governance` which contains the Hardhat project that is used to deploy the governance smart contract on Sepolia. 8 | - `L2-counter` which contains the Hardhat project for the `Counter` L2 smart contract. It also contains scripts that are used to display the value of the counter as well as to call the governance to update the counter from L1. 9 | -------------------------------------------------------------------------------- /custom-aa/.env.example: -------------------------------------------------------------------------------- 1 | WALLET_PRIVATE_KEY= -------------------------------------------------------------------------------- /custom-aa/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | artifacts-zk/ 3 | cache-zk/ 4 | -------------------------------------------------------------------------------- /custom-aa/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Matter Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /custom-aa/README.md: -------------------------------------------------------------------------------- 1 | # Account abstraction multisig tutorial 📖 2 | 3 | This repository is crafted to guide you through the process of building a native multisig account on zkSync Era. Coupled with this, you'll find a practical, easy-to-follow guide to implement and understand every step [here](https://docs.zksync.io/build/tutorials/smart-contract-development/account-abstraction/custom-aa-tutorial.html). 4 | 5 | ## Need Assistance? 💡 6 | 7 | If you're stumbling upon any issues or uncertainties: 8 | 9 | - 📖 Explore the [multisig tutorial](https://docs.zksync.io/build/tutorials/smart-contract-development/account-abstraction/custom-aa-tutorial.html) for a comprehensive walkthrough of the code in this repository. 10 | - 🗣️ Or simply [reach out on Discord](https://join.zksync.dev/). We're always here to help! 11 | 12 | ## Repository Overview 📂 13 | 14 | Dive into the key sections of this repository: 15 | 16 | - `/contracts`: All the essential smart contracts you need are neatly stored here. 17 | 18 | - `/deploy`: Discover deployment and usage scripts tailored to assist your development process. 19 | 20 | - `/test`: Unit tests for the provided contracts. 21 | 22 | ## Handy Commands 🛠️ 23 | 24 | Here's a lineup of commands to assist you: 25 | 26 | - `yarn install`: Installs the required dependencies. 27 | - `yarn compile`: Compiles the contracts. 28 | - `yarn deploy:factory`: Deploys your contracts smoothly. 29 | - `yarn deploy:multisig`: Executes the `deploy-multisig.ts` script. 30 | - `yarn test`: Runs tests. 31 | 32 | ### Environment variables 🌳 33 | 34 | To prevent the leakage of private keys, we use the `dotenv` package to load environment variables. This is particularly used to load the wallet private key, which is required to run the deployment script. 35 | 36 | To use it, rename `.env.example` to `.env` and input your private key. 37 | 38 | ``` 39 | WALLET_PRIVATE_KEY=123cde574ccff.... 40 | ``` 41 | 42 | ### Local testing 🧪 43 | 44 | Local tests make use of the in-memory-node thanks to the `hardhat-zksync-node` plugin. Please refer to [this section of the docs](https://era.zksync.io/docs/tools/testing/) for more details. 45 | 46 | ## Stay Connected 🌐 47 | 48 | - [zkSync's Documentation](https://era.zksync.io/docs/) 49 | - [GitHub](https://github.com/matter-labs) 50 | - [Twitter @zkSync](https://twitter.com/zksync) 51 | - [Twitter @zkSyncDevs](https://twitter.com/zkSyncDevs) 52 | - [Join our Discord Community](https://join.zksync.dev) 53 | -------------------------------------------------------------------------------- /custom-aa/contracts/AAFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; 5 | import "@matterlabs/zksync-contracts/l2/system-contracts/libraries/SystemContractsCaller.sol"; 6 | 7 | contract AAFactory { 8 | bytes32 public aaBytecodeHash; 9 | 10 | constructor(bytes32 _aaBytecodeHash) { 11 | aaBytecodeHash = _aaBytecodeHash; 12 | } 13 | 14 | function deployAccount( 15 | bytes32 salt, 16 | address owner1, 17 | address owner2 18 | ) external returns (address accountAddress) { 19 | (bool success, bytes memory returnData) = SystemContractsCaller 20 | .systemCallWithReturndata( 21 | uint32(gasleft()), 22 | address(DEPLOYER_SYSTEM_CONTRACT), 23 | uint128(0), 24 | abi.encodeCall( 25 | DEPLOYER_SYSTEM_CONTRACT.create2Account, 26 | (salt, aaBytecodeHash, abi.encode(owner1, owner2), IContractDeployer.AccountAbstractionVersion.Version1) 27 | ) 28 | ); 29 | require(success, "Deployment failed"); 30 | 31 | (accountAddress) = abi.decode(returnData, (address)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /custom-aa/deploy/deploy-factory.ts: -------------------------------------------------------------------------------- 1 | import { utils, Wallet } from "zksync-ethers"; 2 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 3 | import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; 4 | import dotenv from "dotenv"; 5 | 6 | // Load env file 7 | dotenv.config(); 8 | 9 | const PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY || ""; 10 | 11 | export default async function (hre: HardhatRuntimeEnvironment) { 12 | // Private key of the account used to deploy 13 | const wallet = new Wallet(PRIVATE_KEY); 14 | const deployer = new Deployer(hre, wallet); 15 | const factoryArtifact = await deployer.loadArtifact("AAFactory"); 16 | const aaArtifact = await deployer.loadArtifact("TwoUserMultisig"); 17 | 18 | // Getting the bytecodeHash of the account 19 | const bytecodeHash = utils.hashBytecode(aaArtifact.bytecode); 20 | 21 | const factory = await deployer.deploy( 22 | factoryArtifact, 23 | [bytecodeHash], 24 | undefined, 25 | [ 26 | // Since the factory requires the code of the multisig to be available, 27 | // we should pass it here as well. 28 | aaArtifact.bytecode, 29 | ], 30 | ); 31 | 32 | const factoryAddress = await factory.getAddress(); 33 | 34 | console.log(`AA factory address: ${factoryAddress}`); 35 | 36 | const verificationRequestId: number = await hre.run("verify:verify", { 37 | address: factoryAddress, 38 | contract: `${factoryArtifact.sourceName}:${factoryArtifact.contractName}`, 39 | constructorArguments: [bytecodeHash], 40 | bytecode: factoryArtifact.bytecode, 41 | // don't compile contract before sending verification request 42 | noCompile: true, 43 | }); 44 | 45 | console.log(`AA factory verification request id: ${verificationRequestId}`); 46 | } 47 | -------------------------------------------------------------------------------- /custom-aa/deploy/deploy-multisig.ts: -------------------------------------------------------------------------------- 1 | import { utils, Wallet, Provider, EIP712Signer, types } from "zksync-ethers"; 2 | import * as ethers from "ethers"; 3 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 4 | 5 | import dotenv from "dotenv"; 6 | 7 | // Load env file 8 | dotenv.config(); 9 | 10 | const PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY || ""; 11 | 12 | // Put the address of your AA factory 13 | const AA_FACTORY_ADDRESS = ""; 14 | 15 | export default async function (hre: HardhatRuntimeEnvironment) { 16 | const provider = new Provider("https://sepolia.era.zksync.dev"); 17 | // const provider = new Provider('http://127.0.0.1:8011') 18 | // Private key of the account used to deploy 19 | const wallet = new Wallet(PRIVATE_KEY).connect(provider); 20 | const factoryArtifact = await hre.artifacts.readArtifact("AAFactory"); 21 | 22 | const aaFactory = new ethers.Contract( 23 | AA_FACTORY_ADDRESS, 24 | factoryArtifact.abi, 25 | wallet, 26 | ); 27 | 28 | // The two owners of the multisig 29 | const owner1 = Wallet.createRandom(); 30 | const owner2 = Wallet.createRandom(); 31 | 32 | // For the simplicity of the tutorial, we will use zero hash as salt 33 | const salt = ethers.ZeroHash; 34 | 35 | // deploy account owned by owner1 & owner2 36 | const tx = await aaFactory.deployAccount( 37 | salt, 38 | owner1.address, 39 | owner2.address, 40 | ); 41 | await tx.wait(); 42 | 43 | // Getting the address of the deployed contract account 44 | // Always use the JS utility methods 45 | const abiCoder = new ethers.AbiCoder(); 46 | 47 | const multisigAddress = utils.create2Address( 48 | AA_FACTORY_ADDRESS, 49 | await aaFactory.aaBytecodeHash(), 50 | salt, 51 | abiCoder.encode(["address", "address"], [owner1.address, owner2.address]), 52 | ); 53 | console.log(`Multisig account deployed on address ${multisigAddress}`); 54 | 55 | console.log("Sending funds to multisig account"); 56 | // Send funds to the multisig account we just deployed 57 | await ( 58 | await wallet.sendTransaction({ 59 | to: multisigAddress, 60 | // You can increase the amount of ETH sent to the multisig 61 | value: ethers.parseEther("0.008"), 62 | nonce: await wallet.getNonce(), 63 | }) 64 | ).wait(); 65 | 66 | let multisigBalance = await provider.getBalance(multisigAddress); 67 | 68 | console.log(`Multisig account balance is ${multisigBalance.toString()}`); 69 | 70 | // Transaction to deploy a new account using the multisig we just deployed 71 | let aaTx = await aaFactory.deployAccount.populateTransaction( 72 | salt, 73 | // These are accounts that will own the newly deployed account 74 | Wallet.createRandom().address, 75 | Wallet.createRandom().address, 76 | ); 77 | 78 | const gasLimit = await provider.estimateGas({ 79 | ...aaTx, 80 | from: wallet.address, 81 | }); 82 | const gasPrice = await provider.getGasPrice(); 83 | 84 | aaTx = { 85 | ...aaTx, 86 | // deploy a new account using the multisig 87 | from: multisigAddress, 88 | gasLimit: gasLimit, 89 | gasPrice: gasPrice, 90 | chainId: (await provider.getNetwork()).chainId, 91 | nonce: await provider.getTransactionCount(multisigAddress), 92 | type: 113, 93 | customData: { 94 | gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, 95 | } as types.Eip712Meta, 96 | value: 0n, 97 | }; 98 | 99 | const signedTxHash = EIP712Signer.getSignedDigest(aaTx); 100 | 101 | // Sign the transaction with both owners 102 | const signature = ethers.concat([ 103 | ethers.Signature.from(owner1.signingKey.sign(signedTxHash)).serialized, 104 | ethers.Signature.from(owner2.signingKey.sign(signedTxHash)).serialized, 105 | ]); 106 | 107 | aaTx.customData = { 108 | ...aaTx.customData, 109 | customSignature: signature, 110 | }; 111 | 112 | console.log( 113 | `The multisig's nonce before the first tx is ${await provider.getTransactionCount( 114 | multisigAddress, 115 | )}`, 116 | ); 117 | 118 | const sentTx = await provider.broadcastTransaction( 119 | types.Transaction.from(aaTx).serialized, 120 | ); 121 | console.log(`Transaction sent from multisig with hash ${sentTx.hash}`); 122 | 123 | await sentTx.wait(); 124 | 125 | // Checking that the nonce for the account has increased 126 | console.log( 127 | `The multisig's nonce after the first tx is ${await provider.getTransactionCount( 128 | multisigAddress, 129 | )}`, 130 | ); 131 | 132 | multisigBalance = await provider.getBalance(multisigAddress); 133 | 134 | console.log(`Multisig account balance is now ${multisigBalance.toString()}`); 135 | } 136 | -------------------------------------------------------------------------------- /custom-aa/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig } from "hardhat/config"; 2 | 3 | import "@matterlabs/hardhat-zksync-deploy"; 4 | import "@matterlabs/hardhat-zksync-solc"; 5 | import "@matterlabs/hardhat-zksync-node"; 6 | import "@matterlabs/hardhat-zksync-verify"; 7 | import "@matterlabs/hardhat-zksync-chai-matchers"; 8 | 9 | const config: HardhatUserConfig = { 10 | zksolc: { 11 | version: "latest", 12 | settings: { 13 | isSystem: true, 14 | }, 15 | }, 16 | defaultNetwork: "zkSyncSepoliaTestnet", 17 | networks: { 18 | zkSyncSepoliaTestnet: { 19 | url: "https://sepolia.era.zksync.dev", 20 | ethNetwork: "sepolia", 21 | zksync: true, 22 | verifyURL: 23 | "https://explorer.sepolia.era.zksync.dev/contract_verification", 24 | }, 25 | zkSyncMainnet: { 26 | url: "https://mainnet.era.zksync.io", 27 | ethNetwork: "mainnet", 28 | zksync: true, 29 | verifyURL: 30 | "https://zksync2-mainnet-explorer.zksync.io/contract_verification", 31 | }, 32 | zkSyncGoerliTestnet: { 33 | // deprecated network 34 | url: "https://testnet.era.zksync.dev", 35 | ethNetwork: "goerli", 36 | zksync: true, 37 | verifyURL: 38 | "https://zksync2-testnet-explorer.zksync.dev/contract_verification", 39 | }, 40 | dockerizedNode: { 41 | url: "http://localhost:3050", 42 | ethNetwork: "http://localhost:8545", 43 | zksync: true, 44 | }, 45 | inMemoryNode: { 46 | url: "http://127.0.0.1:8011", 47 | ethNetwork: "", // in-memory node doesn't support eth node; removing this line will cause an error 48 | zksync: true, 49 | }, 50 | hardhat: { 51 | zksync: true, 52 | }, 53 | }, 54 | solidity: { 55 | version: "0.8.17", 56 | }, 57 | }; 58 | 59 | export default config; 60 | -------------------------------------------------------------------------------- /custom-aa/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "custom-aa-tutorial", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "compile": "hardhat compile", 8 | "fix:fmt": "prettier --write \"{deploy,test}/**/*.{ts,js,jsx,tsx}\"", 9 | "lint:fmt": "prettier --check \"{deploy,test}/**/*.{ts,js,jsx,tsx}\"", 10 | "test": "hardhat test --network hardhat", 11 | "deploy:factory": "hardhat deploy-zksync --script deploy-factory.ts", 12 | "deploy:multisig": "hardhat deploy-zksync --script deploy-multisig.ts" 13 | }, 14 | "devDependencies": { 15 | "@matterlabs/hardhat-zksync-chai-matchers": "^1.3.0", 16 | "@matterlabs/hardhat-zksync-deploy": "^1.2.0", 17 | "@matterlabs/hardhat-zksync-node": "^1.0.0", 18 | "@matterlabs/hardhat-zksync-solc": "^1.0.6", 19 | "@matterlabs/zksync-contracts": "^0.6.1", 20 | "@nomicfoundation/hardhat-chai-matchers": "^2.0.1", 21 | "@nomiclabs/hardhat-ethers": "^2.2.3", 22 | "@openzeppelin/contracts": "^4.9.5", 23 | "@types/chai": "^4.3.5", 24 | "@types/mocha": "^10.0.1", 25 | "chai": "^4.3.7", 26 | "chai-as-promised": "^7.1.1", 27 | "dotenv": "^16.3.1", 28 | "ethers": "^6.7.0", 29 | "hardhat": "^2.20.1", 30 | "ts-node": "^10.9.1", 31 | "typescript": "^4.8.4", 32 | "zksync-ethers": "^6.3.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /custom-paymaster/.env.example: -------------------------------------------------------------------------------- 1 | WALLET_PRIVATE_KEY= -------------------------------------------------------------------------------- /custom-paymaster/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts-zk 3 | cache-zk 4 | .env -------------------------------------------------------------------------------- /custom-paymaster/.prettierignore: -------------------------------------------------------------------------------- 1 | .tmp 2 | .cache/ 3 | coverage/ 4 | .nyc_output/ 5 | **/.yarn/** 6 | **/.pnp.* 7 | /dist*/ 8 | node_modules/ -------------------------------------------------------------------------------- /custom-paymaster/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": false, 5 | "printWidth": 80, 6 | "tabWidth": 2 7 | } 8 | -------------------------------------------------------------------------------- /custom-paymaster/README.md: -------------------------------------------------------------------------------- 1 | # Custom Paymaster Tutorial 📖 2 | 3 | Welcome aboard to the custom paymaster journey with zkSync! 🚀🌌 4 | 5 | This repository is crafted to guide you through the process of building a custom paymaster on zkSync Era. Coupled with this, you'll find a practical, easy-to-follow guide to implement and understand every step [here](https://era.zksync.io/docs/dev/tutorials/custom-paymaster-tutorial.html). 6 | 7 | ## Need Assistance? 💡 8 | 9 | If you're stumbling upon any issues or uncertainties: 10 | 11 | - 📖 Explore the [custom paymaster tutorial](https://era.zksync.io/docs/dev/tutorials/custom-paymaster-tutorial.html) for a comprehensive walkthrough of the code in this repository. 12 | - 🗣️ Or simply [reach out on Discord](https://join.zksync.dev/). We're always here to help! 13 | 14 | ## Repository Overview 📂 15 | 16 | Dive into the key sections of this repository: 17 | 18 | - `/contracts`: All the essential smart contracts you need are neatly stored here. 19 | 20 | - `/deploy`: Discover deployment and usage scripts tailored to assist your development process. 21 | 22 | - `/test`: Unit tests for the provided contracts. 23 | 24 | ## Handy Commands 🛠️ 25 | 26 | Here's a lineup of commands to assist you: 27 | 28 | - `yarn install`: Installs the required dependencies. 29 | - `yarn compile`: Compiles the contracts. 30 | - `yarn deploy-pm`: Deploys your contracts smoothly. 31 | - `yarn use-pm`: Executes the `use-paymaster.ts` script. 32 | - `yarn test`: Runs tests. 33 | 34 | ### Environment variables 🌳 35 | 36 | To prevent the leakage of private keys, we use the `dotenv` package to load environment variables. This is particularly used to load the wallet private key, which is required to run the deployment script. 37 | 38 | To use it, rename `.env.example` to `.env` and input your private key. 39 | 40 | ``` 41 | WALLET_PRIVATE_KEY=123cde574ccff.... 42 | ``` 43 | 44 | ### Local testing 🧪 45 | 46 | Local tests make use of the in-memory-node thanks to the `hardhat-zksync-node` plugin. Please refer to [this section of the docs](https://era.zksync.io/docs/tools/testing/) for more details. 47 | 48 | ## Stay Connected 🌐 49 | 50 | - [zkSync's Documentation](https://era.zksync.io/docs/) 51 | - [GitHub](https://github.com/matter-labs) 52 | - [Twitter @zkSync](https://twitter.com/zksync) 53 | - [Twitter @zkSyncDevs](https://twitter.com/zkSyncDevs) 54 | - [Join our Discord Community](https://join.zksync.dev) 55 | -------------------------------------------------------------------------------- /custom-paymaster/contracts/MyERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | 7 | contract MyERC20 is ERC20 { 8 | uint8 private _decimals; 9 | 10 | constructor( 11 | string memory name_, 12 | string memory symbol_, 13 | uint8 decimals_ 14 | ) ERC20(name_, symbol_) { 15 | _decimals = decimals_; 16 | } 17 | 18 | function mint(address _to, uint256 _amount) public returns (bool) { 19 | _mint(_to, _amount); 20 | return true; 21 | } 22 | 23 | function decimals() public view override returns (uint8) { 24 | return _decimals; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /custom-paymaster/contracts/MyPaymaster.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | 6 | import {IPaymaster, ExecutionResult, PAYMASTER_VALIDATION_SUCCESS_MAGIC} from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymaster.sol"; 7 | import {IPaymasterFlow} from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymasterFlow.sol"; 8 | import {TransactionHelper, Transaction} from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol"; 9 | 10 | import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; 11 | 12 | contract MyPaymaster is IPaymaster { 13 | uint256 constant PRICE_FOR_PAYING_FEES = 1; 14 | 15 | address public allowedToken; 16 | 17 | modifier onlyBootloader() { 18 | require( 19 | msg.sender == BOOTLOADER_FORMAL_ADDRESS, 20 | "Only bootloader can call this method" 21 | ); 22 | // Continue execution if called from the bootloader. 23 | _; 24 | } 25 | 26 | constructor(address _erc20) { 27 | allowedToken = _erc20; 28 | } 29 | 30 | function validateAndPayForPaymasterTransaction( 31 | bytes32, 32 | bytes32, 33 | Transaction calldata _transaction 34 | ) 35 | external 36 | payable 37 | onlyBootloader 38 | returns (bytes4 magic, bytes memory context) 39 | { 40 | // By default we consider the transaction as accepted. 41 | magic = PAYMASTER_VALIDATION_SUCCESS_MAGIC; 42 | require( 43 | _transaction.paymasterInput.length >= 4, 44 | "The standard paymaster input must be at least 4 bytes long" 45 | ); 46 | 47 | bytes4 paymasterInputSelector = bytes4( 48 | _transaction.paymasterInput[0:4] 49 | ); 50 | if (paymasterInputSelector == IPaymasterFlow.approvalBased.selector) { 51 | // While the transaction data consists of address, uint256 and bytes data, 52 | // the data is not needed for this paymaster 53 | (address token, uint256 amount, bytes memory data) = abi.decode( 54 | _transaction.paymasterInput[4:], 55 | (address, uint256, bytes) 56 | ); 57 | 58 | // Verify if token is the correct one 59 | require(token == allowedToken, "Invalid token"); 60 | 61 | // We verify that the user has provided enough allowance 62 | address userAddress = address(uint160(_transaction.from)); 63 | 64 | address thisAddress = address(this); 65 | 66 | uint256 providedAllowance = IERC20(token).allowance( 67 | userAddress, 68 | thisAddress 69 | ); 70 | require( 71 | providedAllowance >= PRICE_FOR_PAYING_FEES, 72 | "Min allowance too low" 73 | ); 74 | 75 | // Note, that while the minimal amount of ETH needed is tx.gasPrice * tx.gasLimit, 76 | // neither paymaster nor account are allowed to access this context variable. 77 | uint256 requiredETH = _transaction.gasLimit * 78 | _transaction.maxFeePerGas; 79 | 80 | try 81 | IERC20(token).transferFrom(userAddress, thisAddress, amount) 82 | {} catch (bytes memory revertReason) { 83 | // If the revert reason is empty or represented by just a function selector, 84 | // we replace the error with a more user-friendly message 85 | if (revertReason.length <= 4) { 86 | revert("Failed to transferFrom from user\'s account"); 87 | } else { 88 | assembly { 89 | revert(add(0x20, revertReason), mload(revertReason)) 90 | } 91 | } 92 | } 93 | 94 | // The bootloader never returns any data, so it can safely be ignored here. 95 | (bool success, ) = payable(BOOTLOADER_FORMAL_ADDRESS).call{ 96 | value: requiredETH 97 | }(""); 98 | require( 99 | success, 100 | "Failed to transfer tx fee to the bootloader. Paymaster balance might not be enough." 101 | ); 102 | } else { 103 | revert("Unsupported paymaster flow"); 104 | } 105 | } 106 | 107 | function postTransaction( 108 | bytes calldata _context, 109 | Transaction calldata _transaction, 110 | bytes32, 111 | bytes32, 112 | ExecutionResult _txResult, 113 | uint256 _maxRefundedGas 114 | ) external payable override onlyBootloader { 115 | // Refunds are not supported yet. 116 | } 117 | 118 | receive() external payable {} 119 | } 120 | -------------------------------------------------------------------------------- /custom-paymaster/deploy/deploy-paymaster.ts: -------------------------------------------------------------------------------- 1 | import { deployContract, getWallet, getProvider } from "./utils"; 2 | import * as ethers from "ethers"; 3 | 4 | export default async function () { 5 | const erc20 = await deployContract("MyERC20", ["MyToken", "MyToken", 18]); 6 | const erc20Address = await erc20.getAddress(); 7 | const paymaster = await deployContract("MyPaymaster", [erc20Address]); 8 | 9 | const paymasterAddress = await paymaster.getAddress(); 10 | 11 | // Supplying paymaster with ETH 12 | console.log("Funding paymaster with ETH..."); 13 | const wallet = getWallet(); 14 | await ( 15 | await wallet.sendTransaction({ 16 | to: paymasterAddress, 17 | value: ethers.parseEther("0.06"), 18 | }) 19 | ).wait(); 20 | 21 | const provider = getProvider(); 22 | const paymasterBalance = await provider.getBalance(paymasterAddress); 23 | console.log(`Paymaster ETH balance is now ${paymasterBalance.toString()}`); 24 | 25 | // Supplying the ERC20 tokens to the wallet: 26 | // We will give the wallet 3 units of the token: 27 | await (await erc20.mint(wallet.address, 3)).wait(); 28 | 29 | console.log("Minted 3 tokens for the wallet"); 30 | console.log(`Done!`); 31 | } 32 | -------------------------------------------------------------------------------- /custom-paymaster/deploy/use-paymaster.ts: -------------------------------------------------------------------------------- 1 | import { utils, Wallet } from "zksync-ethers"; 2 | import { getWallet, getProvider } from "./utils"; 3 | import * as ethers from "ethers"; 4 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 5 | 6 | // Put the address of the deployed paymaster here 7 | const PAYMASTER_ADDRESS = "0x08f62b10f5C949Af8d6d8656F86A0Cc3436FB31a"; 8 | 9 | // Put the address of the ERC20 token here: 10 | const TOKEN_ADDRESS = "0x03615ff4Af613BC55206E179dAccC5631CaA00B6"; 11 | 12 | function getToken(hre: HardhatRuntimeEnvironment, wallet: Wallet) { 13 | const artifact = hre.artifacts.readArtifactSync("MyERC20"); 14 | return new ethers.Contract(TOKEN_ADDRESS, artifact.abi, wallet); 15 | } 16 | 17 | export default async function (hre: HardhatRuntimeEnvironment) { 18 | const provider = getProvider(); 19 | const wallet = getWallet(); 20 | 21 | console.log( 22 | `ERC20 token balance of the wallet before mint: ${await wallet.getBalance( 23 | TOKEN_ADDRESS, 24 | )}`, 25 | ); 26 | 27 | let paymasterBalance = await provider.getBalance(PAYMASTER_ADDRESS); 28 | console.log(`Paymaster ETH balance is ${paymasterBalance.toString()}`); 29 | 30 | const erc20 = getToken(hre, wallet); 31 | const gasPrice = await provider.getGasPrice(); 32 | 33 | // Encoding the "ApprovalBased" paymaster flow's input 34 | const paymasterParams = utils.getPaymasterParams(PAYMASTER_ADDRESS, { 35 | type: "ApprovalBased", 36 | token: TOKEN_ADDRESS, 37 | // set minimalAllowance as we defined in the paymaster contract 38 | minimalAllowance: BigInt("1"), 39 | // empty bytes as testnet paymaster does not use innerInput 40 | innerInput: new Uint8Array(), 41 | }); 42 | 43 | // Estimate gas fee for mint transaction 44 | const gasLimit = await erc20.mint.estimateGas(wallet.address, 5, { 45 | customData: { 46 | gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, 47 | paymasterParams: paymasterParams, 48 | }, 49 | }); 50 | 51 | const fee = gasPrice * gasLimit; 52 | console.log("Transaction fee estimation is :>> ", fee.toString()); 53 | 54 | console.log(`Minting 5 tokens for the wallet via paymaster...`); 55 | await ( 56 | await erc20.mint(wallet.address, 5, { 57 | // paymaster info 58 | customData: { 59 | paymasterParams: paymasterParams, 60 | gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, 61 | }, 62 | }) 63 | ).wait(); 64 | 65 | console.log( 66 | `Paymaster ERC20 token balance is now ${await erc20.balanceOf( 67 | PAYMASTER_ADDRESS, 68 | )}`, 69 | ); 70 | paymasterBalance = await provider.getBalance(PAYMASTER_ADDRESS); 71 | 72 | console.log(`Paymaster ETH balance is now ${paymasterBalance.toString()}`); 73 | console.log( 74 | `ERC20 token balance of the the wallet after mint: ${await wallet.getBalance( 75 | TOKEN_ADDRESS, 76 | )}`, 77 | ); 78 | } 79 | -------------------------------------------------------------------------------- /custom-paymaster/deployments/zkSyncEraTestNode/.chainId: -------------------------------------------------------------------------------- 1 | 0x104 -------------------------------------------------------------------------------- /custom-paymaster/deployments/zkSyncSepoliaTestnet/.chainId: -------------------------------------------------------------------------------- 1 | 0x12c -------------------------------------------------------------------------------- /custom-paymaster/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig } from "hardhat/config"; 2 | 3 | import "@matterlabs/hardhat-zksync-deploy"; 4 | import "@matterlabs/hardhat-zksync-solc"; 5 | import "@matterlabs/hardhat-zksync-node"; 6 | import "@matterlabs/hardhat-zksync-verify"; 7 | 8 | const config: HardhatUserConfig = { 9 | zksolc: { 10 | version: "latest", 11 | settings: { 12 | isSystem: true, 13 | }, 14 | }, 15 | defaultNetwork: "zkSyncSepoliaTestnet", 16 | networks: { 17 | zkSyncSepoliaTestnet: { 18 | url: "https://sepolia.era.zksync.dev", 19 | ethNetwork: "sepolia", 20 | zksync: true, 21 | verifyURL: 22 | "https://explorer.sepolia.era.zksync.dev/contract_verification", 23 | }, 24 | zkSyncMainnet: { 25 | url: "https://mainnet.era.zksync.io", 26 | ethNetwork: "mainnet", 27 | zksync: true, 28 | verifyURL: 29 | "https://zksync2-mainnet-explorer.zksync.io/contract_verification", 30 | }, 31 | zkSyncGoerliTestnet: { 32 | // deprecated network 33 | url: "https://testnet.era.zksync.dev", 34 | ethNetwork: "goerli", 35 | zksync: true, 36 | verifyURL: 37 | "https://zksync2-testnet-explorer.zksync.dev/contract_verification", 38 | }, 39 | dockerizedNode: { 40 | url: "http://localhost:3050", 41 | ethNetwork: "http://localhost:8545", 42 | zksync: true, 43 | }, 44 | inMemoryNode: { 45 | url: "http://127.0.0.1:8011", 46 | ethNetwork: "", // in-memory node doesn't support eth node; removing this line will cause an error 47 | zksync: true, 48 | }, 49 | hardhat: { 50 | zksync: true, 51 | }, 52 | }, 53 | solidity: { 54 | version: "0.8.17", 55 | }, 56 | }; 57 | 58 | export default config; 59 | -------------------------------------------------------------------------------- /custom-paymaster/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "custom-paymaster-tutorial", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "devDependencies": { 7 | "@matterlabs/hardhat-zksync-chai-matchers": "^1.3.0", 8 | "@matterlabs/hardhat-zksync-deploy": "^1.2.0", 9 | "@matterlabs/hardhat-zksync-node": "^1.0.0", 10 | "@matterlabs/hardhat-zksync-solc": "^1.0.6", 11 | "@matterlabs/zksync-contracts": "^0.6.1", 12 | "@matterlabs/hardhat-zksync-verify": "^1.4.0", 13 | "@nomicfoundation/hardhat-chai-matchers": "^2.0.1", 14 | "@nomiclabs/hardhat-ethers": "^2.2.3", 15 | "@openzeppelin/contracts": "^4.7.3", 16 | "@types/chai": "^4.3.5", 17 | "@types/mocha": "^10.0.1", 18 | "chai": "^4.3.7", 19 | "chai-as-promised": "^7.1.1", 20 | "dotenv": "^16.3.1", 21 | "ethers": "^6.7.2", 22 | "hardhat": "^2.12.0", 23 | "prettier": "^3.0.0", 24 | "ts-node": "^10.9.1", 25 | "typescript": "^4.8.4", 26 | "zksync-ethers": "^6.4.0" 27 | }, 28 | "scripts": { 29 | "test": "NODE_ENV=test hardhat test", 30 | "deploy-pm": "hardhat deploy-zksync --script deploy-paymaster.ts", 31 | "use-pm": "hardhat deploy-zksync --script use-paymaster.ts", 32 | "compile": "hardhat compile", 33 | "fix:fmt": "prettier --write \"{deploy,test}/**/*.{ts,js,jsx,tsx}\"", 34 | "lint:fmt": "prettier --check \"{deploy,test}/**/*.{ts,js,jsx,tsx}\"" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /custom-paymaster/test/MyERC20.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { Wallet, Provider, Contract } from "zksync-ethers"; 3 | import * as hre from "hardhat"; 4 | import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; 5 | import * as ethers from "ethers"; 6 | import "@matterlabs/hardhat-zksync-chai-matchers"; 7 | 8 | import { deployContract, fundAccount } from "./utils"; 9 | 10 | import dotenv from "dotenv"; 11 | dotenv.config(); 12 | 13 | // rich wallet from era-test-node 14 | const PRIVATE_KEY = 15 | process.env.WALLET_PRIVATE_KEY || 16 | "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110"; 17 | 18 | describe("MyERC20", function () { 19 | let provider: Provider; 20 | let wallet: Wallet; 21 | let deployer: Deployer; 22 | let userWallet: Wallet; 23 | let erc20: Contract; 24 | 25 | before(async function () { 26 | provider = new Provider(hre.network.config.url); 27 | wallet = new Wallet(PRIVATE_KEY, provider); 28 | deployer = new Deployer(hre, wallet); 29 | 30 | userWallet = Wallet.createRandom(); 31 | userWallet = new Wallet(userWallet.privateKey, provider); 32 | 33 | erc20 = await deployContract(deployer, "MyERC20", ["TestToken", "TTK", 18]); 34 | }); 35 | 36 | it("should mint tokens to the specified address", async function () { 37 | const amount = BigInt(100); 38 | await erc20.connect(wallet); 39 | const tx = await erc20.mint(userWallet.address, amount); 40 | await tx.wait(); 41 | const balance = await erc20.balanceOf(userWallet.address); 42 | expect(balance).to.be.eql(amount); 43 | }); 44 | 45 | it("should have correct decimals", async function () { 46 | const decimals = await erc20.decimals(); 47 | expect(decimals).to.be.eql(18n); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /custom-paymaster/test/MyPaymaster.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { Wallet, Provider, Contract, utils } from "zksync-ethers"; 3 | import * as hre from "hardhat"; 4 | import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; 5 | import * as ethers from "ethers"; 6 | import "@matterlabs/hardhat-zksync-chai-matchers"; 7 | 8 | import { deployContract, fundAccount } from "./utils"; 9 | 10 | import dotenv from "dotenv"; 11 | dotenv.config(); 12 | 13 | // rich wallet from era-test-node 14 | const PRIVATE_KEY = 15 | process.env.WALLET_PRIVATE_KEY || 16 | "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110"; 17 | const GAS_LIMIT = 6000000; 18 | 19 | describe("MyPaymaster", function () { 20 | let provider: Provider; 21 | let wallet: Wallet; 22 | let deployer: Deployer; 23 | let userWallet: Wallet; 24 | let initialBalance: ethers.BigNumber; 25 | let paymaster: Contract; 26 | let erc20: Contract; 27 | 28 | before(async function () { 29 | provider = new Provider(hre.network.config.url); 30 | wallet = new Wallet(PRIVATE_KEY, provider); 31 | deployer = new Deployer(hre, wallet); 32 | 33 | userWallet = Wallet.createRandom(); 34 | userWallet = new Wallet(userWallet.privateKey, provider); 35 | initialBalance = await userWallet.getBalance(); 36 | 37 | erc20 = await deployContract(deployer, "MyERC20", ["TestToken", "TTK", 18]); 38 | paymaster = await deployContract(deployer, "MyPaymaster", [ 39 | await erc20.getAddress(), 40 | ]); 41 | 42 | await fundAccount(wallet, await paymaster.getAddress(), "13"); 43 | await (await erc20.mint(userWallet.address, 130)).wait(); 44 | }); 45 | 46 | async function getToken(wallet: Wallet) { 47 | const artifact = hre.artifacts.readArtifactSync("MyERC20"); 48 | return new Contract(await erc20.getAddress(), artifact.abi, wallet); 49 | } 50 | 51 | async function executeTransaction( 52 | user: Wallet, 53 | payType: "ApprovalBased" | "General", 54 | tokenAddress: string, 55 | ) { 56 | const erc20Contract = await getToken(user); 57 | const gasPrice = await provider.getGasPrice(); 58 | 59 | erc20Contract.connect(user); 60 | 61 | const paymasterParams = utils.getPaymasterParams( 62 | await paymaster.getAddress(), 63 | { 64 | type: payType, 65 | token: tokenAddress, 66 | minimalAllowance: BigInt(1), 67 | innerInput: new Uint8Array(), 68 | }, 69 | ); 70 | 71 | await ( 72 | await erc20Contract.mint(userWallet.address, 5, { 73 | maxPriorityFeePerGas: BigInt(0), 74 | maxFeePerGas: gasPrice, 75 | gasLimit: GAS_LIMIT, 76 | from: user.address, 77 | type: 113, 78 | customData: { 79 | paymasterParams: paymasterParams, 80 | gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, 81 | }, 82 | }) 83 | ).wait(); 84 | } 85 | 86 | it("should validate and pay for paymaster transaction", async function () { 87 | await executeTransaction( 88 | userWallet, 89 | "ApprovalBased", 90 | await erc20.getAddress(), 91 | ); 92 | const newBalance = await userWallet.getBalance(); 93 | expect(newBalance).to.be.eql(initialBalance); 94 | }); 95 | 96 | it("should revert if unsupported paymaster flow", async function () { 97 | await expect( 98 | executeTransaction(userWallet, "General", await erc20.getAddress()), 99 | ).to.be.rejectedWith("Unsupported paymaster flow"); 100 | }); 101 | 102 | it("should revert if invalid token is provided", async function () { 103 | const invalidTokenAddress = "0x000000000000000000000000000000000000dead"; 104 | await expect( 105 | executeTransaction(userWallet, "ApprovalBased", invalidTokenAddress), 106 | ).to.be.rejectedWith("failed pre-paymaster preparation"); 107 | }); 108 | 109 | it("should revert if allowance is too low", async function () { 110 | const erc20Contract = await getToken(userWallet); 111 | await fundAccount(wallet, userWallet.address, "13"); 112 | await erc20Contract.approve(await paymaster.getAddress(), BigInt(0)); 113 | try { 114 | await executeTransaction( 115 | userWallet, 116 | "ApprovalBased", 117 | await erc20.getAddress().toString(), 118 | ); 119 | } catch (e) { 120 | expect(e.shortMessage).to.include("Min allowance too low"); 121 | } 122 | }); 123 | }); 124 | -------------------------------------------------------------------------------- /custom-paymaster/test/utils.ts: -------------------------------------------------------------------------------- 1 | import { Contract, Wallet } from "zksync-ethers"; 2 | import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; 3 | import * as ethers from "ethers"; 4 | 5 | async function deployContract( 6 | deployer: Deployer, 7 | contract: string, 8 | params: any[], 9 | ): Promise { 10 | const artifact = await deployer.loadArtifact(contract); 11 | const deploymentFee = await deployer.estimateDeployFee(artifact, params); 12 | const parsedFee = ethers.formatEther(deploymentFee.toString()); 13 | 14 | return await deployer.deploy(artifact, params); 15 | } 16 | 17 | async function fundAccount(wallet: Wallet, address: string, amount: string) { 18 | await ( 19 | await wallet.sendTransaction({ 20 | to: address, 21 | value: ethers.parseEther(amount), 22 | }) 23 | ).wait(); 24 | } 25 | 26 | export { deployContract, fundAccount }; 27 | -------------------------------------------------------------------------------- /gated-nft/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | .DS_Store -------------------------------------------------------------------------------- /gated-nft/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": false, 5 | "printWidth": 80, 6 | "tabWidth": 2 7 | } 8 | -------------------------------------------------------------------------------- /gated-nft/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Dustin Brickwood 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /gated-nft/README.md: -------------------------------------------------------------------------------- 1 | # gated-nft-tutorial-starter 💥🎉 2 | 3 | This repository serves as a starter template for developing a dApp that interacts with a 4 | gated NFT paymaster contract. 5 | 6 | Read the tutorial [here](https://era.zksync.io/docs/dev/tutorials/gated-nft-paymaster-tutorial.html). 7 | 8 | ## Official Links 🔗 9 | 10 | For more information and support, visit our official channels: 11 | 12 | - [Website](https://zksync.io/) 13 | - [Documentation](https://era.zksync.io/docs/dev/tutorials/gated-nft-paymaster-tutorial.html) 14 | - [GitHub](https://github.com/matter-labs) 15 | - [Twitter](https://twitter.com/zksync) 16 | - [Discord](https://join.zksync.dev/) 17 | 18 | Jump in, and let's make the most of paymasters together! 🚀 19 | -------------------------------------------------------------------------------- /gated-nft/frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /gated-nft/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /gated-nft/frontend/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | ``` 14 | 15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 16 | 17 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 18 | 19 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /gated-nft/frontend/app/assets/zkSync_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matter-labs/tutorials/a34d7dcb33204b8105fbb855a25c24fcadad44d6/gated-nft/frontend/app/assets/zkSync_logo.png -------------------------------------------------------------------------------- /gated-nft/frontend/app/components/GreeterMessage.tsx: -------------------------------------------------------------------------------- 1 | type GreetingProps = { 2 | greeting: string; 3 | }; 4 | 5 | const Greeting = ({ greeting }: GreetingProps) => { 6 | return ( 7 |
8 |

Greeter says:

9 |

{greeting} 👋

10 |
11 | ); 12 | }; 13 | 14 | export default Greeting; 15 | -------------------------------------------------------------------------------- /gated-nft/frontend/app/components/Input.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { PencilIcon } from "@heroicons/react/20/solid"; 3 | import React, { useState, useEffect } from "react"; 4 | import Modal from "./Modal"; 5 | import * as ethers from "ethers"; 6 | import { InputProps } from "../types/types"; 7 | 8 | export default function Input({ 9 | greeterInstance, 10 | setGreetingMessage, 11 | provider, 12 | nfts, 13 | }: InputProps) { 14 | // State variables 15 | const [isOpen, setIsOpen] = useState(false); 16 | const [message, setMessage] = useState(""); 17 | const [cost, setCost] = useState(""); 18 | const [price, setPrice] = useState(""); 19 | const [gas, setGas] = useState(""); 20 | 21 | useEffect(() => { 22 | if (message !== "") { 23 | getEstimate(); 24 | } 25 | }, [message]); 26 | 27 | const openModal = () => { 28 | setIsOpen(true); 29 | }; 30 | 31 | const closeModal = () => { 32 | setIsOpen(false); 33 | }; 34 | 35 | const handleInputChange = (event: React.ChangeEvent) => { 36 | setMessage(event.target.value); 37 | }; 38 | 39 | async function getEstimate() { 40 | // Get gas price 41 | if (!provider) return; 42 | let gasPrice = await provider.getGasPrice(); 43 | let price = ethers.utils.formatEther(gasPrice.toString()); 44 | setPrice(price); 45 | // Estimate gas required for transaction 46 | if (!greeterInstance) return; 47 | let gasEstimate = await greeterInstance.estimateGas["setGreeting"](message); 48 | let gas = ethers.utils.formatEther(gasEstimate.toString()); 49 | setGas(gas); 50 | // Calculate the cost: gasPrice * gasEstimate 51 | let transactionCost = gasPrice.mul(gasEstimate); 52 | let cost = ethers.utils.formatEther(transactionCost.toString()); 53 | // Set the cost state 54 | setCost(cost); 55 | } 56 | 57 | return ( 58 |
59 |
60 |
61 |
62 |
64 | 71 |
72 | 79 |
80 | {isOpen && ( 81 | 91 | )} 92 |
93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /gated-nft/frontend/app/components/Modal.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, useState } from "react"; 2 | import { Dialog, Transition } from "@headlessui/react"; 3 | import { XMarkIcon } from "@heroicons/react/24/outline"; 4 | import Checkout from "./Checkout"; 5 | import { ModalProps } from "../types/types"; 6 | 7 | export default function Modal({ 8 | closeModal, 9 | greeterInstance, 10 | message, 11 | setGreetingMessage, 12 | cost, 13 | price, 14 | gas, 15 | nfts, 16 | }: ModalProps) { 17 | const [open, setOpen] = useState(true); 18 | 19 | return ( 20 | 21 | 22 | 31 |
32 | 33 | 34 |
35 |
36 | 45 | 46 |
47 | 55 |
56 |
57 | 66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /gated-nft/frontend/app/components/Text.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | type TextProps = { 4 | children: React.ReactNode; 5 | }; 6 | 7 | const Text = ({ children }: TextProps) => { 8 | return ( 9 |
10 |

{children}

11 |
12 | ); 13 | }; 14 | 15 | export default Text; 16 | -------------------------------------------------------------------------------- /gated-nft/frontend/app/constants/consts.tsx: -------------------------------------------------------------------------------- 1 | export const GREETER_CONTRACT_ABI = 2 | require("../../../zksync/artifacts-zk/contracts/Greeter.sol/Greeter.json").abi; 3 | export const NFT_CONTRACT_ABI = 4 | require("../../../zksync/artifacts-zk/contracts/ERC721.sol/InfinityStones.json").abi; 5 | export const NETWORK_NAME = "zkSync Era Sepolia Testnet"; 6 | export const NETWORK_ID = "0x12c"; 7 | export const GREETER_ADDRESS = "YOUR-GREETER-ADDRESS"; 8 | export const NFT_CONTRACT_ADDRESS = 9 | "0x4B5DF730c2e6b28E17013A1485E5d9BC41Efe021"; 10 | export const PAYMASTER_CONTRACT_ADDRESS = 11 | "0x27d45764490b8C4135d1EC70130163791BDE6db5"; 12 | -------------------------------------------------------------------------------- /gated-nft/frontend/app/context/Web3Context.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Contract, Web3Provider, Signer } from "zksync-ethers"; 3 | import { PowerStoneNft } from "../types/types"; 4 | 5 | export interface Web3ContextType { 6 | greeterContractInstance: Contract | null; 7 | greeting: string; 8 | nfts: PowerStoneNft[]; 9 | provider: Web3Provider | null; 10 | signer: Signer | null; 11 | setGreeterContractInstance: (instance: Contract | null) => void; 12 | setGreetingMessage: React.Dispatch>; 13 | setNfts: (nfts: PowerStoneNft[]) => void; 14 | setProvider: (provider: Web3Provider | null) => void; 15 | setSigner: (signer: Signer | null) => void; 16 | } 17 | 18 | export const defaultWeb3State: Web3ContextType = { 19 | greeterContractInstance: null, 20 | greeting: "", 21 | nfts: [], 22 | provider: null, 23 | signer: null, 24 | setGreeterContractInstance: () => {}, 25 | setGreetingMessage: () => {}, 26 | setNfts: () => {}, 27 | setProvider: () => {}, 28 | setSigner: () => {}, 29 | }; 30 | 31 | const Web3Context = React.createContext(defaultWeb3State); 32 | 33 | export default Web3Context; 34 | -------------------------------------------------------------------------------- /gated-nft/frontend/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matter-labs/tutorials/a34d7dcb33204b8105fbb855a25c24fcadad44d6/gated-nft/frontend/app/favicon.ico -------------------------------------------------------------------------------- /gated-nft/frontend/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | -------------------------------------------------------------------------------- /gated-nft/frontend/app/hooks/usePaymaster.tsx: -------------------------------------------------------------------------------- 1 | import { Contract, utils } from "zksync-ethers"; 2 | import { PAYMASTER_CONTRACT_ADDRESS } from "../constants/consts"; 3 | import { PaymasterProps } from "../types/types"; 4 | import * as ethers from "ethers"; 5 | 6 | const usePaymaster = async ({ 7 | greeterInstance, 8 | message, 9 | price, 10 | }: PaymasterProps) => { 11 | let gasPrice = ethers.utils.parseEther(price); 12 | const paymasterParams = utils.getPaymasterParams(PAYMASTER_CONTRACT_ADDRESS, { 13 | type: "General", 14 | innerInput: new Uint8Array(), 15 | }); 16 | 17 | // estimate gasLimit via paymaster 18 | const gasLimit = await greeterInstance.estimateGas.setGreeting(message, { 19 | customData: { 20 | gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, 21 | paymasterParams: paymasterParams, 22 | }, 23 | }); 24 | 25 | return { 26 | maxFeePerGas: gasPrice, 27 | maxPriorityFeePerGas: ethers.BigNumber.from(0), 28 | gasLimit: gasLimit, 29 | customData: { 30 | gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, 31 | paymasterParams: paymasterParams, 32 | }, 33 | }; 34 | }; 35 | 36 | export default usePaymaster; 37 | -------------------------------------------------------------------------------- /gated-nft/frontend/app/layout.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import "./globals.css"; 4 | import { Inter } from "next/font/google"; 5 | import { useState } from "react"; 6 | import Web3Context from "./context/Web3Context"; 7 | import { PowerStoneNft } from "./types/powerStoneNft"; 8 | import { Contract, Web3Provider, Signer } from "zksync-ethers"; 9 | const inter = Inter({ subsets: ["latin"] }); 10 | 11 | export default function RootLayout({ 12 | children, 13 | }: { 14 | children: React.ReactNode; 15 | }) { 16 | const [greeterContractInstance, setGreeterContractInstance] = 17 | useState(null); 18 | const [greeting, setGreetingMessage] = useState(""); 19 | const [nfts, setNfts] = useState([]); 20 | const [provider, setProvider] = useState(null); 21 | const [signer, setSigner] = useState(null); 22 | return ( 23 | 24 | 38 | {children} 39 | 40 | 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /gated-nft/frontend/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useContext } from "react"; 4 | import Image from "next/image"; 5 | import Text from "./components/Text"; 6 | import WalletButton from "./components/WalletButton"; 7 | import Greeting from "./components/GreeterMessage"; 8 | import Input from "./components/Input"; 9 | import Web3Context from "./context/Web3Context"; 10 | import zkSyncImage from "./assets/zkSync_logo.png"; 11 | 12 | export default function Home() { 13 | const web3Context = useContext(Web3Context); 14 | 15 | return ( 16 |
17 |
18 | 19 |
20 |
21 | zkSync Era Logo 28 |
29 |
30 | 31 | Explore this demonstrative dApp showcasing the key benefits of 32 | Paymasters on zkSync Era. Enter a message, and discover if you own an 33 | Infinity Stone NFT. Lucky holders enjoy gas-free transactions, covered 34 | by Stark Industries paymaster. Give it a try now! 35 | 36 |
37 | 38 | 44 |
45 |
46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /gated-nft/frontend/app/types/types.d.ts: -------------------------------------------------------------------------------- 1 | import { Contract, Web3Provider } from "zksync-ethers"; 2 | 3 | type InputProps = { 4 | greeterInstance: Contract | null; 5 | setGreetingMessage: React.Dispatch>; 6 | provider: Web3Provider | null; 7 | nfts: PowerStoneNft[]; 8 | }; 9 | 10 | type CheckoutProps = { 11 | greeterInstance: Contract | null; 12 | message: string; 13 | setGreetingMessage: React.Dispatch>; 14 | cost: string; 15 | price: string; 16 | gas: string; 17 | nfts: PowerStoneNft[]; 18 | }; 19 | 20 | type GreeterData = { 21 | message: string; 22 | }; 23 | 24 | type ModalProps = { 25 | closeModal: () => void; 26 | greeterInstance: Contract | null; 27 | message: string; 28 | setGreetingMessage: React.Dispatch>; 29 | cost: string; 30 | price: string; 31 | gas: string; 32 | nfts: PowerStoneNft[]; 33 | }; 34 | 35 | export interface PowerStoneNft { 36 | attributes: PowerStoneAttributes[]; 37 | description: string; 38 | image: string; 39 | name: string; 40 | } 41 | 42 | export interface PowerStoneAttributes { 43 | trait_type: string; 44 | value: string; 45 | } 46 | 47 | type PaymasterProps = { 48 | greeterInstance: Contract; 49 | message: string; 50 | price: string; 51 | }; 52 | 53 | export { 54 | InputProps, 55 | CheckoutProps, 56 | GreeterData, 57 | ModalProps, 58 | PowerStoneNft, 59 | PowerStoneAttributes, 60 | PaymasterProps, 61 | }; 62 | -------------------------------------------------------------------------------- /gated-nft/frontend/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | module.exports = nextConfig; 5 | -------------------------------------------------------------------------------- /gated-nft/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@types/node": "20.3.3", 13 | "@types/react": "18.2.14", 14 | "@types/react-dom": "18.2.6", 15 | "autoprefixer": "10.4.14", 16 | "eslint": "8.44.0", 17 | "eslint-config-next": "13.4.8", 18 | "next": "13.4.8", 19 | "postcss": "8.4.24", 20 | "react": "18.2.0", 21 | "react-dom": "18.2.0", 22 | "tailwindcss": "3.3.2", 23 | "typescript": "5.1.6", 24 | "zksync-ethers": "^5" 25 | }, 26 | "devDependencies": { 27 | "@headlessui/react": "^1.7.15", 28 | "@heroicons/react": "^2.0.18", 29 | "@tailwindcss/forms": "^0.5.3", 30 | "ethers": "5.7.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /gated-nft/frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /gated-nft/frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 5 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 7 | ], 8 | theme: { 9 | extend: { 10 | backgroundImage: { 11 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 12 | "gradient-conic": 13 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", 14 | }, 15 | colors: { 16 | custom: "#002555", 17 | hover: "#558ce6", 18 | }, 19 | }, 20 | }, 21 | plugins: [], 22 | }; 23 | -------------------------------------------------------------------------------- /gated-nft/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "typeRoots": ["./types"], 28 | "exclude": ["node_modules"] 29 | } 30 | -------------------------------------------------------------------------------- /gated-nft/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gated-nft-tutorial-starter", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": "git@github.com:matter-labs/gated-nft-tutorial-starter.git", 6 | "author": "The Matter Labs Team ", 7 | "license": "MIT", 8 | "private": true, 9 | "workspaces": [ 10 | "zksync", 11 | "frontend" 12 | ], 13 | "scripts": { 14 | "serve:ui": "yarn workspace frontend dev", 15 | "deploy:contracts": "yarn workspace zksync deploy", 16 | "deploy:greeter": "yarn workspace zksync greeter", 17 | "deploy:nft": "yarn workspace zksync nft", 18 | "deploy:paymaster": "yarn workspace zksync paymaster", 19 | "compile:contracts": "yarn workspace zksync compile", 20 | "format": "prettier --write \"{frontend,zksync}/**/*.{ts,js,jsx,tsx}\"" 21 | }, 22 | "dependencies": { 23 | "prettier": "^3.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /gated-nft/zksync/.env.example: -------------------------------------------------------------------------------- 1 | WALLET_PRIVATE_KEY= 2 | -------------------------------------------------------------------------------- /gated-nft/zksync/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .vscode 9 | 10 | # hardhat artifacts 11 | artifacts 12 | cache 13 | 14 | # zksync artifacts 15 | artifacts-zk 16 | cache-zk 17 | 18 | # Diagnostic reports (https://nodejs.org/api/report.html) 19 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 20 | 21 | # Runtime data 22 | pids 23 | *.pid 24 | *.seed 25 | *.pid.lock 26 | 27 | # Directory for instrumented libs generated by jscoverage/JSCover 28 | lib-cov 29 | 30 | # Coverage directory used by tools like istanbul 31 | coverage 32 | *.lcov 33 | 34 | # nyc test coverage 35 | .nyc_output 36 | 37 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 38 | .grunt 39 | 40 | # Bower dependency directory (https://bower.io/) 41 | bower_components 42 | 43 | # node-waf configuration 44 | .lock-wscript 45 | 46 | # Compiled binary addons (https://nodejs.org/api/addons.html) 47 | build/Release 48 | 49 | # Dependency directories 50 | node_modules/ 51 | jspm_packages/ 52 | 53 | # TypeScript v1 declaration files 54 | typings/ 55 | 56 | # TypeScript cache 57 | *.tsbuildinfo 58 | 59 | # Optional npm cache directory 60 | .npm 61 | 62 | # Optional eslint cache 63 | .eslintcache 64 | 65 | # Microbundle cache 66 | .rpt2_cache/ 67 | .rts2_cache_cjs/ 68 | .rts2_cache_es/ 69 | .rts2_cache_umd/ 70 | 71 | # Optional REPL history 72 | .node_repl_history 73 | 74 | # Output of 'npm pack' 75 | *.tgz 76 | 77 | # Yarn Integrity file 78 | .yarn-integrity 79 | 80 | # dotenv environment variables file 81 | .env 82 | .env.test 83 | 84 | # parcel-bundler cache (https://parceljs.org/) 85 | .cache 86 | 87 | # Next.js build output 88 | .next 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # Serverless directories 104 | .serverless/ 105 | 106 | # FuseBox cache 107 | .fusebox/ 108 | 109 | # DynamoDB Local files 110 | .dynamodb/ 111 | 112 | # TernJS port file 113 | .tern-port 114 | .env 115 | .DS_Store -------------------------------------------------------------------------------- /gated-nft/zksync/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Antonio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /gated-nft/zksync/README.md: -------------------------------------------------------------------------------- 1 | # zkSync Hardhat project 2 | 3 | This project was scaffolded with [zksync-cli](https://github.com/matter-labs/zksync-cli). 4 | 5 | ## Project structure 6 | 7 | - `/contracts`: smart contracts. 8 | - `/deploy`: deployment and contract interaction scripts. 9 | - `/test`: test files 10 | - `hardhat.config.ts`: configuration file. 11 | 12 | ## Commands 13 | 14 | - `yarn hardhat compile` will compile the contracts. 15 | - `yarn run deploy` will execute the deployment script `/deploy/deploy-greeter.ts`. Requires [environment variable setup](#environment-variables). 16 | - `yarn test`: run tests. **Check test requirements below.** 17 | 18 | Both `yarn run deploy` and `yarn run greet` are configured in the `package.json` file and run `yarn hardhat deploy-zksync`. 19 | 20 | ### Environment variables 21 | 22 | In order to prevent users to leak private keys, this project includes the `dotenv` package which is used to load environment variables. It's used to load the wallet private key, required to run the deploy script. 23 | 24 | To use it, rename `.env.example` to `.env` and enter your private key. 25 | 26 | ``` 27 | WALLET_PRIVATE_KEY=123cde574ccff.... 28 | ``` 29 | 30 | ### Local testing 31 | 32 | In order to run test, you need to start the zkSync local environment. Please check [this section of the docs](https://v2-docs.zksync.io/api/hardhat/testing.html#prerequisites) which contains all the details. 33 | 34 | If you do not start the zkSync local environment, the tests will fail with error `Error: could not detect network (event="noNetwork", code=NETWORK_ERROR, version=providers/5.7.2)` 35 | 36 | ## Official Links 37 | 38 | - [Website](https://zksync.io/) 39 | - [Documentation](https://v2-docs.zksync.io/dev/) 40 | - [GitHub](https://github.com/matter-labs) 41 | - [Twitter](https://twitter.com/zksync) 42 | - [Discord](https://join.zksync.dev/) 43 | -------------------------------------------------------------------------------- /gated-nft/zksync/contracts/ERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/utils/Strings.sol"; 7 | 8 | contract InfinityStones is ERC721URIStorage, Ownable { 9 | uint256 public tokenId; 10 | string public baseURI; 11 | mapping (string => bool) public stoneExists; 12 | mapping (address => uint256[]) private _ownedTokens; 13 | 14 | string[] public stones = [ 15 | "Space Stone", 16 | "Mind Stone", 17 | "Reality Stone", 18 | "Power Stone", 19 | "Time Stone", 20 | "Soul Stone" 21 | ]; 22 | 23 | constructor() ERC721("InfinityStones", "ISTN") {} 24 | 25 | function mint(address recipient, string memory stoneName) public onlyOwner { 26 | require(bytes(stoneName).length > 0, "stoneName must not be empty"); 27 | require(recipient != address(0), "recipient must not be the zero address"); 28 | require(!stoneExists[stoneName], "This stone already exists"); 29 | 30 | for(uint i=0; i 0 ? string(abi.encodePacked(baseURI, "/", Strings.toString(_tokenId))) : ""; 49 | } 50 | 51 | function tokensOfOwner(address owner) public view returns (uint256[] memory) { 52 | return _ownedTokens[owner]; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /gated-nft/zksync/contracts/ERC721GatedPaymaster.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | 7 | import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; 8 | 9 | import {IPaymaster, ExecutionResult, PAYMASTER_VALIDATION_SUCCESS_MAGIC} from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymaster.sol"; 10 | import {IPaymasterFlow} from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymasterFlow.sol"; 11 | import {TransactionHelper, Transaction} from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol"; 12 | 13 | /// @author Matter Labs 14 | /// @notice This smart contract pays the gas fees on behalf of users that are the owner of a specific NFT asset 15 | contract ERC721GatedPaymaster is IPaymaster, Ownable { 16 | IERC721 private immutable nft_asset; 17 | 18 | modifier onlyBootloader() { 19 | require( 20 | msg.sender == BOOTLOADER_FORMAL_ADDRESS, 21 | "Only bootloader can call this method" 22 | ); 23 | // Continue execution if called from the bootloader. 24 | _; 25 | } 26 | 27 | // The constructor takes the address of the ERC721 contract as an argument. 28 | // The ERC721 contract is the asset that the user must hold in order to use the paymaster. 29 | constructor(address _erc721) { 30 | nft_asset = IERC721(_erc721); // Initialize the ERC721 contract 31 | } 32 | 33 | // The gas fees will be paid for by the paymaster if the user is the owner of the required NFT asset. 34 | function validateAndPayForPaymasterTransaction( 35 | bytes32, 36 | bytes32, 37 | Transaction calldata _transaction 38 | ) 39 | external 40 | payable 41 | onlyBootloader 42 | returns (bytes4 magic, bytes memory context) 43 | { 44 | magic = PAYMASTER_VALIDATION_SUCCESS_MAGIC; 45 | require( 46 | _transaction.paymasterInput.length >= 4, 47 | "The standard paymaster input must be at least 4 bytes long" 48 | ); 49 | 50 | bytes4 paymasterInputSelector = bytes4( 51 | _transaction.paymasterInput[0:4] 52 | ); 53 | 54 | if (paymasterInputSelector == IPaymasterFlow.general.selector) { 55 | address userAddress = address(uint160(_transaction.from)); 56 | 57 | require( 58 | nft_asset.balanceOf(userAddress) > 0, 59 | "User does not hold the required NFT asset and therefore must pay for their own gas!" 60 | ); 61 | 62 | uint256 requiredETH = _transaction.gasLimit * 63 | _transaction.maxFeePerGas; 64 | 65 | (bool success, ) = payable(BOOTLOADER_FORMAL_ADDRESS).call{ 66 | value: requiredETH 67 | }(""); 68 | } else { 69 | revert("Invalid paymaster flow"); 70 | } 71 | } 72 | 73 | function postTransaction( 74 | bytes calldata _context, 75 | Transaction calldata _transaction, 76 | bytes32, 77 | bytes32, 78 | ExecutionResult _txResult, 79 | uint256 _maxRefundedGas 80 | ) external payable override onlyBootloader { 81 | } 82 | 83 | function withdraw(address payable _to) external onlyOwner { 84 | // send paymaster funds to the owner 85 | uint256 balance = address(this).balance; 86 | (bool success, ) = _to.call{value: balance}(""); 87 | require(success, "Failed to withdraw funds from paymaster."); 88 | } 89 | 90 | receive() external payable {} 91 | } -------------------------------------------------------------------------------- /gated-nft/zksync/contracts/Greeter.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.8.0; 3 | 4 | contract Greeter { 5 | string private greeting; 6 | 7 | constructor(string memory _greeting) { 8 | greeting = _greeting; 9 | } 10 | 11 | function greet() public view returns (string memory) { 12 | return greeting; 13 | } 14 | 15 | function setGreeting(string memory _greeting) public { 16 | greeting = _greeting; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /gated-nft/zksync/contracts/metadata/0: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Power Stone", 3 | "description": "The Power Stone, contained within the Orb, can destroy entire planets when unleashed.", 4 | "image": "https://ipfs.io/ipfs/QmTxy9zys87GE6fLhityMd9cVNqj6MRB1Q63ZATGibhY2Z?filename=purple_stone.png", 5 | "attributes": [ 6 | { 7 | "trait_type": "Color", 8 | "value": "Purple" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /gated-nft/zksync/contracts/metadata/1: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Mind Stone", 3 | "description": "The Mind Stone, embedded in the Vision's forehead, allows the user to control the minds of others.", 4 | "image": "https://ipfs.io/ipfs/QmRjzXm8KhvNuY8dvS1YgxbZtLQo22eEHRS1HQQb9LqC7x?filename=yellow_stone.png", 5 | "attributes": [ 6 | { 7 | "trait_type": "Color", 8 | "value": "Yellow" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /gated-nft/zksync/contracts/metadata/2: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Reality Stone", 3 | "description": "The Reality Stone, also known as the Aether, can transform reality as we know it.", 4 | "image": "https://ipfs.io/ipfs/QmYoCFu8rzwQ7GLgtCCP36GojFKAb56TMpATQFBqcTk9V4?filename=ruby_stone.png", 5 | "attributes": [ 6 | { 7 | "trait_type": "Color", 8 | "value": "Red" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /gated-nft/zksync/contracts/metadata/3: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Soul Stone", 3 | "description": "The Soul Stone, found on Vormir, can control life and death, but demands a sacrifice.", 4 | "image": "https://ipfs.io/ipfs/QmWUWDYBUc1hqTXDcHck3E9gWFwFAEVPcNbsKo5m8XKbMu?filename=orange_stone.png", 5 | "attributes": [ 6 | { 7 | "trait_type": "Color", 8 | "value": "Orange" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /gated-nft/zksync/contracts/metadata/4: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Space Stone", 3 | "description": "The Space Stone, contained within the Tesseract, grants the user control over space itself.", 4 | "image": "https://ipfs.io/ipfs/QmQqMYPEBAABacEUFfVrbAuRWLCayXbqykpq9LWV1kbUJD?filename=blue_stone.png", 5 | "attributes": [ 6 | { 7 | "trait_type": "Color", 8 | "value": "Blue" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /gated-nft/zksync/contracts/metadata/5: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Time Stone", 3 | "description": "The Time Stone, stored in the Eye of Agamotto, gives the user control over the past, present, and future.", 4 | "image": "https://ipfs.io/ipfs/QmW2BNbR9vTFUaPUSYdxyfGScZuiBHL5p9gozE5xRPrmfC?filename=green_stone.png", 5 | "attributes": [ 6 | { 7 | "trait_type": "Color", 8 | "value": "Green" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /gated-nft/zksync/deploy/deploy-ERC721.ts: -------------------------------------------------------------------------------- 1 | import { Provider, Wallet } from "zksync-ethers"; 2 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 3 | import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; 4 | import * as fs from "fs"; 5 | import * as readline from "readline"; 6 | 7 | // load env file 8 | import dotenv from "dotenv"; 9 | dotenv.config(); 10 | 11 | // load wallet private key from env file 12 | const PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY || ""; 13 | 14 | if (!PRIVATE_KEY) 15 | throw "⛔️ Private key not detected! Add it to the .env file!"; 16 | 17 | const rl = readline.createInterface({ 18 | input: process.stdin, 19 | output: process.stdout, 20 | }); 21 | 22 | async function getRecipientAddress(): Promise { 23 | return new Promise((resolve, reject) => { 24 | rl.question( 25 | "Please provide the recipient address to receive an NFT: ", 26 | (address) => { 27 | if (!address) { 28 | reject("⛔️ RECIPIENT_ADDRESS not provided!"); 29 | } else { 30 | resolve(address); 31 | } 32 | }, 33 | ); 34 | }); 35 | } 36 | 37 | export default async function (hre: HardhatRuntimeEnvironment) { 38 | console.log(`Running deploy script for the ERC721 contract...`); 39 | console.log( 40 | `You first need to add a RECIPIENT_ADDRESS to mint the NFT to...`, 41 | ); 42 | // We will mint the NFTs to this address 43 | const RECIPIENT_ADDRESS = await getRecipientAddress(); 44 | if (!RECIPIENT_ADDRESS) throw "⛔️ RECIPIENT_ADDRESS not detected!"; 45 | 46 | // It is assumed that this wallet already has sufficient funds on zkSync 47 | const wallet = new Wallet(PRIVATE_KEY); 48 | const deployer = new Deployer(hre, wallet); 49 | 50 | // Deploying the ERC721 contract 51 | const nftContractArtifact = await deployer.loadArtifact("InfinityStones"); 52 | const nftContract = await deployer.deploy(nftContractArtifact, []); 53 | console.log(`NFT Contract address: ${nftContract.address}`); 54 | 55 | // Mint NFTs to the recipient address 56 | const stone = "Power Stone"; 57 | const tx = await nftContract.mint(RECIPIENT_ADDRESS, stone); 58 | await tx.wait(); 59 | console.log(`The ${stone} has been given to ${RECIPIENT_ADDRESS}`); 60 | 61 | // Get and log the balance of the recipient 62 | const balance = await nftContract.balanceOf(RECIPIENT_ADDRESS); 63 | console.log(`Balance of the recipient: ${balance}`); 64 | 65 | // Update base URI 66 | let setBaseUriTransaction = await nftContract.setBaseURI( 67 | "https://ipfs.io/ipfs/QmPtDtJEJDzxthbKmdgvYcLa9oNUUUkh7vvz5imJFPQdKx", 68 | ); 69 | await setBaseUriTransaction.wait(); 70 | console.log(`New baseURI is ${await nftContract.baseURI()}`); 71 | 72 | // Verify contract programmatically 73 | // 74 | // Contract MUST be fully qualified name (e.g. path/sourceName:contractName) 75 | const contractFullyQualifedName = "contracts/ERC721.sol:InfinityStones"; 76 | const verificationId = await hre.run("verify:verify", { 77 | address: nftContract.address, 78 | contract: contractFullyQualifedName, 79 | constructorArguments: [], 80 | bytecode: nftContractArtifact.bytecode, 81 | }); 82 | console.log( 83 | `${contractFullyQualifedName} verified! VerificationId: ${verificationId}`, 84 | ); 85 | 86 | // Update frontend with contract address 87 | const frontendConstantsFilePath = 88 | __dirname + "/../../frontend/app/constants/consts.tsx"; 89 | const data = fs.readFileSync(frontendConstantsFilePath, "utf8"); 90 | const result = data.replace(/NFT-CONTRACT-ADDRESS/g, nftContract.address); 91 | fs.writeFileSync(frontendConstantsFilePath, result, "utf8"); 92 | 93 | // Update paymaster deploy script with contract address 94 | const paymasterDeploymentFilePath = 95 | __dirname + "/deploy-ERC721GatedPaymaster.ts"; 96 | const res = fs.readFileSync(paymasterDeploymentFilePath, "utf8"); 97 | const final = res.replace(/NFT-CONTRACT-ADDRESS-HERE/g, nftContract.address); 98 | fs.writeFileSync(paymasterDeploymentFilePath, final, "utf8"); 99 | 100 | console.log(`Done!`); 101 | } 102 | -------------------------------------------------------------------------------- /gated-nft/zksync/deploy/deploy-ERC721GatedPaymaster.ts: -------------------------------------------------------------------------------- 1 | import * as ethers from "ethers"; 2 | import * as fs from "fs"; 3 | 4 | import { Provider, Wallet } from "zksync-ethers"; 5 | 6 | import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; 7 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 8 | // load env file 9 | import dotenv from "dotenv"; 10 | 11 | dotenv.config(); 12 | 13 | const PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY || ""; 14 | // The address of the NFT collection contract 15 | const NFT_COLLECTION_ADDRESS = "NFT-CONTRACT-ADDRESS-HERE"; 16 | 17 | if (!PRIVATE_KEY) 18 | throw "⛔️ Private key not detected! Add it to the .env file!"; 19 | 20 | if (!NFT_COLLECTION_ADDRESS) 21 | throw "⛔️ NFT_COLLECTION_ADDRESS not detected! Add it to the NFT_COLLECTION_ADDRESS variable!"; 22 | 23 | export default async function (hre: HardhatRuntimeEnvironment) { 24 | console.log(`Running deploy script for the ERC721GatedPaymaster contract...`); 25 | const provider = new Provider("https://sepolia.era.zksync.dev"); 26 | 27 | // The wallet that will deploy the token and the paymaster 28 | // It is assumed that this wallet already has sufficient funds on zkSync 29 | const wallet = new Wallet(PRIVATE_KEY); 30 | const deployer = new Deployer(hre, wallet); 31 | 32 | // Deploying the paymaster 33 | const paymasterArtifact = await deployer.loadArtifact("ERC721GatedPaymaster"); 34 | const deploymentFee = await deployer.estimateDeployFee(paymasterArtifact, [ 35 | NFT_COLLECTION_ADDRESS, 36 | ]); 37 | const parsedFee = ethers.utils.formatEther(deploymentFee.toString()); 38 | console.log(`The deployment is estimated to cost ${parsedFee} ETH`); 39 | // Deploy the contract 40 | const paymaster = await deployer.deploy(paymasterArtifact, [ 41 | NFT_COLLECTION_ADDRESS, 42 | ]); 43 | console.log(`Paymaster address: ${paymaster.address}`); 44 | 45 | console.log("Funding paymaster with ETH"); 46 | // Supplying paymaster with ETH 47 | await ( 48 | await deployer.zkWallet.sendTransaction({ 49 | to: paymaster.address, 50 | value: ethers.utils.parseEther("0.005"), 51 | }) 52 | ).wait(); 53 | 54 | let paymasterBalance = await provider.getBalance(paymaster.address); 55 | console.log(`Paymaster ETH balance is now ${paymasterBalance.toString()}`); 56 | 57 | // Verify contract programmatically 58 | // 59 | // Contract MUST be fully qualified name (e.g. path/sourceName:contractName) 60 | const contractFullyQualifedName = 61 | "contracts/ERC721GatedPaymaster.sol:ERC721GatedPaymaster"; 62 | const verificationId = await hre.run("verify:verify", { 63 | address: paymaster.address, 64 | contract: contractFullyQualifedName, 65 | constructorArguments: [NFT_COLLECTION_ADDRESS], 66 | bytecode: paymasterArtifact.bytecode, 67 | }); 68 | console.log( 69 | `${contractFullyQualifedName} verified! VerificationId: ${verificationId}`, 70 | ); 71 | 72 | // Update frontend with contract address 73 | const frontendConstantsFilePath = 74 | __dirname + "/../../frontend/app/constants/consts.tsx"; 75 | const data = fs.readFileSync(frontendConstantsFilePath, "utf8"); 76 | const result = data.replace(/PAYMASTER-CONTRACT-ADDRESS/g, paymaster.address); 77 | fs.writeFileSync(frontendConstantsFilePath, result, "utf8"); 78 | 79 | console.log(`Done!`); 80 | } 81 | -------------------------------------------------------------------------------- /gated-nft/zksync/deploy/deploy-greeter.ts: -------------------------------------------------------------------------------- 1 | import { Wallet, utils } from "zksync-ethers"; 2 | import * as ethers from "ethers"; 3 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 4 | import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; 5 | import * as fs from "fs"; 6 | 7 | // load env file 8 | import dotenv from "dotenv"; 9 | dotenv.config(); 10 | 11 | // load wallet private key from env file 12 | const PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY || ""; 13 | 14 | if (!PRIVATE_KEY) 15 | throw "⛔️ Private key not detected! Add it to the .env file!"; 16 | 17 | // An example of a deploy script that will deploy and call a simple contract. 18 | export default async function (hre: HardhatRuntimeEnvironment) { 19 | console.log(`Running deploy script for the Greeter contract`); 20 | 21 | // Initialize the wallet. 22 | const wallet = new Wallet(PRIVATE_KEY); 23 | 24 | // Create deployer object and load the artifact of the contract you want to deploy. 25 | const deployer = new Deployer(hre, wallet); 26 | const artifact = await deployer.loadArtifact("Greeter"); 27 | 28 | // Estimate contract deployment fee 29 | const greeting = "Hi there!"; 30 | const deploymentFee = await deployer.estimateDeployFee(artifact, [greeting]); 31 | 32 | // Deploy this contract. The returned object will be of a `Contract` type, similarly to ones in `ethers`. 33 | // `greeting` is an argument for contract constructor. 34 | const parsedFee = ethers.utils.formatEther(deploymentFee.toString()); 35 | console.log(`The deployment is estimated to cost ${parsedFee} ETH`); 36 | 37 | const greeterContract = await deployer.deploy(artifact, [greeting]); 38 | 39 | //obtain the Constructor Arguments 40 | console.log( 41 | "Constructor args:" + greeterContract.interface.encodeDeploy([greeting]), 42 | ); 43 | 44 | // Show the contract info. 45 | const contractAddress = greeterContract.address; 46 | console.log(`${artifact.contractName} was deployed to ${contractAddress}`); 47 | 48 | // verify contract for tesnet & mainnet 49 | if (process.env.NODE_ENV != "test") { 50 | // Contract MUST be fully qualified name (e.g. path/sourceName:contractName) 51 | const contractFullyQualifedName = "contracts/Greeter.sol:Greeter"; 52 | 53 | // Verify contract programmatically 54 | const verificationId = await hre.run("verify:verify", { 55 | address: contractAddress, 56 | contract: contractFullyQualifedName, 57 | constructorArguments: [greeting], 58 | bytecode: artifact.bytecode, 59 | }); 60 | } else { 61 | console.log(`Contract not verified, deployed locally.`); 62 | } 63 | 64 | // Update frontend with contract address 65 | const frontendConstantsFilePath = 66 | __dirname + "/../../frontend/app/constants/consts.tsx"; 67 | const data = fs.readFileSync(frontendConstantsFilePath, "utf8"); 68 | const result = data.replace(/YOUR-GREETER-ADDRESS/g, contractAddress); 69 | fs.writeFileSync(frontendConstantsFilePath, result, "utf8"); 70 | 71 | console.log("Done!"); 72 | } 73 | -------------------------------------------------------------------------------- /gated-nft/zksync/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig } from "hardhat/config"; 2 | 3 | import "@matterlabs/hardhat-zksync-deploy"; 4 | import "@matterlabs/hardhat-zksync-solc"; 5 | import "@matterlabs/hardhat-zksync-verify"; 6 | 7 | // dynamically alters endpoints for local tests 8 | const zkSyncTestnet = 9 | process.env.NODE_ENV == "test" 10 | ? { 11 | url: "http://localhost:3050", 12 | ethNetwork: "http://localhost:8545", 13 | zksync: true, 14 | } 15 | : { 16 | url: "https://sepolia.era.zksync.dev", 17 | ethNetwork: "sepolia", 18 | zksync: true, 19 | verifyURL: 20 | "https://explorer.sepolia.era.zksync.dev/contract_verification", // Verification endpoint 21 | }; 22 | 23 | const config: HardhatUserConfig = { 24 | zksolc: { 25 | version: "latest", // Uses latest available in https://github.com/matter-labs/zksolc-bin/ 26 | settings: {}, 27 | }, 28 | defaultNetwork: "zkSyncTestnet", 29 | networks: { 30 | hardhat: { 31 | zksync: false, 32 | }, 33 | zkSyncTestnet, 34 | }, 35 | solidity: { 36 | version: "0.8.17", 37 | }, 38 | }; 39 | 40 | export default config; 41 | -------------------------------------------------------------------------------- /gated-nft/zksync/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zksync", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "Antonio ", 6 | "license": "MIT", 7 | "devDependencies": { 8 | "@matterlabs/hardhat-zksync-deploy": "^0.7.0", 9 | "@matterlabs/hardhat-zksync-node": "^0.0.1", 10 | "@matterlabs/hardhat-zksync-solc": "^0.4.2", 11 | "@matterlabs/hardhat-zksync-verify": "^0.4.0", 12 | "@matterlabs/zksync-contracts": "^0.6.1", 13 | "@nomicfoundation/hardhat-verify": "^1.0.3", 14 | "@nomiclabs/hardhat-etherscan": "^3.1.7", 15 | "@openzeppelin/contracts": "^4.9.2", 16 | "@types/chai": "^4.3.4", 17 | "@types/mocha": "^10.0.1", 18 | "chai": "^4.3.7", 19 | "dotenv": "^16.0.3", 20 | "ethers": "^5.7.2", 21 | "hardhat": "^2.12.4", 22 | "lodash": "^4.17.21", 23 | "mocha": "^10.2.0", 24 | "ts-node": "^10.9.1", 25 | "typescript": "^4.9.4", 26 | "zksync-ethers": "^5" 27 | }, 28 | "scripts": { 29 | "test": "hardhat test", 30 | "deploy": "yarn hardhat deploy-zksync", 31 | "greeter": "yarn hardhat deploy-zksync --script deploy-greeter.ts", 32 | "nft": "yarn hardhat deploy-zksync --script deploy-ERC721.ts", 33 | "paymaster": "yarn hardhat deploy-zksync --script deploy-ERC721GatedPaymaster.ts", 34 | "compile": "yarn hardhat compile" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /gated-nft/zksync/test/utils/deploy-ERC721GatedPaymaster.ts: -------------------------------------------------------------------------------- 1 | import * as ethers from "ethers"; 2 | import * as fs from "fs"; 3 | 4 | import { Provider, Wallet } from "zksync-ethers"; 5 | 6 | import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; 7 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 8 | import { localConfig } from "../../../../tests/testConfig"; 9 | 10 | const PRIVATE_KEY = localConfig.privateKey; 11 | // The address of the NFT collection contract 12 | const NFT_COLLECTION_ADDRESS = "0x3ccA24e1A0e49654bc3482ab70199b7400eb7A3a"; 13 | 14 | export default async function (hre: HardhatRuntimeEnvironment) { 15 | console.log(`Running deploy script for the ERC721GatedPaymaster contract...`); 16 | const provider = new Provider("https://sepolia.era.zksync.dev"); 17 | 18 | // The wallet that will deploy the token and the paymaster 19 | // It is assumed that this wallet already has sufficient funds on zkSync 20 | const wallet = new Wallet(PRIVATE_KEY); 21 | const deployer = new Deployer(hre, wallet); 22 | 23 | // Deploying the paymaster 24 | const paymasterArtifact = await deployer.loadArtifact("ERC721GatedPaymaster"); 25 | const deploymentFee = await deployer.estimateDeployFee(paymasterArtifact, [ 26 | NFT_COLLECTION_ADDRESS, 27 | ]); 28 | const parsedFee = ethers.utils.formatEther(deploymentFee.toString()); 29 | console.log(`The deployment is estimated to cost ${parsedFee} ETH`); 30 | // Deploy the contract 31 | const paymaster = await deployer.deploy(paymasterArtifact, [ 32 | NFT_COLLECTION_ADDRESS, 33 | ]); 34 | console.log(`Paymaster address: ${paymaster.address}`); 35 | 36 | console.log("Funding paymaster with ETH"); 37 | // Supplying paymaster with ETH 38 | await ( 39 | await deployer.zkWallet.sendTransaction({ 40 | to: paymaster.address, 41 | value: ethers.utils.parseEther("0.005"), 42 | }) 43 | ).wait(); 44 | 45 | let paymasterBalance = await provider.getBalance(paymaster.address); 46 | console.log(`Paymaster ETH balance is now ${paymasterBalance.toString()}`); 47 | 48 | // Verify contract programmatically 49 | // 50 | // Contract MUST be fully qualified name (e.g. path/sourceName:contractName) 51 | const contractFullyQualifedName = 52 | "contracts/ERC721GatedPaymaster.sol:ERC721GatedPaymaster"; 53 | 54 | // Update frontend with contract address 55 | const frontendConstantsFilePath = 56 | __dirname + "/../../frontend/app/constants/consts.tsx"; 57 | const data = fs.readFileSync(frontendConstantsFilePath, "utf8"); 58 | const result = data.replace(/PAYMASTER-CONTRACT-ADDRESS/g, paymaster.address); 59 | fs.writeFileSync(frontendConstantsFilePath, result, "utf8"); 60 | 61 | console.log(`Done!`); 62 | } 63 | -------------------------------------------------------------------------------- /gated-nft/zksync/test/utils/deploy-greeter.ts: -------------------------------------------------------------------------------- 1 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 2 | import { Wallet, Provider } from "zksync-ethers"; 3 | import * as ethers from "ethers"; 4 | import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; 5 | import * as fs from "fs"; 6 | 7 | // load env file 8 | import { localConfig } from "../../../../tests/testConfig"; 9 | 10 | // load wallet private key from env file 11 | const PRIVATE_KEY = localConfig.privateKey; 12 | 13 | if (!PRIVATE_KEY) 14 | throw "⛔️ Private key not detected! Add it to the .env file!"; 15 | 16 | // An example of a deploy script that will deploy and call a simple contract. 17 | export default async function (hre: HardhatRuntimeEnvironment) { 18 | console.log(`Running deploy script for the Greeter contract`); 19 | 20 | // Initialize the wallet. 21 | const provider = new Provider(localConfig.L2Network); 22 | const wallet = new Wallet(PRIVATE_KEY).connect(provider); 23 | const deployer = new Deployer(hre, wallet); 24 | 25 | // Load the artifact of the contract you want to deploy. 26 | const artifact = await deployer.loadArtifact("Greeter"); 27 | 28 | // Estimate contract deployment fee 29 | const greeting = "Hi there!"; 30 | const deploymentFee = await deployer.estimateDeployFee(artifact, [greeting]); 31 | 32 | // Deploy this contract. The returned object will be of a `Contract` type, similarly to ones in `ethers`. 33 | // `greeting` is an argument for contract constructor. 34 | const parsedFee = ethers.utils.formatEther(deploymentFee.toString()); 35 | console.log(`The deployment is estimated to cost ${parsedFee} ETH`); 36 | 37 | const greeterContract = await deployer.deploy(artifact, [greeting]); 38 | 39 | //obtain the Constructor Arguments 40 | console.log( 41 | "Constructor args:" + greeterContract.interface.encodeDeploy([greeting]), 42 | ); 43 | 44 | // Show the contract info. 45 | const contractAddress = greeterContract.address; 46 | console.log(`${artifact.contractName} was deployed to ${contractAddress}`); 47 | 48 | // verify contract for tesnet & mainnet 49 | if (process.env.NODE_ENV != "test") { 50 | // Contract MUST be fully qualified name (e.g. path/sourceName:contractName) 51 | const contractFullyQualifedName = "contracts/Greeter.sol:Greeter"; 52 | 53 | // Verify contract programmatically 54 | const verificationId = await hre.run("verify:verify", { 55 | address: contractAddress, 56 | contract: contractFullyQualifedName, 57 | constructorArguments: [greeting], 58 | bytecode: artifact.bytecode, 59 | }); 60 | } else { 61 | console.log(`Contract not verified, deployed locally.`); 62 | } 63 | 64 | // Update frontend with contract address 65 | const frontendConstantsFilePath = 66 | __dirname + "/../../frontend/app/constants/consts.tsx"; 67 | const data = fs.readFileSync(frontendConstantsFilePath, "utf8"); 68 | const result = data.replace(/YOUR-GREETER-ADDRESS/g, contractAddress); 69 | fs.writeFileSync(frontendConstantsFilePath, result, "utf8"); 70 | 71 | console.log("Done!"); 72 | } 73 | -------------------------------------------------------------------------------- /hello-world-docker/.env.example: -------------------------------------------------------------------------------- 1 | WALLET_PRIVATE_KEY= 2 | -------------------------------------------------------------------------------- /hello-world-docker/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .vscode 9 | 10 | # hardhat artifacts 11 | artifacts 12 | cache 13 | 14 | # zksync artifacts 15 | artifacts-zk 16 | cache-zk 17 | 18 | # Diagnostic reports (https://nodejs.org/api/report.html) 19 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 20 | 21 | # Runtime data 22 | pids 23 | *.pid 24 | *.seed 25 | *.pid.lock 26 | 27 | # Directory for instrumented libs generated by jscoverage/JSCover 28 | lib-cov 29 | 30 | # Coverage directory used by tools like istanbul 31 | coverage 32 | *.lcov 33 | 34 | # nyc test coverage 35 | .nyc_output 36 | 37 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 38 | .grunt 39 | 40 | # Bower dependency directory (https://bower.io/) 41 | bower_components 42 | 43 | # node-waf configuration 44 | .lock-wscript 45 | 46 | # Compiled binary addons (https://nodejs.org/api/addons.html) 47 | build/Release 48 | 49 | # Dependency directories 50 | node_modules/ 51 | jspm_packages/ 52 | 53 | # TypeScript v1 declaration files 54 | typings/ 55 | 56 | # TypeScript cache 57 | *.tsbuildinfo 58 | 59 | # Optional npm cache directory 60 | .npm 61 | 62 | # Optional eslint cache 63 | .eslintcache 64 | 65 | # Microbundle cache 66 | .rpt2_cache/ 67 | .rts2_cache_cjs/ 68 | .rts2_cache_es/ 69 | .rts2_cache_umd/ 70 | 71 | # Optional REPL history 72 | .node_repl_history 73 | 74 | # Output of 'npm pack' 75 | *.tgz 76 | 77 | # Yarn Integrity file 78 | .yarn-integrity 79 | 80 | # dotenv environment variables file 81 | .env 82 | .env.test 83 | 84 | # parcel-bundler cache (https://parceljs.org/) 85 | .cache 86 | 87 | # Next.js build output 88 | .next 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # Serverless directories 104 | .serverless/ 105 | 106 | # FuseBox cache 107 | .fusebox/ 108 | 109 | # DynamoDB Local files 110 | .dynamodb/ 111 | 112 | # TernJS port file 113 | .tern-port 114 | -------------------------------------------------------------------------------- /hello-world-docker/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Antonio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /hello-world-docker/README.md: -------------------------------------------------------------------------------- 1 | # zkSync Hardhat project 2 | 3 | This project was scaffolded with [zksync-cli](https://github.com/matter-labs/zksync-cli). 4 | 5 | ## Project structure 6 | 7 | - `/contracts`: smart contracts. 8 | - `/deploy`: deployment and contract interaction scripts. 9 | - `/test`: test files 10 | - `hardhat.config.ts`: configuration file. 11 | 12 | ## Commands 13 | 14 | - `yarn hardhat compile` will compile the contracts. 15 | - `yarn run deploy` will execute the deployment script `/deploy/deploy-greeter.ts`. Requires [environment variable setup](#environment-variables). 16 | - `yarn run greet` will execute the script `/deploy/use-greeter.ts` which interacts with the Greeter contract deployed. 17 | - `yarn test`: run tests. **Check test requirements below.** 18 | 19 | Both `yarn run deploy` and `yarn run greet` are configured in the `package.json` file and run `yarn hardhat deploy-zksync`. 20 | 21 | ### Environment variables 22 | 23 | In order to prevent users to leak private keys, this project includes the `dotenv` package which is used to load environment variables. It's used to load the wallet private key, required to run the deploy script. 24 | 25 | To use it, rename `.env.example` to `.env` and enter your private key. 26 | 27 | ``` 28 | WALLET_PRIVATE_KEY=123cde574ccff.... 29 | ``` 30 | 31 | ### Local testing 32 | 33 | In order to run test, you need to start the zkSync local environment. Please check [this section of the docs](https://v2-docs.zksync.io/api/hardhat/testing.html#prerequisites) which contains all the details. 34 | 35 | If you do not start the zkSync local environment, the tests will fail with error `Error: could not detect network (event="noNetwork", code=NETWORK_ERROR, version=providers/5.7.2)` 36 | 37 | ## Official Links 38 | 39 | - [Website](https://zksync.io/) 40 | - [Documentation](https://v2-docs.zksync.io/dev/) 41 | - [GitHub](https://github.com/matter-labs) 42 | - [Twitter](https://twitter.com/zksync) 43 | - [Discord](https://join.zksync.dev/) 44 | -------------------------------------------------------------------------------- /hello-world-docker/contracts/Greeter.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.8.0; 3 | 4 | contract Greeter { 5 | string private greeting; 6 | 7 | constructor(string memory _greeting) { 8 | greeting = _greeting; 9 | } 10 | 11 | function greet() public view returns (string memory) { 12 | return greeting; 13 | } 14 | 15 | function setGreeting(string memory _greeting) public { 16 | greeting = _greeting; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /hello-world-docker/deploy/deploy-greeter.ts: -------------------------------------------------------------------------------- 1 | import { Wallet, utils } from "zksync-ethers"; 2 | import * as ethers from "ethers"; 3 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 4 | import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; 5 | 6 | // load env file 7 | import dotenv from "dotenv"; 8 | dotenv.config(); 9 | 10 | // load wallet private key from env file 11 | const PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY || ""; 12 | 13 | if (!PRIVATE_KEY) 14 | throw "⛔️ Private key not detected! Add it to the .env file!"; 15 | 16 | // An example of a deploy script that will deploy and call a simple contract. 17 | export default async function (hre: HardhatRuntimeEnvironment) { 18 | console.log(`Running deploy script for the Greeter contract`); 19 | 20 | // Initialize the wallet. 21 | const wallet = new Wallet(PRIVATE_KEY); 22 | 23 | // Create deployer object and load the artifact of the contract you want to deploy. 24 | const deployer = new Deployer(hre, wallet); 25 | const artifact = await deployer.loadArtifact("Greeter"); 26 | 27 | // Estimate contract deployment fee 28 | const greeting = "Hi there!"; 29 | const deploymentFee = await deployer.estimateDeployFee(artifact, [greeting]); 30 | 31 | // ⚠️ OPTIONAL: You can skip this block if your account already has funds in L2 32 | // Deposit funds to L2 33 | // const depositHandle = await deployer.zkWallet.deposit({ 34 | // to: deployer.zkWallet.address, 35 | // token: utils.ETH_ADDRESS, 36 | // amount: deploymentFee.mul(2), 37 | // }); 38 | // // Wait until the deposit is processed on zkSync 39 | // await depositHandle.wait(); 40 | 41 | // Deploy this contract. The returned object will be of a `Contract` type, similarly to ones in `ethers`. 42 | // `greeting` is an argument for contract constructor. 43 | const parsedFee = ethers.utils.formatEther(deploymentFee.toString()); 44 | console.log(`The deployment is estimated to cost ${parsedFee} ETH`); 45 | 46 | const greeterContract = await deployer.deploy(artifact, [greeting]); 47 | 48 | //obtain the Constructor Arguments 49 | console.log( 50 | "Constructor args:" + greeterContract.interface.encodeDeploy([greeting]), 51 | ); 52 | 53 | // Show the contract info. 54 | const contractAddress = greeterContract.address; 55 | console.log(`${artifact.contractName} was deployed to ${contractAddress}`); 56 | 57 | // verify contract for tesnet & mainnet 58 | if (process.env.NODE_ENV != "test") { 59 | // Contract MUST be fully qualified name (e.g. path/sourceName:contractName) 60 | const contractFullyQualifedName = "contracts/Greeter.sol:Greeter"; 61 | 62 | // Verify contract programmatically 63 | const verificationId = await hre.run("verify:verify", { 64 | address: contractAddress, 65 | contract: contractFullyQualifedName, 66 | constructorArguments: [greeting], 67 | bytecode: artifact.bytecode, 68 | }); 69 | } else { 70 | console.log(`Contract not verified, deployed locally.`); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /hello-world-docker/deploy/use-greeter.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from "zksync-ethers"; 2 | import * as ethers from "ethers"; 3 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 4 | 5 | // load env file 6 | import dotenv from "dotenv"; 7 | dotenv.config(); 8 | 9 | // load contract artifact. Make sure to compile first! 10 | import * as ContractArtifact from "../artifacts-zk/contracts/Greeter.sol/Greeter.json"; 11 | 12 | const PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY || ""; 13 | 14 | if (!PRIVATE_KEY) 15 | throw "⛔️ Private key not detected! Add it to the .env file!"; 16 | 17 | // Address of the contract on zksync testnet 18 | const CONTRACT_ADDRESS = ""; 19 | 20 | if (!CONTRACT_ADDRESS) throw "⛔️ Contract address not provided"; 21 | 22 | // An example of a deploy script that will deploy and call a simple contract. 23 | export default async function (hre: HardhatRuntimeEnvironment) { 24 | console.log(`Running script to interact with contract ${CONTRACT_ADDRESS}`); 25 | 26 | // Initialize the provider. 27 | // @ts-ignore 28 | const provider = new Provider(hre.userConfig.networks?.zkSyncTestnet?.url); 29 | const signer = new ethers.Wallet(PRIVATE_KEY, provider); 30 | 31 | // Initialize contract instance 32 | const contract = new ethers.Contract( 33 | CONTRACT_ADDRESS, 34 | ContractArtifact.abi, 35 | signer, 36 | ); 37 | 38 | // Read message from contract 39 | console.log(`The message is ${await contract.greet()}`); 40 | 41 | // send transaction to update the message 42 | const newMessage = "Hello people!"; 43 | const tx = await contract.setGreeting(newMessage); 44 | 45 | console.log(`Transaction to change the message is ${tx.hash}`); 46 | await tx.wait(); 47 | 48 | // Read message after transaction 49 | console.log(`The message now is ${await contract.greet()}`); 50 | } 51 | -------------------------------------------------------------------------------- /hello-world-docker/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import "@matterlabs/hardhat-zksync-deploy"; 2 | import "@matterlabs/hardhat-zksync-solc"; 3 | import "@matterlabs/hardhat-zksync-verify"; 4 | 5 | import { HardhatUserConfig } from "hardhat/config"; 6 | 7 | // dynamically changes endpoints for local tests 8 | const zkSyncTestnet = 9 | process.env.NODE_ENV == "test" 10 | ? { 11 | url: "http://localhost:3050", 12 | ethNetwork: "http://localhost:8545", 13 | zksync: true, 14 | } 15 | : { 16 | url: "https://sepolia.era.zksync.dev", 17 | ethNetwork: "sepolia", 18 | zksync: true, 19 | // contract verification endpoint 20 | verifyURL: 21 | "https://explorer.sepolia.era.zksync.dev/contract_verification", 22 | }; 23 | 24 | const config: HardhatUserConfig = { 25 | zksolc: { 26 | version: "latest", 27 | settings: {}, 28 | }, 29 | defaultNetwork: "zkSyncTestnet", 30 | networks: { 31 | hardhat: { 32 | zksync: false, 33 | }, 34 | zkSyncTestnet, 35 | }, 36 | solidity: { 37 | version: "0.8.17", 38 | }, 39 | }; 40 | 41 | export default config; 42 | -------------------------------------------------------------------------------- /hello-world-docker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world-docker", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "Antonio ", 6 | "license": "MIT", 7 | "devDependencies": { 8 | "@matterlabs/hardhat-zksync-deploy": "^0.7.0", 9 | "@matterlabs/hardhat-zksync-solc": "^0.4.2", 10 | "@matterlabs/hardhat-zksync-verify": "^0.4.0", 11 | "@nomiclabs/hardhat-etherscan": "^3.1.7", 12 | "@types/chai": "^4.3.4", 13 | "@types/mocha": "^10.0.1", 14 | "chai": "^4.3.7", 15 | "dotenv": "^16.0.3", 16 | "ethers": "^5.7.2", 17 | "hardhat": "^2.12.4", 18 | "mocha": "^10.2.0", 19 | "ts-node": "^10.9.1", 20 | "typescript": "^4.9.4", 21 | "zksync-ethers": "^5" 22 | }, 23 | "scripts": { 24 | "test": "NODE_ENV=test hardhat test --network zkSyncTestnet", 25 | "deploy": "yarn hardhat deploy-zksync --script deploy-greeter.ts", 26 | "greet": "yarn hardhat deploy-zksync --script use-greeter.ts" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /hello-world-docker/test/main.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { Wallet, Provider, Contract } from "zksync-ethers"; 3 | import * as hre from "hardhat"; 4 | import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; 5 | 6 | const RICH_WALLET_PK = 7 | "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110"; 8 | 9 | async function deployGreeter(deployer: Deployer): Promise { 10 | const artifact = await deployer.loadArtifact("Greeter"); 11 | return await deployer.deploy(artifact, ["Hi"]); 12 | } 13 | 14 | describe("Greeter", function () { 15 | it("Should return the new greeting once it's changed", async function () { 16 | const provider = new Provider("http://127.0.0.1:3050"); 17 | 18 | const wallet = new Wallet(RICH_WALLET_PK, provider); 19 | const deployer = new Deployer(hre, wallet); 20 | 21 | const greeter = await deployGreeter(deployer); 22 | 23 | expect(await greeter.greet()).to.eq("Hi"); 24 | 25 | const setGreetingTx = await greeter.setGreeting("Hola, mundo!"); 26 | // wait until the transaction is mined 27 | await setGreetingTx.wait(); 28 | 29 | expect(await greeter.greet()).to.equal("Hola, mundo!"); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /hello-world/.env.example: -------------------------------------------------------------------------------- 1 | WALLET_PRIVATE_KEY= 2 | -------------------------------------------------------------------------------- /hello-world/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .vscode 9 | 10 | # hardhat artifacts 11 | artifacts 12 | cache 13 | 14 | # zksync artifacts 15 | artifacts-zk 16 | cache-zk 17 | 18 | # Diagnostic reports (https://nodejs.org/api/report.html) 19 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 20 | 21 | # Runtime data 22 | pids 23 | *.pid 24 | *.seed 25 | *.pid.lock 26 | 27 | # Directory for instrumented libs generated by jscoverage/JSCover 28 | lib-cov 29 | 30 | # Coverage directory used by tools like istanbul 31 | coverage 32 | *.lcov 33 | 34 | # nyc test coverage 35 | .nyc_output 36 | 37 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 38 | .grunt 39 | 40 | # Bower dependency directory (https://bower.io/) 41 | bower_components 42 | 43 | # node-waf configuration 44 | .lock-wscript 45 | 46 | # Compiled binary addons (https://nodejs.org/api/addons.html) 47 | build/Release 48 | 49 | # Dependency directories 50 | node_modules/ 51 | jspm_packages/ 52 | 53 | # TypeScript v1 declaration files 54 | typings/ 55 | 56 | # TypeScript cache 57 | *.tsbuildinfo 58 | 59 | # Optional npm cache directory 60 | .npm 61 | 62 | # Optional eslint cache 63 | .eslintcache 64 | 65 | # Microbundle cache 66 | .rpt2_cache/ 67 | .rts2_cache_cjs/ 68 | .rts2_cache_es/ 69 | .rts2_cache_umd/ 70 | 71 | # Optional REPL history 72 | .node_repl_history 73 | 74 | # Output of 'npm pack' 75 | *.tgz 76 | 77 | # Yarn Integrity file 78 | .yarn-integrity 79 | 80 | # dotenv environment variables file 81 | .env 82 | .env.test 83 | 84 | # parcel-bundler cache (https://parceljs.org/) 85 | .cache 86 | 87 | # Next.js build output 88 | .next 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # Serverless directories 104 | .serverless/ 105 | 106 | # FuseBox cache 107 | .fusebox/ 108 | 109 | # DynamoDB Local files 110 | .dynamodb/ 111 | 112 | # TernJS port file 113 | .tern-port 114 | -------------------------------------------------------------------------------- /hello-world/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Antonio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /hello-world/README.md: -------------------------------------------------------------------------------- 1 | # Hello World Tutorial 2 | 3 | ## Project structure 4 | 5 | - `/frontend`: frontend application. 6 | - `/contracts`: smart contracts. 7 | - `/deploy`: deployment and contract interaction scripts. 8 | - `/test`: test files 9 | - `hardhat.config.ts`: configuration file. 10 | 11 | ## Commands 12 | 13 | - `yarn hardhat compile` will compile the contracts. 14 | - `yarn run deploy` will execute the deployment script `/deploy/deploy-greeter.ts`. Requires [environment variable setup](#environment-variables). 15 | - `yarn run greet` will execute the script `/deploy/use-greeter.ts` which interacts with the Greeter contract deployed. 16 | - `yarn test`: run tests. **Check test requirements below.** 17 | 18 | Both `yarn run deploy` and `yarn run greet` are configured in the `package.json` file and run `yarn hardhat deploy-zksync`. 19 | 20 | ### Environment variables 21 | 22 | In order to prevent users to leak private keys, this project includes the `dotenv` package which is used to load environment variables. It's used to load the wallet private key, required to run the deploy script. 23 | 24 | To use it, rename `.env.example` to `.env` and enter your private key. 25 | 26 | ``` 27 | WALLET_PRIVATE_KEY=123cde574ccff.... 28 | ``` 29 | 30 | ### Local testing 31 | 32 | In order to run test, you need to start the zkSync local environment. Please check [this section of the docs](https://v2-docs.zksync.io/api/hardhat/testing.html#prerequisites) which contains all the details. 33 | 34 | If you do not start the zkSync local environment, the tests will fail with error `Error: could not detect network (event="noNetwork", code=NETWORK_ERROR, version=providers/5.7.2)` 35 | 36 | ## Official Links 37 | 38 | - [Website](https://zksync.io/) 39 | - [Documentation](https://v2-docs.zksync.io/dev/) 40 | - [GitHub](https://github.com/matter-labs) 41 | - [Twitter](https://twitter.com/zksync) 42 | - [Discord](https://join.zksync.dev/) 43 | -------------------------------------------------------------------------------- /hello-world/contracts/Greeter.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.8.0; 3 | 4 | contract Greeter { 5 | string private greeting; 6 | 7 | constructor(string memory _greeting) { 8 | greeting = _greeting; 9 | } 10 | 11 | function greet() public view returns (string memory) { 12 | return greeting; 13 | } 14 | 15 | function setGreeting(string memory _greeting) public { 16 | greeting = _greeting; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /hello-world/deploy/deploy-greeter.ts: -------------------------------------------------------------------------------- 1 | import { Wallet, utils } from "zksync-ethers"; 2 | import * as ethers from "ethers"; 3 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 4 | import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; 5 | 6 | // load env file 7 | import dotenv from "dotenv"; 8 | dotenv.config(); 9 | 10 | // load wallet private key from env file 11 | const PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY || ""; 12 | 13 | if (!PRIVATE_KEY) 14 | throw "⛔️ Private key not detected! Add it to the .env file!"; 15 | 16 | // An example of a deploy script that will deploy and call a simple contract. 17 | export default async function (hre: HardhatRuntimeEnvironment) { 18 | console.log(`Running deploy script for the Greeter contract`); 19 | 20 | // Initialize the wallet. 21 | const wallet = new Wallet(PRIVATE_KEY); 22 | 23 | // Create deployer object and load the artifact of the contract you want to deploy. 24 | const deployer = new Deployer(hre, wallet); 25 | const artifact = await deployer.loadArtifact("Greeter"); 26 | 27 | // Estimate contract deployment fee 28 | const greeting = "Hi there!"; 29 | const deploymentFee = await deployer.estimateDeployFee(artifact, [greeting]); 30 | 31 | // ⚠️ OPTIONAL: You can skip this block if your account already has funds in L2 32 | // Deposit funds to L2 33 | // const depositHandle = await deployer.zkWallet.deposit({ 34 | // to: deployer.zkWallet.address, 35 | // token: utils.ETH_ADDRESS, 36 | // amount: deploymentFee.mul(2), 37 | // }); 38 | // // Wait until the deposit is processed on zkSync 39 | // await depositHandle.wait(); 40 | 41 | // Deploy this contract. The returned object will be of a `Contract` type, similarly to ones in `ethers`. 42 | // `greeting` is an argument for contract constructor. 43 | const parsedFee = ethers.utils.formatEther(deploymentFee.toString()); 44 | console.log(`The deployment is estimated to cost ${parsedFee} ETH`); 45 | 46 | const greeterContract = await deployer.deploy(artifact, [greeting]); 47 | 48 | //obtain the Constructor Arguments 49 | console.log( 50 | "Constructor args:" + greeterContract.interface.encodeDeploy([greeting]), 51 | ); 52 | 53 | // Show the contract info. 54 | const contractAddress = greeterContract.address; 55 | console.log(`${artifact.contractName} was deployed to ${contractAddress}`); 56 | 57 | // verify contract for tesnet & mainnet 58 | if (process.env.NODE_ENV != "test") { 59 | // Contract MUST be fully qualified name (e.g. path/sourceName:contractName) 60 | const contractFullyQualifedName = "contracts/Greeter.sol:Greeter"; 61 | 62 | // Verify contract programmatically 63 | const verificationId = await hre.run("verify:verify", { 64 | address: contractAddress, 65 | contract: contractFullyQualifedName, 66 | constructorArguments: [greeting], 67 | bytecode: artifact.bytecode, 68 | }); 69 | } else { 70 | console.log(`Contract not verified, deployed locally.`); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /hello-world/deploy/use-greeter.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from "zksync-ethers"; 2 | import * as ethers from "ethers"; 3 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 4 | 5 | // load env file 6 | import dotenv from "dotenv"; 7 | dotenv.config(); 8 | 9 | // load contract artifact. Make sure to compile first! 10 | import * as ContractArtifact from "../artifacts-zk/contracts/Greeter.sol/Greeter.json"; 11 | 12 | const PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY || ""; 13 | 14 | if (!PRIVATE_KEY) 15 | throw "⛔️ Private key not detected! Add it to the .env file!"; 16 | 17 | // Address of the contract on zksync testnet 18 | const CONTRACT_ADDRESS = ""; 19 | 20 | if (!CONTRACT_ADDRESS) throw "⛔️ Contract address not provided"; 21 | 22 | // An example of a deploy script that will deploy and call a simple contract. 23 | export default async function (hre: HardhatRuntimeEnvironment) { 24 | console.log(`Running script to interact with contract ${CONTRACT_ADDRESS}`); 25 | 26 | // Initialize the provider. 27 | // @ts-ignore 28 | const provider = new Provider(hre.userConfig.networks?.zkSyncTestnet?.url); 29 | const signer = new ethers.Wallet(PRIVATE_KEY, provider); 30 | 31 | // Initialize contract instance 32 | const contract = new ethers.Contract( 33 | CONTRACT_ADDRESS, 34 | ContractArtifact.abi, 35 | signer, 36 | ); 37 | 38 | // Read message from contract 39 | console.log(`The message is ${await contract.greet()}`); 40 | 41 | // send transaction to update the message 42 | const newMessage = "Hello people!"; 43 | const tx = await contract.setGreeting(newMessage); 44 | 45 | console.log(`Transaction to change the message is ${tx.hash}`); 46 | await tx.wait(); 47 | 48 | // Read message after transaction 49 | console.log(`The message now is ${await contract.greet()}`); 50 | } 51 | -------------------------------------------------------------------------------- /hello-world/frontend/.env.example: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matter-labs/tutorials/a34d7dcb33204b8105fbb855a25c24fcadad44d6/hello-world/frontend/.env.example -------------------------------------------------------------------------------- /hello-world/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /hello-world/frontend/README.md: -------------------------------------------------------------------------------- 1 | # greeter-front-end 2 | 3 | Make sure to check out the [tutorial here](https://docs.zksync.io/build/tutorials/dapp-development/frontend-quickstart-paymaster.html). 4 | 5 | ## Project setup 6 | 7 | ``` 8 | yarn install 9 | ``` 10 | 11 | ### Compiles and hot-reloads for development 12 | 13 | ``` 14 | yarn dev 15 | ``` 16 | 17 | ### Compiles and minifies for production 18 | 19 | ``` 20 | yarn build 21 | ``` 22 | -------------------------------------------------------------------------------- /hello-world/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | zkSync Frontend Quickstart 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /hello-world/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-zksync-frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vue-tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "vue": "^3.3.11" 13 | }, 14 | "devDependencies": { 15 | "@metamask/providers": "^14.0.2", 16 | "@vitejs/plugin-vue": "^4.5.2", 17 | "typescript": "^5.2.2", 18 | "vite": "^5.0.8", 19 | "vue-tsc": "^1.8.25" 20 | }, 21 | "keywords": [ 22 | "vite", 23 | "vue", 24 | "typescript", 25 | "zksync", 26 | "web3", 27 | "ethers" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /hello-world/frontend/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hello-world/frontend/src/abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "string", 6 | "name": "_greeting", 7 | "type": "string" 8 | } 9 | ], 10 | "stateMutability": "nonpayable", 11 | "type": "constructor" 12 | }, 13 | { 14 | "inputs": [], 15 | "name": "greet", 16 | "outputs": [ 17 | { 18 | "internalType": "string", 19 | "name": "", 20 | "type": "string" 21 | } 22 | ], 23 | "stateMutability": "view", 24 | "type": "function" 25 | }, 26 | { 27 | "inputs": [ 28 | { 29 | "internalType": "string", 30 | "name": "_greeting", 31 | "type": "string" 32 | } 33 | ], 34 | "name": "setGreeting", 35 | "outputs": [], 36 | "stateMutability": "nonpayable", 37 | "type": "function" 38 | } 39 | ] 40 | -------------------------------------------------------------------------------- /hello-world/frontend/src/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hello-world/frontend/src/erc20.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "address": "0x0000000000000000000000000000000000000000", 4 | "decimals": 18, 5 | "name": "Ether", 6 | "symbol": "ETH" 7 | }, 8 | { 9 | "address": "0x7E2026D8f35872923F5459BbEDDB809F6aCEfEB3", 10 | "decimals": 18, 11 | "name": "Test Token (zkSync Sepolia)", 12 | "symbol": "TEST" 13 | }, 14 | { 15 | "address": "0xe1405bA6D206e978f739E2D9702CC8a0C712755A", 16 | "decimals": 6, 17 | "name": "USD Coin (zkSync Sepolia)", 18 | "symbol": "USDC" 19 | }, 20 | { 21 | "address": "0x6Ff473f001877D553833B6e312C89b3c8fACa7Ac", 22 | "decimals": 18, 23 | "name": "DAI (zkSync Sepolia)", 24 | "symbol": "DAI" 25 | } 26 | ] 27 | -------------------------------------------------------------------------------- /hello-world/frontend/src/eth.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "address": "0x0000000000000000000000000000000000000000", 4 | "decimals": 18, 5 | "name": "Ether", 6 | "symbol": "ETH" 7 | } 8 | ] 9 | -------------------------------------------------------------------------------- /hello-world/frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import "./style.css"; 3 | import App from "./App.vue"; 4 | 5 | createApp(App).mount("#app"); 6 | -------------------------------------------------------------------------------- /hello-world/frontend/src/metamask.d.ts: -------------------------------------------------------------------------------- 1 | // metamask.d.ts 2 | import { MetaMaskInpageProvider } from "@metamask/providers"; 3 | 4 | declare global { 5 | interface Window { 6 | ethereum?: MetaMaskInpageProvider; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /hello-world/frontend/src/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | } 32 | 33 | h1 { 34 | font-size: 3.2em; 35 | line-height: 1.1; 36 | } 37 | 38 | button { 39 | border-radius: 8px; 40 | border: 1px solid transparent; 41 | padding: 0.6em 1.2em; 42 | font-size: 1em; 43 | font-weight: 500; 44 | font-family: inherit; 45 | background-color: #1a1a1a; 46 | cursor: pointer; 47 | transition: border-color 0.25s; 48 | } 49 | button:hover { 50 | border-color: #646cff; 51 | } 52 | button:focus, 53 | button:focus-visible { 54 | outline: 4px auto -webkit-focus-ring-color; 55 | } 56 | 57 | #app { 58 | max-width: 1280px; 59 | margin: 0 auto; 60 | padding: 2rem; 61 | text-align: center; 62 | } 63 | 64 | @media (prefers-color-scheme: light) { 65 | :root { 66 | color: #213547; 67 | background-color: #ffffff; 68 | } 69 | a:hover { 70 | color: #747bff; 71 | } 72 | button { 73 | background-color: #f9f9f9; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /hello-world/frontend/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /hello-world/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "preserve", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /hello-world/frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /hello-world/frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import vue from "@vitejs/plugin-vue"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | }); 8 | -------------------------------------------------------------------------------- /hello-world/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig } from "hardhat/config"; 2 | 3 | import "@matterlabs/hardhat-zksync-deploy"; 4 | import "@matterlabs/hardhat-zksync-solc"; 5 | 6 | import "@matterlabs/hardhat-zksync-verify"; 7 | 8 | const zkSyncTestnet = { 9 | url: "https://sepolia.era.zksync.dev", 10 | ethNetwork: "sepolia", 11 | zksync: true, 12 | // contract verification endpoint 13 | verifyURL: "https://explorer.sepolia.era.zksync.dev/contract_verification", 14 | }; 15 | 16 | const config: HardhatUserConfig = { 17 | zksolc: { 18 | version: "latest", 19 | settings: {}, 20 | }, 21 | defaultNetwork: "zkSyncTestnet", 22 | networks: { 23 | hardhat: { 24 | zksync: false, 25 | }, 26 | zkSyncTestnet, 27 | }, 28 | solidity: { 29 | version: "0.8.17", 30 | }, 31 | }; 32 | 33 | export default config; 34 | -------------------------------------------------------------------------------- /hello-world/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "Antonio ", 6 | "license": "MIT", 7 | "devDependencies": { 8 | "@matterlabs/hardhat-zksync-deploy": "^0.7.0", 9 | "@matterlabs/hardhat-zksync-node": "^0.0.1", 10 | "@matterlabs/hardhat-zksync-solc": "^0.4.2", 11 | "@matterlabs/hardhat-zksync-verify": "^0.4.0", 12 | "@nomiclabs/hardhat-etherscan": "^3.1.7", 13 | "@types/chai": "^4.3.4", 14 | "@types/mocha": "^10.0.1", 15 | "chai": "^4.3.7", 16 | "dotenv": "^16.0.3", 17 | "ethers": "^5.7.2", 18 | "hardhat": "^2.12.4", 19 | "mocha": "^10.2.0", 20 | "ts-node": "^10.9.1", 21 | "typescript": "^4.9.4", 22 | "zksync-ethers": "^5" 23 | }, 24 | "scripts": { 25 | "test": "hardhat test", 26 | "deploy": "yarn hardhat deploy-zksync --script deploy-greeter.ts", 27 | "greet": "yarn hardhat deploy-zksync --script use-greeter.ts" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /hello-world/test/main.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { deploy } from "./utils/utils"; 3 | 4 | describe("Greeter", function () { 5 | let contract: any; 6 | let result: string; 7 | 8 | beforeEach(async function () { 9 | contract = await deploy(); 10 | }); 11 | 12 | it("Should be deployed and have address", async function () { 13 | result = typeof (await contract.address); 14 | 15 | expect(result).to.be.a("string"); 16 | }); 17 | 18 | it("Should be deployed and have tx hash", async function () { 19 | result = await contract.deployTransaction.hash; 20 | 21 | expect(result).to.be.a("string"); 22 | }); 23 | 24 | it("Should return 'Hi' as an expected message", async function () { 25 | result = await contract.greet(); 26 | 27 | expect(result).to.eq("Hi"); 28 | }); 29 | 30 | it("Should return the new greeting once it's changed", async function () { 31 | const setGreetingTx = await contract.setGreeting("Hola, mundo!"); 32 | 33 | await setGreetingTx.wait(1); // wait until the transaction is mined 34 | 35 | result = await contract.greet(); 36 | 37 | expect(result).to.equal("Hola, mundo!"); 38 | }); 39 | 40 | it("Should return an empty string value if a parameter has a number type", async function () { 41 | const setGreetingTx = await contract.setGreeting(1); 42 | 43 | await setGreetingTx.wait(1); // wait until the transaction is mined 44 | 45 | result = await contract.greet(); 46 | 47 | expect(result).to.equal(""); 48 | }); 49 | 50 | it("Should return an empty string value if a parameter has an object type", async function () { 51 | const setGreetingTx = await contract.setGreeting({}); 52 | 53 | await setGreetingTx.wait(1); // wait until the transaction is mined 54 | 55 | result = await contract.greet(); 56 | 57 | expect(result).to.equal(""); 58 | }); 59 | 60 | it("Should return an empty string value if a parameter has an array type", async function () { 61 | const setGreetingTx = await contract.setGreeting([]); 62 | 63 | await setGreetingTx.wait(1); // wait until the transaction is mined 64 | 65 | result = await contract.greet(); 66 | 67 | expect(result).to.equal(""); 68 | }); 69 | 70 | it("Should return an empty string value if a parameter has an empty string type", async function () { 71 | const setGreetingTx = await contract.setGreeting(""); 72 | 73 | await setGreetingTx.wait(1); // wait until the transaction is mined 74 | 75 | result = await contract.greet(); 76 | 77 | expect(result).to.equal(""); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /hello-world/test/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import { Wallet, Provider, Contract } from "zksync-ethers"; 2 | import * as hre from "hardhat"; 3 | import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; 4 | import { Wallets } from "../../../tests/testData"; 5 | import { localConfig } from "../../../tests/testConfig"; 6 | 7 | export const deploy = async () => { 8 | const provider = new Provider(localConfig.L2Network); 9 | 10 | const wallet = new Wallet(Wallets.firstWalletPrivateKey, provider); 11 | const deployer = new Deployer(hre, wallet); 12 | const artifact = await deployer.loadArtifact("Greeter"); 13 | const contract = await deployer.deploy(artifact, ["Hi"]); 14 | 15 | return contract; 16 | }; 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zksync-tutorials", 3 | "private": true, 4 | "workspaces": [ 5 | "cross-chain/L1-governance", 6 | "cross-chain/L2-counter", 7 | "hello-world", 8 | "hello-world-docker", 9 | "custom-aa", 10 | "custom-paymaster", 11 | "spend-limit", 12 | "tests", 13 | "gated-nft", 14 | "greeter" 15 | ], 16 | "version": "0.1.0", 17 | "license": "MIT", 18 | "devDependencies": { 19 | "prettier": "3.0.0" 20 | }, 21 | "scripts": { 22 | "fix:fmt": "prettier --write .", 23 | "lint:fmt": "prettier --check ." 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /spend-limit/.env.example: -------------------------------------------------------------------------------- 1 | NODE_ENV= 2 | WALLET_PRIVATE_KEY= 3 | DEPLOYED_ACCOUNT_OWNER_PRIVATE_KEY= 4 | DEPLOYED_ACCOUNT_ADDRESS= 5 | RECEIVER_ACCOUNT= 6 | -------------------------------------------------------------------------------- /spend-limit/.gitignore: -------------------------------------------------------------------------------- 1 | artifacts-zk 2 | cache-zk 3 | local-setup 4 | node_modules 5 | .env 6 | package-lock.json 7 | yarn.lock -------------------------------------------------------------------------------- /spend-limit/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Matter Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /spend-limit/README.md: -------------------------------------------------------------------------------- 1 | # Daily Spending Limit 2 | 3 | This repository is the submission to [the zkSync bounty of the daily spending limit tutorial](https://github.com/matter-labs/zksync-web-v2-docs/issues/241). 4 | 5 | `TUTORIAL.md` is the tutorial documentation. 6 | 7 | Deployed Account contract that has the daily spending limit feature: [0x6b6B8ea196a6F27EFE408288a4FEeBE9A9e12005](https://zksync2-testnet.zkscan.io/address/0x6b6B8ea196a6F27EFE408288a4FEeBE9A9e12005/transactions) and owner pk:`0x957aff65500eda28beb7130b7c1bc48f783556bb84fa6874d2204c1d66a0ddc7` 8 | 9 | ## Credits 10 | 11 | Forked from [this original repository](https://github.com/porco-rosso-j/daily-spendlimit-tutorial) by [porco-rosso](https://linktr.ee/porcorossoj) 12 | 13 | ## Deployment & Test 14 | 15 | ### zkSync2.0 testnet 16 | 17 | As for deployment and simple test on zkSync2.0 testnet, please take a look at the tutorial doc, TUTORIAL.md. 18 | 19 | ### zkSync local network. 20 | 21 | `spend-limit.test.ts` in [the test folder](./test/) offers more detailed tests for each functionality of the SpendLimit contract. 22 | 23 | ```shell 24 | git clone git@github.com:porco-rosso-j/daily-spendlimit-tutorial.git 25 | ``` 26 | 27 | - Enter the repo and install dependencies. 28 | 29 | ```shell 30 | cd daily-spendlimit-tutorial 31 | yarn 32 | ``` 33 | 34 | - To set up a local environment, Docker and docker-compose should be installed. 35 | If they are not installed on your computer: [Install](https://docs.docker.com/get-docker/). 36 | 37 | - To run zkSync local chain, do: 38 | 39 | ```shell 40 | git clone https://github.com/matter-labs/local-setup.git 41 | cd local-setup 42 | ./start.sh 43 | ``` 44 | 45 | \*check details and common errors for running local zksync chain [here](https://era.zksync.io/docs/tools/testing/dockerized-testing.html#resetting-the-zksync-state). 46 | 47 | - Compile: 48 | 49 | ```shell 50 | yarn hardhat compile 51 | ``` 52 | 53 | - Additional configuration: rename .env.example to `.env` and add `NODE_ENV=test`. 54 | 55 | Then run: 56 | 57 | ```shell 58 | yarn hardhat test 59 | ``` 60 | 61 | **Some tests are not passing due to different error messages returned by the zkSync Era node.** This repo will be updated to fix the remaining tests. 62 | -------------------------------------------------------------------------------- /spend-limit/contracts/AAFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; 5 | import "@matterlabs/zksync-contracts/l2/system-contracts/libraries/SystemContractsCaller.sol"; 6 | 7 | contract AAFactory { 8 | bytes32 public aaBytecodeHash; 9 | 10 | constructor(bytes32 _aaBytecodeHash) { 11 | aaBytecodeHash = _aaBytecodeHash; 12 | } 13 | 14 | function deployAccount( 15 | bytes32 salt, 16 | address owner 17 | ) external returns (address accountAddress) { 18 | (bool success, bytes memory returnData) = SystemContractsCaller 19 | .systemCallWithReturndata( 20 | uint32(gasleft()), 21 | address(DEPLOYER_SYSTEM_CONTRACT), 22 | uint128(0), 23 | abi.encodeCall( 24 | DEPLOYER_SYSTEM_CONTRACT.create2Account, 25 | ( 26 | salt, 27 | aaBytecodeHash, 28 | abi.encode(owner), 29 | IContractDeployer.AccountAbstractionVersion.Version1 30 | ) 31 | ) 32 | ); 33 | require(success, "Deployment failed"); 34 | 35 | (accountAddress) = abi.decode(returnData, (address)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /spend-limit/contracts/SpendLimit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | contract SpendLimit { 5 | // uint public ONE_DAY = 24 hours; 6 | uint public ONE_DAY = 1 minutes; // set to 1 min for tutorial 7 | 8 | /// This struct serves as data storage of daily spending limits users enable 9 | /// limit: the amount of a daily spending limit 10 | /// available: the available amount that can be spent 11 | /// resetTime: block.timestamp at the available amount is restored 12 | /// isEnabled: true when a daily spending limit is enabled 13 | struct Limit { 14 | uint limit; 15 | uint available; 16 | uint resetTime; 17 | bool isEnabled; 18 | } 19 | 20 | mapping(address => Limit) public limits; // token => Limit 21 | 22 | modifier onlyAccount() { 23 | require( 24 | msg.sender == address(this), 25 | "Only the account that inherits this contract can call this method." 26 | ); 27 | _; 28 | } 29 | 30 | /// this function enables a daily spending limit for specific tokens. 31 | /// @param _token ETH or ERC20 token address that a given spending limit is applied. 32 | /// @param _amount non-zero limit. 33 | function setSpendingLimit(address _token, uint _amount) public onlyAccount { 34 | require(_amount != 0, "Invalid amount"); 35 | 36 | uint resetTime; 37 | uint timestamp = block.timestamp; // L2 block timestamp 38 | 39 | if (isValidUpdate(_token)) { 40 | resetTime = timestamp + ONE_DAY; 41 | } else { 42 | resetTime = timestamp; 43 | } 44 | 45 | _updateLimit(_token, _amount, _amount, resetTime, true); 46 | } 47 | 48 | // this function disables an active daily spending limit, 49 | // decreasing each uint number in the Limit struct to zero and setting isEnabled false. 50 | function removeSpendingLimit(address _token) public onlyAccount { 51 | require(isValidUpdate(_token), "Invalid Update"); 52 | _updateLimit(_token, 0, 0, 0, false); 53 | } 54 | 55 | // verify if the update to a Limit struct is valid 56 | // Ensure that users can't freely modify(increase or remove) the daily limit to spend more. 57 | function isValidUpdate(address _token) internal view returns (bool) { 58 | // Reverts unless it is first spending after enabling 59 | // or called after 24 hours have passed since the last update. 60 | if (limits[_token].isEnabled) { 61 | require( 62 | limits[_token].limit == limits[_token].available || 63 | block.timestamp > limits[_token].resetTime, 64 | "Invalid Update" 65 | ); 66 | 67 | return true; 68 | } else { 69 | return false; 70 | } 71 | } 72 | 73 | // storage-modifying private function called by either setSpendingLimit or removeSpendingLimit 74 | function _updateLimit( 75 | address _token, 76 | uint _limit, 77 | uint _available, 78 | uint _resetTime, 79 | bool _isEnabled 80 | ) private { 81 | Limit storage limit = limits[_token]; 82 | limit.limit = _limit; 83 | limit.available = _available; 84 | limit.resetTime = _resetTime; 85 | limit.isEnabled = _isEnabled; 86 | } 87 | 88 | // this function is called by the account before execution. 89 | // Verify the account is able to spend a given amount of tokens. And it records a new available amount. 90 | function _checkSpendingLimit(address _token, uint _amount) internal { 91 | Limit memory limit = limits[_token]; 92 | 93 | // return if spending limit hasn't been enabled yet 94 | if (!limit.isEnabled) return; 95 | 96 | uint timestamp = block.timestamp; // L2 block timestamp 97 | 98 | // Renew resetTime and available amount, which is only performed 99 | // if a day has already passed since the last update: timestamp > resetTime 100 | if (limit.limit != limit.available && timestamp > limit.resetTime) { 101 | limit.resetTime = timestamp + ONE_DAY; 102 | limit.available = limit.limit; 103 | 104 | // Or only resetTime is updated if it's the first spending after enabling limit 105 | } else if (limit.limit == limit.available) { 106 | limit.resetTime = timestamp + ONE_DAY; 107 | } 108 | 109 | // reverts if the amount exceeds the remaining available amount. 110 | require(limit.available >= _amount, "Exceed daily limit"); 111 | 112 | // decrement `available` 113 | limit.available -= _amount; 114 | limits[_token] = limit; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /spend-limit/contracts/test/TestSpendLimit.sol: -------------------------------------------------------------------------------- 1 | 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity ^0.8.0; 4 | 5 | import '../SpendLimit.sol'; 6 | 7 | contract TestSpendLimit is SpendLimit { 8 | 9 | // testing purpose: can set it to 10~30 sec. 10 | function changeONE_DAY(uint _time) public { 11 | ONE_DAY = _time; 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /spend-limit/deploy/deployFactoryAccount.ts: -------------------------------------------------------------------------------- 1 | import { utils, Wallet, Provider } from "zksync-ethers"; 2 | import * as ethers from "ethers"; 3 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 4 | import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; 5 | 6 | // load env file 7 | import dotenv from "dotenv"; 8 | dotenv.config(); 9 | 10 | const DEPLOYER_PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY || ""; 11 | 12 | export default async function (hre: HardhatRuntimeEnvironment) { 13 | // @ts-ignore target zkSyncSepoliaTestnet in config file which can be testnet or local 14 | const provider = new Provider(hre.config.networks.zkSyncSepoliaTestnet.url); 15 | const wallet = new Wallet(DEPLOYER_PRIVATE_KEY, provider); 16 | const deployer = new Deployer(hre, wallet); 17 | const factoryArtifact = await deployer.loadArtifact("AAFactory"); 18 | const aaArtifact = await deployer.loadArtifact("Account"); 19 | 20 | // Bridge funds if the wallet on zkSync doesn't have enough funds. 21 | // const depositAmount = ethers.utils.parseEther('0.1'); 22 | // const depositHandle = await deployer.zkWallet.deposit({ 23 | // to: deployer.zkWallet.address, 24 | // token: utils.ETH_ADDRESS, 25 | // amount: depositAmount, 26 | // }); 27 | // await depositHandle.wait(); 28 | 29 | const factory = await deployer.deploy( 30 | factoryArtifact, 31 | [utils.hashBytecode(aaArtifact.bytecode)], 32 | undefined, 33 | [aaArtifact.bytecode], 34 | ); 35 | const factoryAddress = await factory.getAddress(); 36 | console.log(`AA factory address: ${factoryAddress}`); 37 | 38 | const aaFactory = new ethers.Contract( 39 | factoryAddress, 40 | factoryArtifact.abi, 41 | wallet, 42 | ); 43 | 44 | const owner = Wallet.createRandom(); 45 | console.log("SC Account owner pk: ", owner.privateKey); 46 | 47 | const salt = ethers.ZeroHash; 48 | const tx = await aaFactory.deployAccount(salt, owner.address); 49 | await tx.wait(); 50 | 51 | const abiCoder = new ethers.AbiCoder(); 52 | const accountAddress = utils.create2Address( 53 | factoryAddress, 54 | await aaFactory.aaBytecodeHash(), 55 | salt, 56 | abiCoder.encode(["address"], [owner.address]), 57 | ); 58 | 59 | console.log(`SC Account deployed on address ${accountAddress}`); 60 | 61 | console.log("Funding smart contract account with some ETH"); 62 | await ( 63 | await wallet.sendTransaction({ 64 | to: accountAddress, 65 | value: ethers.parseEther("0.02"), 66 | }) 67 | ).wait(); 68 | console.log(`Done!`); 69 | } 70 | -------------------------------------------------------------------------------- /spend-limit/deploy/setLimit.ts: -------------------------------------------------------------------------------- 1 | import { 2 | utils, 3 | Wallet, 4 | Provider, 5 | Contract, 6 | EIP712Signer, 7 | types, 8 | } from "zksync-ethers"; 9 | import * as ethers from "ethers"; 10 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 11 | 12 | // load env file 13 | import dotenv from "dotenv"; 14 | dotenv.config(); 15 | 16 | // load the values into .env file after deploying the FactoryAccount 17 | const DEPLOYED_ACCOUNT_OWNER_PRIVATE_KEY = 18 | process.env.DEPLOYED_ACCOUNT_OWNER_PRIVATE_KEY || ""; 19 | const ETH_ADDRESS = 20 | process.env.ETH_ADDRESS || "0x000000000000000000000000000000000000800A"; 21 | const ACCOUNT_ADDRESS = process.env.DEPLOYED_ACCOUNT_ADDRESS || ""; 22 | 23 | export default async function (hre: HardhatRuntimeEnvironment) { 24 | // @ts-ignore target zkSyncSepoliaTestnet in config file which can be testnet or local 25 | const provider = new Provider(hre.config.networks.zkSyncSepoliaTestnet.url); 26 | 27 | const owner = new Wallet(DEPLOYED_ACCOUNT_OWNER_PRIVATE_KEY, provider); 28 | 29 | const accountArtifact = await hre.artifacts.readArtifact("Account"); 30 | const account = new Contract(ACCOUNT_ADDRESS, accountArtifact.abi, owner); 31 | 32 | let setLimitTx = await account.setSpendingLimit.populateTransaction( 33 | ETH_ADDRESS, 34 | ethers.parseEther("0.0005"), 35 | ); 36 | 37 | setLimitTx = { 38 | ...setLimitTx, 39 | from: ACCOUNT_ADDRESS, 40 | chainId: (await provider.getNetwork()).chainId, 41 | nonce: await provider.getTransactionCount(ACCOUNT_ADDRESS), 42 | type: 113, 43 | customData: { 44 | gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, 45 | } as types.Eip712Meta, 46 | value: BigInt(0), 47 | }; 48 | 49 | setLimitTx.gasPrice = await provider.getGasPrice(); 50 | setLimitTx.gasLimit = await provider.estimateGas(setLimitTx); 51 | 52 | const signedTxHash = EIP712Signer.getSignedDigest(setLimitTx); 53 | 54 | const signature = ethers.concat([ 55 | ethers.Signature.from(owner.signingKey.sign(signedTxHash)).serialized, 56 | ]); 57 | 58 | setLimitTx.customData = { 59 | ...setLimitTx.customData, 60 | customSignature: signature, 61 | }; 62 | 63 | console.log("Setting limit for account..."); 64 | const sentTx = await provider.broadcastTransaction( 65 | types.Transaction.from(setLimitTx).serialized, 66 | ); 67 | 68 | await sentTx.wait(); 69 | 70 | const limit = await account.limits(ETH_ADDRESS); 71 | console.log("Account limit enabled?: ", limit.isEnabled); 72 | console.log("Account limit: ", limit.limit.toString()); 73 | console.log("Available limit today: ", limit.available.toString()); 74 | console.log("Time to reset limit: ", limit.resetTime.toString()); 75 | } 76 | -------------------------------------------------------------------------------- /spend-limit/deploy/transferETH.ts: -------------------------------------------------------------------------------- 1 | import { 2 | utils, 3 | Wallet, 4 | Provider, 5 | Contract, 6 | EIP712Signer, 7 | types, 8 | } from "zksync-ethers"; 9 | import * as ethers from "ethers"; 10 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 11 | 12 | // load env file 13 | import dotenv from "dotenv"; 14 | dotenv.config(); 15 | 16 | // load the values into .env file after deploying the FactoryAccount 17 | const DEPLOYED_ACCOUNT_OWNER_PRIVATE_KEY = 18 | process.env.DEPLOYED_ACCOUNT_OWNER_PRIVATE_KEY || ""; 19 | const ETH_ADDRESS = 20 | process.env.ETH_ADDRESS || "0x000000000000000000000000000000000000800A"; 21 | const ACCOUNT_ADDRESS = process.env.DEPLOYED_ACCOUNT_ADDRESS || ""; 22 | const RECEIVER_ACCOUNT = process.env.RECEIVER_ACCOUNT || ""; 23 | 24 | export default async function (hre: HardhatRuntimeEnvironment) { 25 | // @ts-ignore target zkSyncSepoliaTestnet in config file which can be testnet or local 26 | const provider = new Provider(hre.config.networks.zkSyncSepoliaTestnet.url); 27 | 28 | const owner = new Wallet(DEPLOYED_ACCOUNT_OWNER_PRIVATE_KEY, provider); 29 | 30 | // ⚠️ update this amount to test if the limit works; 0.00051 fails but 0.00049 succeeds 31 | const transferAmount = "0.000051"; 32 | 33 | let ethTransferTx = { 34 | from: ACCOUNT_ADDRESS, 35 | to: RECEIVER_ACCOUNT, // account that will receive the ETH transfer 36 | chainId: (await provider.getNetwork()).chainId, 37 | nonce: await provider.getTransactionCount(ACCOUNT_ADDRESS), 38 | type: 113, 39 | customData: { 40 | gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, 41 | } as types.Eip712Meta, 42 | 43 | value: ethers.parseEther(transferAmount), 44 | data: "0x", 45 | } as types.Transaction; 46 | 47 | ethTransferTx.gasPrice = await provider.getGasPrice(); 48 | ethTransferTx.gasLimit = await provider.estimateGas(ethTransferTx); 49 | 50 | const signedTxHash = EIP712Signer.getSignedDigest(ethTransferTx); 51 | const signature = ethers.concat([ 52 | ethers.Signature.from(owner.signingKey.sign(signedTxHash)).serialized, 53 | ]); 54 | 55 | ethTransferTx.customData = { 56 | ...ethTransferTx.customData, 57 | customSignature: signature, 58 | }; 59 | 60 | const accountArtifact = await hre.artifacts.readArtifact("Account"); 61 | 62 | // read account limits 63 | const account = new Contract(ACCOUNT_ADDRESS, accountArtifact.abi, owner); 64 | const limitData = await account.limits(ETH_ADDRESS); 65 | 66 | console.log("Account ETH limit is: ", limitData.limit.toString()); 67 | console.log("Available today: ", limitData.available.toString()); 68 | 69 | console.log( 70 | "Limit will reset on timestamp: ", 71 | limitData.resetTime.toString(), 72 | ); 73 | 74 | // actually do the ETH transfer 75 | console.log("Sending ETH transfer from smart contract account"); 76 | const sentTx = await provider.broadcastTransaction( 77 | types.Transaction.from(ethTransferTx).serialized, 78 | ); 79 | await sentTx.wait(); 80 | console.log(`ETH transfer tx hash is ${sentTx.hash}`); 81 | 82 | console.log("Transfer completed and limits updated!"); 83 | 84 | const newLimitData = await account.limits(ETH_ADDRESS); 85 | console.log("Account limit: ", newLimitData.limit.toString()); 86 | console.log("Available today: ", newLimitData.available.toString()); 87 | console.log( 88 | "Limit will reset on timestamp:", 89 | newLimitData.resetTime.toString(), 90 | ); 91 | 92 | const currentTimestamp = Math.floor(Date.now() / 1000); 93 | console.log("Current timestamp: ", currentTimestamp); 94 | 95 | if (newLimitData.resetTime > currentTimestamp) { 96 | console.log("Reset time was not updated as not enough time has passed"); 97 | } else { 98 | console.log("Limit timestamp was reset"); 99 | } 100 | return; 101 | } 102 | -------------------------------------------------------------------------------- /spend-limit/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import "@matterlabs/hardhat-zksync-deploy"; 2 | import "@matterlabs/hardhat-zksync-solc"; 3 | import "@matterlabs/hardhat-zksync-verify"; 4 | import "@matterlabs/hardhat-zksync-node"; 5 | import * as dotenv from "dotenv"; 6 | 7 | import { HardhatUserConfig } from "hardhat/config"; 8 | 9 | dotenv.config(); 10 | 11 | const zkSyncTestnet = 12 | process.env.NODE_ENV == "test" 13 | ? { 14 | url: "http://localhost:3050", 15 | ethNetwork: "http://localhost:8545", 16 | zksync: true, 17 | } 18 | : { 19 | url: "https://sepolia.era.zksync.dev", 20 | ethNetwork: "sepolia", // Can also be the RPC URL of the network (e.g. `https://sepolia.infura.io/v3/`) 21 | zksync: true, 22 | verifyURL: 23 | "https://explorer.sepolia.era.zksync.dev/contract_verification", 24 | }; 25 | 26 | const config: HardhatUserConfig = { 27 | zksolc: { 28 | version: "latest", 29 | settings: { 30 | isSystem: true, 31 | }, 32 | }, 33 | 34 | defaultNetwork: "zkSyncSepoliaTestnet", 35 | 36 | networks: { 37 | zkSyncSepoliaTestnet: { 38 | url: "https://sepolia.era.zksync.dev", 39 | ethNetwork: "sepolia", 40 | zksync: true, 41 | verifyURL: 42 | "https://explorer.sepolia.era.zksync.dev/contract_verification", 43 | }, 44 | hardhat: { 45 | zksync: true, 46 | }, 47 | zkSyncTestnet, 48 | }, 49 | solidity: { 50 | version: "0.8.17", 51 | }, 52 | }; 53 | 54 | export default config; 55 | -------------------------------------------------------------------------------- /spend-limit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "daily-spendlimit-tutorial", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": "git@github.com:porco-rosso-j/daily-spendlimit-tutorial.git", 6 | "author": "Porco ", 7 | "license": "MIT", 8 | "scripts": { 9 | "deploy": "hardhat deploy-zksync --script deploy.ts", 10 | "interact": "hardhat deploy-zksync --script interact.ts", 11 | "compile": "hardhat compile", 12 | "clean": "hardhat clean", 13 | "test": "hardhat test --network hardhat" 14 | }, 15 | "devDependencies": { 16 | "@matterlabs/hardhat-zksync-deploy": "^1.1.2", 17 | "@matterlabs/hardhat-zksync-node": "^1.0.1", 18 | "@matterlabs/hardhat-zksync-solc": "^1.0.6", 19 | "@matterlabs/hardhat-zksync-verify": "^1.2.2", 20 | "@matterlabs/zksync-contracts": "^0.6.1", 21 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.5", 22 | "@nomiclabs/hardhat-etherscan": "^3.1.7", 23 | "@openzeppelin/contracts": "4.9.5", 24 | "@types/chai": "^4.3.4", 25 | "@types/mocha": "^10.0.1", 26 | "chai": "^4.3.7", 27 | "dotenv": "^16.0.3", 28 | "ethers": "^6.9.2", 29 | "hardhat": "^2.12.4", 30 | "mocha": "^10.2.0", 31 | "ts-node": "^10.9.1", 32 | "typescript": "^4.9.5", 33 | "zksync-ethers": "^6.0.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /spend-limit/test/utils/deploy.ts: -------------------------------------------------------------------------------- 1 | import { Wallet, Contract, utils } from "zksync-ethers"; 2 | import * as hre from "hardhat"; 3 | import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; 4 | import { ethers } from "ethers"; 5 | 6 | export async function deployAAFactory(wallet: Wallet): Promise { 7 | let deployer: Deployer = new Deployer(hre, wallet); 8 | const factoryArtifact = await deployer.loadArtifact("AAFactory"); 9 | const accountArtifact = await deployer.loadArtifact("TestAccount"); 10 | const bytecodeHash = utils.hashBytecode(accountArtifact.bytecode); 11 | 12 | return await deployer.deploy(factoryArtifact, [bytecodeHash], undefined, [ 13 | accountArtifact.bytecode, 14 | ]); 15 | } 16 | 17 | export async function deployAccount( 18 | wallet: Wallet, 19 | owner: Wallet, 20 | factory_address: string, 21 | ): Promise { 22 | let deployer: Deployer = new Deployer(hre, wallet); 23 | const factoryArtifact = await hre.artifacts.readArtifact("AAFactory"); 24 | const factory = new ethers.Contract( 25 | factory_address, 26 | factoryArtifact.abi, 27 | wallet, 28 | ); 29 | 30 | const salt = ethers.constants.HashZero; 31 | await (await factory.deployAccount(salt, owner.address)).wait(); 32 | 33 | const AbiCoder = new ethers.utils.AbiCoder(); 34 | const account_address = utils.create2Address( 35 | factory.address, 36 | await factory.aaBytecodeHash(), 37 | salt, 38 | AbiCoder.encode(["address"], [owner.address]), 39 | ); 40 | 41 | const accountArtifact = await deployer.loadArtifact("TestAccount"); 42 | 43 | return new ethers.Contract(account_address, accountArtifact.abi, wallet); 44 | } 45 | -------------------------------------------------------------------------------- /spend-limit/test/utils/helper.ts: -------------------------------------------------------------------------------- 1 | import { ethers, BigNumber } from "ethers"; 2 | import { Wallet } from "zksync-ethers"; 3 | 4 | export const toBN = (x: string): BigNumber => { 5 | return ethers.utils.parseEther(x); 6 | }; 7 | 8 | export const Tx = (wallet: Wallet, value: string) => { 9 | return { 10 | to: wallet.address, 11 | value: ethers.utils.parseEther(value), 12 | data: "0x", 13 | }; 14 | }; 15 | 16 | export async function consoleLimit(limit) { 17 | console.log( 18 | "\n", 19 | '"Limit"', 20 | "\n", 21 | "- Limit: ", 22 | limit.limit.toString(), 23 | "\n", 24 | "- Available: ", 25 | limit.available.toString(), 26 | "\n", 27 | "- Reset Time: ", 28 | limit.resetTime.toString(), 29 | "\n", 30 | "- Now: ", 31 | Math.floor(Date.now() / 1000).toString(), 32 | "\n", 33 | "- isEnabled: ", 34 | limit.isEnabled.toString(), 35 | "\n", 36 | "\n", 37 | ); 38 | } 39 | 40 | export async function consoleAddreses(wallet, factory, account, user) { 41 | console.log( 42 | "\n", 43 | "-- Addresses -- ", 44 | "\n", 45 | "- Wallet: ", 46 | wallet.address, 47 | "\n", 48 | "- Factory: ", 49 | factory.address, 50 | "\n", 51 | "- Account: ", 52 | account.address, 53 | "\n", 54 | "- User: ", 55 | user.address, 56 | "\n", 57 | "\n", 58 | ); 59 | } 60 | 61 | export async function getBalances(provider, wallet, account, user) { 62 | const WalletETHBal = await provider.getBalance(wallet.address); 63 | const AccountETHBal = await provider.getBalance(account.address); 64 | const UserETHBal = await provider.getBalance(user.address); 65 | 66 | // console.log( 67 | // '\n', 68 | // 'Balances', '\n', 69 | // '- Wallet ETH balance: ', WalletETHBal.toString(), '\n', 70 | // '- Account ETH balance: ', AccountETHBal.toString(), '\n', 71 | // '- User ETH balance: ', UserETHBal.toString(), '\n', 72 | // '\n', 73 | // ) 74 | 75 | const balances = { 76 | WalletETHBal, 77 | AccountETHBal, 78 | UserETHBal, 79 | }; 80 | 81 | return balances; 82 | } 83 | -------------------------------------------------------------------------------- /spend-limit/test/utils/rich-wallets.ts: -------------------------------------------------------------------------------- 1 | export const rich_wallet = [ 2 | { 3 | address: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", 4 | privateKey: 5 | "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110", 6 | }, 7 | { 8 | address: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", 9 | privateKey: 10 | "0xac1e735be8536c6534bb4f17f06f6afc73b2b5ba84ac2cfb12f7461b20c0bbe3", 11 | }, 12 | { 13 | address: "0x0D43eB5B8a47bA8900d84AA36656c92024e9772e", 14 | privateKey: 15 | "0xd293c684d884d56f8d6abd64fc76757d3664904e309a0645baf8522ab6366d9e", 16 | }, 17 | { 18 | address: "0xA13c10C0D5bd6f79041B9835c63f91de35A15883", 19 | privateKey: 20 | "0x850683b40d4a740aa6e745f889a6fdc8327be76e122f5aba645a5b02d0248db8", 21 | }, 22 | { 23 | address: "0x8002cD98Cfb563492A6fB3E7C8243b7B9Ad4cc92", 24 | privateKey: 25 | "0xf12e28c0eb1ef4ff90478f6805b68d63737b7f33abfa091601140805da450d93", 26 | }, 27 | { 28 | address: "0x4F9133D1d3F50011A6859807C837bdCB31Aaab13", 29 | privateKey: 30 | "0xe667e57a9b8aaa6709e51ff7d093f1c5b73b63f9987e4ab4aa9a5c699e024ee8", 31 | }, 32 | { 33 | address: "0xbd29A1B981925B94eEc5c4F1125AF02a2Ec4d1cA", 34 | privateKey: 35 | "0x28a574ab2de8a00364d5dd4b07c4f2f574ef7fcc2a86a197f65abaec836d1959", 36 | }, 37 | { 38 | address: "0xedB6F5B4aab3dD95C7806Af42881FF12BE7e9daa", 39 | privateKey: 40 | "0x74d8b3a188f7260f67698eb44da07397a298df5427df681ef68c45b34b61f998", 41 | }, 42 | { 43 | address: "0xe706e60ab5Dc512C36A4646D719b889F398cbBcB", 44 | privateKey: 45 | "0xbe79721778b48bcc679b78edac0ce48306a8578186ffcb9f2ee455ae6efeace1", 46 | }, 47 | { 48 | address: "0xE90E12261CCb0F3F7976Ae611A29e84a6A85f424", 49 | privateKey: 50 | "0x3eb15da85647edd9a1159a4a13b9e7c56877c4eb33f614546d4db06a51868b1c", 51 | }, 52 | ]; 53 | -------------------------------------------------------------------------------- /spend-limit/test/utils/sendtx.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Wallet, 3 | Contract, 4 | Provider, 5 | utils, 6 | EIP712Signer, 7 | types, 8 | } from "zksync-ethers"; 9 | import { ethers } from "ethers"; 10 | 11 | export async function sendTx( 12 | provider: Provider, 13 | account: Contract, 14 | user: Wallet, 15 | tx: any, 16 | ) { 17 | tx = { 18 | ...tx, 19 | from: account.address, 20 | chainId: (await provider.getNetwork()).chainId, 21 | nonce: await provider.getTransactionCount(account.address), 22 | type: 113, 23 | customData: { 24 | gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, 25 | } as types.Eip712Meta, 26 | }; 27 | 28 | tx.gasPrice = await provider.getGasPrice(); 29 | if (tx.gasLimit == undefined) { 30 | tx.gasLimit = await provider.estimateGas(tx); 31 | } 32 | 33 | const signedTxHash = EIP712Signer.getSignedDigest(tx); 34 | const signature = ethers.utils.arrayify( 35 | ethers.utils.joinSignature(user._signingKey().signDigest(signedTxHash)), 36 | ); 37 | 38 | tx.customData = { 39 | ...tx.customData, 40 | customSignature: signature, 41 | }; 42 | 43 | return await provider.sendTransaction(utils.serialize(tx)); 44 | } 45 | -------------------------------------------------------------------------------- /spend-limit/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "CommonJS", 4 | "moduleResolution": "Node", 5 | "esModuleInterop": true, 6 | "resolveJsonModule": true 7 | }, 8 | "ts-node": { 9 | "esm": true, 10 | "experimentalSpecifierResolution": "node" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | ![Gradient Banner](https://github.com/matter-labs/tutorials/assets/10233439/8efffb9b-ad1f-4bf2-8f73-9cab8f7ccd22) 2 | 3 | # zkSync Tutorials 4 | 5 | This document provides instructions for preparing the test infrastructure and running tests locally. 6 | 7 | ## The environment preparation 8 | 9 | To execute tests, you need to run a local node (L2) 10 | 11 | 1. Run the node: 12 | 13 | ```bash 14 | npx zksync-cli dev start 15 | ``` 16 | 17 | 2. Run tests (Change directory (cd) into a specific folder, eg. hello-world): 18 | 19 | ```bash 20 | cd hello-world && yarn run test 21 | ``` 22 | -------------------------------------------------------------------------------- /tests/helper.ts: -------------------------------------------------------------------------------- 1 | export class Helper { 2 | /** 3 | * Checks the validity of an Ethereum address (wallet/contract). 4 | * @param {string} value - The input string to validate. 5 | * @returns {boolean} - Returns `true` if the input matches the expected Ethereum value format, and `false` otherwise. 6 | */ 7 | async isValidEthFormat(value: string): Promise { 8 | if (typeof value !== "string" || !value.match(/^(0x)?[0-9a-fA-F]{40}$/)) { 9 | return false; 10 | } 11 | return true; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/testConfig.ts: -------------------------------------------------------------------------------- 1 | import { Wallets } from "./testData"; 2 | 3 | export const localConfig = { 4 | gasLimit: { gasLimit: 90000000 }, 5 | L1Network: "http://127.0.0.1:8545", 6 | L2Network: "http://127.0.0.1:8011", 7 | chainId: 260, 8 | privateKey: Wallets.firstWalletPrivateKey, 9 | }; 10 | -------------------------------------------------------------------------------- /tests/testData.ts: -------------------------------------------------------------------------------- 1 | export enum Wallets { 2 | firstWalletAddress = "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", 3 | firstWalletPrivateKey = "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110", 4 | secondWalletPrivateKey = "0xd293c684d884d56f8d6abd64fc76757d3664904e309a0645baf8522ab6366d9e", 5 | secondWalletAddress = "0x0D43eB5B8a47bA8900d84AA36656c92024e9772e", 6 | } 7 | --------------------------------------------------------------------------------