├── .github ├── CODEOWNERS └── workflows │ ├── slither.yaml │ └── test.yaml ├── .gitignore ├── LICENSE ├── examples ├── call │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── contracts │ │ ├── Connected.sol │ │ └── Universal.sol │ ├── foundry.toml │ ├── hardhat.config.ts │ ├── package.json │ ├── scripts │ │ └── localnet.sh │ ├── solana │ │ ├── .gitignore │ │ ├── .prettierignore │ │ ├── Anchor.toml │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── programs │ │ │ └── connected │ │ │ │ ├── Cargo.toml │ │ │ │ ├── Xargo.toml │ │ │ │ └── src │ │ │ │ └── lib.rs │ │ └── setup │ │ │ ├── connected-keypair.json │ │ │ ├── constants.ts │ │ │ ├── encodeCallArgs.ts │ │ │ └── main.ts │ ├── sui │ │ ├── Move.lock │ │ ├── Move.toml │ │ ├── setup │ │ │ └── encodeCallArgs.ts │ │ └── sources │ │ │ ├── cetusmock.move │ │ │ ├── connected.move │ │ │ └── token.move │ ├── tasks │ │ ├── connectedCall.ts │ │ ├── connectedDeposit.ts │ │ ├── connectedDepositAndCall.ts │ │ ├── deploy.ts │ │ ├── index.ts │ │ ├── universalCall.ts │ │ ├── universalCallMulti.ts │ │ ├── universalWithdraw.ts │ │ └── universalWithdrawAndCall.ts │ ├── tsconfig.json │ └── yarn.lock ├── hello │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── contracts │ │ └── Universal.sol │ ├── foundry.toml │ ├── hardhat.config.ts │ ├── package.json │ ├── scripts │ │ └── localnet.sh │ ├── tasks │ │ └── deploy.ts │ ├── tsconfig.json │ └── yarn.lock ├── nft │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── contracts │ │ ├── EVMUniversalNFT.sol │ │ └── ZetaChainUniversalNFT.sol │ ├── foundry.toml │ ├── hardhat.config.ts │ ├── package.json │ ├── scripts │ │ └── localnet.sh │ ├── tsconfig.json │ └── yarn.lock ├── swap │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── contracts │ │ ├── Swap.sol │ │ └── SwapCompanion.sol │ ├── foundry.toml │ ├── hardhat.config.ts │ ├── package.json │ ├── scripts │ │ └── localnet.sh │ ├── tasks │ │ ├── companionSwap.ts │ │ ├── deploy.ts │ │ ├── deployCompanion.ts │ │ ├── evmSwap.ts │ │ ├── index.ts │ │ └── zetachainSwap.ts │ ├── tsconfig.json │ └── yarn.lock └── token │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── contracts │ ├── EVMUniversalToken.sol │ └── ZetaChainUniversalToken.sol │ ├── foundry.toml │ ├── hardhat.config.ts │ ├── package.json │ ├── scripts │ └── localnet.sh │ ├── tsconfig.json │ └── yarn.lock ├── package.json ├── readme.md ├── scripts └── build.sh ├── slither.config.json └── yarn.lock /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @zeta-chain/smart-contracts 2 | -------------------------------------------------------------------------------- /.github/workflows/slither.yaml: -------------------------------------------------------------------------------- 1 | name: Slither 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - "*" 10 | types: 11 | - synchronize 12 | - opened 13 | - reopened 14 | - ready_for_review 15 | 16 | jobs: 17 | slither: 18 | runs-on: ubuntu-latest 19 | strategy: 20 | matrix: 21 | include: 22 | - project: "examples/hello" 23 | file: "hello.sarif" 24 | - project: "examples/call" 25 | file: "call.sarif" 26 | - project: "examples/swap" 27 | file: "swap.sarif" 28 | permissions: 29 | contents: read 30 | security-events: write 31 | 32 | steps: 33 | - name: Checkout 34 | uses: actions/checkout@v3 35 | 36 | - name: Install Node.js 37 | uses: actions/setup-node@v2 38 | with: 39 | node-version: "18" 40 | 41 | - name: Install Dependencies 42 | run: yarn install 43 | 44 | - name: Install Foundry 45 | uses: foundry-rs/foundry-toolchain@v1 46 | 47 | - name: Build projects 48 | continue-on-error: true 49 | run: yarn build 50 | 51 | - name: Run Slither on ${{ matrix.project}} 52 | uses: crytic/slither-action@main 53 | continue-on-error: true 54 | with: 55 | ignore-compile: true 56 | sarif: ${{ matrix.file}} 57 | node-version: "18" 58 | target: ${{ matrix.project}} 59 | fail-on: none 60 | 61 | - name: Upload zevm-app-contracts SARIF file 62 | uses: github/codeql-action/upload-sarif@v3 63 | with: 64 | sarif_file: ${{ matrix.file}} 65 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: 4 | pull_request: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | setup-matrix: 9 | runs-on: ubuntu-latest 10 | outputs: 11 | matrix: ${{ steps.set-matrix.outputs.matrix }} 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: Set up Test Matrix 17 | id: set-matrix 18 | run: | 19 | test_dirs=$(find examples/*/scripts -type f -name 'localnet.sh' -exec dirname {} \; | xargs dirname) 20 | matrix_json=$(echo "$test_dirs" | jq -R '{"example-dir": .}' | jq -s . | jq -c .) 21 | echo "matrix=$matrix_json" >> $GITHUB_OUTPUT 22 | 23 | test: 24 | needs: setup-matrix 25 | runs-on: ubuntu-latest 26 | strategy: 27 | matrix: 28 | include: ${{ fromJSON(needs.setup-matrix.outputs.matrix) }} 29 | fail-fast: false 30 | 31 | steps: 32 | - name: Checkout code 33 | uses: actions/checkout@v4 34 | 35 | - name: Install Foundry 36 | uses: foundry-rs/foundry-toolchain@v1 37 | 38 | - name: Install Anchor (Solana) 39 | uses: metadaoproject/setup-anchor@v3.1 40 | 41 | - name: Run Test Script 42 | run: | 43 | cd "${{ matrix.example-dir }}" 44 | yarn 45 | chmod +x ./scripts/localnet.sh 46 | ./scripts/localnet.sh start 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | .DS_Store 8 | 9 | # Hardhat files 10 | cache 11 | artifacts 12 | abi -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Meta Protocol, Inc. 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. -------------------------------------------------------------------------------- /examples/call/.eslintignore: -------------------------------------------------------------------------------- 1 | .yarn 2 | artifacts 3 | cache 4 | coverage 5 | node_modules 6 | typechain-types -------------------------------------------------------------------------------- /examples/call/.eslintrc.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | /** 4 | * @type {import("eslint").Linter.Config} 5 | */ 6 | module.exports = { 7 | env: { 8 | browser: false, 9 | es2021: true, 10 | mocha: true, 11 | node: true, 12 | }, 13 | extends: ["plugin:prettier/recommended"], 14 | parser: "@typescript-eslint/parser", 15 | parserOptions: { 16 | ecmaVersion: 12, 17 | }, 18 | plugins: [ 19 | "@typescript-eslint", 20 | "prettier", 21 | "simple-import-sort", 22 | "sort-keys-fix", 23 | "typescript-sort-keys", 24 | ], 25 | rules: { 26 | "@typescript-eslint/sort-type-union-intersection-members": "error", 27 | camelcase: "off", 28 | "simple-import-sort/exports": "error", 29 | "simple-import-sort/imports": "error", 30 | "sort-keys-fix/sort-keys-fix": "error", 31 | "typescript-sort-keys/interface": "error", 32 | "typescript-sort-keys/string-enum": "error", 33 | }, 34 | settings: { 35 | "import/parsers": { 36 | "@typescript-eslint/parser": [".js", ".jsx", ".ts", ".tsx", ".d.ts"], 37 | }, 38 | "import/resolver": { 39 | node: { 40 | extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"], 41 | }, 42 | typescript: { 43 | project: path.join(__dirname, "tsconfig.json"), 44 | }, 45 | }, 46 | }, 47 | }; 48 | -------------------------------------------------------------------------------- /examples/call/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | dependencies 8 | 9 | # Hardhat files 10 | cache 11 | artifacts 12 | 13 | # Foundry files 14 | out 15 | cache_forge 16 | 17 | access_token 18 | 19 | localnet.json 20 | 21 | sui/build 22 | test-ledger 23 | -------------------------------------------------------------------------------- /examples/call/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 ZetaChain 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 | -------------------------------------------------------------------------------- /examples/call/README.md: -------------------------------------------------------------------------------- 1 | # Call Example 2 | 3 | Tutorial: https://www.zetachain.com/docs/developers/tutorials/call 4 | -------------------------------------------------------------------------------- /examples/call/contracts/Connected.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.26; 3 | 4 | import {RevertContext} from "@zetachain/protocol-contracts/contracts/Revert.sol"; 5 | import "@zetachain/protocol-contracts/contracts/evm/GatewayEVM.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 7 | 8 | contract Connected { 9 | using SafeERC20 for IERC20; 10 | 11 | GatewayEVM public immutable gateway; 12 | 13 | event RevertEvent(string, RevertContext); 14 | event HelloEvent(string, string); 15 | 16 | error Unauthorized(); 17 | 18 | modifier onlyGateway() { 19 | if (msg.sender != address(gateway)) revert Unauthorized(); 20 | _; 21 | } 22 | 23 | constructor(address payable gatewayAddress) { 24 | gateway = GatewayEVM(gatewayAddress); 25 | } 26 | 27 | function call( 28 | address receiver, 29 | bytes calldata message, 30 | RevertOptions memory revertOptions 31 | ) external { 32 | gateway.call(receiver, message, revertOptions); 33 | } 34 | 35 | function deposit( 36 | address receiver, 37 | RevertOptions memory revertOptions 38 | ) external payable { 39 | gateway.deposit{value: msg.value}(receiver, revertOptions); 40 | } 41 | 42 | function deposit( 43 | address receiver, 44 | uint256 amount, 45 | address asset, 46 | RevertOptions memory revertOptions 47 | ) external { 48 | IERC20(asset).safeTransferFrom(msg.sender, address(this), amount); 49 | IERC20(asset).approve(address(gateway), amount); 50 | gateway.deposit(receiver, amount, asset, revertOptions); 51 | } 52 | 53 | function depositAndCall( 54 | address receiver, 55 | uint256 amount, 56 | address asset, 57 | bytes calldata message, 58 | RevertOptions memory revertOptions 59 | ) external { 60 | IERC20(asset).safeTransferFrom(msg.sender, address(this), amount); 61 | IERC20(asset).approve(address(gateway), amount); 62 | gateway.depositAndCall(receiver, amount, asset, message, revertOptions); 63 | } 64 | 65 | function depositAndCall( 66 | address receiver, 67 | bytes calldata message, 68 | RevertOptions memory revertOptions 69 | ) external payable { 70 | gateway.depositAndCall{value: msg.value}( 71 | receiver, 72 | message, 73 | revertOptions 74 | ); 75 | } 76 | 77 | function hello(string memory message) external payable { 78 | emit HelloEvent("Hello on EVM", message); 79 | } 80 | 81 | function onCall( 82 | MessageContext calldata context, 83 | bytes calldata message 84 | ) external payable onlyGateway returns (bytes4) { 85 | emit HelloEvent("Hello on EVM from onCall()", "hey"); 86 | return ""; 87 | } 88 | 89 | function onRevert( 90 | RevertContext calldata revertContext 91 | ) external onlyGateway { 92 | emit RevertEvent("Revert on EVM", revertContext); 93 | } 94 | 95 | receive() external payable {} 96 | 97 | fallback() external payable {} 98 | } 99 | -------------------------------------------------------------------------------- /examples/call/contracts/Universal.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.26; 3 | 4 | import {RevertContext, RevertOptions} from "@zetachain/protocol-contracts/contracts/Revert.sol"; 5 | import "@zetachain/protocol-contracts/contracts/zevm/interfaces/UniversalContract.sol"; 6 | import "@zetachain/protocol-contracts/contracts/zevm/interfaces/IGatewayZEVM.sol"; 7 | import "@zetachain/protocol-contracts/contracts/zevm/GatewayZEVM.sol"; 8 | 9 | contract Universal is UniversalContract { 10 | GatewayZEVM public immutable gateway; 11 | 12 | event HelloEvent(string, string); 13 | event RevertEvent(string, RevertContext); 14 | event AbortEvent(string, AbortContext); 15 | 16 | error TransferFailed(); 17 | error Unauthorized(); 18 | 19 | modifier onlyGateway() { 20 | if (msg.sender != address(gateway)) revert Unauthorized(); 21 | _; 22 | } 23 | 24 | constructor(address payable gatewayAddress) { 25 | gateway = GatewayZEVM(gatewayAddress); 26 | } 27 | 28 | function call( 29 | bytes memory receiver, 30 | address zrc20, 31 | bytes calldata message, 32 | CallOptions memory callOptions, 33 | RevertOptions memory revertOptions 34 | ) external { 35 | (, uint256 gasFee) = IZRC20(zrc20).withdrawGasFeeWithGasLimit( 36 | callOptions.gasLimit 37 | ); 38 | if (!IZRC20(zrc20).transferFrom(msg.sender, address(this), gasFee)) { 39 | revert TransferFailed(); 40 | } 41 | IZRC20(zrc20).approve(address(gateway), gasFee); 42 | gateway.call(receiver, zrc20, message, callOptions, revertOptions); 43 | } 44 | 45 | function callMulti( 46 | bytes[] memory receiverArray, 47 | address[] memory zrc20Array, 48 | bytes calldata messages, 49 | CallOptions memory callOptions, 50 | RevertOptions memory revertOptions 51 | ) external { 52 | for (uint256 i = 0; i < zrc20Array.length; i++) { 53 | (, uint256 gasFee) = IZRC20(zrc20Array[i]) 54 | .withdrawGasFeeWithGasLimit(callOptions.gasLimit); 55 | if ( 56 | !IZRC20(zrc20Array[i]).transferFrom( 57 | msg.sender, 58 | address(this), 59 | gasFee 60 | ) 61 | ) { 62 | revert TransferFailed(); 63 | } 64 | IZRC20(zrc20Array[i]).approve(address(gateway), gasFee); 65 | gateway.call( 66 | receiverArray[i], 67 | zrc20Array[i], 68 | messages, 69 | callOptions, 70 | revertOptions 71 | ); 72 | } 73 | } 74 | 75 | function withdraw( 76 | bytes memory receiver, 77 | uint256 amount, 78 | address zrc20, 79 | RevertOptions memory revertOptions 80 | ) external { 81 | (address gasZRC20, uint256 gasFee) = IZRC20(zrc20).withdrawGasFee(); 82 | uint256 target = zrc20 == gasZRC20 ? amount + gasFee : amount; 83 | if (!IZRC20(zrc20).transferFrom(msg.sender, address(this), target)) { 84 | revert TransferFailed(); 85 | } 86 | IZRC20(zrc20).approve(address(gateway), target); 87 | if (zrc20 != gasZRC20) { 88 | if ( 89 | !IZRC20(gasZRC20).transferFrom( 90 | msg.sender, 91 | address(this), 92 | gasFee 93 | ) 94 | ) { 95 | revert TransferFailed(); 96 | } 97 | IZRC20(gasZRC20).approve(address(gateway), gasFee); 98 | } 99 | gateway.withdraw(receiver, amount, zrc20, revertOptions); 100 | } 101 | 102 | function withdrawAndCall( 103 | bytes memory receiver, 104 | uint256 amount, 105 | address zrc20, 106 | bytes calldata message, 107 | CallOptions memory callOptions, 108 | RevertOptions memory revertOptions 109 | ) external { 110 | (address gasZRC20, uint256 gasFee) = IZRC20(zrc20) 111 | .withdrawGasFeeWithGasLimit(callOptions.gasLimit); 112 | uint256 target = zrc20 == gasZRC20 ? amount + gasFee : amount; 113 | if (!IZRC20(zrc20).transferFrom(msg.sender, address(this), target)) 114 | revert TransferFailed(); 115 | IZRC20(zrc20).approve(address(gateway), target); 116 | if (zrc20 != gasZRC20) { 117 | if ( 118 | !IZRC20(gasZRC20).transferFrom( 119 | msg.sender, 120 | address(this), 121 | gasFee 122 | ) 123 | ) { 124 | revert TransferFailed(); 125 | } 126 | IZRC20(gasZRC20).approve(address(gateway), gasFee); 127 | } 128 | gateway.withdrawAndCall( 129 | receiver, 130 | amount, 131 | zrc20, 132 | message, 133 | callOptions, 134 | revertOptions 135 | ); 136 | } 137 | 138 | function onCall( 139 | MessageContext calldata context, 140 | address zrc20, 141 | uint256 amount, 142 | bytes calldata message 143 | ) external override onlyGateway { 144 | string memory name = abi.decode(message, (string)); 145 | emit HelloEvent("Hello on ZetaChain", name); 146 | } 147 | 148 | function onRevert(RevertContext calldata context) external onlyGateway { 149 | emit RevertEvent("Revert on ZetaChain", context); 150 | } 151 | 152 | function onAbort(AbortContext calldata context) external onlyGateway { 153 | emit AbortEvent("Abort on ZetaChain", context); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /examples/call/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'contracts' 3 | out = 'out' 4 | viaIR = true 5 | libs = ['node_modules', 'lib'] 6 | test = 'test' 7 | cache_path = 'cache_forge' 8 | verbosity = 3 9 | 10 | [dependencies] 11 | forge-std = { version = "1.9.2" } 12 | -------------------------------------------------------------------------------- /examples/call/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import "@nomicfoundation/hardhat-toolbox"; 2 | import { HardhatUserConfig } from "hardhat/config"; 3 | import * as dotenv from "dotenv"; 4 | 5 | import "./tasks"; 6 | import "@zetachain/localnet/tasks"; 7 | import "@zetachain/toolkit/tasks"; 8 | import { getHardhatConfig } from "@zetachain/toolkit/client"; 9 | 10 | dotenv.config(); 11 | 12 | const config: HardhatUserConfig = { 13 | ...getHardhatConfig({ accounts: [process.env.PRIVATE_KEY] }), 14 | }; 15 | 16 | export default config; 17 | -------------------------------------------------------------------------------- /examples/call/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "call", 3 | "version": "1.0.0", 4 | "description": "Example of how to make cross-chain calls, transfer native tokens, and handle reverts.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "lint:fix": "npx eslint . --ext .js,.ts --fix", 9 | "lint": "npx eslint . --ext .js,.ts", 10 | "deploy:localnet": "npx hardhat compile --force && npx hardhat deploy --network localhost --gateway 0x9A676e781A523b5d0C0e43731313A708CB607508 && npx hardhat deploy --name Echo --network localhost --gateway 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@ethersproject/abi": "^5.4.7", 17 | "@ethersproject/providers": "^5.4.7", 18 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.0", 19 | "@nomicfoundation/hardhat-foundry": "^1.1.2", 20 | "@nomicfoundation/hardhat-network-helpers": "^1.0.0", 21 | "@nomicfoundation/hardhat-toolbox": "^2.0.0", 22 | "@nomiclabs/hardhat-ethers": "^2.0.0", 23 | "@nomiclabs/hardhat-etherscan": "^3.0.0", 24 | "@typechain/ethers-v5": "^10.1.0", 25 | "@typechain/hardhat": "^6.1.2", 26 | "@types/chai": "^4.2.0", 27 | "@types/mocha": ">=9.1.0", 28 | "@types/node": ">=12.0.0", 29 | "@typescript-eslint/eslint-plugin": "^5.59.9", 30 | "@typescript-eslint/parser": "^5.59.9", 31 | "@zetachain/localnet": "7.1.0", 32 | "@zetachain/toolkit": "13.0.0-rc17", 33 | "axios": "^1.3.6", 34 | "chai": "^4.2.0", 35 | "dotenv": "^16.0.3", 36 | "envfile": "^6.18.0", 37 | "eslint": "^8.42.0", 38 | "eslint-config-prettier": "^8.8.0", 39 | "eslint-import-resolver-typescript": "^3.5.5", 40 | "eslint-plugin-import": "^2.27.5", 41 | "eslint-plugin-prettier": "^4.2.1", 42 | "eslint-plugin-simple-import-sort": "^10.0.0", 43 | "eslint-plugin-sort-keys-fix": "^1.1.2", 44 | "eslint-plugin-typescript-sort-keys": "^2.3.0", 45 | "ethers": "^5.4.7", 46 | "hardhat": "^2.17.2", 47 | "hardhat-gas-reporter": "^1.0.8", 48 | "prettier": "^2.8.8", 49 | "solidity-coverage": "^0.8.0", 50 | "ts-node": ">=8.0.0", 51 | "typechain": "^8.1.0", 52 | "typescript": ">=4.5.0" 53 | }, 54 | "packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72", 55 | "dependencies": { 56 | "@coral-xyz/anchor": "0.30.0", 57 | "@solana-developers/helpers": "^2.4.0", 58 | "@solana/spl-memo": "^0.2.5", 59 | "@solana/spl-token": "^0.4.13", 60 | "@solana/web3.js": "^1.95.8", 61 | "@zetachain/networks": "13.0.0-rc1", 62 | "@zetachain/protocol-contracts": "13.0.0", 63 | "@zetachain/protocol-contracts-solana": "2.0.0-rc1", 64 | "zetachain": "^3.0.0" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /examples/call/scripts/localnet.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | set -o pipefail 6 | 7 | yarn zetachain localnet start --skip sui ton solana --exit-on-error & 8 | 9 | while [ ! -f "localnet.json" ]; do sleep 1; done 10 | 11 | npx hardhat compile --force --quiet 12 | 13 | ZRC20_ETHEREUM=$(jq -r '.addresses[] | select(.type=="ZRC-20 ETH on 5") | .address' localnet.json) 14 | ERC20_ETHEREUM=$(jq -r '.addresses[] | select(.type=="ERC-20 USDC" and .chain=="ethereum") | .address' localnet.json) 15 | ZRC20_BNB=$(jq -r '.addresses[] | select(.type=="ZRC-20 BNB on 97") | .address' localnet.json) 16 | # ZRC20_SOL=$(jq -r '.addresses[] | select(.type=="ZRC-20 SOL on 901") | .address' localnet.json) 17 | ZRC20_SPL=$(jq -r '.addresses[] | select(.type=="ZRC-20 USDC on 901") | .address' localnet.json) 18 | USDC_SPL=$(jq -r '.addresses[] | select(.type=="SPL-20 USDC") | .address' localnet.json) 19 | GATEWAY_ETHEREUM=$(jq -r '.addresses[] | select(.type=="gatewayEVM" and .chain=="ethereum") | .address' localnet.json) 20 | GATEWAY_ZETACHAIN=$(jq -r '.addresses[] | select(.type=="gatewayZEVM" and .chain=="zetachain") | .address' localnet.json) 21 | SENDER=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 22 | 23 | CONTRACT_ZETACHAIN=$(npx hardhat deploy --name Universal --network localhost --gateway "$GATEWAY_ZETACHAIN" --json | jq -r '.contractAddress') 24 | echo -e "\n🚀 Deployed contract on ZetaChain: $CONTRACT_ZETACHAIN" 25 | 26 | CONTRACT_ETHEREUM=$(npx hardhat deploy --name Connected --json --network localhost --gateway "$GATEWAY_ETHEREUM" | jq -r '.contractAddress') 27 | echo -e "🚀 Deployed contract on Ethereum: $CONTRACT_ETHEREUM" 28 | 29 | # CONTRACT_SOL=9BjVGjn28E58LgSi547JYEpqpgRoo1TErkbyXiRSNDQy 30 | # cd solana && anchor build && npx ts-node setup/main.ts "$USDC_SPL" && cd - 31 | 32 | npx hardhat connected-deposit \ 33 | --contract "$CONTRACT_ETHEREUM" \ 34 | --receiver "$CONTRACT_ZETACHAIN" \ 35 | --network localhost \ 36 | --abort-address "$CONTRACT_ZETACHAIN" \ 37 | --amount 1 38 | 39 | yarn zetachain localnet check 40 | 41 | npx hardhat connected-deposit \ 42 | --contract "$CONTRACT_ETHEREUM" \ 43 | --receiver "$CONTRACT_ZETACHAIN" \ 44 | --network localhost \ 45 | --erc20 "$ERC20_ETHEREUM" \ 46 | --abort-address "$CONTRACT_ZETACHAIN" \ 47 | --amount 1 48 | 49 | yarn zetachain localnet check 50 | 51 | npx hardhat connected-call \ 52 | --contract "$CONTRACT_ETHEREUM" \ 53 | --receiver "$CONTRACT_ZETACHAIN" \ 54 | --network localhost \ 55 | --abort-address "$CONTRACT_ZETACHAIN" \ 56 | --types '["string"]' alice 57 | 58 | yarn zetachain localnet check 59 | 60 | npx hardhat connected-deposit-and-call \ 61 | --contract "$CONTRACT_ETHEREUM" \ 62 | --receiver "$CONTRACT_ZETACHAIN" \ 63 | --network localhost \ 64 | --amount 1 \ 65 | --abort-address "$CONTRACT_ZETACHAIN" \ 66 | --types '["string"]' alice 67 | 68 | yarn zetachain localnet check 69 | 70 | npx hardhat connected-deposit-and-call \ 71 | --contract "$CONTRACT_ETHEREUM" \ 72 | --receiver "$CONTRACT_ZETACHAIN" \ 73 | --network localhost \ 74 | --amount 1 \ 75 | --erc20 "$ERC20_ETHEREUM" \ 76 | --abort-address "$CONTRACT_ZETACHAIN" \ 77 | --types '["string"]' alice 78 | 79 | yarn zetachain localnet check 80 | 81 | npx hardhat universal-withdraw \ 82 | --contract "$CONTRACT_ZETACHAIN" \ 83 | --receiver "$CONTRACT_ETHEREUM" \ 84 | --zrc20 "$ZRC20_ETHEREUM" \ 85 | --network localhost \ 86 | --abort-address "$CONTRACT_ZETACHAIN" \ 87 | --amount 1 88 | 89 | yarn zetachain localnet check 90 | 91 | npx hardhat universal-call \ 92 | --contract "$CONTRACT_ZETACHAIN" \ 93 | --receiver "$CONTRACT_ETHEREUM" \ 94 | --zrc20 "$ZRC20_ETHEREUM" \ 95 | --function "hello(string)" \ 96 | --network localhost \ 97 | --abort-address "$CONTRACT_ZETACHAIN" \ 98 | --types '["string"]' alice 99 | 100 | yarn zetachain localnet check 101 | 102 | npx hardhat universal-withdraw-and-call \ 103 | --contract "$CONTRACT_ZETACHAIN" \ 104 | --receiver "$CONTRACT_ETHEREUM" \ 105 | --zrc20 "$ZRC20_ETHEREUM" \ 106 | --function "hello(string)" \ 107 | --amount 1 \ 108 | --network localhost \ 109 | --call-options-is-arbitrary-call \ 110 | --abort-address "$CONTRACT_ZETACHAIN" \ 111 | --types '["string"]' hello 112 | 113 | yarn zetachain localnet check 114 | 115 | npx hardhat universal-withdraw-and-call \ 116 | --contract "$CONTRACT_ZETACHAIN" \ 117 | --receiver "$CONTRACT_ETHEREUM" \ 118 | --zrc20 "$ZRC20_ETHEREUM" \ 119 | --amount 1 \ 120 | --network localhost \ 121 | --abort-address "$CONTRACT_ZETACHAIN" \ 122 | --types '["string"]' hello 123 | 124 | yarn zetachain localnet check 125 | 126 | # Testing toolkit methods 127 | 128 | npx hardhat evm-deposit \ 129 | --receiver "$CONTRACT_ZETACHAIN" \ 130 | --gateway-evm "$GATEWAY_ETHEREUM" \ 131 | --network localhost \ 132 | --amount 1 133 | 134 | yarn zetachain localnet check 135 | 136 | npx hardhat evm-deposit \ 137 | --receiver "$CONTRACT_ZETACHAIN" \ 138 | --gateway-evm "$GATEWAY_ETHEREUM" \ 139 | --network localhost \ 140 | --erc20 "$ERC20_ETHEREUM" \ 141 | --amount 1 142 | 143 | yarn zetachain localnet check 144 | 145 | npx hardhat evm-call \ 146 | --receiver "$CONTRACT_ZETACHAIN" \ 147 | --gateway-evm "$GATEWAY_ETHEREUM" \ 148 | --network localhost \ 149 | --types '["string"]' alice 150 | 151 | yarn zetachain localnet check 152 | 153 | npx hardhat evm-deposit-and-call \ 154 | --receiver "$CONTRACT_ZETACHAIN" \ 155 | --gateway-evm "$GATEWAY_ETHEREUM" \ 156 | --network localhost \ 157 | --amount 1 \ 158 | --types '["string"]' alice 159 | 160 | yarn zetachain localnet check 161 | 162 | npx hardhat evm-deposit-and-call \ 163 | --receiver "$CONTRACT_ZETACHAIN" \ 164 | --gateway-evm "$GATEWAY_ETHEREUM" \ 165 | --network localhost \ 166 | --amount 1 \ 167 | --erc20 "$ERC20_ETHEREUM" \ 168 | --types '["string"]' alice 169 | 170 | yarn zetachain localnet check 171 | 172 | npx hardhat zetachain-withdraw \ 173 | --receiver "$CONTRACT_ETHEREUM" \ 174 | --gateway-zeta-chain "$GATEWAY_ZETACHAIN" \ 175 | --zrc20 "$ZRC20_ETHEREUM" \ 176 | --network localhost \ 177 | --amount 1 178 | 179 | yarn zetachain localnet check 180 | 181 | # npx hardhat zetachain-withdraw \ 182 | # --receiver "DrexsvCMH9WWjgnjVbx1iFf3YZcKadupFmxnZLfSyotd" \ 183 | # --gateway-zeta-chain "$GATEWAY_ZETACHAIN" \ 184 | # --zrc20 "$ZRC20_SOL" \ 185 | # --network localhost \ 186 | # --amount 1 187 | 188 | # yarn zetachain localnet check 189 | 190 | npx hardhat zetachain-call \ 191 | --receiver "$CONTRACT_ETHEREUM" \ 192 | --gateway-zeta-chain "$GATEWAY_ZETACHAIN" \ 193 | --zrc20 "$ZRC20_ETHEREUM" \ 194 | --function "hello(string)" \ 195 | --network localhost \ 196 | --types '["string"]' alice 197 | 198 | yarn zetachain localnet check 199 | 200 | npx hardhat zetachain-withdraw-and-call \ 201 | --receiver "$CONTRACT_ETHEREUM" \ 202 | --gateway-zeta-chain "$GATEWAY_ZETACHAIN" \ 203 | --zrc20 "$ZRC20_ETHEREUM" \ 204 | --function "hello(string)" \ 205 | --amount 1 \ 206 | --network localhost \ 207 | --call-options-is-arbitrary-call \ 208 | --types '["string"]' hello 209 | 210 | yarn zetachain localnet check 211 | 212 | # ENCODED_ACCOUNTS_AND_DATA=$(npx ts-node solana/setup/encodeCallArgs.ts "sol" "$USDC_SPL") 213 | # npx hardhat zetachain-withdraw-and-call \ 214 | # --receiver "$CONTRACT_SOL" \ 215 | # --gateway-zeta-chain "$GATEWAY_ZETACHAIN" \ 216 | # --zrc20 "$ZRC20_SOL" \ 217 | # --amount 1 \ 218 | # --network localhost \ 219 | # --types '["bytes"]' $ENCODED_ACCOUNTS_AND_DATA 220 | 221 | # yarn zetachain localnet check 222 | 223 | # ENCODED_ACCOUNTS_AND_DATA=$(npx ts-node solana/setup/encodeCallArgs.ts "spl" "$USDC_SPL") 224 | # npx hardhat zetachain-withdraw-and-call \ 225 | # --receiver "$CONTRACT_SOL" \ 226 | # --gateway-zeta-chain "$GATEWAY_ZETACHAIN" \ 227 | # --zrc20 "$ZRC20_SPL" \ 228 | # --amount 1 \ 229 | # --network localhost \ 230 | # --types '["bytes"]' $ENCODED_ACCOUNTS_AND_DATA 231 | 232 | # yarn zetachain localnet check 233 | 234 | npx hardhat zetachain-withdraw-and-call \ 235 | --receiver "$CONTRACT_ETHEREUM" \ 236 | --gateway-zeta-chain "$GATEWAY_ZETACHAIN" \ 237 | --zrc20 "$ZRC20_ETHEREUM" \ 238 | --amount 1 \ 239 | --network localhost \ 240 | --types '["string"]' hello 241 | 242 | yarn zetachain localnet check 243 | 244 | yarn zetachain localnet stop -------------------------------------------------------------------------------- /examples/call/solana/.gitignore: -------------------------------------------------------------------------------- 1 | .anchor 2 | .DS_Store 3 | target 4 | **/*.rs.bk 5 | node_modules 6 | test-ledger 7 | .yarn 8 | -------------------------------------------------------------------------------- /examples/call/solana/.prettierignore: -------------------------------------------------------------------------------- 1 | .anchor 2 | .DS_Store 3 | target 4 | node_modules 5 | dist 6 | build 7 | test-ledger 8 | -------------------------------------------------------------------------------- /examples/call/solana/Anchor.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | 3 | [features] 4 | resolution = true 5 | skip-lint = false 6 | 7 | [programs.localnet] 8 | connected = "9BjVGjn28E58LgSi547JYEpqpgRoo1TErkbyXiRSNDQy" 9 | 10 | [registry] 11 | url = "https://api.apr.dev" 12 | 13 | [provider] 14 | cluster = "Localnet" 15 | wallet = "~/.config/solana/id.json" 16 | 17 | [scripts] 18 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 19 | -------------------------------------------------------------------------------- /examples/call/solana/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "programs/*" 4 | ] 5 | resolver = "2" 6 | 7 | [profile.release] 8 | overflow-checks = true 9 | lto = "fat" 10 | codegen-units = 1 11 | [profile.release.build-override] 12 | opt-level = 3 13 | incremental = false 14 | codegen-units = 1 15 | -------------------------------------------------------------------------------- /examples/call/solana/programs/connected/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "connected" 3 | version = "0.1.0" 4 | description = "Test program used for testing withdraw and call feature" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "connected" 10 | 11 | [features] 12 | default = [] 13 | cpi = ["no-entrypoint"] 14 | no-entrypoint = [] 15 | no-idl = [] 16 | no-log-ix-name = [] 17 | idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] 18 | 19 | [dependencies] 20 | anchor-lang = { version = "=0.30.0" } 21 | anchor-spl = { version = "=0.30.0" } 22 | spl-associated-token-account = "3.0.2" 23 | solana-program = "=1.18.15" 24 | -------------------------------------------------------------------------------- /examples/call/solana/programs/connected/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /examples/call/solana/programs/connected/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use std::mem::size_of; 3 | use anchor_spl::token::{Mint, Token, TokenAccount}; 4 | 5 | declare_id!("9BjVGjn28E58LgSi547JYEpqpgRoo1TErkbyXiRSNDQy"); 6 | 7 | // NOTE: this is just example contract that can be called from gateway in execute function for testing withdraw and call 8 | #[program] 9 | pub mod connected { 10 | use super::*; 11 | 12 | pub fn initialize(ctx: Context) -> Result<()> { 13 | Ok(()) 14 | } 15 | 16 | pub fn on_call( 17 | ctx: Context, 18 | amount: u64, 19 | sender: [u8; 20], 20 | data: Vec, 21 | ) -> Result<()> { 22 | let pda = &mut ctx.accounts.pda; 23 | 24 | // Store the sender's public key 25 | pda.last_sender = sender; 26 | 27 | // Convert data to a string and store it 28 | let message = String::from_utf8(data).map_err(|_| ErrorCode::InvalidDataFormat)?; 29 | pda.last_message = message; 30 | 31 | if pda.last_message == "sol" { 32 | msg!( 33 | "On call sol executed with amount {}, sender {:?} and message {}", 34 | amount, 35 | pda.last_sender, 36 | pda.last_message 37 | ); 38 | } else { 39 | msg!( 40 | "On call spl executed with amount {}, spl {:?}, sender {:?} and message {}", 41 | amount, 42 | ctx.accounts.mint_account, 43 | pda.last_sender, 44 | pda.last_message 45 | ); 46 | } 47 | 48 | Ok(()) 49 | } 50 | } 51 | 52 | #[derive(Accounts)] 53 | pub struct Initialize<'info> { 54 | #[account(mut)] 55 | pub signer: Signer<'info>, 56 | 57 | #[account(init, payer = signer, space = size_of::() + 32, seeds = [b"connected"], bump)] 58 | pub pda: Account<'info, Pda>, 59 | 60 | pub system_program: Program<'info, System>, 61 | } 62 | 63 | #[derive(Accounts)] 64 | pub struct OnCall<'info> { 65 | #[account(mut, seeds = [b"connected"], bump)] 66 | pub pda: Account<'info, Pda>, 67 | 68 | #[account(mut)] 69 | pub pda_ata: Account<'info, TokenAccount>, 70 | 71 | pub mint_account: Account<'info, Mint>, 72 | 73 | /// CHECK: Test contract 74 | pub gateway_pda: UncheckedAccount<'info>, 75 | 76 | pub token_program: Program<'info, Token>, 77 | 78 | pub system_program: Program<'info, System>, 79 | } 80 | 81 | #[account] 82 | pub struct Pda { 83 | pub last_sender: [u8; 20], 84 | pub last_message: String, 85 | } 86 | 87 | #[error_code] 88 | pub enum ErrorCode { 89 | #[msg("The data provided could not be converted to a valid UTF-8 string.")] 90 | InvalidDataFormat, 91 | } 92 | -------------------------------------------------------------------------------- /examples/call/solana/setup/connected-keypair.json: -------------------------------------------------------------------------------- 1 | [23,9,162,245,132,162,140,90,69,60,45,239,63,63,28,221,177,66,153,133,141,34,127,159,11,52,59,49,43,47,124,128,121,157,88,29,118,83,34,80,110,121,63,92,133,21,36,39,78,130,22,120,8,34,154,139,96,105,197,3,214,138,225,54] -------------------------------------------------------------------------------- /examples/call/solana/setup/constants.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor"; 2 | 3 | export const GATEWAY = new anchor.web3.PublicKey( 4 | "94U5AHQMKkV5txNJ17QPXWoh474PheGou6cNP2FEuL1d" 5 | ); 6 | export const CONNECTED_PROGRAM = new anchor.web3.PublicKey( 7 | "9BjVGjn28E58LgSi547JYEpqpgRoo1TErkbyXiRSNDQy" 8 | ); 9 | 10 | export const [pdaAccount] = anchor.web3.PublicKey.findProgramAddressSync( 11 | [Buffer.from("meta", "utf-8")], 12 | GATEWAY 13 | ); 14 | export const [connectedPdaAccount] = 15 | anchor.web3.PublicKey.findProgramAddressSync( 16 | [Buffer.from("connected", "utf-8")], 17 | CONNECTED_PROGRAM 18 | ); 19 | 20 | // NOTE: same payer as in localnet 21 | const PAYER_SECRET_KEY = [ 22 | 241, 170, 134, 107, 198, 204, 4, 113, 117, 201, 246, 19, 196, 39, 229, 23, 73, 23 | 128, 156, 88, 136, 174, 226, 33, 12, 104, 73, 236, 103, 2, 169, 219, 224, 118, 24 | 30, 35, 71, 2, 161, 234, 85, 206, 192, 21, 80, 143, 103, 39, 142, 40, 128, 25 | 183, 210, 145, 62, 75, 10, 253, 218, 135, 228, 49, 125, 186, 26 | ]; 27 | 28 | export const payer: anchor.web3.Keypair = anchor.web3.Keypair.fromSecretKey( 29 | new Uint8Array(PAYER_SECRET_KEY) 30 | ); 31 | -------------------------------------------------------------------------------- /examples/call/solana/setup/encodeCallArgs.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor"; 2 | import { ethers } from "ethers"; 3 | 4 | import { connectedPdaAccount, pdaAccount } from "./constants"; 5 | 6 | async function encode() { 7 | const { getAssociatedTokenAddress } = await import("@solana/spl-token"); 8 | const args = process.argv.slice(2); 9 | const msg = args[0]; 10 | const mint = args[1]; 11 | // this script abi encodes accounts and data to be passed to connected programs 12 | // in protocol this encoded bytes array is passed to GatewayZEVM.withdrawAndCall 13 | const mintPubkey = new anchor.web3.PublicKey(mint); 14 | 15 | const connectedPdaATA = await getAssociatedTokenAddress( 16 | mintPubkey, 17 | connectedPdaAccount, 18 | true 19 | ); 20 | 21 | const accounts = [ 22 | { 23 | isWritable: true, 24 | publicKey: ethers.utils.hexlify(connectedPdaAccount.toBytes()), 25 | }, 26 | { 27 | isWritable: true, 28 | publicKey: ethers.utils.hexlify(connectedPdaATA.toBytes()), 29 | }, 30 | { 31 | isWritable: false, 32 | publicKey: ethers.utils.hexlify(mintPubkey.toBytes()), 33 | }, 34 | { 35 | isWritable: false, 36 | publicKey: ethers.utils.hexlify(pdaAccount.toBytes()), 37 | }, 38 | { 39 | isWritable: false, 40 | publicKey: ethers.utils.hexlify( 41 | anchor.utils.token.TOKEN_PROGRAM_ID.toBytes() 42 | ), 43 | }, 44 | { 45 | isWritable: false, 46 | publicKey: ethers.utils.hexlify( 47 | anchor.web3.SystemProgram.programId.toBytes() 48 | ), 49 | }, 50 | ]; 51 | const data = ethers.utils.hexlify(ethers.utils.toUtf8Bytes(msg)); 52 | 53 | // accounts and data are abi encoded 54 | const encoded = ethers.utils.defaultAbiCoder.encode( 55 | ["tuple(tuple(bytes32 publicKey, bool isWritable)[] accounts, bytes data)"], 56 | [[accounts, data]] 57 | ); 58 | 59 | console.log(encoded); 60 | } 61 | 62 | encode().catch((err) => 63 | console.error("Encode args for solana examples error:", err) 64 | ); 65 | -------------------------------------------------------------------------------- /examples/call/solana/setup/main.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor"; 2 | import { exec } from "child_process"; 3 | import path from "path"; 4 | import util from "util"; 5 | 6 | import Connected_IDL from "../target/idl/connected.json"; 7 | import { connectedPdaAccount, payer, pdaAccount } from "./constants"; 8 | 9 | const deployPath = path.resolve(__dirname, "../target/deploy/"); 10 | const keypairPath = `${path.resolve(__dirname)}/connected-keypair.json`; 11 | const programPath = path.join(deployPath, "connected.so"); 12 | 13 | const execAsync = util.promisify(exec); 14 | 15 | const setupAnchor = () => { 16 | process.env.ANCHOR_WALLET = path.resolve( 17 | process.env.HOME || process.env.USERPROFILE || "", 18 | ".config/solana/id.json" 19 | ); 20 | process.env.ANCHOR_PROVIDER_URL = "http://localhost:8899"; 21 | anchor.setProvider(anchor.AnchorProvider.env()); 22 | }; 23 | 24 | async function setup() { 25 | const { getOrCreateAssociatedTokenAccount } = await import( 26 | "@solana/spl-token" 27 | ); 28 | setupAnchor(); 29 | 30 | const args = process.argv.slice(2); 31 | const mint = args[0]; 32 | const mintPubkey = new anchor.web3.PublicKey(mint); 33 | 34 | const { stdout } = await execAsync( 35 | `solana program deploy --program-id ${keypairPath} ${programPath} --url localhost` 36 | ); 37 | console.log(`Connected program deployment output: ${stdout}`); 38 | await new Promise((r) => setTimeout(r, 1000)); 39 | 40 | await getOrCreateAssociatedTokenAccount( 41 | anchor.getProvider().connection, 42 | payer, 43 | mintPubkey, 44 | connectedPdaAccount, 45 | true 46 | ); 47 | 48 | await getOrCreateAssociatedTokenAccount( 49 | anchor.getProvider().connection, 50 | payer, 51 | mintPubkey, 52 | pdaAccount, 53 | true 54 | ); 55 | 56 | const connectedProgram = new anchor.Program(Connected_IDL as anchor.Idl); 57 | await connectedProgram.methods.initialize().rpc(); 58 | console.log("Initialized connected program"); 59 | } 60 | 61 | setup().catch((err) => 62 | console.error("Deploy and init solana examples error:", err) 63 | ); 64 | -------------------------------------------------------------------------------- /examples/call/sui/Move.lock: -------------------------------------------------------------------------------- 1 | # @generated by Move, please check-in and do not edit manually. 2 | 3 | [move] 4 | version = 3 5 | manifest_digest = "F7637F8158027D0E2F57BA42B0A909AA6391135141CFF820577A4C3F7BA9E62D" 6 | deps_digest = "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600" 7 | dependencies = [ 8 | { id = "Sui", name = "Sui" }, 9 | { id = "gateway", name = "gateway" }, 10 | ] 11 | 12 | [[move.package]] 13 | id = "MoveStdlib" 14 | source = { git = "https://github.com/MystenLabs/sui.git", rev = "framework/testnet", subdir = "crates/sui-framework/packages/move-stdlib" } 15 | 16 | [[move.package]] 17 | id = "Sui" 18 | source = { git = "https://github.com/MystenLabs/sui.git", rev = "framework/testnet", subdir = "crates/sui-framework/packages/sui-framework" } 19 | 20 | dependencies = [ 21 | { id = "MoveStdlib", name = "MoveStdlib" }, 22 | ] 23 | 24 | [[move.package]] 25 | id = "gateway" 26 | source = { local = "/usr/local/share/localnet/protocol-contracts-sui" } 27 | 28 | dependencies = [ 29 | { id = "Sui", name = "Sui" }, 30 | ] 31 | 32 | [move.toolchain-version] 33 | compiler-version = "1.45.0" 34 | edition = "2024.beta" 35 | flavor = "sui" 36 | 37 | [env] 38 | 39 | [env.local] 40 | chain-id = "59d1348f" 41 | original-published-id = "0x1a4bd5ee637990fe0649d860c3ffc673d608c601dbd518d84551814c3e5902b6" 42 | latest-published-id = "0x1a4bd5ee637990fe0649d860c3ffc673d608c601dbd518d84551814c3e5902b6" 43 | published-version = "1" 44 | 45 | [env.localnet] 46 | chain-id = "992fdbfb" 47 | original-published-id = "0x723be41ba96120e33a464c7b59597a4edaf934614f7ea05349b71d2545819a4b" 48 | latest-published-id = "0x723be41ba96120e33a464c7b59597a4edaf934614f7ea05349b71d2545819a4b" 49 | published-version = "1" 50 | -------------------------------------------------------------------------------- /examples/call/sui/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "call" 3 | edition = "2024.beta" 4 | 5 | [dependencies] 6 | Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" } 7 | gateway = { local = "/usr/local/share/localnet/protocol-contracts-sui"} 8 | 9 | [addresses] 10 | connected = "0x0" 11 | gateway = "_" -------------------------------------------------------------------------------- /examples/call/sui/setup/encodeCallArgs.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | 3 | async function encode() { 4 | // this script abi encodes type arguments, objects and the message to be passed to the Sui connected contract 5 | // in protocol this encoded bytes array is passed to GatewayZEVM.withdrawAndCall 6 | 7 | const args = process.argv.slice(2); 8 | 9 | if (args.length != 3) { 10 | throw new Error(`invalid argument number, expected 3, got ${args.length}`); 11 | } 12 | 13 | const typeArguments: string[] = args[0].split(",").map((s) => s.trim()); 14 | const objects: string[] = args[1].split(",").map((s) => 15 | ethers.utils.hexZeroPad(s.trim(), 32) 16 | ); 17 | const message = args[2]; 18 | 19 | // values are abi encoded 20 | const encoded = ethers.utils.defaultAbiCoder.encode( 21 | ["tuple(string[] typeArguments, bytes32[] objects, bytes message)"], 22 | [[typeArguments, objects, message]] 23 | ); 24 | 25 | console.log(encoded); 26 | } 27 | 28 | encode().catch((err) => 29 | console.error(`Encode args for sui examples error: ${err}, usage: encodeCall.ts `,) 30 | ); 31 | -------------------------------------------------------------------------------- /examples/call/sui/sources/cetusmock.move: -------------------------------------------------------------------------------- 1 | /// This module provides a mock implementation of the Cetus DEX (Decentralized Exchange) interface 2 | /// for the Sui blockchain. It simulates token swap functionality between two token types. 3 | /// 4 | /// The mock implementation includes: 5 | /// - A simulated liquidity pool for token pairs 6 | /// - Basic swap functionality between token A and token B 7 | /// - Shared objects for configuration and state management 8 | /// 9 | /// This is used only for testing and development purposes to simulate 10 | /// token swaps without interacting with the actual Cetus protocol. 11 | module connected::cetusmock; 12 | 13 | use sui::balance::{Self, Balance}; 14 | use sui::coin::{Self, Coin}; 15 | 16 | /// Global configuration object 17 | public struct GlobalConfig has key { 18 | id: UID, 19 | } 20 | 21 | /// Partner object representing a protocol participant 22 | public struct Partner has key { 23 | id: UID, 24 | } 25 | 26 | /// Clock object for time-based operations 27 | public struct Clock has key { 28 | id: UID, 29 | } 30 | 31 | /// Represents a liquidity pool for a pair of tokens (CoinA and CoinB) 32 | /// Holds balances of both tokens that can be swapped 33 | public struct Pool has key { 34 | id: UID, 35 | balance_a: Balance, 36 | balance_b: Balance, 37 | } 38 | 39 | /// Initializes the mock protocol by creating and sharing all necessary objects 40 | /// Creates a pool for SUI and TOKEN pair 41 | fun init(ctx: &mut TxContext) { 42 | let global_config = GlobalConfig { 43 | id: object::new(ctx), 44 | }; 45 | let pool = Pool { 46 | id: object::new(ctx), 47 | balance_a: balance::zero(), 48 | balance_b: balance::zero(), 49 | }; 50 | let partner = Partner { 51 | id: object::new(ctx), 52 | }; 53 | let clock = Clock { 54 | id: object::new(ctx), 55 | }; 56 | 57 | transfer::share_object(global_config); 58 | transfer::share_object(pool); 59 | transfer::share_object(partner); 60 | transfer::share_object(clock); 61 | } 62 | 63 | /// Deposits tokens into the pool to be available for swaps 64 | /// @param pool The liquidity pool to deposit into 65 | /// @param coin The coin to deposit 66 | public entry fun deposit(pool: &mut Pool, coin: Coin) { 67 | balance::join(&mut pool.balance_b, coin.into_balance()); 68 | } 69 | 70 | /// Simulates a token swap from CoinA to CoinB 71 | /// @param _config Global configuration object 72 | /// @param pool The liquidity pool to swap from 73 | /// @param _partner Partner object 74 | /// @param coin_a The input coin to swap 75 | /// @param _clock Clock object for time-based operations 76 | /// @param ctx Transaction context 77 | /// @return The swapped coins of type CoinB 78 | public fun swap_a2b( 79 | _config: &GlobalConfig, 80 | pool: &mut Pool, 81 | _partner: &mut Partner, 82 | coin_a: Coin, 83 | _clock: &Clock, 84 | ctx: &mut TxContext, 85 | ): Coin { 86 | // deposit all coins in 87 | balance::join(&mut pool.balance_a, coin_a.into_balance()); 88 | 89 | // take all the coins out 90 | let value = pool.balance_b.value(); 91 | let coins_out = coin::take(&mut pool.balance_b, value, ctx); 92 | 93 | coins_out 94 | } 95 | -------------------------------------------------------------------------------- /examples/call/sui/sources/connected.move: -------------------------------------------------------------------------------- 1 | /// This module provides two main functionalities: 2 | /// 1. An on_call function that gets executed when a call is made from ZetaChain. 3 | /// This function replicates Cetus's swap functionality, maintaining the same 4 | /// parameter structure and behavior for seamless integration with Cetus pools. 5 | /// 6 | /// 2. Examples of how to use the ZetaChain gateway to make calls from Sui to 7 | /// universal contracts on ZetaChain through the deposit and deposit_and_call 8 | /// functions. 9 | module connected::connected; 10 | 11 | use connected::cetusmock::{GlobalConfig, Partner, Pool, Clock, swap_a2b}; 12 | use gateway::gateway::Gateway; 13 | use std::ascii::String; 14 | use sui::address::from_bytes; 15 | use sui::coin::Coin; 16 | use sui::event; 17 | use sui::tx_context; 18 | 19 | // Withdraw and Call Example 20 | 21 | public entry fun on_call( 22 | in_coins: Coin, 23 | cetus_config: &GlobalConfig, 24 | pool: &mut Pool, 25 | cetus_partner: &mut Partner, 26 | clock: &Clock, 27 | data: vector, 28 | ctx: &mut TxContext, 29 | ) { 30 | let coins_out = swap_a2b( 31 | cetus_config, 32 | pool, 33 | cetus_partner, 34 | in_coins, 35 | clock, 36 | ctx, 37 | ); 38 | 39 | let receiver = from_bytes(data); 40 | 41 | // transfer the coins to the provided address 42 | transfer::public_transfer(coins_out, receiver) 43 | } 44 | 45 | // Deposit and Call Example 46 | 47 | public entry fun deposit( 48 | gateway: &mut Gateway, 49 | coin: Coin, 50 | receiver: String, 51 | ctx: &mut tx_context::TxContext, 52 | ) { 53 | gateway.deposit(coin, receiver, ctx); 54 | } 55 | 56 | public entry fun deposit_and_call( 57 | gateway: &mut Gateway, 58 | coin: Coin, 59 | receiver: String, 60 | payload: vector, 61 | ctx: &mut tx_context::TxContext, 62 | ) { 63 | gateway.deposit_and_call(coin, receiver, payload, ctx); 64 | } 65 | -------------------------------------------------------------------------------- /examples/call/sui/sources/token.move: -------------------------------------------------------------------------------- 1 | module connected::token; 2 | 3 | use sui::coin::{Self, TreasuryCap}; 4 | 5 | public struct TOKEN has drop {} 6 | 7 | fun init(witness: TOKEN, ctx: &mut TxContext) { 8 | let (treasury, metadata) = coin::create_currency( 9 | witness, 10 | 6, 11 | b"TOKEN", 12 | b"TOKEN", 13 | b"An example token", 14 | option::none(), // url: no URL provided for this example token 15 | ctx, 16 | ); 17 | transfer::public_freeze_object(metadata); 18 | transfer::public_transfer(treasury, ctx.sender()) 19 | } 20 | 21 | public entry fun mint_and_transfer( 22 | treasury_cap: &mut TreasuryCap, 23 | amount: u64, 24 | recipient: address, 25 | ctx: &mut TxContext, 26 | ) { 27 | let coin = coin::mint(treasury_cap, amount, ctx); 28 | transfer::public_transfer(coin, recipient) 29 | } 30 | -------------------------------------------------------------------------------- /examples/call/tasks/connectedCall.ts: -------------------------------------------------------------------------------- 1 | import { task, types } from "hardhat/config"; 2 | import type { HardhatRuntimeEnvironment } from "hardhat/types"; 3 | 4 | const main = async (args: any, hre: HardhatRuntimeEnvironment) => { 5 | const { ethers } = hre; 6 | const [signer] = await ethers.getSigners(); 7 | 8 | const revertOptions = { 9 | abortAddress: args.abortAddress, 10 | callOnRevert: args.callOnRevert, 11 | onRevertGasLimit: args.onRevertGasLimit, 12 | revertAddress: args.revertAddress, 13 | revertMessage: ethers.utils.hexlify( 14 | ethers.utils.toUtf8Bytes(args.revertMessage) 15 | ), 16 | }; 17 | 18 | const types = JSON.parse(args.types); 19 | 20 | if (types.length !== args.values.length) { 21 | throw new Error( 22 | `The number of types (${types.length}) does not match the number of values (${args.values.length}).` 23 | ); 24 | } 25 | 26 | const valuesArray = args.values.map((value: any, index: number) => { 27 | const type = types[index]; 28 | 29 | if (type === "bool") { 30 | try { 31 | return JSON.parse(value.toLowerCase()); 32 | } catch (e) { 33 | throw new Error(`Invalid boolean value: ${value}`); 34 | } 35 | } else if (type.startsWith("uint") || type.startsWith("int")) { 36 | return ethers.BigNumber.from(value); 37 | } else { 38 | return value; 39 | } 40 | }); 41 | const encodedParameters = ethers.utils.defaultAbiCoder.encode( 42 | types, 43 | valuesArray 44 | ); 45 | 46 | const factory = (await hre.ethers.getContractFactory(args.name)) as any; 47 | const contract = factory.attach(args.contract).connect(signer); 48 | 49 | const tx = await contract.call( 50 | args.receiver, 51 | encodedParameters, 52 | revertOptions 53 | ); 54 | 55 | await tx.wait(); 56 | console.log(`Transaction hash: ${tx.hash}`); 57 | }; 58 | 59 | task( 60 | "connected-call", 61 | "Make a call from a connected chain to a universal app on ZetaChain", 62 | main 63 | ) 64 | .addParam("contract", "The address of the deployed contract") 65 | .addFlag("callOnRevert", "Whether to call on revert") 66 | .addOptionalParam( 67 | "revertAddress", 68 | "Revert address", 69 | "0x0000000000000000000000000000000000000000" 70 | ) 71 | .addOptionalParam( 72 | "abortAddress", 73 | "Abort address", 74 | "0x0000000000000000000000000000000000000000" 75 | ) 76 | .addOptionalParam("revertMessage", "Revert message", "0x") 77 | .addParam( 78 | "receiver", 79 | "The address of the receiver contract on a connected chain" 80 | ) 81 | .addOptionalParam( 82 | "onRevertGasLimit", 83 | "The gas limit for the revert transaction", 84 | 500000, 85 | types.int 86 | ) 87 | .addParam("name", "The name of the contract", "Connected") 88 | .addParam("types", `The types of the parameters (example: '["string"]')`) 89 | .addVariadicPositionalParam("values", "The values of the parameters"); 90 | -------------------------------------------------------------------------------- /examples/call/tasks/connectedDeposit.ts: -------------------------------------------------------------------------------- 1 | import ERC20_ABI from "@openzeppelin/contracts/build/contracts/ERC20.json"; 2 | import { task, types } from "hardhat/config"; 3 | import type { HardhatRuntimeEnvironment } from "hardhat/types"; 4 | 5 | const main = async (args: any, hre: HardhatRuntimeEnvironment) => { 6 | const { ethers } = hre; 7 | const [signer] = await ethers.getSigners(); 8 | 9 | const revertOptions = { 10 | abortAddress: args.abortAddress, 11 | callOnRevert: args.callOnRevert, 12 | onRevertGasLimit: args.onRevertGasLimit, 13 | revertAddress: args.revertAddress, 14 | revertMessage: ethers.utils.hexlify( 15 | ethers.utils.toUtf8Bytes(args.revertMessage) 16 | ), 17 | }; 18 | 19 | const factory = (await hre.ethers.getContractFactory("Connected")) as any; 20 | const contract = factory.attach(args.contract).connect(signer); 21 | 22 | let tx; 23 | if (args.erc20) { 24 | const erc20Contract = new ethers.Contract( 25 | args.erc20, 26 | ERC20_ABI.abi, 27 | signer 28 | ); 29 | const decimals = await erc20Contract.decimals(); 30 | const value = hre.ethers.utils.parseUnits(args.amount, decimals); 31 | const approveTx = await erc20Contract 32 | .connect(signer) 33 | .approve(args.contract, value); 34 | await approveTx.wait(); 35 | const method = 36 | "deposit(address,uint256,address,(address,bool,address,bytes,uint256))"; 37 | tx = await contract[method]( 38 | args.receiver, 39 | value, 40 | args.erc20, 41 | revertOptions 42 | ); 43 | } else { 44 | const value = hre.ethers.utils.parseEther(args.amount); 45 | const method = "deposit(address,(address,bool,address,bytes,uint256))"; 46 | tx = await contract[method](args.receiver, revertOptions, { value }); 47 | } 48 | 49 | await tx.wait(); 50 | console.log(`Transaction hash: ${tx.hash}`); 51 | }; 52 | 53 | task("connected-deposit", "Deposit tokens to ZetaChain", main) 54 | .addParam("contract", "The address of the deployed contract") 55 | .addFlag("callOnRevert", "Whether to call on revert") 56 | .addOptionalParam( 57 | "revertAddress", 58 | "Revert address", 59 | "0x0000000000000000000000000000000000000000" 60 | ) 61 | .addOptionalParam( 62 | "abortAddress", 63 | "Abort address", 64 | "0x0000000000000000000000000000000000000000" 65 | ) 66 | .addOptionalParam("revertMessage", "Revert message", "") 67 | .addParam( 68 | "receiver", 69 | "The address of the receiver contract on a connected chain" 70 | ) 71 | .addOptionalParam( 72 | "onRevertGasLimit", 73 | "The gas limit for the revert transaction", 74 | 500000, 75 | types.int 76 | ) 77 | .addOptionalParam("erc20", "The address of the ERC20 token to deposit") 78 | .addParam("amount", "The amount of tokens to deposit"); 79 | -------------------------------------------------------------------------------- /examples/call/tasks/connectedDepositAndCall.ts: -------------------------------------------------------------------------------- 1 | import ERC20_ABI from "@openzeppelin/contracts/build/contracts/ERC20.json"; 2 | import { task, types } from "hardhat/config"; 3 | import type { HardhatRuntimeEnvironment } from "hardhat/types"; 4 | 5 | const main = async (args: any, hre: HardhatRuntimeEnvironment) => { 6 | const { ethers } = hre; 7 | const [signer] = await ethers.getSigners(); 8 | 9 | const revertOptions = { 10 | abortAddress: args.abortAddress, 11 | callOnRevert: args.callOnRevert, 12 | onRevertGasLimit: args.onRevertGasLimit, 13 | revertAddress: args.revertAddress, 14 | revertMessage: ethers.utils.hexlify( 15 | ethers.utils.toUtf8Bytes(args.revertMessage) 16 | ), 17 | }; 18 | 19 | const types = JSON.parse(args.types); 20 | 21 | if (types.length !== args.values.length) { 22 | throw new Error( 23 | `The number of types (${types.length}) does not match the number of values (${args.values.length}).` 24 | ); 25 | } 26 | 27 | const valuesArray = args.values.map((value: any, index: number) => { 28 | const type = types[index]; 29 | 30 | if (type === "bool") { 31 | try { 32 | return JSON.parse(value.toLowerCase()); 33 | } catch (e) { 34 | throw new Error(`Invalid boolean value: ${value}`); 35 | } 36 | } else if (type.startsWith("uint") || type.startsWith("int")) { 37 | return ethers.BigNumber.from(value); 38 | } else { 39 | return value; 40 | } 41 | }); 42 | const encodedParameters = ethers.utils.defaultAbiCoder.encode( 43 | types, 44 | valuesArray 45 | ); 46 | 47 | const factory = (await hre.ethers.getContractFactory(args.name)) as any; 48 | const contract = factory.attach(args.contract).connect(signer); 49 | 50 | let tx; 51 | if (args.erc20) { 52 | const erc20Contract = new ethers.Contract( 53 | args.erc20, 54 | ERC20_ABI.abi, 55 | signer 56 | ); 57 | const decimals = await erc20Contract.decimals(); 58 | const value = hre.ethers.utils.parseUnits(args.amount, decimals); 59 | const approveTx = await erc20Contract 60 | .connect(signer) 61 | .approve(args.contract, value); 62 | await approveTx.wait(); 63 | const method = 64 | "depositAndCall(address,uint256,address,bytes,(address,bool,address,bytes,uint256))"; 65 | tx = await contract[method]( 66 | args.receiver, 67 | value, 68 | args.erc20, 69 | encodedParameters, 70 | revertOptions 71 | ); 72 | } else { 73 | const value = hre.ethers.utils.parseEther(args.amount); 74 | const method = 75 | "depositAndCall(address,bytes,(address,bool,address,bytes,uint256))"; 76 | tx = await contract[method]( 77 | args.receiver, 78 | encodedParameters, 79 | revertOptions, 80 | { value } 81 | ); 82 | } 83 | 84 | await tx.wait(); 85 | console.log(`Transaction hash: ${tx.hash}`); 86 | }; 87 | 88 | task( 89 | "connected-deposit-and-call", 90 | "Deposit tokens and make a call from a connected chain to a universal app on ZetaChain", 91 | main 92 | ) 93 | .addParam("contract", "The address of the deployed contract") 94 | .addFlag("callOnRevert", "Whether to call on revert") 95 | .addOptionalParam( 96 | "revertAddress", 97 | "Revert address", 98 | "0x0000000000000000000000000000000000000000" 99 | ) 100 | .addOptionalParam( 101 | "abortAddress", 102 | "Abort address", 103 | "0x0000000000000000000000000000000000000000" 104 | ) 105 | .addOptionalParam("revertMessage", "Revert message", "0x") 106 | .addParam( 107 | "receiver", 108 | "The address of the receiver contract on a connected chain" 109 | ) 110 | .addOptionalParam( 111 | "onRevertGasLimit", 112 | "The gas limit for the revert transaction", 113 | 500000, 114 | types.int 115 | ) 116 | .addParam("amount", "The amount of tokens to deposit") 117 | .addParam("name", "The name of the contract", "Connected") 118 | .addOptionalParam("erc20", "The address of the ERC20 token to deposit") 119 | .addParam("types", `The types of the parameters (example: '["string"]')`) 120 | .addVariadicPositionalParam("values", "The values of the parameters"); 121 | -------------------------------------------------------------------------------- /examples/call/tasks/deploy.ts: -------------------------------------------------------------------------------- 1 | import { task, types } from "hardhat/config"; 2 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 3 | 4 | const main = async (args: any, hre: HardhatRuntimeEnvironment) => { 5 | const network = hre.network.name; 6 | 7 | const [signer] = await hre.ethers.getSigners(); 8 | if (signer === undefined) { 9 | throw new Error( 10 | `Wallet not found. Please, run "npx hardhat account --save" or set PRIVATE_KEY env variable (for example, in a .env file)` 11 | ); 12 | } 13 | 14 | const factory = await hre.ethers.getContractFactory(args.name); 15 | const contract = await (factory as any).deploy(args.gateway); 16 | await contract.deployed(); 17 | 18 | if (args.json) { 19 | console.log( 20 | JSON.stringify({ 21 | contractAddress: contract.address, 22 | deployer: signer.address, 23 | network: network, 24 | transactionHash: contract.deployTransaction.hash, 25 | }) 26 | ); 27 | } else { 28 | console.log(`🔑 Using account: ${signer.address} 29 | 30 | 🚀 Successfully deployed "${args.name}" contract on ${network}. 31 | 📜 Contract address: ${contract.address} 32 | `); 33 | } 34 | }; 35 | 36 | task("deploy", "Deploy the contract", main) 37 | .addFlag("json", "Output in JSON") 38 | .addOptionalParam("name", "Contract to deploy", "Universal") 39 | .addOptionalParam( 40 | "gateway", 41 | "Gateway address (default: ZetaChain Gateway on testnet)", 42 | "0x6c533f7fe93fae114d0954697069df33c9b74fd7" 43 | ); 44 | -------------------------------------------------------------------------------- /examples/call/tasks/index.ts: -------------------------------------------------------------------------------- 1 | import "./deploy"; 2 | import "./universalCall"; 3 | import "./universalCallMulti"; 4 | import "./connectedCall"; 5 | import "./connectedDeposit"; 6 | import "./connectedDepositAndCall"; 7 | import "./universalWithdraw"; 8 | import "./universalWithdrawAndCall"; 9 | -------------------------------------------------------------------------------- /examples/call/tasks/universalCall.ts: -------------------------------------------------------------------------------- 1 | import { task, types } from "hardhat/config"; 2 | import type { HardhatRuntimeEnvironment } from "hardhat/types"; 3 | import ZRC20ABI from "@zetachain/protocol-contracts/abi/ZRC20.sol/ZRC20.json"; 4 | 5 | const main = async (args: any, hre: HardhatRuntimeEnvironment) => { 6 | const { ethers } = hre; 7 | const [signer] = await ethers.getSigners(); 8 | 9 | const callOptions = { 10 | isArbitraryCall: args.callOptionsIsArbitraryCall, 11 | gasLimit: args.callOptionsGasLimit, 12 | }; 13 | 14 | const revertOptions = { 15 | abortAddress: args.abortAddress, 16 | callOnRevert: args.callOnRevert, 17 | onRevertGasLimit: args.onRevertGasLimit, 18 | revertAddress: args.revertAddress, 19 | revertMessage: ethers.utils.hexlify( 20 | ethers.utils.toUtf8Bytes(args.revertMessage) 21 | ), 22 | }; 23 | 24 | const functionSignature = ethers.utils.id(args.function).slice(0, 10); 25 | 26 | const types = JSON.parse(args.types); 27 | 28 | if (types.length !== args.values.length) { 29 | throw new Error( 30 | `The number of types (${types.length}) does not match the number of values (${args.values.length}).` 31 | ); 32 | } 33 | 34 | const valuesArray = args.values.map((value: any, index: number) => { 35 | const type = types[index]; 36 | 37 | if (type === "bool") { 38 | try { 39 | return JSON.parse(value.toLowerCase()); 40 | } catch (e) { 41 | throw new Error(`Invalid boolean value: ${value}`); 42 | } 43 | } else if (type.startsWith("uint") || type.startsWith("int")) { 44 | return ethers.BigNumber.from(value); 45 | } else { 46 | return value; 47 | } 48 | }); 49 | const encodedParameters = ethers.utils.defaultAbiCoder.encode( 50 | types, 51 | valuesArray 52 | ); 53 | 54 | const message = ethers.utils.hexlify( 55 | ethers.utils.concat([functionSignature, encodedParameters]) 56 | ); 57 | 58 | const gasLimit = hre.ethers.BigNumber.from(callOptions.gasLimit); 59 | const zrc20 = new ethers.Contract(args.zrc20, ZRC20ABI.abi, signer); 60 | const [, gasFee] = await zrc20.withdrawGasFeeWithGasLimit(gasLimit); 61 | const zrc20TransferTx = await zrc20.approve(args.contract, gasFee); 62 | 63 | await zrc20TransferTx.wait(); 64 | 65 | const factory = (await hre.ethers.getContractFactory(args.name)) as any; 66 | const contract = factory.attach(args.contract); 67 | 68 | const tx = await contract.call( 69 | ethers.utils.hexlify(args.receiver), 70 | args.zrc20, 71 | message, 72 | callOptions, 73 | revertOptions 74 | ); 75 | 76 | await tx.wait(); 77 | console.log(`Transaction hash: ${tx.hash}`); 78 | }; 79 | 80 | task( 81 | "universal-call", 82 | "Make a call from a universal app to a contract on a connected chain", 83 | main 84 | ) 85 | .addParam("contract", "The address of the deployed universal contract") 86 | .addParam("zrc20", "The address of ZRC-20 to pay fees") 87 | .addFlag("callOnRevert", "Whether to call on revert") 88 | .addOptionalParam( 89 | "revertAddress", 90 | "Revert address", 91 | "0x0000000000000000000000000000000000000000" 92 | ) 93 | .addOptionalParam( 94 | "abortAddress", 95 | "Abort address", 96 | "0x0000000000000000000000000000000000000000" 97 | ) 98 | .addOptionalParam("revertMessage", "Revert message", "0x") 99 | .addParam( 100 | "receiver", 101 | "The address of the receiver contract on a connected chain" 102 | ) 103 | .addOptionalParam( 104 | "onRevertGasLimit", 105 | "The gas limit for the revert transaction", 106 | 500000, 107 | types.int 108 | ) 109 | .addFlag("callOptionsIsArbitraryCall", "Call any function") 110 | .addOptionalParam( 111 | "callOptionsGasLimit", 112 | "The gas limit for the call", 113 | 500000, 114 | types.int 115 | ) 116 | .addParam("function", `Function to call (example: "hello(string)")`) 117 | .addParam("name", "The name of the contract", "Universal") 118 | .addParam("types", `The types of the parameters (example: '["string"]')`) 119 | .addVariadicPositionalParam("values", "The values of the parameters"); 120 | -------------------------------------------------------------------------------- /examples/call/tasks/universalCallMulti.ts: -------------------------------------------------------------------------------- 1 | import { task, types } from "hardhat/config"; 2 | import type { HardhatRuntimeEnvironment } from "hardhat/types"; 3 | import ZRC20ABI from "@zetachain/protocol-contracts/abi/ZRC20.sol/ZRC20.json"; 4 | 5 | const main = async (args: any, hre: HardhatRuntimeEnvironment) => { 6 | const { ethers } = hre; 7 | const [signer] = await ethers.getSigners(); 8 | 9 | const callOptions = { 10 | isArbitraryCall: args.callOptionsIsArbitraryCall, 11 | gasLimit: args.callOptionsGasLimit, 12 | }; 13 | 14 | const revertOptions = { 15 | abortAddress: args.abortAddress, 16 | callOnRevert: args.callOnRevert, 17 | onRevertGasLimit: args.onRevertGasLimit, 18 | revertAddress: args.revertAddress, 19 | revertMessage: ethers.utils.hexlify( 20 | ethers.utils.toUtf8Bytes(args.revertMessage) 21 | ), 22 | }; 23 | 24 | const functionSignature = ethers.utils.id(args.function).slice(0, 10); 25 | 26 | const types = JSON.parse(args.types); 27 | 28 | if (types.length !== args.values.length) { 29 | throw new Error( 30 | `The number of types (${types.length}) does not match the number of values (${args.values.length}).` 31 | ); 32 | } 33 | 34 | const valuesArray = args.values.map((value: any, index: number) => { 35 | const type = types[index]; 36 | 37 | if (type === "bool") { 38 | try { 39 | return JSON.parse(value.toLowerCase()); 40 | } catch (e) { 41 | throw new Error(`Invalid boolean value: ${value}`); 42 | } 43 | } else if (type.startsWith("uint") || type.startsWith("int")) { 44 | return ethers.BigNumber.from(value); 45 | } else { 46 | return value; 47 | } 48 | }); 49 | const encodedParameters = ethers.utils.defaultAbiCoder.encode( 50 | types, 51 | valuesArray 52 | ); 53 | 54 | const message = ethers.utils.hexlify( 55 | ethers.utils.concat([functionSignature, encodedParameters]) 56 | ); 57 | 58 | const gasLimit = hre.ethers.BigNumber.from(callOptions.gasLimit); 59 | const factory = (await hre.ethers.getContractFactory(args.name)) as any; 60 | const contract = factory.attach(args.contract); 61 | 62 | const zrc20Array = JSON.parse(args.zrc20Array); 63 | const zrc20Contracts = zrc20Array.map( 64 | (address: string) => new ethers.Contract(address, ZRC20ABI.abi, signer) 65 | ); 66 | 67 | for (const zrc20 of zrc20Contracts) { 68 | const [, gasFee] = await zrc20.withdrawGasFeeWithGasLimit(gasLimit); 69 | const zrc20TransferTx = await zrc20.approve(args.contract, gasFee); 70 | await zrc20TransferTx.wait(); 71 | } 72 | 73 | const encodedReceivers = ethers.utils.defaultAbiCoder.encode( 74 | ["address[]"], 75 | [JSON.parse(args.receiverArray)] 76 | ); 77 | 78 | const tx = await contract.callMulti( 79 | encodedReceivers, 80 | zrc20Array, 81 | message, 82 | callOptions, 83 | revertOptions 84 | ); 85 | 86 | await tx.wait(); 87 | console.log(`Transaction hash: ${tx.hash}`); 88 | }; 89 | 90 | task( 91 | "universal-call-multi", 92 | "Make a multi-call from a universal app to a contract on connected chains", 93 | main 94 | ) 95 | .addParam("contract", "The address of the deployed universal contract") 96 | .addParam("zrc20Array", "The array of ZRC-20 addresses to pay fees", "[]") 97 | .addFlag("callOnRevert", "Whether to call on revert") 98 | .addOptionalParam( 99 | "revertAddress", 100 | "Revert address", 101 | "0x0000000000000000000000000000000000000000" 102 | ) 103 | .addOptionalParam( 104 | "abortAddress", 105 | "Abort address", 106 | "0x0000000000000000000000000000000000000000" 107 | ) 108 | .addOptionalParam("revertMessage", "Revert message", "0x") 109 | .addParam( 110 | "receiverArray", 111 | "The addresses of the receiver contract on a connected chain", 112 | "[]" 113 | ) 114 | .addOptionalParam( 115 | "onRevertGasLimit", 116 | "The gas limit for the revert transaction", 117 | 500000, 118 | types.int 119 | ) 120 | .addFlag("callOptionsIsArbitraryCall", "Call any function") 121 | .addOptionalParam( 122 | "callOptionsGasLimit", 123 | "The gas limit for the call", 124 | 2000000, 125 | types.int 126 | ) 127 | .addParam("function", `Function to call (example: "hello(string)")`) 128 | .addParam("name", "The name of the contract", "Universal") 129 | .addParam("types", `The types of the parameters (example: '["string"]')`) 130 | .addVariadicPositionalParam("values", "The values of the parameters"); 131 | -------------------------------------------------------------------------------- /examples/call/tasks/universalWithdraw.ts: -------------------------------------------------------------------------------- 1 | import { task, types } from "hardhat/config"; 2 | import type { HardhatRuntimeEnvironment } from "hardhat/types"; 3 | import ZRC20ABI from "@zetachain/protocol-contracts/abi/ZRC20.sol/ZRC20.json"; 4 | 5 | const main = async (args: any, hre: HardhatRuntimeEnvironment) => { 6 | const { ethers } = hre; 7 | const [signer] = await ethers.getSigners(); 8 | 9 | const revertOptions = { 10 | abortAddress: args.abortAddress, 11 | callOnRevert: args.callOnRevert, 12 | onRevertGasLimit: args.onRevertGasLimit, 13 | revertAddress: args.revertAddress, 14 | revertMessage: ethers.utils.hexlify( 15 | ethers.utils.toUtf8Bytes(args.revertMessage) 16 | ), 17 | }; 18 | 19 | const amount = hre.ethers.utils.parseUnits(args.amount, 18); 20 | 21 | const zrc20 = new ethers.Contract(args.zrc20, ZRC20ABI.abi, signer); 22 | const [gasZRC20, gasFee] = await zrc20.withdrawGasFee(); 23 | const gasZRC20Contract = new ethers.Contract(gasZRC20, ZRC20ABI.abi, signer); 24 | const gasFeeApprove = await gasZRC20Contract.approve( 25 | args.contract, 26 | gasZRC20 == args.zrc20 ? gasFee.add(amount) : gasFee 27 | ); 28 | await gasFeeApprove.wait(); 29 | 30 | if (gasZRC20 !== args.zrc20) { 31 | const targetTokenApprove = await zrc20.approve( 32 | args.contract, 33 | gasFee.add(amount) 34 | ); 35 | await targetTokenApprove.wait(); 36 | } 37 | 38 | const factory = (await hre.ethers.getContractFactory(args.name)) as any; 39 | const contract = factory.attach(args.contract); 40 | 41 | const tx = await contract.withdraw( 42 | ethers.utils.hexlify(args.receiver), 43 | amount, 44 | args.zrc20, 45 | revertOptions 46 | ); 47 | 48 | await tx.wait(); 49 | console.log(`Transaction hash: ${tx.hash}`); 50 | }; 51 | 52 | task("universal-withdraw", "Withdraw ZRC-20", main) 53 | .addParam("contract", "The address of the deployed Hello contract") 54 | .addParam("zrc20", "The address of ZRC-20 to pay fees") 55 | .addOptionalParam( 56 | "txOptionsGasPrice", 57 | "The gas price for the transaction", 58 | 20000000000, 59 | types.int 60 | ) 61 | .addOptionalParam( 62 | "txOptionsGasLimit", 63 | "The gas limit for the transaction", 64 | 500000, 65 | types.int 66 | ) 67 | .addFlag("callOnRevert", "Whether to call on revert") 68 | .addOptionalParam( 69 | "revertAddress", 70 | "Revert address", 71 | "0x0000000000000000000000000000000000000000" 72 | ) 73 | .addOptionalParam( 74 | "abortAddress", 75 | "Abort address", 76 | "0x0000000000000000000000000000000000000000" 77 | ) 78 | .addOptionalParam("revertMessage", "Revert message", "0x") 79 | .addParam( 80 | "receiver", 81 | "The address of the receiver contract on a connected chain" 82 | ) 83 | .addOptionalParam( 84 | "onRevertGasLimit", 85 | "The gas limit for the revert transaction", 86 | 500000, 87 | types.int 88 | ) 89 | .addParam("name", "The name of the contract", "Universal") 90 | .addParam("amount", "Amount of ZRC-20 to withdraw"); 91 | -------------------------------------------------------------------------------- /examples/call/tasks/universalWithdrawAndCall.ts: -------------------------------------------------------------------------------- 1 | import { task, types } from "hardhat/config"; 2 | import type { HardhatRuntimeEnvironment } from "hardhat/types"; 3 | import ZRC20ABI from "@zetachain/protocol-contracts/abi/ZRC20.sol/ZRC20.json"; 4 | 5 | const main = async (args: any, hre: HardhatRuntimeEnvironment) => { 6 | const { ethers } = hre; 7 | const [signer] = await ethers.getSigners(); 8 | 9 | if (args.callOptionsIsArbitraryCall && !args.function) { 10 | throw new Error("Function is required for arbitrary calls"); 11 | } 12 | 13 | if (!args.callOptionsIsArbitraryCall && args.function) { 14 | throw new Error("Function is not allowed for non-arbitrary calls"); 15 | } 16 | 17 | const callOptions = { 18 | isArbitraryCall: args.callOptionsIsArbitraryCall, 19 | gasLimit: args.callOptionsGasLimit, 20 | }; 21 | 22 | const revertOptions = { 23 | abortAddress: args.abortAddress, 24 | callOnRevert: args.callOnRevert, 25 | onRevertGasLimit: args.onRevertGasLimit, 26 | revertAddress: args.revertAddress, 27 | revertMessage: ethers.utils.hexlify( 28 | ethers.utils.toUtf8Bytes(args.revertMessage) 29 | ), 30 | }; 31 | 32 | const types = JSON.parse(args.types); 33 | 34 | if (types.length !== args.values.length) { 35 | throw new Error( 36 | `The number of types (${types.length}) does not match the number of values (${args.values.length}).` 37 | ); 38 | } 39 | 40 | const valuesArray = args.values.map((value: any, index: number) => { 41 | const type = types[index]; 42 | 43 | if (type === "bool") { 44 | try { 45 | return JSON.parse(value.toLowerCase()); 46 | } catch (e) { 47 | throw new Error(`Invalid boolean value: ${value}`); 48 | } 49 | } else if (type.startsWith("uint") || type.startsWith("int")) { 50 | return ethers.BigNumber.from(value); 51 | } else { 52 | return value; 53 | } 54 | }); 55 | const encodedParameters = ethers.utils.defaultAbiCoder.encode( 56 | types, 57 | valuesArray 58 | ); 59 | 60 | let message; 61 | 62 | if (args.callOptionsIsArbitraryCall) { 63 | let functionSignature = ethers.utils.id(args.function).slice(0, 10); 64 | message = ethers.utils.hexlify( 65 | ethers.utils.concat([functionSignature, encodedParameters]) 66 | ); 67 | } else { 68 | message = encodedParameters; 69 | } 70 | 71 | const gasLimit = hre.ethers.BigNumber.from(callOptions.gasLimit); 72 | 73 | const amount = hre.ethers.utils.parseUnits(args.amount, 18); 74 | 75 | const zrc20 = new ethers.Contract(args.zrc20, ZRC20ABI.abi, signer); 76 | const [gasZRC20, gasFee] = await zrc20.withdrawGasFeeWithGasLimit(gasLimit); 77 | const gasZRC20Contract = new ethers.Contract(gasZRC20, ZRC20ABI.abi, signer); 78 | const gasFeeApprove = await gasZRC20Contract.approve( 79 | args.contract, 80 | gasZRC20 == args.zrc20 ? gasFee.add(amount) : gasFee 81 | ); 82 | await gasFeeApprove.wait(); 83 | 84 | if (gasZRC20 !== args.zrc20) { 85 | const targetTokenApprove = await zrc20.approve( 86 | args.contract, 87 | gasFee.add(amount) 88 | ); 89 | await targetTokenApprove.wait(); 90 | } 91 | 92 | const factory = (await hre.ethers.getContractFactory(args.name)) as any; 93 | const contract = factory.attach(args.contract); 94 | 95 | const tx = await contract.withdrawAndCall( 96 | ethers.utils.hexlify(args.receiver), 97 | amount, 98 | args.zrc20, 99 | message, 100 | callOptions, 101 | revertOptions 102 | ); 103 | 104 | await tx.wait(); 105 | console.log(`Transaction hash: ${tx.hash}`); 106 | }; 107 | 108 | task( 109 | "universal-withdraw-and-call", 110 | "Withdraw ZRC-20 and call a function on a connected chain", 111 | main 112 | ) 113 | .addParam("contract", "The address of the deployed Hello contract") 114 | .addParam("zrc20", "The address of ZRC-20 to pay fees") 115 | .addFlag("callOnRevert", "Whether to call on revert") 116 | .addOptionalParam( 117 | "revertAddress", 118 | "Revert address", 119 | "0x0000000000000000000000000000000000000000" 120 | ) 121 | .addOptionalParam( 122 | "abortAddress", 123 | "Abort address", 124 | "0x0000000000000000000000000000000000000000" 125 | ) 126 | .addOptionalParam("revertMessage", "Revert message", "0x") 127 | .addParam( 128 | "receiver", 129 | "The address of the receiver contract on a connected chain" 130 | ) 131 | .addOptionalParam( 132 | "onRevertGasLimit", 133 | "The gas limit for the revert transaction", 134 | 500000, 135 | types.int 136 | ) 137 | .addFlag("callOptionsIsArbitraryCall", "Call any function") 138 | .addOptionalParam( 139 | "callOptionsGasLimit", 140 | "The gas limit for the call", 141 | 500000, 142 | types.int 143 | ) 144 | .addOptionalParam("function", `Function to call (example: "hello(string)")`) 145 | .addParam("name", "The name of the contract", "Universal") 146 | .addParam("amount", "Amount of ZRC-20 to withdraw") 147 | .addParam("types", `The types of the parameters (example: '["string"]')`) 148 | .addVariadicPositionalParam("values", "The values of the parameters"); 149 | -------------------------------------------------------------------------------- /examples/call/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "nodenext", 4 | "moduleResolution": "nodenext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/hello/.eslintignore: -------------------------------------------------------------------------------- 1 | .yarn 2 | artifacts 3 | cache 4 | coverage 5 | node_modules 6 | typechain-types -------------------------------------------------------------------------------- /examples/hello/.eslintrc.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | /** 4 | * @type {import("eslint").Linter.Config} 5 | */ 6 | module.exports = { 7 | env: { 8 | browser: false, 9 | es2021: true, 10 | mocha: true, 11 | node: true, 12 | }, 13 | extends: ["plugin:prettier/recommended"], 14 | parser: "@typescript-eslint/parser", 15 | parserOptions: { 16 | ecmaVersion: 12, 17 | }, 18 | plugins: [ 19 | "@typescript-eslint", 20 | "prettier", 21 | "simple-import-sort", 22 | "sort-keys-fix", 23 | "typescript-sort-keys", 24 | ], 25 | rules: { 26 | "@typescript-eslint/sort-type-union-intersection-members": "error", 27 | camelcase: "off", 28 | "simple-import-sort/exports": "error", 29 | "simple-import-sort/imports": "error", 30 | "sort-keys-fix/sort-keys-fix": "error", 31 | "typescript-sort-keys/interface": "error", 32 | "typescript-sort-keys/string-enum": "error", 33 | }, 34 | settings: { 35 | "import/parsers": { 36 | "@typescript-eslint/parser": [".js", ".jsx", ".ts", ".tsx", ".d.ts"], 37 | }, 38 | "import/resolver": { 39 | node: { 40 | extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"], 41 | }, 42 | typescript: { 43 | project: path.join(__dirname, "tsconfig.json"), 44 | }, 45 | }, 46 | }, 47 | }; 48 | -------------------------------------------------------------------------------- /examples/hello/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | dependencies 8 | 9 | # Hardhat files 10 | cache 11 | artifacts 12 | 13 | # Foundry files 14 | out 15 | cache_forge 16 | 17 | access_token 18 | 19 | localnet.json 20 | 21 | test-ledger 22 | -------------------------------------------------------------------------------- /examples/hello/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 ZetaChain 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 | -------------------------------------------------------------------------------- /examples/hello/README.md: -------------------------------------------------------------------------------- 1 | # Hello Example 2 | 3 | Tutorial: https://www.zetachain.com/docs/developers/tutorials/hello 4 | -------------------------------------------------------------------------------- /examples/hello/contracts/Universal.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.26; 3 | 4 | import "@zetachain/protocol-contracts/contracts/zevm/GatewayZEVM.sol"; 5 | 6 | contract Universal is UniversalContract { 7 | GatewayZEVM public immutable gateway; 8 | 9 | event HelloEvent(string, string); 10 | error Unauthorized(); 11 | 12 | modifier onlyGateway() { 13 | if (msg.sender != address(gateway)) revert Unauthorized(); 14 | _; 15 | } 16 | 17 | constructor(address payable gatewayAddress) { 18 | gateway = GatewayZEVM(gatewayAddress); 19 | } 20 | 21 | function onCall( 22 | MessageContext calldata context, 23 | address zrc20, 24 | uint256 amount, 25 | bytes calldata message 26 | ) external override onlyGateway { 27 | string memory name = abi.decode(message, (string)); 28 | emit HelloEvent("Hello: ", name); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/hello/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'contracts' 3 | out = 'out' 4 | viaIR = true 5 | libs = ['node_modules', 'lib'] 6 | test = 'test' 7 | cache_path = 'cache_forge' 8 | verbosity = 3 9 | 10 | [dependencies] 11 | forge-std = { version = "1.9.2" } 12 | -------------------------------------------------------------------------------- /examples/hello/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import "@nomicfoundation/hardhat-toolbox"; 2 | import { HardhatUserConfig } from "hardhat/config"; 3 | import * as dotenv from "dotenv"; 4 | 5 | import "./tasks/deploy"; 6 | import "@zetachain/localnet/tasks"; 7 | import "@zetachain/toolkit/tasks"; 8 | import { getHardhatConfig } from "@zetachain/toolkit/client"; 9 | 10 | dotenv.config(); 11 | 12 | const config: HardhatUserConfig = { 13 | ...getHardhatConfig({ accounts: [process.env.PRIVATE_KEY] }), 14 | }; 15 | 16 | export default config; 17 | -------------------------------------------------------------------------------- /examples/hello/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello", 3 | "version": "1.0.0", 4 | "description": "Simple universal app that emits an event when called from a connected chain.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "lint:fix": "npx eslint . --ext .js,.ts --fix", 9 | "lint": "npx eslint . --ext .js,.ts", 10 | "deploy:localnet": "npx hardhat compile --force && npx hardhat deploy --network localhost --gateway 0x9A676e781A523b5d0C0e43731313A708CB607508 && npx hardhat deploy --name Echo --network localhost --gateway 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@ethersproject/abi": "^5.4.7", 17 | "@ethersproject/providers": "^5.4.7", 18 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.0", 19 | "@nomicfoundation/hardhat-foundry": "^1.1.2", 20 | "@nomicfoundation/hardhat-network-helpers": "^1.0.0", 21 | "@nomicfoundation/hardhat-toolbox": "^2.0.0", 22 | "@nomiclabs/hardhat-ethers": "^2.0.0", 23 | "@nomiclabs/hardhat-etherscan": "^3.0.0", 24 | "@typechain/ethers-v5": "^10.1.0", 25 | "@typechain/hardhat": "^6.1.2", 26 | "@types/chai": "^4.2.0", 27 | "@types/mocha": ">=9.1.0", 28 | "@types/node": ">=12.0.0", 29 | "@typescript-eslint/eslint-plugin": "^5.59.9", 30 | "@typescript-eslint/parser": "^5.59.9", 31 | "@zetachain/localnet": "7.1.0", 32 | "@zetachain/toolkit": "13.0.0-rc17", 33 | "axios": "^1.3.6", 34 | "chai": "^4.2.0", 35 | "dotenv": "^16.0.3", 36 | "envfile": "^6.18.0", 37 | "eslint": "^8.42.0", 38 | "eslint-config-prettier": "^8.8.0", 39 | "eslint-import-resolver-typescript": "^3.5.5", 40 | "eslint-plugin-import": "^2.27.5", 41 | "eslint-plugin-prettier": "^4.2.1", 42 | "eslint-plugin-simple-import-sort": "^10.0.0", 43 | "eslint-plugin-sort-keys-fix": "^1.1.2", 44 | "eslint-plugin-typescript-sort-keys": "^2.3.0", 45 | "ethers": "^5.4.7", 46 | "hardhat": "^2.17.2", 47 | "hardhat-gas-reporter": "^1.0.8", 48 | "prettier": "^2.8.8", 49 | "solidity-coverage": "^0.8.0", 50 | "ts-node": ">=8.0.0", 51 | "typechain": "^8.1.0", 52 | "typescript": ">=4.5.0" 53 | }, 54 | "packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72", 55 | "dependencies": { 56 | "@coral-xyz/anchor": "0.30.0", 57 | "@solana-developers/helpers": "^2.4.0", 58 | "@solana/spl-memo": "^0.2.5", 59 | "@solana/web3.js": "^1.95.8", 60 | "@zetachain/protocol-contracts": "13.0.0", 61 | "zetachain": "^3.0.0" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /examples/hello/scripts/localnet.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | set -o pipefail 6 | 7 | yarn zetachain localnet start --skip sui ton solana --exit-on-error & 8 | 9 | while [ ! -f "localnet.json" ]; do sleep 1; done 10 | 11 | npx hardhat compile --force --quiet 12 | 13 | GATEWAY_ETHEREUM=$(jq -r '.addresses[] | select(.type=="gatewayEVM" and .chain=="ethereum") | .address' localnet.json) 14 | GATEWAY_ZETACHAIN=$(jq -r '.addresses[] | select(.type=="gatewayZEVM" and .chain=="zetachain") | .address' localnet.json) 15 | 16 | CONTRACT_ZETACHAIN=$(npx hardhat deploy --name Universal --network localhost --gateway "$GATEWAY_ZETACHAIN" --json | jq -r '.contractAddress') 17 | echo -e "\n🚀 Deployed contract on ZetaChain: $CONTRACT_ZETACHAIN" 18 | 19 | npx hardhat evm-call \ 20 | --gateway-evm "$GATEWAY_ETHEREUM" \ 21 | --receiver "$CONTRACT_ZETACHAIN" \ 22 | --network localhost \ 23 | --types '["string"]' alice 24 | 25 | yarn zetachain localnet check 26 | 27 | yarn zetachain localnet stop -------------------------------------------------------------------------------- /examples/hello/tasks/deploy.ts: -------------------------------------------------------------------------------- 1 | import { task, types } from "hardhat/config"; 2 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 3 | 4 | const main = async (args: any, hre: HardhatRuntimeEnvironment) => { 5 | const network = hre.network.name; 6 | 7 | const [signer] = await hre.ethers.getSigners(); 8 | if (signer === undefined) { 9 | throw new Error( 10 | `Wallet not found. Please, run "npx hardhat account --save" or set PRIVATE_KEY env variable (for example, in a .env file)` 11 | ); 12 | } 13 | 14 | const factory = await hre.ethers.getContractFactory(args.name); 15 | const contract = await (factory as any).deploy(args.gateway); 16 | await contract.deployed(); 17 | 18 | if (args.json) { 19 | console.log( 20 | JSON.stringify({ 21 | contractAddress: contract.address, 22 | deployer: signer.address, 23 | network: network, 24 | transactionHash: contract.deployTransaction.hash, 25 | }) 26 | ); 27 | } else { 28 | console.log(`🔑 Using account: ${signer.address} 29 | 30 | 🚀 Successfully deployed "${args.name}" contract on ${network}. 31 | 📜 Contract address: ${contract.address} 32 | `); 33 | } 34 | }; 35 | 36 | task("deploy", "Deploy the contract", main) 37 | .addFlag("json", "Output in JSON") 38 | .addOptionalParam("name", "Contract to deploy", "Universal") 39 | .addOptionalParam( 40 | "gateway", 41 | "Gateway address (default: ZetaChain Gateway on testnet)", 42 | "0x6c533f7fe93fae114d0954697069df33c9b74fd7" 43 | ); 44 | -------------------------------------------------------------------------------- /examples/hello/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "nodenext", 4 | "moduleResolution": "nodenext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/nft/.eslintignore: -------------------------------------------------------------------------------- 1 | .yarn 2 | artifacts 3 | cache 4 | coverage 5 | node_modules 6 | typechain-types -------------------------------------------------------------------------------- /examples/nft/.eslintrc.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | /** 4 | * @type {import("eslint").Linter.Config} 5 | */ 6 | module.exports = { 7 | env: { 8 | browser: false, 9 | es2021: true, 10 | mocha: true, 11 | node: true, 12 | }, 13 | extends: ["plugin:prettier/recommended"], 14 | parser: "@typescript-eslint/parser", 15 | parserOptions: { 16 | ecmaVersion: 12, 17 | }, 18 | plugins: [ 19 | "@typescript-eslint", 20 | "prettier", 21 | "simple-import-sort", 22 | "sort-keys-fix", 23 | "typescript-sort-keys", 24 | ], 25 | rules: { 26 | "@typescript-eslint/sort-type-union-intersection-members": "error", 27 | camelcase: "off", 28 | "simple-import-sort/exports": "error", 29 | "simple-import-sort/imports": "error", 30 | "sort-keys-fix/sort-keys-fix": "error", 31 | "typescript-sort-keys/interface": "error", 32 | "typescript-sort-keys/string-enum": "error", 33 | }, 34 | settings: { 35 | "import/parsers": { 36 | "@typescript-eslint/parser": [".js", ".jsx", ".ts", ".tsx", ".d.ts"], 37 | }, 38 | "import/resolver": { 39 | node: { 40 | extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"], 41 | }, 42 | typescript: { 43 | project: path.join(__dirname, "tsconfig.json"), 44 | }, 45 | }, 46 | }, 47 | }; 48 | -------------------------------------------------------------------------------- /examples/nft/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | dependencies 8 | 9 | # Hardhat files 10 | cache 11 | artifacts 12 | 13 | # Foundry files 14 | out 15 | cache_forge 16 | 17 | access_token 18 | 19 | localnet.json 20 | 21 | test-ledger -------------------------------------------------------------------------------- /examples/nft/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 ZetaChain 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 | -------------------------------------------------------------------------------- /examples/nft/README.md: -------------------------------------------------------------------------------- 1 | # Universal NFT 2 | 3 | Learn more: https://www.zetachain.com/docs/developers/standards/nft -------------------------------------------------------------------------------- /examples/nft/contracts/EVMUniversalNFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.26; 3 | 4 | import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; 5 | import {ERC721BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol"; 6 | import {ERC721EnumerableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol"; 7 | import {ERC721PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721PausableUpgradeable.sol"; 8 | import {ERC721URIStorageUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol"; 9 | import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 10 | import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 11 | import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; 12 | 13 | // Import UniversalNFTCore for universal NFT functionality 14 | import "@zetachain/standard-contracts/contracts/nft/contracts/evm/UniversalNFTCore.sol"; 15 | 16 | contract EVMUniversalNFT is 17 | Initializable, 18 | ERC721Upgradeable, 19 | ERC721EnumerableUpgradeable, 20 | ERC721URIStorageUpgradeable, 21 | ERC721PausableUpgradeable, 22 | OwnableUpgradeable, 23 | ERC721BurnableUpgradeable, 24 | UUPSUpgradeable, 25 | UniversalNFTCore // Add UniversalNFTCore for universal features 26 | { 27 | uint256 private _nextTokenId; // Track next token ID for minting 28 | 29 | /// @custom:oz-upgrades-unsafe-allow constructor 30 | constructor() { 31 | _disableInitializers(); 32 | } 33 | 34 | function initialize( 35 | address initialOwner, 36 | string memory name, 37 | string memory symbol, 38 | address payable gatewayAddress, // Include EVM gateway address 39 | uint256 gas // Set gas limit for universal NFT transfers 40 | ) public initializer { 41 | __ERC721_init(name, symbol); 42 | __ERC721Enumerable_init(); 43 | __ERC721URIStorage_init(); 44 | __ERC721Pausable_init(); 45 | __Ownable_init(initialOwner); 46 | __ERC721Burnable_init(); 47 | __UUPSUpgradeable_init(); 48 | __UniversalNFTCore_init(gatewayAddress, address(this), gas); // Initialize universal NFT core 49 | } 50 | 51 | function safeMint( 52 | address to, 53 | string memory uri 54 | ) public onlyOwner whenNotPaused { 55 | // Generate globally unique token ID, feel free to supply your own logic 56 | uint256 hash = uint256( 57 | keccak256( 58 | abi.encodePacked(address(this), block.number, _nextTokenId++) 59 | ) 60 | ); 61 | 62 | uint256 tokenId = hash & 0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; 63 | 64 | _safeMint(to, tokenId); 65 | _setTokenURI(tokenId, uri); 66 | } 67 | 68 | function pause() public onlyOwner { 69 | _pause(); 70 | } 71 | 72 | function unpause() public onlyOwner { 73 | _unpause(); 74 | } 75 | 76 | function _authorizeUpgrade( 77 | address newImplementation 78 | ) internal override onlyOwner {} 79 | 80 | // The following functions are overrides required by Solidity. 81 | 82 | function _update( 83 | address to, 84 | uint256 tokenId, 85 | address auth 86 | ) 87 | internal 88 | override( 89 | ERC721Upgradeable, 90 | ERC721EnumerableUpgradeable, 91 | ERC721PausableUpgradeable 92 | ) 93 | returns (address) 94 | { 95 | return super._update(to, tokenId, auth); 96 | } 97 | 98 | function _increaseBalance( 99 | address account, 100 | uint128 value 101 | ) internal override(ERC721Upgradeable, ERC721EnumerableUpgradeable) { 102 | super._increaseBalance(account, value); 103 | } 104 | 105 | function tokenURI( 106 | uint256 tokenId 107 | ) 108 | public 109 | view 110 | override( 111 | ERC721Upgradeable, 112 | ERC721URIStorageUpgradeable, 113 | UniversalNFTCore // Include UniversalNFTCore for URI overrides 114 | ) 115 | returns (string memory) 116 | { 117 | return super.tokenURI(tokenId); 118 | } 119 | 120 | function supportsInterface( 121 | bytes4 interfaceId 122 | ) 123 | public 124 | view 125 | override( 126 | ERC721Upgradeable, 127 | ERC721EnumerableUpgradeable, 128 | ERC721URIStorageUpgradeable, 129 | UniversalNFTCore // Include UniversalNFTCore for interface overrides 130 | ) 131 | returns (bool) 132 | { 133 | return super.supportsInterface(interfaceId); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /examples/nft/contracts/ZetaChainUniversalNFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.26; 3 | 4 | import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; 5 | import {ERC721BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol"; 6 | import {ERC721EnumerableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol"; 7 | import {ERC721PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721PausableUpgradeable.sol"; 8 | import {ERC721URIStorageUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol"; 9 | import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 10 | import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 11 | import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; 12 | 13 | // Import UniversalNFTCore for universal NFT functionality 14 | import "@zetachain/standard-contracts/contracts/nft/contracts/zetachain/UniversalNFTCore.sol"; 15 | 16 | contract ZetaChainUniversalNFT is 17 | Initializable, // Allows upgradeable contract initialization 18 | ERC721Upgradeable, // Base ERC721 implementation 19 | ERC721URIStorageUpgradeable, // Enables metadata URI storage 20 | ERC721EnumerableUpgradeable, // Provides enumerable token support 21 | ERC721PausableUpgradeable, // Allows pausing token operations 22 | OwnableUpgradeable, // Restricts access to owner-only functions 23 | ERC721BurnableUpgradeable, // Adds burnable functionality 24 | UUPSUpgradeable, // Supports upgradeable proxy pattern 25 | UniversalNFTCore // Custom core for additional logic 26 | { 27 | uint256 private _nextTokenId; // Track next token ID for minting 28 | 29 | /// @custom:oz-upgrades-unsafe-allow constructor 30 | constructor() { 31 | _disableInitializers(); 32 | } 33 | 34 | function initialize( 35 | address initialOwner, 36 | string memory name, 37 | string memory symbol, 38 | address payable gatewayAddress, // Include EVM gateway address 39 | uint256 gas, // Set gas limit for universal NFT calls 40 | address uniswapRouterAddress // Uniswap v2 router address for gas token swaps 41 | ) public initializer { 42 | __ERC721_init(name, symbol); 43 | __ERC721Enumerable_init(); 44 | __ERC721URIStorage_init(); 45 | __ERC721Pausable_init(); 46 | __Ownable_init(initialOwner); 47 | __ERC721Burnable_init(); 48 | __UUPSUpgradeable_init(); 49 | __UniversalNFTCore_init(gatewayAddress, gas, uniswapRouterAddress); // Initialize universal NFT core 50 | } 51 | 52 | function safeMint( 53 | address to, 54 | string memory uri 55 | ) public onlyOwner whenNotPaused { 56 | // Generate globally unique token ID, feel free to supply your own logic 57 | uint256 hash = uint256( 58 | keccak256( 59 | abi.encodePacked(address(this), block.number, _nextTokenId++) 60 | ) 61 | ); 62 | 63 | uint256 tokenId = hash & 0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; 64 | 65 | _safeMint(to, tokenId); 66 | _setTokenURI(tokenId, uri); 67 | } 68 | 69 | // The following functions are overrides required by Solidity. 70 | 71 | function _update( 72 | address to, 73 | uint256 tokenId, 74 | address auth 75 | ) 76 | internal 77 | override( 78 | ERC721Upgradeable, 79 | ERC721EnumerableUpgradeable, 80 | ERC721PausableUpgradeable 81 | ) 82 | returns (address) 83 | { 84 | return super._update(to, tokenId, auth); 85 | } 86 | 87 | function _increaseBalance( 88 | address account, 89 | uint128 value 90 | ) internal override(ERC721Upgradeable, ERC721EnumerableUpgradeable) { 91 | super._increaseBalance(account, value); 92 | } 93 | 94 | function tokenURI( 95 | uint256 tokenId 96 | ) 97 | public 98 | view 99 | override( 100 | ERC721Upgradeable, 101 | ERC721URIStorageUpgradeable, 102 | UniversalNFTCore // Include UniversalNFTCore for URI overrides 103 | ) 104 | returns (string memory) 105 | { 106 | return super.tokenURI(tokenId); 107 | } 108 | 109 | function supportsInterface( 110 | bytes4 interfaceId 111 | ) 112 | public 113 | view 114 | override( 115 | ERC721Upgradeable, 116 | ERC721EnumerableUpgradeable, 117 | ERC721URIStorageUpgradeable, 118 | UniversalNFTCore // Include UniversalNFTCore for interface overrides 119 | ) 120 | returns (bool) 121 | { 122 | return super.supportsInterface(interfaceId); 123 | } 124 | 125 | function _authorizeUpgrade( 126 | address newImplementation 127 | ) internal override onlyOwner {} 128 | 129 | function pause() public onlyOwner { 130 | _pause(); 131 | } 132 | 133 | function unpause() public onlyOwner { 134 | _unpause(); 135 | } 136 | 137 | receive() external payable {} // Receive ZETA to pay for gas 138 | } 139 | -------------------------------------------------------------------------------- /examples/nft/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'contracts' 3 | out = 'out' 4 | viaIR = true 5 | libs = ['node_modules', 'lib'] 6 | test = 'test' 7 | cache_path = 'cache_forge' 8 | verbosity = 3 9 | 10 | [dependencies] 11 | forge-std = { version = "1.9.2" } 12 | -------------------------------------------------------------------------------- /examples/nft/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import "@nomicfoundation/hardhat-toolbox"; 2 | import { HardhatUserConfig } from "hardhat/config"; 3 | import * as dotenv from "dotenv"; 4 | 5 | import "@zetachain/standard-contracts/tasks/nft"; 6 | import "@zetachain/localnet/tasks"; 7 | import "@zetachain/toolkit/tasks"; 8 | import { getHardhatConfig } from "@zetachain/toolkit/client"; 9 | 10 | import "@nomiclabs/hardhat-ethers"; 11 | import "@openzeppelin/hardhat-upgrades"; 12 | 13 | dotenv.config(); 14 | 15 | const config: HardhatUserConfig = { 16 | ...getHardhatConfig({ accounts: [process.env.PRIVATE_KEY] }), 17 | solidity: { 18 | compilers: [ 19 | { 20 | settings: { 21 | optimizer: { 22 | enabled: true, 23 | runs: 1000, 24 | }, 25 | }, 26 | version: "0.8.26", 27 | }, 28 | ], 29 | }, 30 | }; 31 | 32 | export default config; 33 | -------------------------------------------------------------------------------- /examples/nft/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nft", 3 | "version": "1.0.0", 4 | "description": "Universal NFT enables non-fungible ERC-721 tokens to minted and transferred between connected chains.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "lint:fix": "npx eslint . --ext .js,.ts --fix", 9 | "lint": "npx eslint . --ext .js,.ts" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "@ethersproject/abi": "^5.4.7", 16 | "@ethersproject/providers": "^5.4.7", 17 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.0", 18 | "@nomicfoundation/hardhat-foundry": "^1.1.2", 19 | "@nomicfoundation/hardhat-network-helpers": "^1.0.0", 20 | "@nomicfoundation/hardhat-toolbox": "^2.0.0", 21 | "@nomiclabs/hardhat-ethers": "^2.0.0", 22 | "@nomiclabs/hardhat-etherscan": "^3.0.0", 23 | "@typechain/ethers-v5": "^10.1.0", 24 | "@typechain/hardhat": "^6.1.2", 25 | "@types/chai": "^4.2.0", 26 | "@types/mocha": ">=9.1.0", 27 | "@types/node": ">=12.0.0", 28 | "@typescript-eslint/eslint-plugin": "^5.59.9", 29 | "@typescript-eslint/parser": "^5.59.9", 30 | "@zetachain/localnet": "7.1.0", 31 | "axios": "^1.3.6", 32 | "chai": "^4.2.0", 33 | "dotenv": "^16.0.3", 34 | "envfile": "^6.18.0", 35 | "eslint": "^8.42.0", 36 | "eslint-config-prettier": "^8.8.0", 37 | "eslint-import-resolver-typescript": "^3.5.5", 38 | "eslint-plugin-import": "^2.27.5", 39 | "eslint-plugin-prettier": "^4.2.1", 40 | "eslint-plugin-simple-import-sort": "^10.0.0", 41 | "eslint-plugin-sort-keys-fix": "^1.1.2", 42 | "eslint-plugin-typescript-sort-keys": "^2.3.0", 43 | "ethers": "^5.4.7", 44 | "hardhat": "^2.17.2", 45 | "hardhat-gas-reporter": "^1.0.8", 46 | "prettier": "^2.8.8", 47 | "solidity-coverage": "^0.8.0", 48 | "ts-node": ">=8.0.0", 49 | "typechain": "^8.1.0", 50 | "typescript": ">=4.5.0" 51 | }, 52 | "packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72", 53 | "dependencies": { 54 | "@coral-xyz/anchor": "0.30.0", 55 | "@openzeppelin/hardhat-upgrades": "1.28.0", 56 | "@solana-developers/helpers": "^2.4.0", 57 | "@solana/spl-memo": "^0.2.5", 58 | "@solana/web3.js": "^1.95.8", 59 | "@zetachain/protocol-contracts": "13.0.0", 60 | "@zetachain/standard-contracts": "2.0.0-rc5", 61 | "@zetachain/toolkit": "13.0.0-rc17", 62 | "validator": "^13.12.0", 63 | "zetachain": "^3.0.0" 64 | } 65 | } -------------------------------------------------------------------------------- /examples/nft/scripts/localnet.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | bash ./node_modules/@zetachain/standard-contracts/contracts/nft/scripts/localnet.sh -------------------------------------------------------------------------------- /examples/nft/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "nodenext", 4 | "moduleResolution": "nodenext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/swap/.eslintignore: -------------------------------------------------------------------------------- 1 | .yarn 2 | artifacts 3 | cache 4 | coverage 5 | node_modules 6 | typechain-types -------------------------------------------------------------------------------- /examples/swap/.eslintrc.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | /** 4 | * @type {import("eslint").Linter.Config} 5 | */ 6 | module.exports = { 7 | env: { 8 | browser: false, 9 | es2021: true, 10 | mocha: true, 11 | node: true, 12 | }, 13 | extends: ["plugin:prettier/recommended"], 14 | parser: "@typescript-eslint/parser", 15 | parserOptions: { 16 | ecmaVersion: 12, 17 | }, 18 | plugins: [ 19 | "@typescript-eslint", 20 | "prettier", 21 | "simple-import-sort", 22 | "sort-keys-fix", 23 | "typescript-sort-keys", 24 | ], 25 | rules: { 26 | "@typescript-eslint/sort-type-union-intersection-members": "error", 27 | camelcase: "off", 28 | "simple-import-sort/exports": "error", 29 | "simple-import-sort/imports": "error", 30 | "sort-keys-fix/sort-keys-fix": "error", 31 | "typescript-sort-keys/interface": "error", 32 | "typescript-sort-keys/string-enum": "error", 33 | }, 34 | settings: { 35 | "import/parsers": { 36 | "@typescript-eslint/parser": [".js", ".jsx", ".ts", ".tsx", ".d.ts"], 37 | }, 38 | "import/resolver": { 39 | node: { 40 | extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"], 41 | }, 42 | typescript: { 43 | project: path.join(__dirname, "tsconfig.json"), 44 | }, 45 | }, 46 | }, 47 | }; 48 | -------------------------------------------------------------------------------- /examples/swap/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | dependencies 8 | 9 | # Hardhat files 10 | cache 11 | artifacts 12 | 13 | # Foundry files 14 | out 15 | cache_forge 16 | 17 | access_token 18 | 19 | localnet.json 20 | 21 | test-ledger 22 | .openzeppelin 23 | -------------------------------------------------------------------------------- /examples/swap/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 ZetaChain 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 | -------------------------------------------------------------------------------- /examples/swap/README.md: -------------------------------------------------------------------------------- 1 | # Universal Swap 2 | 3 | Tutorial: https://www.zetachain.com/docs/developers/tutorials/swap 4 | -------------------------------------------------------------------------------- /examples/swap/contracts/Swap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.26; 3 | 4 | import {SystemContract, IZRC20} from "@zetachain/toolkit/contracts/SystemContract.sol"; 5 | import {SwapHelperLib} from "@zetachain/toolkit/contracts/SwapHelperLib.sol"; 6 | import {BytesHelperLib} from "@zetachain/toolkit/contracts/BytesHelperLib.sol"; 7 | import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; 8 | 9 | import {RevertContext, RevertOptions} from "@zetachain/protocol-contracts/contracts/Revert.sol"; 10 | import "@zetachain/protocol-contracts/contracts/zevm/interfaces/UniversalContract.sol"; 11 | import "@zetachain/protocol-contracts/contracts/zevm/interfaces/IGatewayZEVM.sol"; 12 | import "@zetachain/protocol-contracts/contracts/zevm/interfaces/IWZETA.sol"; 13 | import {GatewayZEVM} from "@zetachain/protocol-contracts/contracts/zevm/GatewayZEVM.sol"; 14 | 15 | import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 16 | import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 17 | import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; 18 | 19 | contract Swap is 20 | UniversalContract, 21 | Initializable, 22 | UUPSUpgradeable, 23 | OwnableUpgradeable 24 | { 25 | address public uniswapRouter; 26 | GatewayZEVM public gateway; 27 | uint256 constant BITCOIN = 8332; 28 | uint256 constant BITCOIN_TESTNET = 18334; 29 | uint256 public gasLimit; 30 | 31 | error InvalidAddress(); 32 | error Unauthorized(); 33 | error ApprovalFailed(); 34 | error TransferFailed(string); 35 | error InsufficientAmount(string); 36 | error InvalidMessageLength(); 37 | 38 | event TokenSwap( 39 | bytes sender, 40 | bytes indexed recipient, 41 | address indexed inputToken, 42 | address indexed targetToken, 43 | uint256 inputAmount, 44 | uint256 outputAmount 45 | ); 46 | 47 | modifier onlyGateway() { 48 | if (msg.sender != address(gateway)) revert Unauthorized(); 49 | _; 50 | } 51 | 52 | /// @custom:oz-upgrades-unsafe-allow constructor 53 | constructor() { 54 | _disableInitializers(); 55 | } 56 | 57 | function initialize( 58 | address payable gatewayAddress, 59 | address uniswapRouterAddress, 60 | uint256 gasLimitAmount, 61 | address owner 62 | ) external initializer { 63 | if (gatewayAddress == address(0) || uniswapRouterAddress == address(0)) 64 | revert InvalidAddress(); 65 | __UUPSUpgradeable_init(); 66 | __Ownable_init(owner); 67 | uniswapRouter = uniswapRouterAddress; 68 | gateway = GatewayZEVM(gatewayAddress); 69 | gasLimit = gasLimitAmount; 70 | } 71 | 72 | struct Params { 73 | address target; 74 | bytes to; 75 | bool withdraw; 76 | } 77 | 78 | /** 79 | * @notice Swap tokens from a connected chain to another connected chain or ZetaChain 80 | */ 81 | function onCall( 82 | MessageContext calldata context, 83 | address zrc20, 84 | uint256 amount, 85 | bytes calldata message 86 | ) external onlyGateway { 87 | Params memory params = Params({ 88 | target: address(0), 89 | to: bytes(""), 90 | withdraw: true 91 | }); 92 | 93 | if (context.chainID == BITCOIN_TESTNET || context.chainID == BITCOIN) { 94 | if (message.length < 41) revert InvalidMessageLength(); 95 | params.target = BytesHelperLib.bytesToAddress(message, 0); 96 | params.to = new bytes(message.length - 21); 97 | for (uint256 i = 0; i < message.length - 21; i++) { 98 | params.to[i] = message[20 + i]; 99 | } 100 | params.withdraw = BytesHelperLib.bytesToBool( 101 | message, 102 | message.length - 1 103 | ); 104 | } else { 105 | ( 106 | address targetToken, 107 | bytes memory recipient, 108 | bool withdrawFlag 109 | ) = abi.decode(message, (address, bytes, bool)); 110 | params.target = targetToken; 111 | params.to = recipient; 112 | params.withdraw = withdrawFlag; 113 | } 114 | 115 | (uint256 out, address gasZRC20, uint256 gasFee) = handleGasAndSwap( 116 | zrc20, 117 | amount, 118 | params.target, 119 | params.withdraw 120 | ); 121 | emit TokenSwap( 122 | context.sender, 123 | params.to, 124 | zrc20, 125 | params.target, 126 | amount, 127 | out 128 | ); 129 | withdraw(params, context.sender, gasFee, gasZRC20, out, zrc20); 130 | } 131 | 132 | /** 133 | * @notice Swap tokens from ZetaChain optionally withdrawing to a connected chain 134 | */ 135 | function swap( 136 | address inputToken, 137 | uint256 amount, 138 | address targetToken, 139 | bytes memory recipient, 140 | bool withdrawFlag 141 | ) external{ 142 | bool success = IZRC20(inputToken).transferFrom( 143 | msg.sender, 144 | address(this), 145 | amount 146 | ); 147 | if (!success) { 148 | revert TransferFailed( 149 | "Failed to transfer ZRC-20 tokens from the sender to the contract" 150 | ); 151 | } 152 | 153 | (uint256 out, address gasZRC20, uint256 gasFee) = handleGasAndSwap( 154 | inputToken, 155 | amount, 156 | targetToken, 157 | withdrawFlag 158 | ); 159 | emit TokenSwap( 160 | abi.encodePacked(msg.sender), 161 | recipient, 162 | inputToken, 163 | targetToken, 164 | amount, 165 | out 166 | ); 167 | withdraw( 168 | Params({ 169 | target: targetToken, 170 | to: recipient, 171 | withdraw: withdrawFlag 172 | }), 173 | abi.encodePacked(msg.sender), 174 | gasFee, 175 | gasZRC20, 176 | out, 177 | inputToken 178 | ); 179 | } 180 | 181 | /** 182 | * @notice Swaps enough tokens to pay gas fees, then swaps the remainder to the target token 183 | */ 184 | function handleGasAndSwap( 185 | address inputToken, 186 | uint256 amount, 187 | address targetToken, 188 | bool withdraw 189 | ) internal returns (uint256, address, uint256) { 190 | uint256 inputForGas; 191 | address gasZRC20; 192 | uint256 gasFee = 0; 193 | uint256 swapAmount = amount; 194 | 195 | if (withdraw) { 196 | (gasZRC20, gasFee) = IZRC20(targetToken).withdrawGasFee(); 197 | uint256 minInput = quoteMinInput(inputToken, targetToken); 198 | if (amount < minInput) { 199 | revert InsufficientAmount( 200 | "The input amount is less than the min amount required to cover the withdraw gas fee" 201 | ); 202 | } 203 | if (gasZRC20 == inputToken) { 204 | swapAmount = amount - gasFee; 205 | } else { 206 | inputForGas = SwapHelperLib.swapTokensForExactTokens( 207 | uniswapRouter, 208 | inputToken, 209 | gasFee, 210 | gasZRC20, 211 | amount 212 | ); 213 | swapAmount = amount - inputForGas; 214 | } 215 | } 216 | 217 | uint256 out = SwapHelperLib.swapExactTokensForTokens( 218 | uniswapRouter, 219 | inputToken, 220 | swapAmount, 221 | targetToken, 222 | 0 223 | ); 224 | return (out, gasZRC20, gasFee); 225 | } 226 | 227 | /** 228 | * @notice Transfer tokens to the recipient on ZetaChain or withdraw to a connected chain 229 | */ 230 | function withdraw( 231 | Params memory params, 232 | bytes memory sender, 233 | uint256 gasFee, 234 | address gasZRC20, 235 | uint256 out, 236 | address inputToken 237 | ) internal { 238 | if (params.withdraw) { 239 | if (gasZRC20 == params.target) { 240 | if (!IZRC20(gasZRC20).approve(address(gateway), out + gasFee)) { 241 | revert ApprovalFailed(); 242 | } 243 | } else { 244 | if (!IZRC20(gasZRC20).approve(address(gateway), gasFee)) { 245 | revert ApprovalFailed(); 246 | } 247 | if (!IZRC20(params.target).approve(address(gateway), out)) { 248 | revert ApprovalFailed(); 249 | } 250 | } 251 | gateway.withdraw( 252 | abi.encodePacked(params.to), 253 | out, 254 | params.target, 255 | RevertOptions({ 256 | revertAddress: address(this), 257 | callOnRevert: true, 258 | abortAddress: address(0), 259 | revertMessage: abi.encode(sender, inputToken), 260 | onRevertGasLimit: gasLimit 261 | }) 262 | ); 263 | } else { 264 | bool success = IWETH9(params.target).transfer( 265 | address(uint160(bytes20(params.to))), 266 | out 267 | ); 268 | if (!success) { 269 | revert TransferFailed( 270 | "Failed to transfer target tokens to the recipient on ZetaChain" 271 | ); 272 | } 273 | } 274 | } 275 | 276 | /** 277 | * @notice onRevert handles an edge-case when a swap fails when the recipient 278 | * on the destination chain is a contract that cannot accept tokens. 279 | */ 280 | function onRevert(RevertContext calldata context) external onlyGateway { 281 | (bytes memory sender, address zrc20) = abi.decode( 282 | context.revertMessage, 283 | (bytes, address) 284 | ); 285 | (uint256 out, , ) = handleGasAndSwap( 286 | context.asset, 287 | context.amount, 288 | zrc20, 289 | true 290 | ); 291 | 292 | gateway.withdraw( 293 | sender, 294 | out, 295 | zrc20, 296 | RevertOptions({ 297 | revertAddress: address(bytes20(sender)), 298 | callOnRevert: false, 299 | abortAddress: address(0), 300 | revertMessage: "", 301 | onRevertGasLimit: gasLimit 302 | }) 303 | ); 304 | } 305 | 306 | /** 307 | * @notice Returns the minimum amount of input tokens required to cover the gas fee for withdrawal 308 | */ 309 | function quoteMinInput( 310 | address inputToken, 311 | address targetToken 312 | ) public view returns (uint256) { 313 | (address gasZRC20, uint256 gasFee) = IZRC20(targetToken) 314 | .withdrawGasFee(); 315 | 316 | if (inputToken == gasZRC20) { 317 | return gasFee; 318 | } 319 | 320 | address zeta = IUniswapV2Router01(uniswapRouter).WETH(); 321 | 322 | address[] memory path; 323 | if (inputToken == zeta || gasZRC20 == zeta) { 324 | path = new address[](2); 325 | path[0] = inputToken; 326 | path[1] = gasZRC20; 327 | } else { 328 | path = new address[](3); 329 | path[0] = inputToken; 330 | path[1] = zeta; 331 | path[2] = gasZRC20; 332 | } 333 | 334 | uint256[] memory amountsIn = IUniswapV2Router02(uniswapRouter) 335 | .getAmountsIn(gasFee, path); 336 | 337 | return amountsIn[0]; 338 | } 339 | 340 | function _authorizeUpgrade( 341 | address newImplementation 342 | ) internal override onlyOwner {} 343 | } 344 | -------------------------------------------------------------------------------- /examples/swap/contracts/SwapCompanion.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.26; 3 | 4 | import "@zetachain/protocol-contracts/contracts/evm/GatewayEVM.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 6 | 7 | contract SwapCompanion { 8 | using SafeERC20 for IERC20; 9 | 10 | error ApprovalFailed(); 11 | 12 | GatewayEVM public immutable gateway; 13 | 14 | constructor(address payable gatewayAddress) { 15 | gateway = GatewayEVM(gatewayAddress); 16 | } 17 | 18 | function swapNativeGas( 19 | address universalSwapContract, 20 | address targetToken, 21 | bytes memory recipient, 22 | bool withdraw 23 | ) public payable { 24 | gateway.depositAndCall{value: msg.value}( 25 | universalSwapContract, 26 | abi.encode(targetToken, recipient, withdraw), 27 | RevertOptions(msg.sender, false, address(0), "", 0) 28 | ); 29 | } 30 | 31 | function swapERC20( 32 | address universalSwapContract, 33 | address targetToken, 34 | bytes memory recipient, 35 | uint256 amount, 36 | address asset, 37 | bool withdraw 38 | ) public { 39 | IERC20(asset).safeTransferFrom(msg.sender, address(this), amount); 40 | if (!IERC20(asset).approve(address(gateway), amount)) { 41 | revert ApprovalFailed(); 42 | } 43 | gateway.depositAndCall( 44 | universalSwapContract, 45 | amount, 46 | asset, 47 | abi.encode(targetToken, recipient, withdraw), 48 | RevertOptions(msg.sender, false, address(0), "", 0) 49 | ); 50 | } 51 | 52 | receive() external payable {} 53 | 54 | fallback() external payable {} 55 | } 56 | -------------------------------------------------------------------------------- /examples/swap/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'contracts' 3 | out = 'out' 4 | viaIR = true 5 | libs = ['node_modules', 'lib'] 6 | test = 'test' 7 | cache_path = 'cache_forge' 8 | verbosity = 3 9 | 10 | [dependencies] 11 | forge-std = { version = "1.9.2" } 12 | -------------------------------------------------------------------------------- /examples/swap/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import "@nomicfoundation/hardhat-toolbox"; 2 | import { HardhatUserConfig } from "hardhat/config"; 3 | import * as dotenv from "dotenv"; 4 | 5 | import "./tasks"; 6 | import "@zetachain/localnet/tasks"; 7 | import "@zetachain/toolkit/tasks"; 8 | import { getHardhatConfig } from "@zetachain/toolkit/client"; 9 | 10 | import "@openzeppelin/hardhat-upgrades"; 11 | 12 | dotenv.config(); 13 | 14 | const config: HardhatUserConfig = { 15 | ...getHardhatConfig({ accounts: [process.env.PRIVATE_KEY] }), 16 | solidity: { 17 | compilers: [{ version: "0.8.20" }, { version: "0.8.26" }], 18 | }, 19 | }; 20 | 21 | export default config; 22 | -------------------------------------------------------------------------------- /examples/swap/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swap", 3 | "version": "1.0.0", 4 | "description": "Cross-chain swap contract enabling token exchanges between Ethereum, Solana, Bitcoin, and other blockchains.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "lint:fix": "npx eslint . --ext .js,.ts --fix", 9 | "lint": "npx eslint . --ext .js,.ts", 10 | "deploy:localnet": "npx hardhat compile --force && npx hardhat deploy --network localhost --gateway 0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@ethersproject/abi": "^5.4.7", 17 | "@ethersproject/providers": "^5.4.7", 18 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.0", 19 | "@nomicfoundation/hardhat-foundry": "^1.1.2", 20 | "@nomicfoundation/hardhat-network-helpers": "^1.0.0", 21 | "@nomicfoundation/hardhat-toolbox": "^2.0.0", 22 | "@nomiclabs/hardhat-ethers": "^2.0.0", 23 | "@nomiclabs/hardhat-etherscan": "^3.0.0", 24 | "@openzeppelin/hardhat-upgrades": "1.28.0", 25 | "@typechain/ethers-v5": "^10.1.0", 26 | "@typechain/hardhat": "^6.1.2", 27 | "@types/chai": "^4.2.0", 28 | "@types/mocha": ">=9.1.0", 29 | "@types/node": ">=12.0.0", 30 | "@typescript-eslint/eslint-plugin": "^5.59.9", 31 | "@typescript-eslint/parser": "^5.59.9", 32 | "@zetachain/localnet": "7.1.0", 33 | "axios": "^1.3.6", 34 | "chai": "^4.2.0", 35 | "dotenv": "^16.0.3", 36 | "envfile": "^6.18.0", 37 | "eslint": "^8.42.0", 38 | "eslint-config-prettier": "^8.8.0", 39 | "eslint-import-resolver-typescript": "^3.5.5", 40 | "eslint-plugin-import": "^2.27.5", 41 | "eslint-plugin-prettier": "^4.2.1", 42 | "eslint-plugin-simple-import-sort": "^10.0.0", 43 | "eslint-plugin-sort-keys-fix": "^1.1.2", 44 | "eslint-plugin-typescript-sort-keys": "^2.3.0", 45 | "ethers": "^5.4.7", 46 | "hardhat": "^2.17.2", 47 | "hardhat-gas-reporter": "^1.0.8", 48 | "prettier": "^2.8.8", 49 | "solidity-coverage": "^0.8.0", 50 | "ts-node": ">=8.0.0", 51 | "typechain": "^8.1.0", 52 | "typescript": ">=4.5.0" 53 | }, 54 | "packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72", 55 | "dependencies": { 56 | "@coral-xyz/anchor": "0.30.0", 57 | "@solana-developers/helpers": "^2.4.0", 58 | "@solana/spl-memo": "^0.2.5", 59 | "@solana/web3.js": "^1.95.8", 60 | "@zetachain/protocol-contracts": "13.0.0", 61 | "@zetachain/toolkit": "13.0.0-rc17", 62 | "zetachain": "^3.0.0" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /examples/swap/scripts/localnet.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | set -o pipefail 6 | 7 | yarn zetachain localnet start --skip sui ton solana --exit-on-error & 8 | 9 | while [ ! -f "localnet.json" ]; do sleep 1; done 10 | 11 | npx hardhat compile --force --quiet 12 | 13 | ZRC20_ETHEREUM=$(jq -r '.addresses[] | select(.type=="ZRC-20 ETH on 5") | .address' localnet.json) 14 | USDC_ETHEREUM=$(jq -r '.addresses[] | select(.type=="ERC-20 USDC" and .chain=="ethereum") | .address' localnet.json) 15 | ZRC20_USDC=$(jq -r '.addresses[] | select(.type=="ZRC-20 USDC on 97") | .address' localnet.json) 16 | ZRC20_BNB=$(jq -r '.addresses[] | select(.type=="ZRC-20 BNB on 97") | .address' localnet.json) 17 | # ZRC20_SOL=$(jq -r '.addresses[] | select(.type=="ZRC-20 SOL on 901") | .address' localnet.json) 18 | WZETA=$(jq -r '.addresses[] | select(.type=="wzeta" and .chain=="zetachain") | .address' localnet.json) 19 | GATEWAY_ETHEREUM=$(jq -r '.addresses[] | select(.type=="gatewayEVM" and .chain=="ethereum") | .address' localnet.json) 20 | GATEWAY_ZETACHAIN=$(jq -r '.addresses[] | select(.type=="gatewayZEVM" and .chain=="zetachain") | .address' localnet.json) 21 | UNISWAP_ROUTER=$(jq -r '.addresses[] | select(.type=="uniswapRouterInstance" and .chain=="zetachain") | .address' localnet.json) 22 | SENDER=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 23 | DEFAULT_MNEMONIC="grape subway rack mean march bubble carry avoid muffin consider thing street" 24 | 25 | CONTRACT_SWAP=$(npx hardhat deploy --name Swap --network localhost --gateway "$GATEWAY_ZETACHAIN" --uniswap-router "$UNISWAP_ROUTER" | jq -r '.contractAddress') 26 | COMPANION=$(npx hardhat deploy-companion --gateway "$GATEWAY_ETHEREUM" --network localhost --json | jq -r '.contractAddress') 27 | 28 | npx hardhat evm-swap \ 29 | --network localhost \ 30 | --receiver "$CONTRACT_SWAP" \ 31 | --amount 0.1 \ 32 | --target "$WZETA" \ 33 | --gateway-evm "$GATEWAY_ETHEREUM" \ 34 | --skip-checks \ 35 | --withdraw false \ 36 | --recipient "$SENDER" 37 | 38 | yarn zetachain localnet check 39 | 40 | npx hardhat evm-swap \ 41 | --network localhost \ 42 | --receiver "$CONTRACT_SWAP" \ 43 | --amount 0.1 \ 44 | --gateway-evm "$GATEWAY_ETHEREUM" \ 45 | --target "$ZRC20_USDC" \ 46 | --skip-checks \ 47 | --recipient "$SENDER" 48 | 49 | yarn zetachain localnet check 50 | 51 | npx hardhat evm-swap \ 52 | --network localhost \ 53 | --receiver "$CONTRACT_SWAP" \ 54 | --amount 0.1 \ 55 | --target "$ZRC20_BNB" \ 56 | --gateway-evm "$GATEWAY_ETHEREUM" \ 57 | --skip-checks \ 58 | --erc20 "$USDC_ETHEREUM" \ 59 | --recipient "$SENDER" 60 | 61 | yarn zetachain localnet check 62 | 63 | npx hardhat evm-swap \ 64 | --skip-checks \ 65 | --network localhost \ 66 | --receiver "$CONTRACT_SWAP" \ 67 | --amount 0.1 \ 68 | --gateway-evm "$GATEWAY_ETHEREUM" \ 69 | --target "$ZRC20_BNB" \ 70 | --recipient "$SENDER" 71 | 72 | yarn zetachain localnet check 73 | 74 | npx hardhat evm-swap \ 75 | --skip-checks \ 76 | --network localhost \ 77 | --receiver "$CONTRACT_SWAP" \ 78 | --amount 0.1 \ 79 | --target "$ZRC20_BNB" \ 80 | --gateway-evm "$GATEWAY_ETHEREUM" \ 81 | --recipient "$SENDER" \ 82 | --withdraw false 83 | 84 | yarn zetachain localnet check 85 | 86 | # npx hardhat companion-swap \ 87 | # --skip-checks \ 88 | # --network localhost \ 89 | # --contract "$COMPANION" \ 90 | # --universal-contract "$CONTRACT_SWAP" \ 91 | # --amount 0.1 \ 92 | # --target "$ZRC20_SOL" \ 93 | # --recipient "8Sw9oNHHyEyAfQHC41QeFBRMhxG6HmFjNQnSbRvsXGb2" 94 | 95 | # yarn zetachain localnet check 96 | 97 | # npx hardhat localnet:solana-deposit-and-call \ 98 | # --receiver "$CONTRACT_SWAP" \ 99 | # --amount 0.1 \ 100 | # --types '["address", "bytes", "bool"]' "$ZRC20_ETHEREUM" "$SENDER" true 101 | 102 | # yarn zetachain localnet check 103 | 104 | npx hardhat companion-swap \ 105 | --network localhost \ 106 | --skip-checks \ 107 | --contract "$COMPANION" \ 108 | --universal-contract "$CONTRACT_SWAP" \ 109 | --amount 0.1 \ 110 | --target "$ZRC20_BNB" \ 111 | --recipient "$SENDER" 112 | 113 | yarn zetachain localnet check 114 | 115 | npx hardhat companion-swap \ 116 | --skip-checks \ 117 | --network localhost \ 118 | --contract "$COMPANION" \ 119 | --universal-contract "$CONTRACT_SWAP" \ 120 | --amount 0.1 \ 121 | --erc20 "$USDC_ETHEREUM" \ 122 | --target "$ZRC20_BNB" \ 123 | --recipient "$SENDER" 124 | 125 | yarn zetachain localnet check 126 | 127 | npx hardhat zetachain-swap \ 128 | --network localhost \ 129 | --contract "$CONTRACT_SWAP" \ 130 | --amount 0.1 \ 131 | --zrc20 "$ZRC20_BNB" \ 132 | --target "$ZRC20_ETHEREUM" \ 133 | --recipient "$SENDER" 134 | 135 | yarn zetachain localnet check 136 | 137 | # SUI deposit to ZetaChain 138 | # npx hardhat localnet:sui-deposit \ 139 | # --mnemonic "grape subway rack mean march bubble carry avoid muffin consider thing street" \ 140 | # --gateway 0x8d6363911564aa624ca1600d3bd0e094b33d5a97fb7f825092480dbf0f4a01ba \ 141 | # --module 0x28737b339892206e07689dc9abb99d7eeb1ade916c400b5853e3a56cce27987b \ 142 | # --receiver 0x0355B7B8cb128fA5692729Ab3AAa199C1753f726 \ 143 | # --amount 100000000 144 | 145 | # SUI to SOL 146 | # npx hardhat localnet:sui-deposit-and-call \ 147 | # --mnemonic "grape subway rack mean march bubble carry avoid muffin consider thing street" \ 148 | # --gateway 0x8d6363911564aa624ca1600d3bd0e094b33d5a97fb7f825092480dbf0f4a01ba \ 149 | # --module 0x28737b339892206e07689dc9abb99d7eeb1ade916c400b5853e3a56cce27987b \ 150 | # --receiver 0x0355B7B8cb128fA5692729Ab3AAa199C1753f726 \ 151 | # --amount 100000000 \ 152 | # --types '["address", "bytes", "bool"]' 0x777915D031d1e8144c90D025C594b3b8Bf07a08d 8Sw9oNHHyEyAfQHC41QeFBRMhxG6HmFjNQnSbRvsXGb2 true 153 | 154 | # SOL to SUI 155 | # npx hardhat localnet:solana-deposit-and-call \ 156 | # --receiver 0x0355B7B8cb128fA5692729Ab3AAa199C1753f726 \ 157 | # --amount 0.1 \ 158 | # --types '["address", "bytes", "bool"]' 0xe573a6e11f8506620F123DBF930222163D46BCB6 0x2fec3fafe08d2928a6b8d9a6a77590856c458d984ae090ccbd4177ac13729e65 true 159 | 160 | # SPL to SUI 161 | # npx hardhat localnet:solana-deposit-and-call \ 162 | # --receiver 0x0355B7B8cb128fA5692729Ab3AAa199C1753f726 \ 163 | # --mint HgpR36oSMi8SmQauUpvcE9kpfHXLn6PMKrYFtNjPAafU \ 164 | # --to 4wehnswdQJFnsxiZ9pt5RU9mPy4Yqvgn86XPgXeHiszn \ 165 | # --from 6DmpL65bceSPQvXbKqoh8qEiz1EeBHmTP1i5B87rgVw7 \ 166 | # --amount 0.1 \ 167 | # --types '["address", "bytes", "bool"]' 0xe573a6e11f8506620F123DBF930222163D46BCB6 0x2fec3fafe08d2928a6b8d9a6a77590856c458d984ae090ccbd4177ac13729e65 true 168 | 169 | # TESTING REVERTS 170 | 171 | # npx hardhat companion-swap \ 172 | # --network localhost \ 173 | # --contract "$COMPANION" \ 174 | # --universal-contract 0x0000000000000000000000000000000000000001 \ 175 | # --amount 1 \ 176 | # --target "$ZRC20_SOL" \ 177 | # --recipient "8Sw9oNHHyEyAfQHC41QeFBRMhxG6HmFjNQnSbRvsXGb2" 178 | 179 | # yarn zetachain localnet check 180 | 181 | # npx hardhat localnet:solana-deposit-and-call \ 182 | # --receiver 0x0000000000000000000000000000000000000001 \ 183 | # --amount 1 \ 184 | # --types '["address", "bytes", "bool"]' 0x0000000000000000000000000000000000000001 0x0000000000000000000000000000000000000001 true 185 | 186 | # yarn zetachain localnet check 187 | 188 | # npx hardhat localnet:solana-deposit \ 189 | # --receiver "$CONTRACT_SWAP" \ 190 | # --amount 1 191 | 192 | # yarn zetachain localnet check 193 | 194 | yarn zetachain localnet stop -------------------------------------------------------------------------------- /examples/swap/tasks/companionSwap.ts: -------------------------------------------------------------------------------- 1 | import ERC20_ABI from "@openzeppelin/contracts/build/contracts/ERC20.json"; 2 | import { task, types } from "hardhat/config"; 3 | import type { HardhatRuntimeEnvironment } from "hardhat/types"; 4 | import { isInputAmountSufficient } from "./evmSwap"; 5 | import { ZetaChainClient } from "@zetachain/toolkit/client"; 6 | 7 | const main = async (args: any, hre: HardhatRuntimeEnvironment) => { 8 | const { ethers } = hre; 9 | const [signer] = await ethers.getSigners(); 10 | 11 | const factory = (await hre.ethers.getContractFactory("SwapCompanion")) as any; 12 | const contract = factory.attach(args.contract).connect(signer); 13 | const recipient = hre.ethers.utils.isAddress(args.recipient) 14 | ? args.recipient 15 | : hre.ethers.utils.toUtf8Bytes(args.recipient); 16 | 17 | const client = new ZetaChainClient({ network: "testnet", signer }); 18 | 19 | if (!args.skipChecks) { 20 | await isInputAmountSufficient({ 21 | hre, 22 | client, 23 | amount: args.amount, 24 | erc20: args.erc20, 25 | target: args.target, 26 | }); 27 | } 28 | 29 | let tx; 30 | if (args.erc20) { 31 | const erc20Contract = new ethers.Contract( 32 | args.erc20, 33 | ERC20_ABI.abi, 34 | signer 35 | ); 36 | const decimals = await erc20Contract.decimals(); 37 | const value = hre.ethers.utils.parseUnits(args.amount, decimals); 38 | const approveTx = await erc20Contract 39 | .connect(signer) 40 | .approve(args.contract, value); 41 | await approveTx.wait(); 42 | 43 | tx = await contract.swapERC20( 44 | args.universalContract, 45 | args.target, 46 | recipient, 47 | value, 48 | args.erc20, 49 | args.withdraw 50 | ); 51 | } else { 52 | const value = hre.ethers.utils.parseEther(args.amount); 53 | tx = await contract.swapNativeGas( 54 | args.universalContract, 55 | args.target, 56 | recipient, 57 | args.withdraw, 58 | { value } 59 | ); 60 | } 61 | 62 | await tx.wait(); 63 | console.log(`Transaction hash: ${tx.hash}`); 64 | }; 65 | 66 | task("companion-swap", "Swap native gas tokens", main) 67 | .addParam("contract", "Contract address") 68 | .addParam("universalContract", "Universal contract address") 69 | .addOptionalParam( 70 | "withdraw", 71 | "Withdraw to destination or keep token on ZetaChain", 72 | true, 73 | types.boolean 74 | ) 75 | .addOptionalParam("erc20", "ERC-20 token address") 76 | .addParam("target", "ZRC-20 address of the token to swap for") 77 | .addParam("amount", "Amount of tokens to swap") 78 | .addFlag("skipChecks", "Skip checks for minimum amount") 79 | .addParam("recipient", "Recipient address"); 80 | -------------------------------------------------------------------------------- /examples/swap/tasks/deploy.ts: -------------------------------------------------------------------------------- 1 | import { task, types } from "hardhat/config"; 2 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 3 | 4 | const main = async (args: any, hre: HardhatRuntimeEnvironment) => { 5 | const network = hre.network.name as any; 6 | 7 | const [signer] = await hre.ethers.getSigners(); 8 | if (signer === undefined) { 9 | throw new Error( 10 | `Wallet not found. Please, run "npx hardhat account --save" or set PRIVATE_KEY env variable (for example, in a .env file)` 11 | ); 12 | } 13 | 14 | const factory = await hre.ethers.getContractFactory(args.name); 15 | 16 | const contract = await hre.upgrades.deployProxy( 17 | factory as any, 18 | [args.gateway, args.uniswapRouter, args.gasLimit, signer.address], 19 | { kind: "uups" } 20 | ); 21 | 22 | console.log( 23 | JSON.stringify({ 24 | contractAddress: contract.address, 25 | deployer: signer.address, 26 | network: network, 27 | }) 28 | ); 29 | }; 30 | 31 | task("deploy", "Deploy the contract", main) 32 | .addOptionalParam("name", "Contract to deploy", "Swap") 33 | .addOptionalParam("uniswapRouter", "Uniswap v2 Router address") 34 | .addOptionalParam( 35 | "gateway", 36 | "Gateway address (default: ZetaChain Gateway)", 37 | "0x6c533f7fe93fae114d0954697069df33c9b74fd7" 38 | ) 39 | .addOptionalParam( 40 | "gasLimit", 41 | "Gas limit for the transaction", 42 | 1000000, 43 | types.int 44 | ); 45 | -------------------------------------------------------------------------------- /examples/swap/tasks/deployCompanion.ts: -------------------------------------------------------------------------------- 1 | import { task, types } from "hardhat/config"; 2 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 3 | 4 | const main = async (args: any, hre: HardhatRuntimeEnvironment) => { 5 | const network = hre.network.name; 6 | 7 | const [signer] = await hre.ethers.getSigners(); 8 | if (signer === undefined) { 9 | throw new Error( 10 | `Wallet not found. Please, run "npx hardhat account --save" or set PRIVATE_KEY env variable (for example, in a .env file)` 11 | ); 12 | } 13 | 14 | const factory = await hre.ethers.getContractFactory(args.name); 15 | const contract = await (factory as any).deploy(args.gateway); 16 | await contract.deployed(); 17 | 18 | if (args.json) { 19 | console.log( 20 | JSON.stringify({ 21 | contractAddress: contract.address, 22 | deployer: signer.address, 23 | network: network, 24 | transactionHash: contract.deployTransaction.hash, 25 | }) 26 | ); 27 | } else { 28 | console.log(`🔑 Using account: ${signer.address} 29 | 30 | 🚀 Successfully deployed "${args.name}" contract on ${network}. 31 | 📜 Contract address: ${contract.address} 32 | `); 33 | } 34 | }; 35 | 36 | task("deploy-companion", "Deploy the companion contract", main) 37 | .addFlag("json", "Output in JSON") 38 | .addOptionalParam("name", "Contract to deploy", "SwapCompanion") 39 | .addOptionalParam( 40 | "gateway", 41 | "Gateway address (default: EVM Gateway on testnet)", 42 | "0x0c487a766110c85d301d96e33579c5b317fa4995" 43 | ); 44 | -------------------------------------------------------------------------------- /examples/swap/tasks/evmSwap.ts: -------------------------------------------------------------------------------- 1 | import { task, types } from "hardhat/config"; 2 | import type { HardhatRuntimeEnvironment } from "hardhat/types"; 3 | 4 | import { ZetaChainClient } from "@zetachain/toolkit/client"; 5 | 6 | export const isInputAmountSufficient = async ({ 7 | hre, 8 | client, 9 | erc20, 10 | amount, 11 | target, 12 | }: any) => { 13 | const inputZRC20 = await (erc20 14 | ? client.getZRC20FromERC20(erc20) 15 | : client.getZRC20GasToken(hre.network.name)); 16 | 17 | const minAmount = await client.getWithdrawFeeInInputToken(inputZRC20, target); 18 | 19 | const minAmountFormatted = hre.ethers.utils.formatUnits( 20 | minAmount.amount, 21 | minAmount.decimals 22 | ); 23 | 24 | const value = hre.ethers.utils.parseUnits(amount, minAmount.decimals); 25 | 26 | if (value.lt(minAmount.amount)) { 27 | throw new Error( 28 | `Input amount ${amount} is less than minimum amount ${minAmountFormatted} required for a withdrawal to the destination chain` 29 | ); 30 | } 31 | }; 32 | 33 | export const evmDepositAndCall = async ( 34 | args: any, 35 | hre: HardhatRuntimeEnvironment 36 | ) => { 37 | try { 38 | const [signer] = await hre.ethers.getSigners(); 39 | const client = new ZetaChainClient({ network: "testnet", signer }); 40 | 41 | if (!args.skipChecks) { 42 | await isInputAmountSufficient({ 43 | hre, 44 | client, 45 | amount: args.amount, 46 | erc20: args.erc20, 47 | target: args.target, 48 | }); 49 | } 50 | 51 | const tx = await client.evmDepositAndCall({ 52 | amount: args.amount, 53 | erc20: args.erc20, 54 | gatewayEvm: args.gatewayEvm, 55 | receiver: args.receiver, 56 | revertOptions: { 57 | callOnRevert: args.callOnRevert, 58 | onRevertGasLimit: args.onRevertGasLimit, 59 | revertAddress: args.revertAddress, 60 | revertMessage: args.revertMessage, 61 | }, 62 | txOptions: { 63 | gasLimit: args.gasLimit, 64 | gasPrice: args.gasPrice, 65 | }, 66 | types: ["address", "bytes", "bool"], 67 | values: [args.target, args.recipient, JSON.stringify(args.withdraw)], 68 | }); 69 | if (tx) { 70 | const receipt = await tx.wait(); 71 | console.log("Transaction hash:", receipt.transactionHash); 72 | } 73 | } catch (e) { 74 | console.error("Transaction error:", e); 75 | } 76 | }; 77 | 78 | task("evm-swap", "Swap tokens from EVM", evmDepositAndCall) 79 | .addParam("receiver", "Receiver address on ZetaChain") 80 | .addParam("gatewayEvm", "contract address of gateway on EVM") 81 | .addFlag("callOnRevert", "Whether to call on revert") 82 | .addOptionalParam( 83 | "revertAddress", 84 | "Revert address", 85 | "0x0000000000000000000000000000000000000000", 86 | types.string 87 | ) 88 | .addOptionalParam( 89 | "gasPrice", 90 | "The gas price for the transaction", 91 | 50000000000, 92 | types.int 93 | ) 94 | .addOptionalParam( 95 | "gasLimit", 96 | "The gas limit for the transaction", 97 | 500000, 98 | types.int 99 | ) 100 | .addOptionalParam( 101 | "onRevertGasLimit", 102 | "The gas limit for the revert transaction", 103 | 50000, 104 | types.int 105 | ) 106 | .addOptionalParam("revertMessage", "Revert message", "0x") 107 | .addParam("amount", "amount of ETH to send with the transaction") 108 | .addOptionalParam("erc20", "ERC-20 token address") 109 | .addFlag("skipChecks", "Skip checks for minimum amount") 110 | .addParam("target", "ZRC-20 address of the token to swap for") 111 | .addParam("recipient", "Recipient address") 112 | .addOptionalParam( 113 | "withdraw", 114 | "Withdraw to destination or keep token on ZetaChain", 115 | true, 116 | types.boolean 117 | ); 118 | -------------------------------------------------------------------------------- /examples/swap/tasks/index.ts: -------------------------------------------------------------------------------- 1 | import "./deploy"; 2 | import "./companionSwap"; 3 | import "./deployCompanion"; 4 | import "./zetachainSwap"; 5 | import "./evmSwap"; 6 | -------------------------------------------------------------------------------- /examples/swap/tasks/zetachainSwap.ts: -------------------------------------------------------------------------------- 1 | import { task, types } from "hardhat/config"; 2 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 3 | import { parseEther } from "@ethersproject/units"; 4 | import { ethers } from "ethers"; 5 | import ZRC20 from "@zetachain/protocol-contracts/abi/ZRC20.sol/ZRC20.json"; 6 | 7 | const main = async (args: any, hre: HardhatRuntimeEnvironment) => { 8 | const [signer] = await hre.ethers.getSigners(); 9 | 10 | const factory = await hre.ethers.getContractFactory("Swap"); 11 | const contract = factory.attach(args.contract); 12 | 13 | const zrc20 = new ethers.Contract(args.zrc20, ZRC20.abi, signer); 14 | 15 | const amount = parseEther(args.amount); 16 | 17 | const approval = await zrc20.approve(args.contract, amount); 18 | await approval.wait(); 19 | 20 | const tx = await contract.swap( 21 | args.zrc20, 22 | amount, 23 | args.target, 24 | ethers.utils.arrayify(args.recipient), 25 | JSON.parse(args.withdraw) 26 | ); 27 | 28 | await tx.wait(); 29 | console.log(`Transaction hash: ${tx.hash}`); 30 | }; 31 | 32 | task("zetachain-swap", "Swap tokens from ZetaChain", main) 33 | .addFlag("json", "Output JSON") 34 | .addParam("contract", "Contract address") 35 | .addParam("amount", "Token amount to send") 36 | .addParam("zrc20", "Input token address") 37 | .addParam("target", "Target token address") 38 | .addParam("recipient", "Recipient address") 39 | .addOptionalParam( 40 | "withdraw", 41 | "Withdraw tokens to destination chain", 42 | true, 43 | types.boolean 44 | ); 45 | -------------------------------------------------------------------------------- /examples/swap/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "nodenext", 4 | "moduleResolution": "nodenext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/token/.eslintignore: -------------------------------------------------------------------------------- 1 | .yarn 2 | artifacts 3 | cache 4 | coverage 5 | node_modules 6 | typechain-types -------------------------------------------------------------------------------- /examples/token/.eslintrc.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | /** 4 | * @type {import("eslint").Linter.Config} 5 | */ 6 | module.exports = { 7 | env: { 8 | browser: false, 9 | es2021: true, 10 | mocha: true, 11 | node: true, 12 | }, 13 | extends: ["plugin:prettier/recommended"], 14 | parser: "@typescript-eslint/parser", 15 | parserOptions: { 16 | ecmaVersion: 12, 17 | }, 18 | plugins: [ 19 | "@typescript-eslint", 20 | "prettier", 21 | "simple-import-sort", 22 | "sort-keys-fix", 23 | "typescript-sort-keys", 24 | ], 25 | rules: { 26 | "@typescript-eslint/sort-type-union-intersection-members": "error", 27 | camelcase: "off", 28 | "simple-import-sort/exports": "error", 29 | "simple-import-sort/imports": "error", 30 | "sort-keys-fix/sort-keys-fix": "error", 31 | "typescript-sort-keys/interface": "error", 32 | "typescript-sort-keys/string-enum": "error", 33 | }, 34 | settings: { 35 | "import/parsers": { 36 | "@typescript-eslint/parser": [".js", ".jsx", ".ts", ".tsx", ".d.ts"], 37 | }, 38 | "import/resolver": { 39 | node: { 40 | extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"], 41 | }, 42 | typescript: { 43 | project: path.join(__dirname, "tsconfig.json"), 44 | }, 45 | }, 46 | }, 47 | }; 48 | -------------------------------------------------------------------------------- /examples/token/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | dependencies 8 | 9 | # Hardhat files 10 | cache 11 | artifacts 12 | 13 | # Foundry files 14 | out 15 | cache_forge 16 | 17 | access_token 18 | 19 | localnet.json 20 | 21 | test-ledger 22 | -------------------------------------------------------------------------------- /examples/token/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 ZetaChain 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 | -------------------------------------------------------------------------------- /examples/token/README.md: -------------------------------------------------------------------------------- 1 | # Universal Token 2 | 3 | Learn more: https://www.zetachain.com/docs/developers/standards/token 4 | -------------------------------------------------------------------------------- /examples/token/contracts/EVMUniversalToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.26; 3 | 4 | import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; 5 | import {ERC20BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol"; 6 | import {ERC20PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol"; 7 | import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 8 | import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 9 | import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; 10 | 11 | // Import the Universal Token core contract 12 | import "@zetachain/standard-contracts/contracts/token/contracts/evm/UniversalTokenCore.sol"; 13 | 14 | contract EVMUniversalToken is 15 | Initializable, 16 | ERC20Upgradeable, 17 | ERC20BurnableUpgradeable, 18 | ERC20PausableUpgradeable, 19 | OwnableUpgradeable, 20 | UUPSUpgradeable, 21 | UniversalTokenCore // Inherit the Universal Token core contract 22 | { 23 | /// @custom:oz-upgrades-unsafe-allow constructor 24 | constructor() { 25 | _disableInitializers(); 26 | } 27 | 28 | function initialize( 29 | address initialOwner, 30 | string memory name, 31 | string memory symbol, 32 | address payable gatewayAddress, // Include EVM gateway address 33 | uint256 gas // Set gas limit for universal Token transfers 34 | ) public initializer { 35 | __ERC20_init(name, symbol); 36 | __ERC20Burnable_init(); 37 | __ERC20Pausable_init(); 38 | __Ownable_init(initialOwner); 39 | __UUPSUpgradeable_init(); 40 | __UniversalTokenCore_init(gatewayAddress, address(this), gas); // Initialize the Universal Token core contract 41 | } 42 | 43 | function pause() public onlyOwner { 44 | _pause(); 45 | } 46 | 47 | function unpause() public onlyOwner { 48 | _unpause(); 49 | } 50 | 51 | function mint(address to, uint256 amount) public onlyOwner { 52 | _mint(to, amount); 53 | } 54 | 55 | function _authorizeUpgrade( 56 | address newImplementation 57 | ) internal override onlyOwner {} 58 | 59 | // The following functions are overrides required by Solidity. 60 | 61 | function _update( 62 | address from, 63 | address to, 64 | uint256 value 65 | ) internal override(ERC20Upgradeable, ERC20PausableUpgradeable) { 66 | super._update(from, to, value); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /examples/token/contracts/ZetaChainUniversalToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.26; 3 | 4 | import {RevertContext, RevertOptions} from "@zetachain/protocol-contracts/contracts/Revert.sol"; 5 | import "@zetachain/protocol-contracts/contracts/zevm/interfaces/UniversalContract.sol"; 6 | import "@zetachain/protocol-contracts/contracts/zevm/interfaces/IGatewayZEVM.sol"; 7 | import "@zetachain/protocol-contracts/contracts/zevm/GatewayZEVM.sol"; 8 | import {SwapHelperLib} from "@zetachain/toolkit/contracts/SwapHelperLib.sol"; 9 | import {ERC20BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol"; 10 | import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; 11 | import {ERC20PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol"; 12 | import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 13 | import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 14 | import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; 15 | 16 | // Import the Universal Token core contract 17 | import "@zetachain/standard-contracts/contracts/token/contracts/zetachain/UniversalTokenCore.sol"; 18 | 19 | contract ZetaChainUniversalToken is 20 | Initializable, 21 | ERC20Upgradeable, 22 | ERC20BurnableUpgradeable, 23 | ERC20PausableUpgradeable, 24 | OwnableUpgradeable, 25 | UUPSUpgradeable, 26 | UniversalTokenCore // Inherit the Universal Token core contract 27 | { 28 | /// @custom:oz-upgrades-unsafe-allow constructor 29 | constructor() { 30 | _disableInitializers(); 31 | } 32 | 33 | function initialize( 34 | address initialOwner, 35 | string memory name, 36 | string memory symbol, 37 | address payable gatewayAddress, // Include EVM gateway address 38 | uint256 gas, // Set gas limit for universal Token transfers 39 | address uniswapRouterAddress // Uniswap v2 router address for gas token swaps 40 | ) public initializer { 41 | __ERC20_init(name, symbol); 42 | __ERC20Burnable_init(); 43 | __ERC20Pausable_init(); 44 | __Ownable_init(initialOwner); 45 | __UUPSUpgradeable_init(); 46 | __UniversalTokenCore_init(gatewayAddress, gas, uniswapRouterAddress); // Initialize the Universal Token core contract 47 | } 48 | 49 | function pause() public onlyOwner { 50 | _pause(); 51 | } 52 | 53 | function unpause() public onlyOwner { 54 | _unpause(); 55 | } 56 | 57 | function mint(address to, uint256 amount) public onlyOwner { 58 | _mint(to, amount); 59 | } 60 | 61 | function _authorizeUpgrade( 62 | address newImplementation 63 | ) internal override onlyOwner {} 64 | 65 | // The following functions are overrides required by Solidity. 66 | 67 | function _update( 68 | address from, 69 | address to, 70 | uint256 value 71 | ) internal override(ERC20Upgradeable, ERC20PausableUpgradeable) { 72 | super._update(from, to, value); 73 | } 74 | 75 | receive() external payable {} 76 | } 77 | -------------------------------------------------------------------------------- /examples/token/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'contracts' 3 | out = 'out' 4 | viaIR = true 5 | libs = ['node_modules', 'lib'] 6 | test = 'test' 7 | cache_path = 'cache_forge' 8 | verbosity = 3 9 | 10 | [dependencies] 11 | forge-std = { version = "1.9.2" } 12 | -------------------------------------------------------------------------------- /examples/token/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import "@nomicfoundation/hardhat-toolbox"; 2 | import { HardhatUserConfig } from "hardhat/config"; 3 | import * as dotenv from "dotenv"; 4 | 5 | import "@zetachain/standard-contracts/tasks/token"; 6 | import "@zetachain/localnet/tasks"; 7 | import "@zetachain/toolkit/tasks"; 8 | import { getHardhatConfig } from "@zetachain/toolkit/client"; 9 | 10 | import "@nomiclabs/hardhat-ethers"; 11 | import "@openzeppelin/hardhat-upgrades"; 12 | 13 | dotenv.config(); 14 | 15 | const config: HardhatUserConfig = { 16 | ...getHardhatConfig({ accounts: [process.env.PRIVATE_KEY] }), 17 | solidity: { 18 | compilers: [ 19 | { 20 | settings: { 21 | optimizer: { 22 | enabled: true, 23 | runs: 1000, 24 | }, 25 | }, 26 | version: "0.8.26", 27 | }, 28 | ], 29 | }, 30 | }; 31 | 32 | export default config; 33 | -------------------------------------------------------------------------------- /examples/token/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "token", 3 | "version": "1.0.0", 4 | "description": "Universal Token enables ERC-20 tokens to minted and transferred between connected chains", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "lint:fix": "npx eslint . --ext .js,.ts --fix", 9 | "lint": "npx eslint . --ext .js,.ts" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "@ethersproject/abi": "^5.4.7", 16 | "@ethersproject/providers": "^5.4.7", 17 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.0", 18 | "@nomicfoundation/hardhat-foundry": "^1.1.2", 19 | "@nomicfoundation/hardhat-network-helpers": "^1.0.0", 20 | "@nomicfoundation/hardhat-toolbox": "^2.0.0", 21 | "@nomiclabs/hardhat-ethers": "^2.0.0", 22 | "@nomiclabs/hardhat-etherscan": "^3.0.0", 23 | "@typechain/ethers-v5": "^10.1.0", 24 | "@typechain/hardhat": "^6.1.2", 25 | "@types/chai": "^4.2.0", 26 | "@types/mocha": ">=9.1.0", 27 | "@types/node": ">=12.0.0", 28 | "@typescript-eslint/eslint-plugin": "^5.59.9", 29 | "@typescript-eslint/parser": "^5.59.9", 30 | "@zetachain/localnet": "7.1.0", 31 | "axios": "^1.3.6", 32 | "chai": "^4.2.0", 33 | "dotenv": "^16.0.3", 34 | "envfile": "^6.18.0", 35 | "eslint": "^8.42.0", 36 | "eslint-config-prettier": "^8.8.0", 37 | "eslint-import-resolver-typescript": "^3.5.5", 38 | "eslint-plugin-import": "^2.27.5", 39 | "eslint-plugin-prettier": "^4.2.1", 40 | "eslint-plugin-simple-import-sort": "^10.0.0", 41 | "eslint-plugin-sort-keys-fix": "^1.1.2", 42 | "eslint-plugin-typescript-sort-keys": "^2.3.0", 43 | "ethers": "^5.4.7", 44 | "hardhat": "^2.17.2", 45 | "hardhat-gas-reporter": "^1.0.8", 46 | "prettier": "^2.8.8", 47 | "solidity-coverage": "^0.8.0", 48 | "ts-node": ">=8.0.0", 49 | "typechain": "^8.1.0", 50 | "typescript": ">=4.5.0" 51 | }, 52 | "packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72", 53 | "dependencies": { 54 | "@coral-xyz/anchor": "0.30.0", 55 | "@openzeppelin/hardhat-upgrades": "1.28.0", 56 | "@solana-developers/helpers": "^2.4.0", 57 | "@solana/spl-memo": "^0.2.5", 58 | "@solana/web3.js": "^1.95.8", 59 | "@zetachain/protocol-contracts": "13.0.0", 60 | "@zetachain/standard-contracts": "2.0.0-rc5", 61 | "@zetachain/toolkit": "13.0.0-rc17", 62 | "validator": "^13.12.0", 63 | "zetachain": "3.0.0" 64 | } 65 | } -------------------------------------------------------------------------------- /examples/token/scripts/localnet.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | bash ./node_modules/@zetachain/standard-contracts/contracts/token/scripts/localnet.sh -------------------------------------------------------------------------------- /examples/token/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "nodenext", 4 | "moduleResolution": "nodenext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@zetachain/example-contracts", 3 | "version": "0.0.0-set-on-publish", 4 | "author": "ZetaChain", 5 | "license": "MIT", 6 | "files": [ 7 | "abi" 8 | ], 9 | "scripts": { 10 | "build": "sh scripts/build.sh" 11 | }, 12 | "packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72" 13 | } 14 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Example ZetaChain contracts 2 | 3 | > Please, note that code from this repository is referenced in [the 4 | > documentation](https://github.com/zeta-chain/docs). When making changes to the 5 | > code especially when moving files, make sure to update the documentation as 6 | > well. 7 | 8 | ## Overview 9 | 10 | Welcome to the ZetaChain example contracts repository! This collection of smart 11 | contract projects is written using Hardhat and Solidity, specifically for the 12 | ZetaChain blockchain. Each project showcases different functionalities and use 13 | cases that can be implemented on the ZetaChain platform. 14 | 15 | ### Universal Apps 16 | 17 | The `examples` directory contains the latest universal omnichain example 18 | contracts. A universal app is a smart contract on ZetaChain that is natively 19 | connected to other blockchains like Ethereum, BNB and Bitcoin. These contracts 20 | use the latest gateway architecture and are compatible with localnet. Learn more 21 | about [universal apps in the 22 | docs](https://www.zetachain.com/docs/developers/apps/intro/). 23 | 24 | ## Tutorials 25 | 26 | If you are new to ZetaChain and would like to learn how to use these example 27 | contracts, check out [the Tutorial section in the 28 | docs](https://www.zetachain.com/docs/developers/tutorials/hello/). These 29 | tutorials provide step-by-step instructions on setting up your development 30 | environment, deploying contracts, and interacting with the ZetaChain network. 31 | 32 | ## Contributing 33 | 34 | Contributions to this repository are welcome! If you have an example project or 35 | an improvement to an existing one, please fork the repository, create a new 36 | branch, make your changes, and submit a pull request. 37 | 38 | ## Disclaimer 39 | 40 | These example projects are provided for educational purposes only. They are not 41 | intended for use in production environments without thorough review, security 42 | audits, and customization to meet specific requirements. Use them at your own 43 | risk. 44 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -rf abi && mkdir -p abi/examples 4 | rm -rf typescript-types && mkdir typescript-types 5 | 6 | for dir in ./examples/*/; do 7 | subdir=$(echo $dir | cut -d'/' -f2) 8 | 9 | cd $dir && yarn && npx hardhat compile --force && cp -r artifacts/contracts/* ../../abi/$subdir/ && cd ../../; 10 | done 11 | 12 | find ./abi/ -name '*.dbg.json' -exec rm {} \; -------------------------------------------------------------------------------- /slither.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "detectors_to_exclude": "", 3 | "compile_force_framework": "hardhat", 4 | "hardhat_ignore_compile": true, 5 | "npx_disable": true, 6 | "foundry_ignore_compile": true, 7 | "filter_paths": "artifacts,cache,data,dist,docs,lib,node_modules,pkg,scripts,tasks,test,testing,typechain-types" 8 | } 9 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | --------------------------------------------------------------------------------