├── README.md ├── customERC20-bridge-example ├── .env.example ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── .solcover.js ├── .solhint.json ├── .vscode │ └── settings.json ├── LICENSE ├── README.md ├── contracts │ ├── ERC20BridgeNativeChain.sol │ ├── ERC20BridgeNonNativeChain.sol │ ├── base │ │ ├── PolygonBridgeBase.sol │ │ └── PolygonERC20BridgeBase.sol │ ├── interfaces │ │ └── IERC20Wrapped.sol │ ├── mocks │ │ ├── CustomERC20Mainnet.sol │ │ └── CustomERC20Wrapped.sol │ └── polygonZKEVMContracts │ │ ├── PolygonZkEVMBridge.sol │ │ ├── PolygonZkEVMGlobalExitRoot.sol │ │ ├── interfaces │ │ ├── IBasePolygonZkEVMGlobalExitRoot.sol │ │ ├── IBridgeMessageReceiver.sol │ │ ├── IPolygonZkEVMBridge.sol │ │ └── IPolygonZkEVMGlobalExitRoot.sol │ │ └── lib │ │ ├── DepositContract.sol │ │ ├── EmergencyManager.sol │ │ ├── GlobalExitRootLib.sol │ │ └── TokenWrapped.sol ├── deployment │ ├── deployERC20Bridge.js │ ├── verifyMainnetContracts.js │ └── verifyZkEVM.js ├── hardhat.config.js ├── package.json └── scripts │ ├── bridgeMockERC20.js │ └── claimMockERC20.js ├── force-batch-tool ├── .env.example ├── .eslintrc.js ├── .gitignore ├── README.md ├── forced-batch-data.example.json ├── networks.json ├── package.json └── src │ ├── generate-force-batch-data │ ├── README.md │ └── eth-transfer.js │ ├── send-force-batch.js │ └── utils.js ├── pingPongExample ├── .env.example ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── .solcover.js ├── .solhint.json ├── .vscode │ └── settings.json ├── LICENSE ├── README.md ├── contracts │ ├── PingReceiver.sol │ ├── PingSender.sol │ └── polygonZKEVMContracts │ │ ├── PolygonZkEVMBridge.sol │ │ ├── PolygonZkEVMGlobalExitRoot.sol │ │ ├── interfaces │ │ ├── IBasePolygonZkEVMGlobalExitRoot.sol │ │ ├── IBridgeMessageReceiver.sol │ │ ├── IPolygonZkEVMBridge.sol │ │ └── IPolygonZkEVMGlobalExitRoot.sol │ │ └── lib │ │ ├── DepositContract.sol │ │ ├── EmergencyManager.sol │ │ ├── GlobalExitRootLib.sol │ │ └── TokenWrapped.sol ├── deployment │ ├── deployPingPong.js │ ├── pingPong_output.json │ ├── verifyReceiver.js │ └── verifySender.js ├── hardhat.config.js ├── package.json └── scripts │ ├── bridgePing.js │ └── claimPong.js ├── rwaERC20-bridge-example ├── .env.example ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── .solcover.js ├── .solhint.json ├── .vscode │ └── settings.json ├── LICENSE ├── README.md ├── contracts │ ├── ERC20BridgeNativeChain.sol │ ├── ERC20BridgeNonNativeChain.sol │ ├── base │ │ ├── PolygonBridgeBase.sol │ │ └── PolygonERC20BridgeBase.sol │ ├── interfaces │ │ └── IERC20Wrapped.sol │ ├── mocks │ │ ├── CustomERC20Mainnet.sol │ │ └── CustomERC20Wrapped.sol │ └── polygonZKEVMContracts │ │ ├── PolygonZkEVMBridge.sol │ │ ├── PolygonZkEVMGlobalExitRoot.sol │ │ ├── interfaces │ │ ├── IBasePolygonZkEVMGlobalExitRoot.sol │ │ ├── IBridgeMessageReceiver.sol │ │ ├── IPolygonZkEVMBridge.sol │ │ └── IPolygonZkEVMGlobalExitRoot.sol │ │ └── lib │ │ ├── DepositContract.sol │ │ ├── EmergencyManager.sol │ │ ├── GlobalExitRootLib.sol │ │ └── TokenWrapped.sol ├── deployment │ ├── deployERC20Bridge.js │ ├── verifyMainnetContracts.js │ └── verifyZkEVM.js ├── hardhat.config.js ├── package.json └── scripts │ ├── bridgeMockERC20.js │ └── claimMockERC20.js └── zkevm-nft-bridge-example ├── .env.example ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── .solcover.js ├── .solhint.json ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── contracts ├── ERC721Wrapped.sol ├── ZkEVMNFTBridge.sol ├── mocks │ ├── ERC20PermitMock.sol │ └── ERC721Mock.sol └── polygonZKEVMContracts │ ├── PolygonZkEVMBridge.sol │ ├── PolygonZkEVMGlobalExitRoot.sol │ ├── interfaces │ ├── IBasePolygonZkEVMGlobalExitRoot.sol │ ├── IBridgeMessageReceiver.sol │ ├── IPolygonZkEVMBridge.sol │ └── IPolygonZkEVMGlobalExitRoot.sol │ └── lib │ ├── DepositContract.sol │ ├── EmergencyManager.sol │ ├── GlobalExitRootLib.sol │ └── TokenWrapped.sol ├── deployment ├── deployNFTBridge.js └── verifyContracts.js ├── hardhat.config.js ├── package.json ├── scripts ├── bridgeMockNFT.js ├── claimMockNFT.js ├── deployMockNFT.js └── verifyMockNFT.js └── test └── contracts └── ZkEVMNFTBridge.js /README.md: -------------------------------------------------------------------------------- 1 | ## zkevm code examples 2 | This repository has various code examples to help you get started developing on the polygon zkevm 3 | 4 | ### zkevm-nft-bridge-example 5 | - Shows how to build an nft-bridge using the messages property to share information between L1 and L2 polygon zkEVM 6 | - example contract nft-bridge 7 | - deployment contract nft-bridge 8 | - example on how to use the nft-bridge with a mock nft token 9 | 10 | ### pingPongExample 11 | - Shows how to send messages using the zkEVMBridge 12 | -------------------------------------------------------------------------------- /customERC20-bridge-example/.env.example: -------------------------------------------------------------------------------- 1 | MNEMONIC="test test test test test test test test test test test junk" 2 | INFURA_PROJECT_ID="" 3 | ETHERSCAN_API_KEY="" 4 | ETHERSCAN_ZKEVM_API_KEY="" 5 | PVTKEY="" 6 | -------------------------------------------------------------------------------- /customERC20-bridge-example/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | 'mocha', 4 | ], 5 | env: { 6 | node: true, 7 | mocha: true, 8 | }, 9 | extends: 'airbnb-base', 10 | rules: { 11 | indent: ['error', 4], 12 | 'mocha/no-exclusive-tests': 'error', 13 | 'max-len': ['error', { 14 | code: 140, comments: 200, ignoreStrings: true, ignoreTemplateLiterals: true, 15 | }], 16 | 'no-unused-vars': [2, { varsIgnorePattern: 'export^' }], 17 | 'no-return-assign': [0], 18 | 'no-underscore-dangle': [0], 19 | 'no-plusplus': ['error', { allowForLoopAfterthoughts: true }], 20 | 'func-names': [0], 21 | 'class-methods-use-this': [0], 22 | 'no-bitwise': [0], 23 | 'no-param-reassign': 'off', 24 | 'global-require': 'off', 25 | 'import/no-dynamic-require': 'off', 26 | 'no-console': [2, { allow: ['warn', 'error'] }], 27 | 'import/prefer-default-export': [0], 28 | 'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }], 29 | 'multiline-comment-style': 'error', 30 | 'import/no-extraneous-dependencies': 'off', 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /customERC20-bridge-example/.gitignore: -------------------------------------------------------------------------------- 1 | coverage.json 2 | .env 3 | cache 4 | artifacts 5 | build 6 | yarn.lock 7 | node_modules 8 | coverage.json 9 | package-lock.json 10 | coverage 11 | .coverage_* 12 | .openzeppelin 13 | .vscode/launch.json 14 | deployment/*_output.json 15 | scripts/*_output.json 16 | *.ignore/ -------------------------------------------------------------------------------- /customERC20-bridge-example/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": "*.sol", 5 | "options": { 6 | "printWidth": 80, 7 | "tabWidth": 4, 8 | "useTabs": false, 9 | "singleQuote": false, 10 | "bracketSpacing": false, 11 | "explicitTypes": "always" 12 | } 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /customERC20-bridge-example/.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: ['mocks', 'interfaces'] 3 | }; -------------------------------------------------------------------------------- /customERC20-bridge-example/.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "mark-callable-contracts": "off", 5 | "no-empty-blocks": "off", 6 | "compiler-version": ["error", "0.8.17"], 7 | "private-vars-leading-underscore": "error", 8 | "bracket-align": "off", 9 | "reason-string": "off", 10 | "not-rely-on-time": "off", 11 | "no-inline-assembly": "off", 12 | "check-send-result": "off" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /customERC20-bridge-example/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "solidity.linter": "solhint", 4 | "solidity.compileUsingRemoteVersion": "v0.8.17+commit.e14f2714" 5 | } 6 | -------------------------------------------------------------------------------- /customERC20-bridge-example/README.md: -------------------------------------------------------------------------------- 1 | # zkEVM ERC20 bridge example 2 | 3 | This folder provides an example on how to **bridge ERC20** using the message layer that `polygonZKEVMBridge` implements 4 | 5 | ## Requirements 6 | 7 | - node version: >= 14.x 8 | - npm version: >= 7.x 9 | 10 | ## Deployment NFT ERC20 11 | 12 | ### Deployment 13 | 14 | In project root execute: 15 | 16 | ``` 17 | npm i 18 | cp .env.example .env 19 | ``` 20 | 21 | Fill `.env` with your `MNEMONIC` or `PVTKEY` and `INFURA_PROJECT_ID` 22 | If you want to verify the contracts also fill in the `ETHERSCAN_API_KEY` and `ETHERSCAN_ZKEVM_API_KEY` 23 | 24 | To deploy use:`deploy:erc20Bridge:${network}` 25 | 26 | As example for `goerli`/`polygonZKEVMTestnet` testnets: 27 | This script will deploy on both networks the same contract using the deterministic deployment: 28 | 29 | ``` 30 | npm run deploy:erc20Bridge:goerli 31 | ``` 32 | 33 | Once the deployment is finished, we will find the results on `ERC20Bridge_output.json` 34 | 35 | To verify contracts use `npm run verify:erc20Bridge:${network}` 36 | 37 | ``` 38 | npm run verify:erc20Bridge:goerli 39 | npm run verify:erc20Bridge:polygonZKEVMTestnet 40 | ``` 41 | 42 | ## Using the erc20 bridge 43 | 44 | In order to use the bridge, some scripts are provided: 45 | 46 | ``` 47 | npm run bridge:MockERC20:goerli 48 | ``` 49 | 50 | - Now we have to wait until the message is forwarded to L2, there is a final script that will check it if the claim is ready. If it is ready, it will actually claim the erc20 in the other layer: 51 | 52 | ``` 53 | npm run claim:MockERC20:polygonZKEVMTestnet 54 | ``` 55 | 56 | ## Example erc20 bridge transaction 57 | 58 | bridge: https://goerli.etherscan.io/tx/0x392827147f8883498cb8fbdaac96b2f453d1cc0ee9078c482d5606fa0058a32f 59 | claim: https://testnet-zkevm.polygonscan.com/tx/0xbeca97d41c250fece46673c297dbc448b64c4a1825d53f360ccfe0d09733e978 60 | -------------------------------------------------------------------------------- /customERC20-bridge-example/contracts/ERC20BridgeNativeChain.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "./base/PolygonERC20BridgeBase.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 7 | 8 | /** 9 | * ERC20BridgeNativeChain is an example contract to use the message layer of the PolygonZkEVMBridge to bridge custom ERC20 10 | * This contract will be deployed on the native erc20 network (usually will be mainnet) 11 | */ 12 | contract ERC20BridgeNativeChain is PolygonERC20BridgeBase { 13 | using SafeERC20 for IERC20; 14 | 15 | // Token address 16 | IERC20 public immutable token; 17 | 18 | /** 19 | * @param _polygonZkEVMBridge Polygon zkevm bridge address 20 | * @param _counterpartContract Couterpart contract 21 | * @param _counterpartNetwork Couterpart network 22 | * @param _token Token address 23 | */ 24 | constructor( 25 | IPolygonZkEVMBridge _polygonZkEVMBridge, 26 | address _counterpartContract, 27 | uint32 _counterpartNetwork, 28 | IERC20 _token 29 | ) 30 | PolygonERC20BridgeBase( 31 | _polygonZkEVMBridge, 32 | _counterpartContract, 33 | _counterpartNetwork 34 | ) 35 | { 36 | token = _token; 37 | } 38 | 39 | /** 40 | * @dev Handle the reception of the tokens 41 | * @param amount Token amount 42 | */ 43 | function _receiveTokens(uint256 amount) internal override { 44 | token.safeTransferFrom(msg.sender, address(this), amount); 45 | } 46 | 47 | /** 48 | * @dev Handle the transfer of the tokens 49 | * @param destinationAddress Address destination that will receive the tokens on the other network 50 | * @param amount Token amount 51 | */ 52 | function _transferTokens( 53 | address destinationAddress, 54 | uint256 amount 55 | ) internal override { 56 | token.safeTransfer(destinationAddress, amount); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /customERC20-bridge-example/contracts/ERC20BridgeNonNativeChain.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "./base/PolygonERC20BridgeBase.sol"; 6 | import "./interfaces/IERC20Wrapped.sol"; 7 | 8 | /** 9 | * ERC20BridgeNonNativeChain is an example contract to use the message layer of the PolygonZkEVMBridge to bridge custom ERC20 10 | * This contract will be deployed on the non-native erc20 network (usually will be zk-EVM) 11 | */ 12 | contract ERC20BridgeNonNativeChain is PolygonERC20BridgeBase { 13 | // Token address 14 | IERC20Wrapped public immutable token; 15 | 16 | /** 17 | * @param _polygonZkEVMBridge Polygon zkevm bridge address 18 | * @param _counterpartContract Couterpart contract 19 | * @param _counterpartNetwork Couterpart network 20 | * @param _token Token address 21 | */ 22 | constructor( 23 | IPolygonZkEVMBridge _polygonZkEVMBridge, 24 | address _counterpartContract, 25 | uint32 _counterpartNetwork, 26 | IERC20Wrapped _token 27 | ) 28 | PolygonERC20BridgeBase( 29 | _polygonZkEVMBridge, 30 | _counterpartContract, 31 | _counterpartNetwork 32 | ) 33 | { 34 | token = _token; 35 | } 36 | 37 | /** 38 | * @dev Handle the reception of the tokens 39 | * @param amount Token amount 40 | */ 41 | function _receiveTokens(uint256 amount) internal override { 42 | token.bridgeBurn(msg.sender, amount); 43 | } 44 | 45 | /** 46 | * @dev Handle the transfer of the tokens 47 | * @param destinationAddress Address destination that will receive the tokens on the other network 48 | * @param amount Token amount 49 | */ 50 | function _transferTokens( 51 | address destinationAddress, 52 | uint256 amount 53 | ) internal override { 54 | token.bridgeMint(destinationAddress, amount); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /customERC20-bridge-example/contracts/base/PolygonBridgeBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "../polygonZKEVMContracts/interfaces/IBasePolygonZkEVMGlobalExitRoot.sol"; 6 | import "../polygonZKEVMContracts/interfaces/IBridgeMessageReceiver.sol"; 7 | import "../polygonZKEVMContracts/interfaces/IPolygonZkEVMBridge.sol"; 8 | 9 | /** 10 | * This contract contains the logic to use the message layer of the bridge to send and receive messages 11 | * to a counterpart contract deployed on another network. 12 | * Is needed to deploy 1 contract on each layer that inherits this base. 13 | */ 14 | abstract contract PolygonBridgeBase { 15 | // Zk-EVM Bridge address 16 | IPolygonZkEVMBridge public immutable polygonZkEVMBridge; 17 | 18 | // Counterpart contract that will be deployed on the other network 19 | // Both contract will send messages to each other 20 | address public immutable counterpartContract; 21 | 22 | // Counterpart network 23 | uint32 public immutable counterpartNetwork; 24 | 25 | /** 26 | * @param _polygonZkEVMBridge Polygon zkevm bridge address 27 | * @param _counterpartContract Couterpart contract 28 | * @param _counterpartNetwork Couterpart network 29 | */ 30 | constructor( 31 | IPolygonZkEVMBridge _polygonZkEVMBridge, 32 | address _counterpartContract, 33 | uint32 _counterpartNetwork 34 | ) { 35 | polygonZkEVMBridge = _polygonZkEVMBridge; 36 | counterpartContract = _counterpartContract; 37 | counterpartNetwork = _counterpartNetwork; 38 | } 39 | 40 | /** 41 | * @notice Send a message to the bridge 42 | * @param messageData Message data 43 | * @param forceUpdateGlobalExitRoot Indicates if the global exit root is updated or not 44 | */ 45 | function _bridgeMessage( 46 | bytes memory messageData, 47 | bool forceUpdateGlobalExitRoot 48 | ) internal virtual { 49 | polygonZkEVMBridge.bridgeMessage( 50 | counterpartNetwork, 51 | counterpartContract, 52 | forceUpdateGlobalExitRoot, 53 | messageData 54 | ); 55 | } 56 | 57 | /** 58 | * @notice Function triggered by the bridge once a message is received by the other network 59 | * @param originAddress Origin address that the message was sended 60 | * @param originNetwork Origin network that the message was sended ( not usefull for this contract) 61 | * @param data Abi encoded metadata 62 | */ 63 | function onMessageReceived( 64 | address originAddress, 65 | uint32 originNetwork, 66 | bytes memory data 67 | ) external payable { 68 | // Can only be called by the bridge 69 | require( 70 | msg.sender == address(polygonZkEVMBridge), 71 | "TokenWrapped::PolygonBridgeBase: Not PolygonZkEVMBridge" 72 | ); 73 | 74 | require( 75 | counterpartContract == originAddress, 76 | "TokenWrapped::PolygonBridgeBase: Not counterpart contract" 77 | ); 78 | require( 79 | counterpartNetwork == originNetwork, 80 | "TokenWrapped::PolygonBridgeBase: Not counterpart network" 81 | ); 82 | 83 | _onMessageReceived(data); 84 | } 85 | 86 | /** 87 | * @dev Handle the data of the message received 88 | * Must be implemented in parent contracts 89 | */ 90 | function _onMessageReceived(bytes memory data) internal virtual; 91 | } 92 | -------------------------------------------------------------------------------- /customERC20-bridge-example/contracts/base/PolygonERC20BridgeBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "./PolygonBridgeBase.sol"; 6 | 7 | /** 8 | * This contract contains the common logic to interact with the message layer of the bridge 9 | * to build a custom erc20 bridge. Is needed to deploy 1 contract on each layer that inherits 10 | * this base. 11 | */ 12 | abstract contract PolygonERC20BridgeBase is PolygonBridgeBase { 13 | /** 14 | * @param _polygonZkEVMBridge Polygon zkevm bridge address 15 | * @param _counterpartContract Couterpart contract 16 | * @param _counterpartNetwork Couterpart network 17 | */ 18 | constructor( 19 | IPolygonZkEVMBridge _polygonZkEVMBridge, 20 | address _counterpartContract, 21 | uint32 _counterpartNetwork 22 | ) 23 | PolygonBridgeBase( 24 | _polygonZkEVMBridge, 25 | _counterpartContract, 26 | _counterpartNetwork 27 | ) 28 | {} 29 | 30 | /** 31 | * @dev Emitted when bridge tokens to the counterpart network 32 | */ 33 | event BridgeTokens(address destinationAddress, uint256 amount); 34 | 35 | /** 36 | * @dev Emitted when claim tokens from the counterpart network 37 | */ 38 | event ClaimTokens(address destinationAddress, uint256 amount); 39 | 40 | /** 41 | * @notice Send a message to the bridge that contains the destination address and the token amount 42 | * The parent contract should implement the receive token protocol and afterwards call this function 43 | * @param destinationAddress Address destination that will receive the tokens on the other network 44 | * @param amount Token amount 45 | * @param forceUpdateGlobalExitRoot Indicates if the global exit root is updated or not 46 | */ 47 | function bridgeToken( 48 | address destinationAddress, 49 | uint256 amount, 50 | bool forceUpdateGlobalExitRoot 51 | ) external { 52 | _receiveTokens(amount); 53 | 54 | // Encode message data 55 | bytes memory messageData = abi.encode(destinationAddress, amount); 56 | 57 | // Send message data through the bridge 58 | _bridgeMessage(messageData, forceUpdateGlobalExitRoot); 59 | 60 | emit BridgeTokens(destinationAddress, amount); 61 | } 62 | 63 | /** 64 | * @notice Internal function triggered when receive a message 65 | * @param data message data containing the destination address and the token amount 66 | */ 67 | function _onMessageReceived(bytes memory data) internal override { 68 | // Decode message data 69 | (address destinationAddress, uint256 amount) = abi.decode( 70 | data, 71 | (address, uint256) 72 | ); 73 | 74 | _transferTokens(destinationAddress, amount); 75 | emit ClaimTokens(destinationAddress, amount); 76 | } 77 | 78 | /** 79 | * @dev Handle the reception of the tokens 80 | * Must be implemented in parent contracts 81 | */ 82 | function _receiveTokens(uint256 amount) internal virtual; 83 | 84 | /** 85 | * @dev Handle the transfer of the tokens 86 | * Must be implemented in parent contracts 87 | */ 88 | function _transferTokens( 89 | address destinationAddress, 90 | uint256 amount 91 | ) internal virtual; 92 | } 93 | -------------------------------------------------------------------------------- /customERC20-bridge-example/contracts/interfaces/IERC20Wrapped.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | /** 6 | * @dev Define interface for erc20 wrapped 7 | */ 8 | interface IERC20Wrapped { 9 | function bridgeMint(address to, uint256 value) external; 10 | 11 | function bridgeBurn(address account, uint256 value) external; 12 | } 13 | -------------------------------------------------------------------------------- /customERC20-bridge-example/contracts/mocks/CustomERC20Mainnet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol"; 6 | import "@openzeppelin/contracts/access/Ownable.sol"; 7 | 8 | contract CustomERC20Mainnet is ERC20Pausable, Ownable { 9 | constructor( 10 | string memory name, 11 | string memory symbol, 12 | address initialAccount, 13 | uint256 initialBalance 14 | ) ERC20(name, symbol) { 15 | _mint(initialAccount, initialBalance); 16 | } 17 | 18 | /** 19 | * @notice This function is used to pause the transferability of the token. 20 | * Only the owner can call this function 21 | */ 22 | function pause() public onlyOwner { 23 | _pause(); 24 | } 25 | 26 | /** 27 | * @notice This function is used to unpause the transferability of the token. 28 | * Only the owner can call this function 29 | */ 30 | function unpause() public onlyOwner { 31 | _unpause(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /customERC20-bridge-example/contracts/mocks/CustomERC20Wrapped.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "./CustomERC20Mainnet.sol"; 6 | import "../interfaces/IERC20Wrapped.sol"; 7 | 8 | contract CustomERC20Wrapped is CustomERC20Mainnet, IERC20Wrapped { 9 | // PolygonZkEVM Bridge address 10 | address public immutable ERC20bridgeAddress; 11 | 12 | // Notice that can inherit any erc20 contract with ANY custom logic 13 | constructor( 14 | string memory name, 15 | string memory symbol, 16 | address initialAccount, 17 | uint256 initialBalance, 18 | address _ERC20bridgeAddress 19 | ) CustomERC20Mainnet(name, symbol, initialAccount, initialBalance) { 20 | ERC20bridgeAddress = _ERC20bridgeAddress; 21 | } 22 | 23 | modifier onlyBridge() { 24 | require( 25 | msg.sender == ERC20bridgeAddress, 26 | "CustomERC20Wrapped::onlyBridge: Not PolygonZkEVMBridge" 27 | ); 28 | _; 29 | } 30 | 31 | function bridgeMint(address to, uint256 value) external onlyBridge { 32 | _mint(to, value); 33 | } 34 | 35 | // Notice that is not require to approve wrapped tokens to use the bridge 36 | function bridgeBurn(address account, uint256 value) external onlyBridge { 37 | _burn(account, value); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /customERC20-bridge-example/contracts/polygonZKEVMContracts/PolygonZkEVMGlobalExitRoot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "./interfaces/IPolygonZkEVMGlobalExitRoot.sol"; 6 | import "./lib/GlobalExitRootLib.sol"; 7 | 8 | /** 9 | * Contract responsible for managing the exit roots across multiple networks 10 | */ 11 | contract PolygonZkEVMGlobalExitRoot is IPolygonZkEVMGlobalExitRoot { 12 | // PolygonZkEVMBridge address 13 | address public immutable bridgeAddress; 14 | 15 | // Rollup contract address 16 | address public immutable rollupAddress; 17 | 18 | // Rollup exit root, this will be updated every time a batch is verified 19 | bytes32 public lastRollupExitRoot; 20 | 21 | // Mainnet exit root, this will be updated every time a deposit is made in mainnet 22 | bytes32 public lastMainnetExitRoot; 23 | 24 | // Store every global exit root: Root --> timestamp 25 | mapping(bytes32 => uint256) public globalExitRootMap; 26 | 27 | /** 28 | * @dev Emitted when the global exit root is updated 29 | */ 30 | event UpdateGlobalExitRoot( 31 | bytes32 indexed mainnetExitRoot, 32 | bytes32 indexed rollupExitRoot 33 | ); 34 | 35 | /** 36 | * @param _rollupAddress Rollup contract address 37 | * @param _bridgeAddress PolygonZkEVMBridge contract address 38 | */ 39 | constructor(address _rollupAddress, address _bridgeAddress) { 40 | rollupAddress = _rollupAddress; 41 | bridgeAddress = _bridgeAddress; 42 | } 43 | 44 | /** 45 | * @notice Update the exit root of one of the networks and the global exit root 46 | * @param newRoot new exit tree root 47 | */ 48 | function updateExitRoot(bytes32 newRoot) external { 49 | // Store storage variables into temporal variables since will be used multiple times 50 | bytes32 cacheLastRollupExitRoot = lastRollupExitRoot; 51 | bytes32 cacheLastMainnetExitRoot = lastMainnetExitRoot; 52 | 53 | if (msg.sender == bridgeAddress) { 54 | lastMainnetExitRoot = newRoot; 55 | cacheLastMainnetExitRoot = newRoot; 56 | } else if (msg.sender == rollupAddress) { 57 | lastRollupExitRoot = newRoot; 58 | cacheLastRollupExitRoot = newRoot; 59 | } else { 60 | revert OnlyAllowedContracts(); 61 | } 62 | 63 | bytes32 newGlobalExitRoot = GlobalExitRootLib.calculateGlobalExitRoot( 64 | cacheLastMainnetExitRoot, 65 | cacheLastRollupExitRoot 66 | ); 67 | 68 | // If it already exists, do not modify the timestamp 69 | if (globalExitRootMap[newGlobalExitRoot] == 0) { 70 | globalExitRootMap[newGlobalExitRoot] = block.timestamp; 71 | emit UpdateGlobalExitRoot( 72 | cacheLastMainnetExitRoot, 73 | cacheLastRollupExitRoot 74 | ); 75 | } 76 | } 77 | 78 | /** 79 | * @notice Return last global exit root 80 | */ 81 | function getLastGlobalExitRoot() public view returns (bytes32) { 82 | return 83 | GlobalExitRootLib.calculateGlobalExitRoot( 84 | lastMainnetExitRoot, 85 | lastRollupExitRoot 86 | ); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /customERC20-bridge-example/contracts/polygonZKEVMContracts/interfaces/IBasePolygonZkEVMGlobalExitRoot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | interface IBasePolygonZkEVMGlobalExitRoot { 6 | /** 7 | * @dev Thrown when the caller is not the allowed contracts 8 | */ 9 | error OnlyAllowedContracts(); 10 | 11 | function updateExitRoot(bytes32 newRollupExitRoot) external; 12 | 13 | function globalExitRootMap( 14 | bytes32 globalExitRootNum 15 | ) external returns (uint256); 16 | } 17 | -------------------------------------------------------------------------------- /customERC20-bridge-example/contracts/polygonZKEVMContracts/interfaces/IBridgeMessageReceiver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | /** 6 | * @dev Define interface for PolygonZkEVM Bridge message receiver 7 | */ 8 | interface IBridgeMessageReceiver { 9 | function onMessageReceived( 10 | address originAddress, 11 | uint32 originNetwork, 12 | bytes memory data 13 | ) external payable; 14 | } 15 | -------------------------------------------------------------------------------- /customERC20-bridge-example/contracts/polygonZKEVMContracts/interfaces/IPolygonZkEVMBridge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | interface IPolygonZkEVMBridge { 6 | /** 7 | * @dev Thrown when sender is not the PolygonZkEVM address 8 | */ 9 | error OnlyPolygonZkEVM(); 10 | 11 | /** 12 | * @dev Thrown when the destination network is invalid 13 | */ 14 | error DestinationNetworkInvalid(); 15 | 16 | /** 17 | * @dev Thrown when the amount does not match msg.value 18 | */ 19 | error AmountDoesNotMatchMsgValue(); 20 | 21 | /** 22 | * @dev Thrown when user is bridging tokens and is also sending a value 23 | */ 24 | error MsgValueNotZero(); 25 | 26 | /** 27 | * @dev Thrown when the Ether transfer on claimAsset fails 28 | */ 29 | error EtherTransferFailed(); 30 | 31 | /** 32 | * @dev Thrown when the message transaction on claimMessage fails 33 | */ 34 | error MessageFailed(); 35 | 36 | /** 37 | * @dev Thrown when the global exit root does not exist 38 | */ 39 | error GlobalExitRootInvalid(); 40 | 41 | /** 42 | * @dev Thrown when the smt proof does not match 43 | */ 44 | error InvalidSmtProof(); 45 | 46 | /** 47 | * @dev Thrown when an index is already claimed 48 | */ 49 | error AlreadyClaimed(); 50 | 51 | /** 52 | * @dev Thrown when the owner of permit does not match the sender 53 | */ 54 | error NotValidOwner(); 55 | 56 | /** 57 | * @dev Thrown when the spender of the permit does not match this contract address 58 | */ 59 | error NotValidSpender(); 60 | 61 | /** 62 | * @dev Thrown when the amount of the permit does not match 63 | */ 64 | error NotValidAmount(); 65 | 66 | /** 67 | * @dev Thrown when the permit data contains an invalid signature 68 | */ 69 | error NotValidSignature(); 70 | 71 | function bridgeAsset( 72 | uint32 destinationNetwork, 73 | address destinationAddress, 74 | uint256 amount, 75 | address token, 76 | bool forceUpdateGlobalExitRoot, 77 | bytes calldata permitData 78 | ) external payable; 79 | 80 | function bridgeMessage( 81 | uint32 destinationNetwork, 82 | address destinationAddress, 83 | bool forceUpdateGlobalExitRoot, 84 | bytes calldata metadata 85 | ) external payable; 86 | 87 | function claimAsset( 88 | bytes32[32] calldata smtProof, 89 | uint32 index, 90 | bytes32 mainnetExitRoot, 91 | bytes32 rollupExitRoot, 92 | uint32 originNetwork, 93 | address originTokenAddress, 94 | uint32 destinationNetwork, 95 | address destinationAddress, 96 | uint256 amount, 97 | bytes calldata metadata 98 | ) external; 99 | 100 | function claimMessage( 101 | bytes32[32] calldata smtProof, 102 | uint32 index, 103 | bytes32 mainnetExitRoot, 104 | bytes32 rollupExitRoot, 105 | uint32 originNetwork, 106 | address originAddress, 107 | uint32 destinationNetwork, 108 | address destinationAddress, 109 | uint256 amount, 110 | bytes calldata metadata 111 | ) external; 112 | 113 | function updateGlobalExitRoot() external; 114 | 115 | function activateEmergencyState() external; 116 | 117 | function deactivateEmergencyState() external; 118 | 119 | function networkID() external returns(uint32); 120 | } 121 | -------------------------------------------------------------------------------- /customERC20-bridge-example/contracts/polygonZKEVMContracts/interfaces/IPolygonZkEVMGlobalExitRoot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | import "./IBasePolygonZkEVMGlobalExitRoot.sol"; 5 | 6 | interface IPolygonZkEVMGlobalExitRoot is IBasePolygonZkEVMGlobalExitRoot { 7 | function getLastGlobalExitRoot() external view returns (bytes32); 8 | } 9 | -------------------------------------------------------------------------------- /customERC20-bridge-example/contracts/polygonZKEVMContracts/lib/EmergencyManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | /** 6 | * @dev Contract helper responsible to manage the emergency state 7 | */ 8 | contract EmergencyManager { 9 | /** 10 | * @dev Thrown when emergency state is active, and the function requires otherwise 11 | */ 12 | error OnlyNotEmergencyState(); 13 | 14 | /** 15 | * @dev Thrown when emergency state is not active, and the function requires otherwise 16 | */ 17 | error OnlyEmergencyState(); 18 | 19 | /** 20 | * @dev This empty reserved space is put in place to allow future versions to add new 21 | * variables without shifting down storage in the inheritance chain. 22 | */ 23 | uint256[10] private _gap; 24 | 25 | // Indicates whether the emergency state is active or not 26 | bool public isEmergencyState; 27 | 28 | /** 29 | * @dev Emitted when emergency state is activated 30 | */ 31 | event EmergencyStateActivated(); 32 | 33 | /** 34 | * @dev Emitted when emergency state is deactivated 35 | */ 36 | event EmergencyStateDeactivated(); 37 | 38 | /** 39 | * @notice Only allows a function to be callable if emergency state is unactive 40 | */ 41 | modifier ifNotEmergencyState() { 42 | if (isEmergencyState) { 43 | revert OnlyNotEmergencyState(); 44 | } 45 | _; 46 | } 47 | 48 | /** 49 | * @notice Only allows a function to be callable if emergency state is active 50 | */ 51 | modifier ifEmergencyState() { 52 | if (!isEmergencyState) { 53 | revert OnlyEmergencyState(); 54 | } 55 | _; 56 | } 57 | 58 | /** 59 | * @notice Activate emergency state 60 | */ 61 | function _activateEmergencyState() internal virtual ifNotEmergencyState { 62 | isEmergencyState = true; 63 | emit EmergencyStateActivated(); 64 | } 65 | 66 | /** 67 | * @notice Deactivate emergency state 68 | */ 69 | function _deactivateEmergencyState() internal virtual ifEmergencyState { 70 | isEmergencyState = false; 71 | emit EmergencyStateDeactivated(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /customERC20-bridge-example/contracts/polygonZKEVMContracts/lib/GlobalExitRootLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | /** 6 | * @dev A library that provides the necessary calculations to calculate the global exit root 7 | */ 8 | library GlobalExitRootLib { 9 | function calculateGlobalExitRoot( 10 | bytes32 mainnetExitRoot, 11 | bytes32 rollupExitRoot 12 | ) internal pure returns (bytes32) { 13 | return keccak256(abi.encodePacked(mainnetExitRoot, rollupExitRoot)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /customERC20-bridge-example/contracts/polygonZKEVMContracts/lib/TokenWrapped.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | // Implementation of permit based on https://github.com/WETH10/WETH10/blob/main/contracts/WETH10.sol 3 | pragma solidity 0.8.17; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | 7 | contract TokenWrapped is ERC20 { 8 | // Domain typehash 9 | bytes32 public constant DOMAIN_TYPEHASH = 10 | keccak256( 11 | "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" 12 | ); 13 | // Permit typehash 14 | bytes32 public constant PERMIT_TYPEHASH = 15 | keccak256( 16 | "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" 17 | ); 18 | 19 | // Version 20 | string public constant VERSION = "1"; 21 | 22 | // Chain id on deployment 23 | uint256 public immutable deploymentChainId; 24 | 25 | // Domain separator calculated on deployment 26 | bytes32 private immutable _DEPLOYMENT_DOMAIN_SEPARATOR; 27 | 28 | // PolygonZkEVM Bridge address 29 | address public immutable bridgeAddress; 30 | 31 | // Decimals 32 | uint8 private immutable _decimals; 33 | 34 | // Permit nonces 35 | mapping(address => uint256) public nonces; 36 | 37 | modifier onlyBridge() { 38 | require( 39 | msg.sender == bridgeAddress, 40 | "TokenWrapped::onlyBridge: Not PolygonZkEVMBridge" 41 | ); 42 | _; 43 | } 44 | 45 | constructor( 46 | string memory name, 47 | string memory symbol, 48 | uint8 __decimals 49 | ) ERC20(name, symbol) { 50 | bridgeAddress = msg.sender; 51 | _decimals = __decimals; 52 | deploymentChainId = block.chainid; 53 | _DEPLOYMENT_DOMAIN_SEPARATOR = _calculateDomainSeparator(block.chainid); 54 | } 55 | 56 | function mint(address to, uint256 value) external onlyBridge { 57 | _mint(to, value); 58 | } 59 | 60 | // Notice that is not require to approve wrapped tokens to use the bridge 61 | function burn(address account, uint256 value) external onlyBridge { 62 | _burn(account, value); 63 | } 64 | 65 | function decimals() public view virtual override returns (uint8) { 66 | return _decimals; 67 | } 68 | 69 | // Permit relative functions 70 | function permit( 71 | address owner, 72 | address spender, 73 | uint256 value, 74 | uint256 deadline, 75 | uint8 v, 76 | bytes32 r, 77 | bytes32 s 78 | ) external { 79 | require( 80 | block.timestamp <= deadline, 81 | "TokenWrapped::permit: Expired permit" 82 | ); 83 | 84 | bytes32 hashStruct = keccak256( 85 | abi.encode( 86 | PERMIT_TYPEHASH, 87 | owner, 88 | spender, 89 | value, 90 | nonces[owner]++, 91 | deadline 92 | ) 93 | ); 94 | 95 | bytes32 digest = keccak256( 96 | abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), hashStruct) 97 | ); 98 | 99 | address signer = ecrecover(digest, v, r, s); 100 | require( 101 | signer != address(0) && signer == owner, 102 | "TokenWrapped::permit: Invalid signature" 103 | ); 104 | 105 | _approve(owner, spender, value); 106 | } 107 | 108 | /** 109 | * @notice Calculate domain separator, given a chainID. 110 | * @param chainId Current chainID 111 | */ 112 | function _calculateDomainSeparator( 113 | uint256 chainId 114 | ) private view returns (bytes32) { 115 | return 116 | keccak256( 117 | abi.encode( 118 | DOMAIN_TYPEHASH, 119 | keccak256(bytes(name())), 120 | keccak256(bytes(VERSION)), 121 | chainId, 122 | address(this) 123 | ) 124 | ); 125 | } 126 | 127 | /// @dev Return the DOMAIN_SEPARATOR. 128 | function DOMAIN_SEPARATOR() public view returns (bytes32) { 129 | return 130 | block.chainid == deploymentChainId 131 | ? _DEPLOYMENT_DOMAIN_SEPARATOR 132 | : _calculateDomainSeparator(block.chainid); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /customERC20-bridge-example/deployment/verifyMainnetContracts.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | /* eslint-disable import/no-dynamic-require, no-await-in-loop, no-restricted-syntax, guard-for-in */ 3 | require('dotenv').config(); 4 | const path = require('path'); 5 | const hre = require('hardhat'); 6 | const { expect } = require('chai'); 7 | 8 | const mainnetBridgeAddress = '0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe'; 9 | const testnetBridgeAddress = '0xF6BEEeBB578e214CA9E23B0e9683454Ff88Ed2A7'; 10 | 11 | const networkIDMainnet = 0; 12 | const networkIDzkEVM = 1; 13 | 14 | async function main() { 15 | const networkName = process.env.HARDHAT_NETWORK; 16 | const pathDeployOutputParameters = path.join(__dirname, './ERC20Bridge_output.json'); 17 | const deployOutputParameters = require(pathDeployOutputParameters); 18 | 19 | let zkEVMBridgeContractAddress; 20 | // Use mainnet bridge address 21 | if (networkName === 'polygonZKEVMMainnet' || networkName === 'mainnet') { 22 | zkEVMBridgeContractAddress = mainnetBridgeAddress; 23 | } 24 | 25 | // Use testnet bridge address 26 | if (networkName === 'polygonZKEVMTestnet' || networkName === 'goerli') { 27 | zkEVMBridgeContractAddress = testnetBridgeAddress; 28 | } 29 | 30 | // Token params 31 | const name = deployOutputParameters.tokenName; 32 | const symbol = deployOutputParameters.tokenSymbol; 33 | const initialAccount = deployOutputParameters.deployerAddress; 34 | const initialBalance = deployOutputParameters.tokenInitialBalance; 35 | 36 | try { 37 | // verify token mainnet 38 | await hre.run( 39 | 'verify:verify', 40 | { 41 | address: deployOutputParameters.erc20MainnetToken, 42 | constructorArguments: [ 43 | name, 44 | symbol, 45 | initialAccount, 46 | initialBalance, 47 | ], 48 | }, 49 | ); 50 | } catch (error) { 51 | console.log(error); 52 | expect(error.message.toLowerCase().includes('already verified')).to.be.equal(true); 53 | } 54 | 55 | try { 56 | // verify ERC20BridgeNativeChain 57 | await hre.run( 58 | 'verify:verify', 59 | { 60 | address: deployOutputParameters.ERC20BridgeMainnet, 61 | constructorArguments: [ 62 | zkEVMBridgeContractAddress, 63 | deployOutputParameters.ERC20BridgezkEVM, 64 | networkIDzkEVM, 65 | deployOutputParameters.erc20MainnetToken, 66 | ], 67 | }, 68 | ); 69 | } catch (error) { 70 | expect(error.message.toLowerCase().includes('already verified')).to.be.equal(true); 71 | } 72 | } 73 | 74 | main() 75 | .then(() => process.exit(0)) 76 | .catch((error) => { 77 | console.error(error); 78 | process.exit(1); 79 | }); 80 | -------------------------------------------------------------------------------- /customERC20-bridge-example/deployment/verifyZkEVM.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | /* eslint-disable import/no-dynamic-require, no-await-in-loop, no-restricted-syntax, guard-for-in */ 3 | require('dotenv').config(); 4 | const path = require('path'); 5 | const hre = require('hardhat'); 6 | const { expect } = require('chai'); 7 | 8 | const mainnetBridgeAddress = '0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe'; 9 | const testnetBridgeAddress = '0xF6BEEeBB578e214CA9E23B0e9683454Ff88Ed2A7'; 10 | 11 | const networkIDMainnet = 0; 12 | 13 | async function main() { 14 | const networkName = process.env.HARDHAT_NETWORK; 15 | const pathDeployOutputParameters = path.join(__dirname, './ERC20Bridge_output.json'); 16 | const deployOutputParameters = require(pathDeployOutputParameters); 17 | 18 | let zkEVMBridgeContractAddress; 19 | // Use mainnet bridge address 20 | if (networkName === 'polygonZKEVMMainnet' || networkName === 'mainnet') { 21 | zkEVMBridgeContractAddress = mainnetBridgeAddress; 22 | } 23 | 24 | // Use testnet bridge address 25 | if (networkName === 'polygonZKEVMTestnet' || networkName === 'goerli') { 26 | zkEVMBridgeContractAddress = testnetBridgeAddress; 27 | } 28 | 29 | // Token params 30 | const name = deployOutputParameters.tokenName; 31 | const symbol = deployOutputParameters.tokenSymbol; 32 | const initialAccount = deployOutputParameters.deployerAddress; 33 | const initialBalance = deployOutputParameters.tokenInitialBalance; 34 | 35 | try { 36 | // verify token mainnet 37 | await hre.run( 38 | 'verify:verify', 39 | { 40 | address: deployOutputParameters.erc20zkEVMToken, 41 | constructorArguments: [ 42 | name, 43 | symbol, 44 | initialAccount, 45 | initialBalance, 46 | deployOutputParameters.ERC20BridgezkEVM, 47 | ], 48 | }, 49 | ); 50 | } catch (error) { 51 | expect(error.message.toLowerCase().includes('already verified')).to.be.equal(true); 52 | } 53 | 54 | try { 55 | // verify ERC20BridgeNonNativeChain 56 | await hre.run( 57 | 'verify:verify', 58 | { 59 | address: deployOutputParameters.ERC20BridgezkEVM, 60 | constructorArguments: [ 61 | zkEVMBridgeContractAddress, 62 | deployOutputParameters.ERC20BridgeMainnet, 63 | networkIDMainnet, 64 | deployOutputParameters.erc20zkEVMToken, 65 | ], 66 | }, 67 | ); 68 | } catch (error) { 69 | expect(error.message.toLowerCase().includes('already verified')).to.be.equal(true); 70 | } 71 | } 72 | 73 | main() 74 | .then(() => process.exit(0)) 75 | .catch((error) => { 76 | console.error(error); 77 | process.exit(1); 78 | }); 79 | -------------------------------------------------------------------------------- /customERC20-bridge-example/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | require('@nomiclabs/hardhat-waffle'); 3 | require('hardhat-gas-reporter'); 4 | require('solidity-coverage'); 5 | require('@nomiclabs/hardhat-etherscan'); 6 | require('@openzeppelin/hardhat-upgrades'); 7 | 8 | const DEFAULT_MNEMONIC = 'test test test test test test test test test test test junk'; 9 | 10 | /* 11 | * You need to export an object to set up your config 12 | * Go to https://hardhat.org/config/ to learn more 13 | */ 14 | 15 | /** 16 | * @type import('hardhat/config').HardhatUserConfig 17 | */ 18 | module.exports = { 19 | solidity: { 20 | compilers: [ 21 | { 22 | version: "0.8.17", 23 | settings: { 24 | optimizer: { 25 | enabled: true, 26 | runs: 999999 27 | } 28 | } 29 | }, 30 | { 31 | version: "0.6.11", 32 | settings: { 33 | optimizer: { 34 | enabled: true, 35 | runs: 999999 36 | } 37 | } 38 | }, 39 | { 40 | version: "0.5.12", 41 | settings: { 42 | optimizer: { 43 | enabled: true, 44 | runs: 999999 45 | } 46 | } 47 | }, 48 | { 49 | version: "0.5.16", 50 | settings: { 51 | optimizer: { 52 | enabled: true, 53 | runs: 999999 54 | } 55 | } 56 | }, 57 | { 58 | version: "0.4.19", 59 | settings: { 60 | optimizer: { 61 | enabled: false, 62 | } 63 | } 64 | } 65 | ] 66 | }, 67 | networks: { 68 | mainnet: { 69 | url: `https://mainnet.infura.io/v3/${process.env.INFURA_PROJECT_ID}`, 70 | accounts: { 71 | mnemonic: process.env.MNEMONIC || DEFAULT_MNEMONIC, 72 | path: "m/44'/60'/0'/0", 73 | initialIndex: 0, 74 | count: 20, 75 | }, 76 | }, 77 | ropsten: { 78 | url: `https://ropsten.infura.io/v3/${process.env.INFURA_PROJECT_ID}`, 79 | accounts: { 80 | mnemonic: process.env.MNEMONIC || DEFAULT_MNEMONIC, 81 | path: "m/44'/60'/0'/0", 82 | initialIndex: 0, 83 | count: 20, 84 | }, 85 | }, 86 | goerli: { 87 | url: `https://goerli.infura.io/v3/${process.env.INFURA_PROJECT_ID}`, 88 | accounts: { 89 | mnemonic: process.env.MNEMONIC || DEFAULT_MNEMONIC, 90 | path: "m/44'/60'/0'/0", 91 | initialIndex: 0, 92 | count: 20, 93 | }, 94 | }, 95 | rinkeby: { 96 | url: `https://rinkeby.infura.io/v3/${process.env.INFURA_PROJECT_ID}`, 97 | accounts: { 98 | mnemonic: process.env.MNEMONIC || DEFAULT_MNEMONIC, 99 | path: "m/44'/60'/0'/0", 100 | initialIndex: 0, 101 | count: 20, 102 | }, 103 | }, 104 | localhost: { 105 | url: 'http://127.0.0.1:8545', 106 | accounts: { 107 | mnemonic: process.env.MNEMONIC || DEFAULT_MNEMONIC, 108 | path: "m/44'/60'/0'/0", 109 | initialIndex: 0, 110 | count: 20, 111 | }, 112 | }, 113 | hardhat: { 114 | initialDate: '0', 115 | allowUnlimitedContractSize: true, 116 | accounts: { 117 | mnemonic: process.env.MNEMONIC || DEFAULT_MNEMONIC, 118 | path: "m/44'/60'/0'/0", 119 | initialIndex: 0, 120 | count: 20, 121 | }, 122 | }, 123 | polygonZKEVMTestnet: { 124 | url: "https://rpc.public.zkevm-test.net", 125 | accounts: { 126 | mnemonic: process.env.MNEMONIC || DEFAULT_MNEMONIC, 127 | path: "m/44'/60'/0'/0", 128 | initialIndex: 0, 129 | count: 20, 130 | }, 131 | }, 132 | polygonZKEVMMainnet: { 133 | url: "https://zkevm-rpc.com", 134 | accounts: { 135 | mnemonic: process.env.MNEMONIC || DEFAULT_MNEMONIC, 136 | path: "m/44'/60'/0'/0", 137 | initialIndex: 0, 138 | count: 20, 139 | }, 140 | }, 141 | }, 142 | gasReporter: { 143 | enabled: !!process.env.REPORT_GAS, 144 | outputFile: process.env.REPORT_GAS_FILE ? "./gas_report.md" : null, 145 | noColors: process.env.REPORT_GAS_FILE ? true : false 146 | }, 147 | etherscan: { 148 | apiKey: { 149 | polygonZKEVMTestnet: `${process.env.ETHERSCAN_ZKEVM_API_KEY}`, 150 | polygonZKEVMMainnet: `${process.env.ETHERSCAN_ZKEVM_API_KEY}`, 151 | goerli: `${process.env.ETHERSCAN_API_KEY}`, 152 | mainnet: `${process.env.ETHERSCAN_API_KEY}` 153 | }, 154 | customChains: [ 155 | { 156 | network: "polygonZKEVMMainnet", 157 | chainId: 1101, 158 | urls: { 159 | apiURL: "https://api-zkevm.polygonscan.com/api", 160 | browserURL: "https://zkevm.polygonscan.com/" 161 | } 162 | }, 163 | { 164 | network: "polygonZKEVMTestnet", 165 | chainId: 1442, 166 | urls: { 167 | apiURL: "https://api-testnet-zkevm.polygonscan.com/api", 168 | browserURL: "https://testnet-zkevm.polygonscan.com/" 169 | } 170 | } 171 | ] 172 | }, 173 | }; -------------------------------------------------------------------------------- /customERC20-bridge-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@0xpolygonhermez/code-examples", 3 | "description": "custom ERC20 bridge example for polygon zkEVM", 4 | "version": "1.0.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/0xPolygonHermez/code-examples.git" 8 | }, 9 | "main": "index.js", 10 | "keywords": [ 11 | "zkevm", 12 | "snark", 13 | "polygon", 14 | "hermez", 15 | "stark", 16 | "EVM", 17 | "ethereum", 18 | "blockchain" 19 | ], 20 | "author": "0xPolygonHermez", 21 | "dependencies": { 22 | "axios": "^1.3.5", 23 | "chai": "^4.3.7", 24 | "ethers": "^5.7.2" 25 | }, 26 | "devDependencies": { 27 | "@0xpolygonhermez/zkevm-commonjs": "github:0xPolygonHermez/zkevm-commonjs#develop", 28 | "@nomiclabs/hardhat-ethers": "^2.2.2", 29 | "@nomiclabs/hardhat-etherscan": "^3.1.7", 30 | "@nomiclabs/hardhat-waffle": "^2.0.5", 31 | "@openzeppelin/contracts": "4.8.2", 32 | "@openzeppelin/contracts-upgradeable": "4.8.2", 33 | "@openzeppelin/hardhat-upgrades": "1.22.1", 34 | "@openzeppelin/test-helpers": "0.5.16", 35 | "dotenv": "^8.6.0", 36 | "eslint": "^8.36.0", 37 | "eslint-config-airbnb-base": "^15.0.0", 38 | "eslint-plugin-mocha": "^9.0.0", 39 | "ethereum-waffle": "^3.4.4", 40 | "hardhat": "^2.13.0", 41 | "hardhat-gas-reporter": "^1.0.9", 42 | "prettier": "^2.8.4", 43 | "prettier-plugin-solidity": "^1.1.3", 44 | "solc-0.8": "npm:solc@0.8.17", 45 | "solidity-coverage": "^0.7.22", 46 | "solidity-docgen": "^0.5.17" 47 | }, 48 | "scripts": { 49 | "deploy:erc20Bridge:goerli": "npx hardhat run deployment/deployERC20Bridge.js --network goerli", 50 | "deploy:erc20Bridge:mainnet": "npx hardhat run deployment/deployERC20Bridge.js --network mainnet", 51 | "verify:erc20Bridge:goerli": "npx hardhat run deployment/verifyMainnetContracts.js --network goerli", 52 | "verify:erc20Bridge:mainnet": "npx hardhat run deployment/verifyMainnetContracts.js --network mainnet", 53 | "verify:erc20Bridge:polygonZKEVMTestnet": "npx hardhat run deployment/verifyZkEVM.js --network polygonZKEVMTestnet", 54 | "verify:erc20Bridge:polygonZKEVMMainnet": "npx hardhat run deployment/verifyZkEVM.js --network polygonZKEVMMainnet", 55 | "lint": "npx eslint ./test && npx eslint ./docker/scripts && npx eslint ./deployment && npx eslint ./src", 56 | "lint:fix": "npx eslint ./test --fix && npx eslint ./docker/scripts --fix && npx eslint ./deployment --fix && npx eslint ./src --fix", 57 | "compile": "npx hardhat compile", 58 | "gas:report": "REPORT_GAS=true npx hardhat test", 59 | "gas:report:file": "rm -f .openzeppelin/unknown-31337.json && REPORT_GAS=true REPORT_GAS_FILE=true npx hardhat test", 60 | "bridge:MockERC20:goerli": "npx hardhat run scripts/bridgeMockERC20.js --network goerli", 61 | "bridge:MockERC20:mainnet": "npx hardhat run scripts/bridgeMockERC20.js --network mainnet", 62 | "bridge:MockERC20:polygonZKEVMTestnet": "npx hardhat run scripts/bridgeMockERC20.js --network polygonZKEVMTestnet", 63 | "bridge:MockERC20:polygonZKEVMMainnet": "npx hardhat run scripts/bridgeMockERC20.js --network polygonZKEVMMainnet", 64 | "claim:MockERC20:goerli": "npx hardhat run scripts/claimMockERC20.js --network goerli", 65 | "claim:MockERC20:mainnet": "npx hardhat run scripts/claimMockERC20.js --network mainnet", 66 | "claim:MockERC20:polygonZKEVMTestnet": "npx hardhat run scripts/claimMockERC20.js --network polygonZKEVMTestnet", 67 | "claim:MockERC20:polygonZKEVMMainnet": "npx hardhat run scripts/claimMockERC20.js --network polygonZKEVMMainnet" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /customERC20-bridge-example/scripts/bridgeMockERC20.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-await-in-loop, no-use-before-define, no-lonely-if, import/no-dynamic-require, global-require */ 2 | /* eslint-disable no-console, no-inner-declarations, no-undef, import/no-unresolved, no-restricted-syntax */ 3 | const path = require('path'); 4 | require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); 5 | const { ethers } = require('hardhat'); 6 | 7 | const networkIDMainnet = 0; 8 | const networkIDRollup = 1; 9 | 10 | const pathdeployeERC20Bridge = path.join(__dirname, '../deployment/ERC20Bridge_output.json'); 11 | const deploymentERC20Bridge = require(pathdeployeERC20Bridge); 12 | 13 | async function main() { 14 | // Load deployer 15 | let deployer; 16 | if (process.env.PVTKEY) { 17 | deployer = new ethers.Wallet(process.env.PVTKEY, ethers.provider); 18 | console.log('Using pvtKey deployer with address: ', deployer.address); 19 | } else if (process.env.MNEMONIC) { 20 | deployer = ethers.Wallet.fromMnemonic(process.env.MNEMONIC, 'm/44\'/60\'/0\'/0/0').connect(ethers.provider); 21 | console.log('Using MNEMONIC deployer with address: ', deployer.address); 22 | } else { 23 | [deployer] = (await ethers.getSigners()); 24 | } 25 | 26 | const networkName = process.env.HARDHAT_NETWORK; 27 | let destinationNetwork; let 28 | ERC20BridgeContractAddress; let erc20TokenAddress; 29 | 30 | if (networkName === 'polygonZKEVMTestnet' || networkName === 'polygonZKEVMMainnet') { 31 | destinationNetwork = networkIDMainnet; 32 | erc20TokenAddress = deploymentERC20Bridge.erc20zkEVMToken; 33 | ERC20BridgeContractAddress = deploymentERC20Bridge.ERC20BridgezkEVM; 34 | } 35 | 36 | if (networkName === 'mainnet' || networkName === 'goerli') { 37 | destinationNetwork = networkIDRollup; 38 | erc20TokenAddress = deploymentERC20Bridge.erc20MainnetToken; 39 | ERC20BridgeContractAddress = deploymentERC20Bridge.ERC20BridgeMainnet; 40 | } 41 | 42 | const erc20BridgeFactory = await ethers.getContractFactory('ERC20BridgeNativeChain', deployer); 43 | const erc20BridgeContract = await erc20BridgeFactory.attach( 44 | ERC20BridgeContractAddress, 45 | ); 46 | 47 | // Approve tokens 48 | const tokenFactory = await ethers.getContractFactory('CustomERC20Mainnet', deployer); 49 | const tokenContract = await tokenFactory.attach( 50 | erc20TokenAddress, 51 | ); 52 | 53 | const tokenAmount = ethers.utils.parseEther('1'); 54 | const destinationAddress = deployer.address; 55 | 56 | await (await tokenContract.approve(ERC20BridgeContractAddress, tokenAmount)).wait(); 57 | console.log('approved tokens'); 58 | 59 | const tx = await erc20BridgeContract.bridgeToken( 60 | destinationAddress, 61 | tokenAmount, 62 | true, 63 | ); 64 | 65 | console.log((await tx.wait()).transactionHash); 66 | 67 | console.log('Bridge done succesfully'); 68 | } 69 | 70 | main().catch((e) => { 71 | console.error(e); 72 | process.exit(1); 73 | }); 74 | -------------------------------------------------------------------------------- /customERC20-bridge-example/scripts/claimMockERC20.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-await-in-loop */ 2 | /* eslint-disable no-console, no-inner-declarations, no-undef, import/no-unresolved */ 3 | 4 | const path = require('path'); 5 | require('dotenv').config({ path: path.resolve(__dirname, '../../.env') }); 6 | const { ethers } = require('hardhat'); 7 | 8 | const mainnetBridgeAddress = '0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe'; 9 | const testnetBridgeAddress = '0xF6BEEeBB578e214CA9E23B0e9683454Ff88Ed2A7'; 10 | 11 | const mekrleProofString = '/merkle-proof'; 12 | const getClaimsFromAcc = '/bridges/'; 13 | 14 | const pathdeployeERC20Bridge = path.join(__dirname, '../deployment/ERC20Bridge_output.json'); 15 | const deploymentERC20Bridge = require(pathdeployeERC20Bridge); 16 | 17 | async function main() { 18 | const currentProvider = ethers.provider; 19 | let deployer; 20 | if (process.env.PVTKEY) { 21 | deployer = new ethers.Wallet(process.env.PVTKEY, currentProvider); 22 | console.log('Using pvtKey deployer with address: ', deployer.address); 23 | } else if (process.env.MNEMONIC) { 24 | deployer = ethers.Wallet.fromMnemonic(process.env.MNEMONIC, 'm/44\'/60\'/0\'/0/0').connect(currentProvider); 25 | console.log('Using MNEMONIC deployer with address: ', deployer.address); 26 | } else { 27 | [deployer] = (await ethers.getSigners()); 28 | } 29 | 30 | let zkEVMBridgeContractAddress; 31 | let baseURL; 32 | const networkName = process.env.HARDHAT_NETWORK; 33 | 34 | // Use mainnet bridge address 35 | if (networkName === 'polygonZKEVMMainnet' || networkName === 'mainnet') { 36 | zkEVMBridgeContractAddress = mainnetBridgeAddress; 37 | baseURL = 'https://bridge-api.zkevm-rpc.com'; 38 | } else if (networkName === 'polygonZKEVMTestnet' || networkName === 'goerli') { 39 | // Use testnet bridge address 40 | zkEVMBridgeContractAddress = testnetBridgeAddress; 41 | baseURL = 'https://bridge-api.public.zkevm-test.net'; 42 | } 43 | 44 | const axios = require('axios').create({ 45 | baseURL, 46 | }); 47 | 48 | const bridgeFactoryZkeEVm = await ethers.getContractFactory('PolygonZkEVMBridge', deployer); 49 | const bridgeContractZkeVM = bridgeFactoryZkeEVm.attach(zkEVMBridgeContractAddress); 50 | 51 | let ERC20BridgeContractAddress; 52 | if (networkName === 'polygonZKEVMTestnet' || networkName === 'polygonZKEVMMainnet') { 53 | ERC20BridgeContractAddress = deploymentERC20Bridge.ERC20BridgezkEVM; 54 | } 55 | 56 | if (networkName === 'mainnet' || networkName === 'goerli') { 57 | ERC20BridgeContractAddress = deploymentERC20Bridge.ERC20BridgeMainnet; 58 | } 59 | 60 | const depositAxions = await axios.get(getClaimsFromAcc + ERC20BridgeContractAddress, { params: { limit: 100, offset: 0 } }); 61 | const depositsArray = depositAxions.data.deposits; 62 | 63 | if (depositsArray.length === 0) { 64 | console.log('Not deposits yet!'); 65 | return; 66 | } 67 | 68 | for (let i = 0; i < depositsArray.length; i++) { 69 | const currentDeposit = depositsArray[i]; 70 | if (currentDeposit.ready_for_claim) { 71 | if (currentDeposit.claim_tx_hash != '') { 72 | console.log('already claimed: ', currentDeposit.claim_tx_hash); 73 | continue; 74 | } 75 | 76 | const proofAxios = await axios.get(mekrleProofString, { 77 | params: { deposit_cnt: currentDeposit.deposit_cnt, net_id: currentDeposit.orig_net }, 78 | }); 79 | 80 | const { proof } = proofAxios.data; 81 | const claimTx = await bridgeContractZkeVM.claimMessage( 82 | proof.merkle_proof, 83 | currentDeposit.deposit_cnt, 84 | proof.main_exit_root, 85 | proof.rollup_exit_root, 86 | currentDeposit.orig_net, 87 | currentDeposit.orig_addr, 88 | currentDeposit.dest_net, 89 | currentDeposit.dest_addr, 90 | currentDeposit.amount, 91 | currentDeposit.metadata, 92 | ); 93 | console.log('claim message succesfully send: ', claimTx.hash); 94 | await claimTx.wait(); 95 | console.log('claim message succesfully mined'); 96 | } else { 97 | console.log('Not ready yet!'); 98 | } 99 | } 100 | } 101 | 102 | main().catch((e) => { 103 | console.error(e); 104 | process.exit(1); 105 | }); 106 | -------------------------------------------------------------------------------- /force-batch-tool/.env.example: -------------------------------------------------------------------------------- 1 | PVTKEY_L1="your_l1_pvt_key" 2 | PVTKEY_L2_ZKEVM="your_l2_pvt_key" 3 | PROVIDER_TESTNET="your_rpc_goerli" 4 | PROVIDER_MAINNET="your_rpc_mainnet" -------------------------------------------------------------------------------- /force-batch-tool/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | 'mocha', 4 | ], 5 | env: { 6 | node: true, 7 | mocha: true, 8 | }, 9 | extends: 'airbnb-base', 10 | rules: { 11 | indent: ['error', 4], 12 | 'mocha/no-exclusive-tests': 'error', 13 | 'max-len': ['error', { 14 | code: 140, comments: 200, ignoreStrings: true, ignoreTemplateLiterals: true, 15 | }], 16 | 'no-unused-vars': [2, { varsIgnorePattern: 'export^' }], 17 | 'no-return-assign': [0], 18 | 'no-underscore-dangle': [0], 19 | 'no-plusplus': ['error', { allowForLoopAfterthoughts: true }], 20 | 'func-names': [0], 21 | 'class-methods-use-this': [0], 22 | 'no-bitwise': [0], 23 | 'no-param-reassign': 'off', 24 | 'no-console': 'off', 25 | 'import/prefer-default-export': [0], 26 | 'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }], 27 | 'no-await-in-loop': 'off', 28 | 'newline-before-return': 'error', 29 | }, 30 | parserOptions: { 31 | ecmaVersion: 2020, 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /force-batch-tool/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | forced-batch-data.json 3 | node_modules 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /force-batch-tool/README.md: -------------------------------------------------------------------------------- 1 | # Force batch tool 2 | This tool provides simple script to send force batches 3 | 4 | ## Requirements 5 | - node version: >= 14.x 6 | - npm version: >= 7.x 7 | 8 | ## Tool configuration 9 | All commands are executed in project root 10 | 11 | ### install dependencies 12 | ``` 13 | npm i 14 | ``` 15 | 16 | ### setup .env 17 | ``` 18 | cp .env.example .env 19 | ``` 20 | Fill `.env` with your parameteres: 21 | - `PVTKEY_L1` 22 | - `PROVIDER_TESTNET` 23 | - `PROVIDER_MAINNET` 24 | 25 | ### Pre-requisites 26 | - sending forces bacthes requires to pay an amount of MATIC (approval is required as well) 27 | - MATIC amount needed could be check in: 28 | - testnet: https://goerli.etherscan.io/address/0xa997cfd539e703921fd1e3cf25b4c241a27a4c7a#readProxyContract#F11 29 | - mainnet: https://etherscan.io/address/0x5132A183E9F3CB7C848b0AAC5Ae0c4f0491B7aB2#readProxyContract#F11 30 | - MATIC in `PVTKEY_L1` would be needed in order to perform the forced batch contract call 31 | - testnet 32 | - MATIC can be minted in: https://goerli.etherscan.io/address/0x1319d23c2f7034f52eb07399702b040ba278ca49#writeContract#F6 33 | - Approve MATIC to `0xa997cfd539e703921fd1e3cf25b4c241a27a4c7a` in: https://goerli.etherscan.io/address/0x1319d23c2f7034f52eb07399702b040ba278ca49#writeContract#F1 34 | - mainnet 35 | - buy MATIC in any DEX 36 | - Approve MATIC to `0x5132A183E9F3CB7C848b0AAC5Ae0c4f0491B7aB2` in: https://etherscan.io/token/0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0#writeContract#F1 37 | 38 | ### Fill forced data 39 | ``` 40 | cp forced-batch-data.example.json forced-batch-data.json 41 | ``` 42 | Fill it with the force batch data that you want to send 43 | 44 | > Note that examples to generate forced batch data has been done in `src/generate-force-batch` folder 45 | > Please, read `src/generate-force-batch/README.md` on how to use it 46 | 47 | ## Usage 48 | - Force batch on zkEVM testnet: 49 | ``` 50 | node src/send-force-batch.js --network testnet 51 | ``` 52 | 53 | - Force batch on zkEVM mainnet: 54 | ``` 55 | node src/send-force-batch.js --network mainnet 56 | ``` -------------------------------------------------------------------------------- /force-batch-tool/forced-batch-data.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "forcedBatchData": "your_data_here" 3 | } -------------------------------------------------------------------------------- /force-batch-tool/networks.json: -------------------------------------------------------------------------------- 1 | { 2 | "testnet": { 3 | "addressBridge": "0xF6BEEeBB578e214CA9E23B0e9683454Ff88Ed2A7", 4 | "addressZkEVM": "0xa997cfD539E703921fD1e3Cf25b4c241a27a4c7A", 5 | "chainID": 1442, 6 | "urlRPC": "https://rpc.public.zkevm-test.net/" 7 | }, 8 | "mainnet": { 9 | "addressBridge": "0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe", 10 | "addressZkEVM": "0x5132A183E9F3CB7C848b0AAC5Ae0c4f0491B7aB2", 11 | "chainID": 1101, 12 | "urlRPC": "https://zkevm-rpc.com" 13 | } 14 | } -------------------------------------------------------------------------------- /force-batch-tool/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "force-batch-tool", 3 | "version": "1.0.0", 4 | "description": "provides a simple script to send force batches", 5 | "main": "index.js", 6 | "scripts": { 7 | "eslint": "npx eslint src/*.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/0xPolygonHermez/code-examples.git" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/0xPolygonHermez/code-examples/issues" 18 | }, 19 | "homepage": "https://github.com/0xPolygonHermez/code-examples#readme", 20 | "dependencies": { 21 | "@0xpolygonhermez/zkevm-commonjs": "github:0xPolygonHermez/zkevm-commonjs#v1.0.0", 22 | "@0xpolygonhermez/zkevm-contracts": "github:0xPolygonHermez/zkevm-contracts#v1.1.0-fork.4", 23 | "dotenv": "^16.0.3", 24 | "eslint": "^8.41.0", 25 | "eslint-config-airbnb-base": "^15.0.0", 26 | "eslint-plugin-mocha": "^10.1.0", 27 | "ethers": "^5.7.2", 28 | "yargs": "^17.7.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /force-batch-tool/src/generate-force-batch-data/README.md: -------------------------------------------------------------------------------- 1 | # Generate force batch data 2 | These are just some examples of how to create forced batch data 3 | 4 | ## Requirements 5 | - node version: >= 14.x 6 | - npm version: >= 7.x 7 | 8 | ## Tool configuration 9 | All commands are executed in `src/generate-force-batch-data` 10 | 11 | ### install dependencies 12 | ``` 13 | npm i 14 | ``` 15 | 16 | ### setup .env 17 | ``` 18 | cp .env.example .env 19 | ``` 20 | Fill `.env` with your parameteres: 21 | - `PVTKEY_L2_ZKEVM` 22 | 23 | ### modify script internal parameters 24 | Each script has its own parameters that needs to be filled by the user 25 | All of them has a comment: `FILL IN BY THE USER` 26 | 27 | ## Script examples 28 | - `eth-transfer.js`: ethereum transfer 29 | 30 | ## Usage 31 | - Generate Force batch data on zkEVM testnet: 32 | ``` 33 | node ${name-script}.js --network testnet 34 | ``` 35 | 36 | - Generate Force batch data on zkEVM mainnet: 37 | ``` 38 | node ${name-script}.js --network mainnet 39 | ``` -------------------------------------------------------------------------------- /force-batch-tool/src/generate-force-batch-data/eth-transfer.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-dynamic-require */ 2 | const ethers = require('ethers'); 3 | const path = require('path'); 4 | require('dotenv').config({ path: path.resolve(__dirname, '../../.env') }); 5 | const { argv } = require('yargs') 6 | .alias('n', 'network'); 7 | 8 | const { rawTxToCustomRawTx } = require('@0xpolygonhermez/zkevm-commonjs').processorUtils; 9 | const params = require(path.join(__dirname, '../utils')); 10 | const networksConfig = require(path.join(__dirname, '../../networks.json')); 11 | 12 | async function main() { 13 | // get network 14 | const { network } = argv; 15 | params.checkNetwork(network, networksConfig); 16 | const networkInfo = networksConfig[network]; 17 | 18 | // load provider 19 | const provider = new ethers.providers.JsonRpcProvider(networkInfo.urlRPC); 20 | 21 | // load wallet 22 | const walletInit = new ethers.Wallet(process.env.PVTKEY_L2_ZKEVM); 23 | const wallet = walletInit.connect(provider); 24 | console.log('Wallet public address: ', wallet.address); 25 | 26 | // tx parameters 27 | // FILL IN BY THE USER: START 28 | const amountEthToSend = ethers.utils.parseUnits('0.01', 'ether'); 29 | const to = '0x0000000000000000000000000000000000000000'; 30 | // FILL IN BY THE USER: FINISH 31 | 32 | const tx = { 33 | to, 34 | value: amountEthToSend, 35 | }; 36 | 37 | // populate transaction 38 | const finalTx = await wallet.populateTransaction(tx); 39 | // sign transaction 40 | const txSigned = await wallet.signTransaction(finalTx); 41 | // get transaction parameters 42 | const parsedTx = ethers.utils.parseTransaction(txSigned); 43 | console.log('TX TO SEND: '); 44 | console.log(parsedTx, '\n'); 45 | 46 | // build batch data 47 | const batchData = rawTxToCustomRawTx(txSigned); 48 | console.log('FORCED BATCH DATA: '); 49 | console.log(batchData, '\n'); 50 | } 51 | 52 | main(); 53 | -------------------------------------------------------------------------------- /force-batch-tool/src/send-force-batch.js: -------------------------------------------------------------------------------- 1 | const ethers = require('ethers'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); 5 | const { argv } = require('yargs') 6 | .alias('n', 'network'); 7 | 8 | const abiPolygonZkEVM = require('@0xpolygonhermez/zkevm-contracts/compiled-contracts/PolygonZkEVM').abi; 9 | const params = require('./utils'); 10 | const networksConfig = require('../networks.json'); 11 | 12 | async function main() { 13 | // get network 14 | const { network } = argv; 15 | params.checkNetwork(network, networksConfig); 16 | const networkInfo = networksConfig[network]; 17 | 18 | // load provider 19 | const provider = new ethers.providers.JsonRpcProvider(process.env[`PROVIDER_${network.toUpperCase()}`]); 20 | 21 | // load wallet 22 | const walletInit = new ethers.Wallet(process.env.PVTKEY_L1); 23 | const wallet = walletInit.connect(provider); 24 | console.log('Wallet public address: ', wallet.address); 25 | 26 | // load polygonZkEVM contract interface 27 | const contract = new ethers.Contract(networkInfo.addressZkEVM, abiPolygonZkEVM, wallet); 28 | 29 | // get amount matic tokens to send 30 | const maticAddress = await contract.matic(); 31 | console.log('MATIC ERC20 token address: ', maticAddress); 32 | 33 | const amount = await contract.getForcedBatchFee(); 34 | console.log('MATIC amount needed to send a force batch: ', ethers.utils.formatUnits(amount, 'ether'), 'MATIC'); 35 | 36 | // get batchL2Data 37 | const { batchData } = JSON.parse(fs.readFileSync(path.join(__dirname, '../forced-batch-data.json'))); 38 | 39 | // send force batch to L1 40 | const resTx = await contract.forceBatch(batchData, amount); 41 | console.log('Forced batch transaction sent'); 42 | console.log('Info tx: ', resTx); 43 | } 44 | 45 | main(); 46 | -------------------------------------------------------------------------------- /force-batch-tool/src/utils.js: -------------------------------------------------------------------------------- 1 | function checkNetwork(network, infoNetworks) { 2 | if (!Object.keys(infoNetworks).includes(network)) { 3 | console.log(`Available deployments: ${Object.keys(infoNetworks)}`); 4 | console.log(' Specify deployment with: --network {networkName}'); 5 | throw new Error(`Deployment ${network} does not match any deployment info`); 6 | } 7 | } 8 | 9 | module.exports = { 10 | checkNetwork, 11 | }; -------------------------------------------------------------------------------- /pingPongExample/.env.example: -------------------------------------------------------------------------------- 1 | MNEMONIC="test test test test test test test test test test test junk" 2 | INFURA_PROJECT_ID="" 3 | ETHERSCAN_API_KEY="" 4 | ETHERSCAN_ZKEVM_API_KEY="" 5 | PVTKEY="" 6 | -------------------------------------------------------------------------------- /pingPongExample/.eslintignore: -------------------------------------------------------------------------------- 1 | hardhat.config.js -------------------------------------------------------------------------------- /pingPongExample/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | 'mocha', 4 | ], 5 | env: { 6 | node: true, 7 | mocha: true, 8 | }, 9 | extends: 'airbnb-base', 10 | rules: { 11 | indent: ['error', 4], 12 | 'mocha/no-exclusive-tests': 'error', 13 | 'max-len': ['error', { 14 | code: 140, comments: 200, ignoreStrings: true, ignoreTemplateLiterals: true, 15 | }], 16 | 'no-unused-vars': [2, { varsIgnorePattern: 'export^' }], 17 | 'no-return-assign': [0], 18 | 'no-underscore-dangle': [0], 19 | 'no-plusplus': ['error', { allowForLoopAfterthoughts: true }], 20 | 'func-names': [0], 21 | 'class-methods-use-this': [0], 22 | 'no-bitwise': [0], 23 | 'no-param-reassign': 'off', 24 | 'global-require': 'off', 25 | 'import/no-dynamic-require': 'off', 26 | 'no-console': [2, { allow: ['warn', 'error'] }], 27 | 'import/prefer-default-export': [0], 28 | 'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }], 29 | 'multiline-comment-style': 'error', 30 | 'import/no-extraneous-dependencies': 'off', 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /pingPongExample/.gitignore: -------------------------------------------------------------------------------- 1 | coverage.json 2 | .env 3 | cache 4 | build 5 | yarn.lock 6 | node_modules 7 | coverage.json 8 | package-lock.json 9 | coverage 10 | .coverage_* 11 | .openzeppelin 12 | artifacts/ 13 | docs/interfaces 14 | docs/mocks 15 | .vscode/launch.json 16 | deploy_output.json 17 | deploy_parameters.json 18 | deployments 19 | upgrade_parameters.json 20 | docker/gethData/ 21 | *.ignore/ -------------------------------------------------------------------------------- /pingPongExample/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": "*.sol", 5 | "options": { 6 | "printWidth": 80, 7 | "tabWidth": 4, 8 | "useTabs": false, 9 | "singleQuote": false, 10 | "bracketSpacing": false, 11 | "explicitTypes": "always" 12 | } 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /pingPongExample/.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: ['mocks', 'interfaces'] 3 | }; -------------------------------------------------------------------------------- /pingPongExample/.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "mark-callable-contracts": "off", 5 | "no-empty-blocks": "off", 6 | "compiler-version": ["error", "0.8.17"], 7 | "private-vars-leading-underscore": "error", 8 | "bracket-align": "off", 9 | "reason-string": "off", 10 | "not-rely-on-time": "off", 11 | "no-inline-assembly": "off", 12 | "check-send-result": "off" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pingPongExample/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "solidity.linter": "solhint", 4 | "solidity.compileUsingRemoteVersion": "v0.8.17+commit.e14f2714" 5 | } 6 | -------------------------------------------------------------------------------- /pingPongExample/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Catenable AG 2 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), 3 | to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 4 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 8 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /pingPongExample/README.md: -------------------------------------------------------------------------------- 1 | # Simple ping pong bridge example 2 | 3 | This repo provides a simple example on how bridge messages between networks 4 | 5 | ## Requirements 6 | 7 | - node version: >= 14.x 8 | - npm version: >= 7.x 9 | 10 | ## Deployment 11 | 12 | In project root execute: 13 | 14 | ``` 15 | npm i 16 | cp .env.example .env 17 | ``` 18 | 19 | Fill `.env` with your `MNEMONIC` or `PVTKEY` and `INFURA_PROJECT_ID` 20 | If you want to verify the contracts also fill the `ETHERSCAN_API_KEY` and `ETHERSCAN_ZKEVM_API_KEY` 21 | 22 | To deploy use:`deploy:pingPong:${network}`, currently is supported `goerli` and `mainnet`. 23 | This script will deploy a contract on 2 networks, a message sender and a message receiver: 24 | 25 | - `PingSender` on `mainnet`/`goerli` 26 | - `PingReceiver` on `polygonZKEVMTestnet`/`polygonZKEVMMainnet` 27 | 28 | As example for goerli testnet: 29 | 30 | ``` 31 | npm run deploy:pingPong:goerli 32 | ``` 33 | 34 | In the deployment we will find the results on `pingPong_output.json` 35 | 36 | To verify contracts use `npm run verify:sender:${network}` and `npm run verify:receiver:${network}` 37 | 38 | ``` 39 | npm run verify:sender:goerli 40 | npm run verify:receiver:polygonZKEVMTestnet 41 | 42 | ``` 43 | 44 | ## Using the bridge 45 | 46 | In order to use the bridge, there are already provided some scripts: 47 | 48 | - Send a message using: 49 | 50 | ``` 51 | npm run bridge:bridgePing:goerli 52 | ``` 53 | 54 | - Now we have to wait until the message is forwarded to L2, there's the final script that will check it and if it's ready will actually claim the NFT in the other layer: 55 | 56 | ``` 57 | npm run claim:claimPong:polygonZKEVMTestnet 58 | 59 | ``` 60 | 61 | - You can check that the value `pingValue` on the `PingReceiver` had change 62 | -------------------------------------------------------------------------------- /pingPongExample/contracts/PingReceiver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "./polygonZKEVMContracts/interfaces/IBridgeMessageReceiver.sol"; 6 | import "./polygonZKEVMContracts/interfaces/IPolygonZkEVMBridge.sol"; 7 | import "@openzeppelin/contracts/access/Ownable.sol"; 8 | 9 | /** 10 | * ZkEVMNFTBridge is an example contract to use the message layer of the PolygonZkEVMBridge to bridge NFTs 11 | */ 12 | contract PingReceiver is IBridgeMessageReceiver, Ownable { 13 | // Global Exit Root address 14 | IPolygonZkEVMBridge public immutable polygonZkEVMBridge; 15 | 16 | // Current network identifier 17 | uint32 public immutable networkID; 18 | 19 | // Address in the other network that will send the message 20 | address public pingSender; 21 | 22 | // Value sent from the other network 23 | uint256 public pingValue; 24 | 25 | /** 26 | * @param _polygonZkEVMBridge Polygon zkevm bridge address 27 | */ 28 | constructor(IPolygonZkEVMBridge _polygonZkEVMBridge) { 29 | polygonZkEVMBridge = _polygonZkEVMBridge; 30 | networkID = polygonZkEVMBridge.networkID(); 31 | } 32 | 33 | /** 34 | * @dev Emitted when a message is received from another network 35 | */ 36 | event PingReceived(uint256 pingValue); 37 | 38 | /** 39 | * @dev Emitted when change the sender 40 | */ 41 | event SetSender(address newPingSender); 42 | 43 | /** 44 | * @notice Set the sender of the message 45 | * @param newPingSender Address of the sender in the other network 46 | */ 47 | function setSender(address newPingSender) external onlyOwner { 48 | pingSender = newPingSender; 49 | emit SetSender(newPingSender); 50 | } 51 | 52 | /** 53 | * @notice Verify merkle proof and withdraw tokens/ether 54 | * @param originAddress Origin address that the message was sended 55 | * @param originNetwork Origin network that the message was sended ( not usefull for this contract) 56 | * @param data Abi encoded metadata 57 | */ 58 | function onMessageReceived( 59 | address originAddress, 60 | uint32 originNetwork, 61 | bytes memory data 62 | ) external payable override { 63 | // Can only be called by the bridge 64 | require( 65 | msg.sender == address(polygonZkEVMBridge), 66 | "PingReceiver::onMessageReceived: Not PolygonZkEVMBridge" 67 | ); 68 | 69 | // Can only be called by the sender on the other network 70 | require( 71 | pingSender == originAddress, 72 | "PingReceiver::onMessageReceived: Not ping Sender" 73 | ); 74 | 75 | // Decode data 76 | pingValue = abi.decode(data, (uint256)); 77 | 78 | emit PingReceived(pingValue); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /pingPongExample/contracts/PingSender.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "./polygonZKEVMContracts/interfaces/IBridgeMessageReceiver.sol"; 6 | import "./polygonZKEVMContracts/interfaces/IPolygonZkEVMBridge.sol"; 7 | import "@openzeppelin/contracts/access/Ownable.sol"; 8 | 9 | /** 10 | * ZkEVMNFTBridge is an example contract to use the message layer of the PolygonZkEVMBridge to bridge NFTs 11 | */ 12 | contract PingSender is Ownable { 13 | // Global Exit Root address 14 | IPolygonZkEVMBridge public immutable polygonZkEVMBridge; 15 | 16 | // Address in the other network that will receive the message 17 | address public pingReceiver; 18 | 19 | /** 20 | * @param _polygonZkEVMBridge Polygon zkevm bridge address 21 | */ 22 | constructor(IPolygonZkEVMBridge _polygonZkEVMBridge) { 23 | polygonZkEVMBridge = _polygonZkEVMBridge; 24 | } 25 | 26 | /** 27 | * @dev Emitted when send a message to another network 28 | */ 29 | event PingMessage(uint256 pingValue); 30 | 31 | /** 32 | * @dev Emitted when change the receiver 33 | */ 34 | event SetReceiver(address newPingReceiver); 35 | 36 | /** 37 | * @notice Send a message to the other network 38 | * @param destinationNetwork Network destination 39 | * @param forceUpdateGlobalExitRoot Indicates if the global exit root is updated or not 40 | */ 41 | function bridgePingMessage( 42 | uint32 destinationNetwork, 43 | bool forceUpdateGlobalExitRoot, 44 | uint256 pingValue 45 | ) public onlyOwner { 46 | bytes memory pingMessage = abi.encode(pingValue); 47 | 48 | // Bridge ping message 49 | polygonZkEVMBridge.bridgeMessage( 50 | destinationNetwork, 51 | pingReceiver, 52 | forceUpdateGlobalExitRoot, 53 | pingMessage 54 | ); 55 | 56 | emit PingMessage(pingValue); 57 | } 58 | 59 | /** 60 | * @notice Set the receiver of the message 61 | * @param newPingReceiver Address of the receiver in the other network 62 | */ 63 | function setReceiver(address newPingReceiver) external onlyOwner { 64 | pingReceiver = newPingReceiver; 65 | emit SetReceiver(newPingReceiver); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /pingPongExample/contracts/polygonZKEVMContracts/PolygonZkEVMGlobalExitRoot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "./interfaces/IPolygonZkEVMGlobalExitRoot.sol"; 6 | import "./lib/GlobalExitRootLib.sol"; 7 | 8 | /** 9 | * Contract responsible for managing the exit roots across multiple networks 10 | */ 11 | contract PolygonZkEVMGlobalExitRoot is IPolygonZkEVMGlobalExitRoot { 12 | // PolygonZkEVMBridge address 13 | address public immutable bridgeAddress; 14 | 15 | // Rollup contract address 16 | address public immutable rollupAddress; 17 | 18 | // Rollup exit root, this will be updated every time a batch is verified 19 | bytes32 public lastRollupExitRoot; 20 | 21 | // Mainnet exit root, this will be updated every time a deposit is made in mainnet 22 | bytes32 public lastMainnetExitRoot; 23 | 24 | // Store every global exit root: Root --> timestamp 25 | mapping(bytes32 => uint256) public globalExitRootMap; 26 | 27 | /** 28 | * @dev Emitted when the global exit root is updated 29 | */ 30 | event UpdateGlobalExitRoot( 31 | bytes32 indexed mainnetExitRoot, 32 | bytes32 indexed rollupExitRoot 33 | ); 34 | 35 | /** 36 | * @param _rollupAddress Rollup contract address 37 | * @param _bridgeAddress PolygonZkEVMBridge contract address 38 | */ 39 | constructor(address _rollupAddress, address _bridgeAddress) { 40 | rollupAddress = _rollupAddress; 41 | bridgeAddress = _bridgeAddress; 42 | } 43 | 44 | /** 45 | * @notice Update the exit root of one of the networks and the global exit root 46 | * @param newRoot new exit tree root 47 | */ 48 | function updateExitRoot(bytes32 newRoot) external { 49 | // Store storage variables into temporal variables since will be used multiple times 50 | bytes32 cacheLastRollupExitRoot = lastRollupExitRoot; 51 | bytes32 cacheLastMainnetExitRoot = lastMainnetExitRoot; 52 | 53 | if (msg.sender == bridgeAddress) { 54 | lastMainnetExitRoot = newRoot; 55 | cacheLastMainnetExitRoot = newRoot; 56 | } else if (msg.sender == rollupAddress) { 57 | lastRollupExitRoot = newRoot; 58 | cacheLastRollupExitRoot = newRoot; 59 | } else { 60 | revert OnlyAllowedContracts(); 61 | } 62 | 63 | bytes32 newGlobalExitRoot = GlobalExitRootLib.calculateGlobalExitRoot( 64 | cacheLastMainnetExitRoot, 65 | cacheLastRollupExitRoot 66 | ); 67 | 68 | // If it already exists, do not modify the timestamp 69 | if (globalExitRootMap[newGlobalExitRoot] == 0) { 70 | globalExitRootMap[newGlobalExitRoot] = block.timestamp; 71 | emit UpdateGlobalExitRoot( 72 | cacheLastMainnetExitRoot, 73 | cacheLastRollupExitRoot 74 | ); 75 | } 76 | } 77 | 78 | /** 79 | * @notice Return last global exit root 80 | */ 81 | function getLastGlobalExitRoot() public view returns (bytes32) { 82 | return 83 | GlobalExitRootLib.calculateGlobalExitRoot( 84 | lastMainnetExitRoot, 85 | lastRollupExitRoot 86 | ); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /pingPongExample/contracts/polygonZKEVMContracts/interfaces/IBasePolygonZkEVMGlobalExitRoot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | interface IBasePolygonZkEVMGlobalExitRoot { 6 | /** 7 | * @dev Thrown when the caller is not the allowed contracts 8 | */ 9 | error OnlyAllowedContracts(); 10 | 11 | function updateExitRoot(bytes32 newRollupExitRoot) external; 12 | 13 | function globalExitRootMap( 14 | bytes32 globalExitRootNum 15 | ) external returns (uint256); 16 | } 17 | -------------------------------------------------------------------------------- /pingPongExample/contracts/polygonZKEVMContracts/interfaces/IBridgeMessageReceiver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | /** 6 | * @dev Define interface for PolygonZkEVM Bridge message receiver 7 | */ 8 | interface IBridgeMessageReceiver { 9 | function onMessageReceived( 10 | address originAddress, 11 | uint32 originNetwork, 12 | bytes memory data 13 | ) external payable; 14 | } 15 | -------------------------------------------------------------------------------- /pingPongExample/contracts/polygonZKEVMContracts/interfaces/IPolygonZkEVMBridge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | interface IPolygonZkEVMBridge { 6 | /** 7 | * @dev Thrown when sender is not the PolygonZkEVM address 8 | */ 9 | error OnlyPolygonZkEVM(); 10 | 11 | /** 12 | * @dev Thrown when the destination network is invalid 13 | */ 14 | error DestinationNetworkInvalid(); 15 | 16 | /** 17 | * @dev Thrown when the amount does not match msg.value 18 | */ 19 | error AmountDoesNotMatchMsgValue(); 20 | 21 | /** 22 | * @dev Thrown when user is bridging tokens and is also sending a value 23 | */ 24 | error MsgValueNotZero(); 25 | 26 | /** 27 | * @dev Thrown when the Ether transfer on claimAsset fails 28 | */ 29 | error EtherTransferFailed(); 30 | 31 | /** 32 | * @dev Thrown when the message transaction on claimMessage fails 33 | */ 34 | error MessageFailed(); 35 | 36 | /** 37 | * @dev Thrown when the global exit root does not exist 38 | */ 39 | error GlobalExitRootInvalid(); 40 | 41 | /** 42 | * @dev Thrown when the smt proof does not match 43 | */ 44 | error InvalidSmtProof(); 45 | 46 | /** 47 | * @dev Thrown when an index is already claimed 48 | */ 49 | error AlreadyClaimed(); 50 | 51 | /** 52 | * @dev Thrown when the owner of permit does not match the sender 53 | */ 54 | error NotValidOwner(); 55 | 56 | /** 57 | * @dev Thrown when the spender of the permit does not match this contract address 58 | */ 59 | error NotValidSpender(); 60 | 61 | /** 62 | * @dev Thrown when the amount of the permit does not match 63 | */ 64 | error NotValidAmount(); 65 | 66 | /** 67 | * @dev Thrown when the permit data contains an invalid signature 68 | */ 69 | error NotValidSignature(); 70 | 71 | function bridgeAsset( 72 | uint32 destinationNetwork, 73 | address destinationAddress, 74 | uint256 amount, 75 | address token, 76 | bool forceUpdateGlobalExitRoot, 77 | bytes calldata permitData 78 | ) external payable; 79 | 80 | function bridgeMessage( 81 | uint32 destinationNetwork, 82 | address destinationAddress, 83 | bool forceUpdateGlobalExitRoot, 84 | bytes calldata metadata 85 | ) external payable; 86 | 87 | function claimAsset( 88 | bytes32[32] calldata smtProof, 89 | uint32 index, 90 | bytes32 mainnetExitRoot, 91 | bytes32 rollupExitRoot, 92 | uint32 originNetwork, 93 | address originTokenAddress, 94 | uint32 destinationNetwork, 95 | address destinationAddress, 96 | uint256 amount, 97 | bytes calldata metadata 98 | ) external; 99 | 100 | function claimMessage( 101 | bytes32[32] calldata smtProof, 102 | uint32 index, 103 | bytes32 mainnetExitRoot, 104 | bytes32 rollupExitRoot, 105 | uint32 originNetwork, 106 | address originAddress, 107 | uint32 destinationNetwork, 108 | address destinationAddress, 109 | uint256 amount, 110 | bytes calldata metadata 111 | ) external; 112 | 113 | function updateGlobalExitRoot() external; 114 | 115 | function activateEmergencyState() external; 116 | 117 | function deactivateEmergencyState() external; 118 | 119 | function networkID() external returns(uint32); 120 | } 121 | -------------------------------------------------------------------------------- /pingPongExample/contracts/polygonZKEVMContracts/interfaces/IPolygonZkEVMGlobalExitRoot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | import "./IBasePolygonZkEVMGlobalExitRoot.sol"; 5 | 6 | interface IPolygonZkEVMGlobalExitRoot is IBasePolygonZkEVMGlobalExitRoot { 7 | function getLastGlobalExitRoot() external view returns (bytes32); 8 | } 9 | -------------------------------------------------------------------------------- /pingPongExample/contracts/polygonZKEVMContracts/lib/EmergencyManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | /** 6 | * @dev Contract helper responsible to manage the emergency state 7 | */ 8 | contract EmergencyManager { 9 | /** 10 | * @dev Thrown when emergency state is active, and the function requires otherwise 11 | */ 12 | error OnlyNotEmergencyState(); 13 | 14 | /** 15 | * @dev Thrown when emergency state is not active, and the function requires otherwise 16 | */ 17 | error OnlyEmergencyState(); 18 | 19 | /** 20 | * @dev This empty reserved space is put in place to allow future versions to add new 21 | * variables without shifting down storage in the inheritance chain. 22 | */ 23 | uint256[10] private _gap; 24 | 25 | // Indicates whether the emergency state is active or not 26 | bool public isEmergencyState; 27 | 28 | /** 29 | * @dev Emitted when emergency state is activated 30 | */ 31 | event EmergencyStateActivated(); 32 | 33 | /** 34 | * @dev Emitted when emergency state is deactivated 35 | */ 36 | event EmergencyStateDeactivated(); 37 | 38 | /** 39 | * @notice Only allows a function to be callable if emergency state is unactive 40 | */ 41 | modifier ifNotEmergencyState() { 42 | if (isEmergencyState) { 43 | revert OnlyNotEmergencyState(); 44 | } 45 | _; 46 | } 47 | 48 | /** 49 | * @notice Only allows a function to be callable if emergency state is active 50 | */ 51 | modifier ifEmergencyState() { 52 | if (!isEmergencyState) { 53 | revert OnlyEmergencyState(); 54 | } 55 | _; 56 | } 57 | 58 | /** 59 | * @notice Activate emergency state 60 | */ 61 | function _activateEmergencyState() internal virtual ifNotEmergencyState { 62 | isEmergencyState = true; 63 | emit EmergencyStateActivated(); 64 | } 65 | 66 | /** 67 | * @notice Deactivate emergency state 68 | */ 69 | function _deactivateEmergencyState() internal virtual ifEmergencyState { 70 | isEmergencyState = false; 71 | emit EmergencyStateDeactivated(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /pingPongExample/contracts/polygonZKEVMContracts/lib/GlobalExitRootLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | /** 6 | * @dev A library that provides the necessary calculations to calculate the global exit root 7 | */ 8 | library GlobalExitRootLib { 9 | function calculateGlobalExitRoot( 10 | bytes32 mainnetExitRoot, 11 | bytes32 rollupExitRoot 12 | ) internal pure returns (bytes32) { 13 | return keccak256(abi.encodePacked(mainnetExitRoot, rollupExitRoot)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pingPongExample/contracts/polygonZKEVMContracts/lib/TokenWrapped.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | // Implementation of permit based on https://github.com/WETH10/WETH10/blob/main/contracts/WETH10.sol 3 | pragma solidity 0.8.17; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | 7 | contract TokenWrapped is ERC20 { 8 | // Domain typehash 9 | bytes32 public constant DOMAIN_TYPEHASH = 10 | keccak256( 11 | "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" 12 | ); 13 | // Permit typehash 14 | bytes32 public constant PERMIT_TYPEHASH = 15 | keccak256( 16 | "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" 17 | ); 18 | 19 | // Version 20 | string public constant VERSION = "1"; 21 | 22 | // Chain id on deployment 23 | uint256 public immutable deploymentChainId; 24 | 25 | // Domain separator calculated on deployment 26 | bytes32 private immutable _DEPLOYMENT_DOMAIN_SEPARATOR; 27 | 28 | // PolygonZkEVM Bridge address 29 | address public immutable bridgeAddress; 30 | 31 | // Decimals 32 | uint8 private immutable _decimals; 33 | 34 | // Permit nonces 35 | mapping(address => uint256) public nonces; 36 | 37 | modifier onlyBridge() { 38 | require( 39 | msg.sender == bridgeAddress, 40 | "TokenWrapped::onlyBridge: Not PolygonZkEVMBridge" 41 | ); 42 | _; 43 | } 44 | 45 | constructor( 46 | string memory name, 47 | string memory symbol, 48 | uint8 __decimals 49 | ) ERC20(name, symbol) { 50 | bridgeAddress = msg.sender; 51 | _decimals = __decimals; 52 | deploymentChainId = block.chainid; 53 | _DEPLOYMENT_DOMAIN_SEPARATOR = _calculateDomainSeparator(block.chainid); 54 | } 55 | 56 | function mint(address to, uint256 value) external onlyBridge { 57 | _mint(to, value); 58 | } 59 | 60 | // Notice that is not require to approve wrapped tokens to use the bridge 61 | function burn(address account, uint256 value) external onlyBridge { 62 | _burn(account, value); 63 | } 64 | 65 | function decimals() public view virtual override returns (uint8) { 66 | return _decimals; 67 | } 68 | 69 | // Permit relative functions 70 | function permit( 71 | address owner, 72 | address spender, 73 | uint256 value, 74 | uint256 deadline, 75 | uint8 v, 76 | bytes32 r, 77 | bytes32 s 78 | ) external { 79 | require( 80 | block.timestamp <= deadline, 81 | "TokenWrapped::permit: Expired permit" 82 | ); 83 | 84 | bytes32 hashStruct = keccak256( 85 | abi.encode( 86 | PERMIT_TYPEHASH, 87 | owner, 88 | spender, 89 | value, 90 | nonces[owner]++, 91 | deadline 92 | ) 93 | ); 94 | 95 | bytes32 digest = keccak256( 96 | abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), hashStruct) 97 | ); 98 | 99 | address signer = ecrecover(digest, v, r, s); 100 | require( 101 | signer != address(0) && signer == owner, 102 | "TokenWrapped::permit: Invalid signature" 103 | ); 104 | 105 | _approve(owner, spender, value); 106 | } 107 | 108 | /** 109 | * @notice Calculate domain separator, given a chainID. 110 | * @param chainId Current chainID 111 | */ 112 | function _calculateDomainSeparator( 113 | uint256 chainId 114 | ) private view returns (bytes32) { 115 | return 116 | keccak256( 117 | abi.encode( 118 | DOMAIN_TYPEHASH, 119 | keccak256(bytes(name())), 120 | keccak256(bytes(VERSION)), 121 | chainId, 122 | address(this) 123 | ) 124 | ); 125 | } 126 | 127 | /// @dev Return the DOMAIN_SEPARATOR. 128 | function DOMAIN_SEPARATOR() public view returns (bytes32) { 129 | return 130 | block.chainid == deploymentChainId 131 | ? _DEPLOYMENT_DOMAIN_SEPARATOR 132 | : _calculateDomainSeparator(block.chainid); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /pingPongExample/deployment/deployPingPong.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-await-in-loop, no-use-before-define, no-lonely-if, import/no-dynamic-require, global-require */ 2 | /* eslint-disable no-console, no-inner-declarations, no-undef, import/no-unresolved, no-restricted-syntax */ 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); 6 | const { ethers } = require('hardhat'); 7 | 8 | const mainnetBridgeAddress = '0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe'; 9 | const testnetBridgeAddress = '0xF6BEEeBB578e214CA9E23B0e9683454Ff88Ed2A7'; 10 | 11 | async function main() { 12 | let zkEVMProvider; 13 | let zkEVMBridgeContractAddress; 14 | 15 | const networkName = process.env.HARDHAT_NETWORK; 16 | 17 | // Use mainnet bridge address 18 | if (networkName === 'mainnet') { 19 | zkEVMBridgeContractAddress = mainnetBridgeAddress; 20 | zkEVMProvider = new ethers.providers.JsonRpcProvider('https://zkevm-rpc.com'); 21 | } else if (networkName === 'goerli') { 22 | // Use testnet bridge address 23 | zkEVMBridgeContractAddress = testnetBridgeAddress; 24 | zkEVMProvider = new ethers.providers.JsonRpcProvider('https://rpc.public.zkevm-test.net'); 25 | } else { 26 | throw new Error('Network not supported'); 27 | } 28 | 29 | // Load deployer 30 | let deployer; 31 | if (process.env.PVTKEY) { 32 | deployer = new ethers.Wallet(process.env.PVTKEY, ethers.provider); 33 | deployerZkEVM = new ethers.Wallet(process.env.PVTKEY, zkEVMProvider); 34 | console.log('Using pvtKey deployer with address: ', deployer.address); 35 | } else if (process.env.MNEMONIC) { 36 | deployer = ethers.Wallet.fromMnemonic(process.env.MNEMONIC, 'm/44\'/60\'/0\'/0/0').connect(ethers.provider); 37 | deployerZkEVM = ethers.Wallet.fromMnemonic(process.env.MNEMONIC, 'm/44\'/60\'/0\'/0/0').connect(zkEVMProvider); 38 | console.log('Using MNEMONIC deployer with address: ', deployer.address); 39 | } else { 40 | [deployer] = (await ethers.getSigners()); 41 | } 42 | 43 | // Deploy Ping sender on goerli / zkevm mainnet 44 | const pingSenderFactory = await ethers.getContractFactory('PingSender', deployer); 45 | const pingSenderContract = await pingSenderFactory.deploy( 46 | zkEVMBridgeContractAddress, 47 | ); 48 | await pingSenderContract.deployed(); 49 | 50 | console.log('Ping sender deployed on: ', pingSenderContract.address); 51 | 52 | // Deploy Ping receiver on zkevm testnet /zkevm mainnet 53 | const pingREceiverFactory = await ethers.getContractFactory('PingReceiver', deployerZkEVM); 54 | const pingReceiverContract = await pingREceiverFactory.deploy( 55 | zkEVMBridgeContractAddress, 56 | ); 57 | await pingReceiverContract.deployed(); 58 | 59 | // Set address on both networks 60 | await pingSenderContract.setReceiver(pingReceiverContract.address); 61 | await pingReceiverContract.setSender(pingSenderContract.address); 62 | 63 | // Write output 64 | const outputJson = { 65 | pingSenderContract: pingSenderContract.address, 66 | pingReceiverContract: pingReceiverContract.address, 67 | }; 68 | const pathOutputJson = path.join(__dirname, './pingPong_output.json'); 69 | fs.writeFileSync(pathOutputJson, JSON.stringify(outputJson, null, 1)); 70 | } 71 | 72 | main().catch((e) => { 73 | console.error(e); 74 | process.exit(1); 75 | }); 76 | -------------------------------------------------------------------------------- /pingPongExample/deployment/pingPong_output.json: -------------------------------------------------------------------------------- 1 | { 2 | "pingSenderContract": "0x4c5efD3f10edc0820938081ae9BB782b60Db1FD7", 3 | "pingReceiverContract": "0xcdb3c0a988a78c4C9E5002EBB9BA879365bB7979" 4 | } -------------------------------------------------------------------------------- /pingPongExample/deployment/verifyReceiver.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-dynamic-require, no-await-in-loop, no-restricted-syntax, guard-for-in */ 2 | require('dotenv').config(); 3 | const path = require('path'); 4 | const hre = require('hardhat'); 5 | const expect = require('chai'); 6 | 7 | const mainnetBridgeAddress = '0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe'; 8 | const testnetBridgeAddress = '0xF6BEEeBB578e214CA9E23B0e9683454Ff88Ed2A7'; 9 | 10 | async function main() { 11 | const networkName = process.env.HARDHAT_NETWORK; 12 | const pathDeployOutputParameters = path.join(__dirname, './pingPong_output.json'); 13 | const deployOutputParameters = require(pathDeployOutputParameters); 14 | 15 | let zkEVMBridgeContractAddress; 16 | // Use mainnet bridge address 17 | if (networkName === 'polygonZKEVMMainnet' || networkName === 'mainnet') { 18 | zkEVMBridgeContractAddress = mainnetBridgeAddress; 19 | } else if (networkName === 'polygonZKEVMTestnet' || networkName === 'goerli') { 20 | // Use testnet bridge address 21 | zkEVMBridgeContractAddress = testnetBridgeAddress; 22 | } 23 | 24 | try { 25 | // verify governance 26 | await hre.run( 27 | 'verify:verify', 28 | { 29 | address: deployOutputParameters.pingReceiverContract, 30 | constructorArguments: [ 31 | zkEVMBridgeContractAddress, 32 | ], 33 | }, 34 | ); 35 | } catch (error) { 36 | expect(error.message.toLowerCase().includes('already verified')).to.be.equal(true); 37 | } 38 | } 39 | 40 | main() 41 | .then(() => process.exit(0)) 42 | .catch((error) => { 43 | console.error(error); 44 | process.exit(1); 45 | }); 46 | -------------------------------------------------------------------------------- /pingPongExample/deployment/verifySender.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-dynamic-require, no-await-in-loop, no-restricted-syntax, guard-for-in */ 2 | require('dotenv').config(); 3 | const path = require('path'); 4 | const hre = require('hardhat'); 5 | const expect = require('chai'); 6 | 7 | const mainnetBridgeAddress = '0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe'; 8 | const testnetBridgeAddress = '0xF6BEEeBB578e214CA9E23B0e9683454Ff88Ed2A7'; 9 | 10 | async function main() { 11 | const networkName = process.env.HARDHAT_NETWORK; 12 | const pathDeployOutputParameters = path.join(__dirname, './pingPong_output.json'); 13 | const deployOutputParameters = require(pathDeployOutputParameters); 14 | 15 | let zkEVMBridgeContractAddress; 16 | // Use mainnet bridge address 17 | if (networkName === 'mainnet') { 18 | zkEVMBridgeContractAddress = mainnetBridgeAddress; 19 | } else if (networkName === 'goerli') { 20 | // Use testnet bridge address 21 | zkEVMBridgeContractAddress = testnetBridgeAddress; 22 | } 23 | 24 | try { 25 | // verify governance 26 | await hre.run( 27 | 'verify:verify', 28 | { 29 | address: deployOutputParameters.pingSenderContract, 30 | constructorArguments: [ 31 | zkEVMBridgeContractAddress, 32 | ], 33 | }, 34 | ); 35 | } catch (error) { 36 | expect(error.message.toLowerCase().includes('already verified')).to.be.equal(true); 37 | } 38 | } 39 | 40 | main() 41 | .then(() => process.exit(0)) 42 | .catch((error) => { 43 | console.error(error); 44 | process.exit(1); 45 | }); 46 | -------------------------------------------------------------------------------- /pingPongExample/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@0xpolygonhermez/code-examples", 3 | "description": "ping pong example for polygon zkEVM", 4 | "version": "0.1.0.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/0xPolygonHermez/code-examples.git" 8 | }, 9 | "main": "index.js", 10 | "keywords": [ 11 | "zkevm", 12 | "snark", 13 | "polygon", 14 | "hermez", 15 | "stark", 16 | "EVM", 17 | "ethereum", 18 | "blockchain" 19 | ], 20 | "author": "0xPolygonHermez", 21 | "dependencies": { 22 | "axios": "^1.3.5", 23 | "chai": "^4.3.7", 24 | "ethers": "^5.7.2" 25 | }, 26 | "devDependencies": { 27 | "@0xpolygonhermez/zkevm-commonjs": "github:0xPolygonHermez/zkevm-commonjs#develop", 28 | "@nomiclabs/hardhat-ethers": "^2.2.2", 29 | "@nomiclabs/hardhat-etherscan": "^3.1.7", 30 | "@nomiclabs/hardhat-waffle": "^2.0.5", 31 | "@openzeppelin/contracts": "4.8.2", 32 | "@openzeppelin/contracts-upgradeable": "4.8.2", 33 | "@openzeppelin/hardhat-upgrades": "1.22.1", 34 | "@openzeppelin/test-helpers": "0.5.16", 35 | "dotenv": "^8.6.0", 36 | "eslint": "^8.36.0", 37 | "eslint-config-airbnb-base": "^15.0.0", 38 | "eslint-plugin-mocha": "^9.0.0", 39 | "ethereum-waffle": "^3.4.4", 40 | "hardhat": "^2.13.0", 41 | "hardhat-gas-reporter": "^1.0.9", 42 | "prettier": "^2.8.4", 43 | "prettier-plugin-solidity": "^1.1.3", 44 | "solc-0.8": "npm:solc@0.8.17", 45 | "solidity-coverage": "^0.7.22", 46 | "solidity-docgen": "^0.5.17" 47 | }, 48 | "scripts": { 49 | "deploy:pingPong:goerli": "npx hardhat run deployment/deployPingPong.js --network goerli", 50 | "deploy:pingPong:mainnet": "npx hardhat run deployment/deployPingPong.js --network mainnet", 51 | "verify:sender:goerli": "npx hardhat run deployment/verifySender.js --network goerli", 52 | "verify:sender:mainnet": "npx hardhat run deployment/verifySender.js --network mainnet", 53 | "verify:receiver:polygonZKEVMTestnet": "npx hardhat run deployment/verifyReceiver.js --network polygonZKEVMTestnet", 54 | "verify:receiver:polygonZKEVMMainnet": "npx hardhat run deployment/verifyReceiver.js --network polygonZKEVMMainnet", 55 | "lint": "npx eslint ./test && npx eslint ./docker/scripts && npx eslint ./deployment && npx eslint ./src", 56 | "lint:fix": "npx eslint ./test --fix && npx eslint ./docker/scripts --fix && npx eslint ./deployment --fix && npx eslint ./src --fix", 57 | "compile": "npx hardhat compile", 58 | "bridge:bridgePing:goerli": "npx hardhat run scripts/bridgePing.js --network goerli", 59 | "bridge:bridgePing:mainnet": "npx hardhat run scripts/bridgePing.js --network mainnet", 60 | "claim:claimPong:polygonZKEVMTestnet": "npx hardhat run scripts/claimPong.js --network polygonZKEVMTestnet", 61 | "claim:claimPong:polygonZKEVMMainnet": "npx hardhat run scripts/claimPong.js --network polygonZKEVMMainnet" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /pingPongExample/scripts/bridgePing.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-await-in-loop, no-use-before-define, no-lonely-if, import/no-dynamic-require, global-require */ 2 | /* eslint-disable no-console, no-inner-declarations, no-undef, import/no-unresolved, no-restricted-syntax */ 3 | const path = require('path'); 4 | require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); 5 | const { ethers } = require('hardhat'); 6 | 7 | // const networkIDMainnet = 0; 8 | const networkIDzkEVM = 1; 9 | 10 | const pathPingPongOutput = path.join(__dirname, '../deployment/pingPong_output.json'); 11 | const pingSenderContractAddress = require(pathPingPongOutput).pingSenderContract; 12 | 13 | async function main() { 14 | // Load deployer 15 | let deployer; 16 | if (process.env.PVTKEY) { 17 | deployer = new ethers.Wallet(process.env.PVTKEY, ethers.provider); 18 | console.log('Using pvtKey deployer with address: ', deployer.address); 19 | } else if (process.env.MNEMONIC) { 20 | deployer = ethers.Wallet.fromMnemonic(process.env.MNEMONIC, 'm/44\'/60\'/0\'/0/0').connect(ethers.provider); 21 | console.log('Using MNEMONIC deployer with address: ', deployer.address); 22 | } else { 23 | [deployer] = (await ethers.getSigners()); 24 | } 25 | 26 | const nftBridgeFactory = await ethers.getContractFactory('PingSender', deployer); 27 | const nftBridgeContract = await nftBridgeFactory.attach( 28 | pingSenderContractAddress, 29 | ); 30 | 31 | const forceUpdateGlobalExitRoot = true; // fast bridge 32 | const pingValue = 69420; 33 | const tx = await nftBridgeContract.bridgePingMessage( 34 | networkIDzkEVM, // Send to the zkEVM 35 | forceUpdateGlobalExitRoot, 36 | pingValue, 37 | ); 38 | 39 | console.log(await tx.wait()); 40 | 41 | console.log('Bridge done succesfully'); 42 | } 43 | 44 | main().catch((e) => { 45 | console.error(e); 46 | process.exit(1); 47 | }); 48 | -------------------------------------------------------------------------------- /pingPongExample/scripts/claimPong.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-await-in-loop */ 2 | /* eslint-disable no-console, no-inner-declarations, no-undef, import/no-unresolved */ 3 | 4 | const path = require('path'); 5 | require('dotenv').config({ path: path.resolve(__dirname, '../../.env') }); 6 | const { ethers } = require('hardhat'); 7 | 8 | const mainnetBridgeAddress = '0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe'; 9 | const testnetBridgeAddress = '0xF6BEEeBB578e214CA9E23B0e9683454Ff88Ed2A7'; 10 | 11 | const mekrleProofString = '/merkle-proof'; 12 | const getClaimsFromAcc = '/bridges/'; 13 | 14 | const pathPingPongOutput = path.join(__dirname, '../deployment/pingPong_output.json'); 15 | const pingReceiverContractAddress = require(pathPingPongOutput).pingReceiverContract; 16 | 17 | async function main() { 18 | const currentProvider = ethers.provider; 19 | let deployer; 20 | if (process.env.PVTKEY) { 21 | deployer = new ethers.Wallet(process.env.PVTKEY, currentProvider); 22 | console.log('Using pvtKey deployer with address: ', deployer.address); 23 | } else if (process.env.MNEMONIC) { 24 | deployer = ethers.Wallet.fromMnemonic(process.env.MNEMONIC, 'm/44\'/60\'/0\'/0/0').connect(currentProvider); 25 | console.log('Using MNEMONIC deployer with address: ', deployer.address); 26 | } else { 27 | [deployer] = (await ethers.getSigners()); 28 | } 29 | 30 | let zkEVMBridgeContractAddress; let 31 | baseURL; 32 | const networkName = process.env.HARDHAT_NETWORK; 33 | 34 | // Use mainnet bridge address 35 | if (networkName === 'polygonZKEVMMainnet' || networkName === 'mainnet') { 36 | zkEVMBridgeContractAddress = mainnetBridgeAddress; 37 | baseURL = 'https://bridge-api.zkevm-rpc.com'; 38 | } else if (networkName === 'polygonZKEVMTestnet' || networkName === 'goerli') { 39 | // Use testnet bridge address 40 | zkEVMBridgeContractAddress = testnetBridgeAddress; 41 | baseURL = 'https://bridge-api.public.zkevm-test.net'; 42 | } 43 | 44 | const axios = require('axios').create({ 45 | baseURL, 46 | }); 47 | 48 | const bridgeFactoryZkeEVm = await ethers.getContractFactory('PolygonZkEVMBridge', deployer); 49 | const bridgeContractZkeVM = bridgeFactoryZkeEVm.attach(zkEVMBridgeContractAddress); 50 | 51 | const depositAxions = await axios.get(getClaimsFromAcc + pingReceiverContractAddress, { params: { limit: 100, offset: 0 } }); 52 | const depositsArray = depositAxions.data.deposits; 53 | 54 | if (depositsArray.length === 0) { 55 | console.log('Not ready yet!'); 56 | return; 57 | } 58 | 59 | for (let i = 0; i < depositsArray.length; i++) { 60 | const currentDeposit = depositsArray[i]; 61 | if (currentDeposit.ready_for_claim) { 62 | const proofAxios = await axios.get(mekrleProofString, { 63 | params: { deposit_cnt: currentDeposit.deposit_cnt, net_id: currentDeposit.orig_net }, 64 | }); 65 | 66 | const { proof } = proofAxios.data; 67 | const claimTx = await bridgeContractZkeVM.claimMessage( 68 | proof.merkle_proof, 69 | currentDeposit.deposit_cnt, 70 | proof.main_exit_root, 71 | proof.rollup_exit_root, 72 | currentDeposit.orig_net, 73 | currentDeposit.orig_addr, 74 | currentDeposit.dest_net, 75 | currentDeposit.dest_addr, 76 | currentDeposit.amount, 77 | currentDeposit.metadata, 78 | ); 79 | console.log('claim message succesfully send: ', claimTx.hash); 80 | await claimTx.wait(); 81 | console.log('claim message succesfully mined'); 82 | } else { 83 | console.log('bridge not ready for claim'); 84 | } 85 | } 86 | } 87 | 88 | main().catch((e) => { 89 | console.error(e); 90 | process.exit(1); 91 | }); 92 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/.env.example: -------------------------------------------------------------------------------- 1 | MNEMONIC="test test test test test test test test test test test junk" 2 | INFURA_PROJECT_ID="" 3 | ETHERSCAN_API_KEY="" 4 | ETHERSCAN_ZKEVM_API_KEY="" 5 | PVTKEY="" 6 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | 'mocha', 4 | ], 5 | env: { 6 | node: true, 7 | mocha: true, 8 | }, 9 | extends: 'airbnb-base', 10 | rules: { 11 | indent: ['error', 4], 12 | 'mocha/no-exclusive-tests': 'error', 13 | 'max-len': ['error', { 14 | code: 140, comments: 200, ignoreStrings: true, ignoreTemplateLiterals: true, 15 | }], 16 | 'no-unused-vars': [2, { varsIgnorePattern: 'export^' }], 17 | 'no-return-assign': [0], 18 | 'no-underscore-dangle': [0], 19 | 'no-plusplus': ['error', { allowForLoopAfterthoughts: true }], 20 | 'func-names': [0], 21 | 'class-methods-use-this': [0], 22 | 'no-bitwise': [0], 23 | 'no-param-reassign': 'off', 24 | 'global-require': 'off', 25 | 'import/no-dynamic-require': 'off', 26 | 'no-console': [2, { allow: ['warn', 'error'] }], 27 | 'import/prefer-default-export': [0], 28 | 'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }], 29 | 'multiline-comment-style': 'error', 30 | 'import/no-extraneous-dependencies': 'off', 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/.gitignore: -------------------------------------------------------------------------------- 1 | coverage.json 2 | .env 3 | cache 4 | artifacts 5 | build 6 | yarn.lock 7 | node_modules 8 | coverage.json 9 | package-lock.json 10 | coverage 11 | .coverage_* 12 | .openzeppelin 13 | .vscode/launch.json 14 | deployment/*_output.json 15 | scripts/*_output.json 16 | *.ignore/ -------------------------------------------------------------------------------- /rwaERC20-bridge-example/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": "*.sol", 5 | "options": { 6 | "printWidth": 80, 7 | "tabWidth": 4, 8 | "useTabs": false, 9 | "singleQuote": false, 10 | "bracketSpacing": false, 11 | "explicitTypes": "always" 12 | } 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /rwaERC20-bridge-example/.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: ['mocks', 'interfaces'] 3 | }; -------------------------------------------------------------------------------- /rwaERC20-bridge-example/.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "mark-callable-contracts": "off", 5 | "no-empty-blocks": "off", 6 | "compiler-version": ["error", "0.8.17"], 7 | "private-vars-leading-underscore": "error", 8 | "bracket-align": "off", 9 | "reason-string": "off", 10 | "not-rely-on-time": "off", 11 | "no-inline-assembly": "off", 12 | "check-send-result": "off" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": false, 3 | "solidity.linter": "solhint", 4 | "solidity.compileUsingRemoteVersion": "v0.8.17+commit.e14f2714" 5 | } 6 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/README.md: -------------------------------------------------------------------------------- 1 | # zkEVM RWA ERC20 bridge example 2 | 3 | This folder provides an example on how to **bridge RWA ERC20** using the message layer that `polygonZKEVMBridge` implements. 4 | 5 | [`CustomERC20Wrapped`](./contracts/mocks/CustomERC20Wrapped.sol) have `BLACKLISTER_ROLE` which can blacklist any account to send or receive wrapped token. 6 | 7 | ## Requirements 8 | 9 | - node version: >= 14.x 10 | - npm version: >= 7.x 11 | 12 | ## Deployment NFT ERC20 13 | 14 | ### Deployment 15 | 16 | In project root execute: 17 | 18 | ``` 19 | npm i 20 | cp .env.example .env 21 | ``` 22 | 23 | Fill `.env` with your `MNEMONIC` or `PVTKEY` and `INFURA_PROJECT_ID` 24 | If you want to verify the contracts also fill in the `ETHERSCAN_API_KEY` and `ETHERSCAN_ZKEVM_API_KEY` 25 | 26 | To deploy use:`deploy:erc20Bridge:${network}` 27 | 28 | As example for `goerli`/`polygonZKEVMTestnet` testnets: 29 | This script will deploy on both networks the same contract using the deterministic deployment: 30 | 31 | ``` 32 | npm run deploy:erc20Bridge:goerli 33 | ``` 34 | 35 | Once the deployment is finished, we will find the results on `ERC20Bridge_output.json` 36 | 37 | To verify contracts use `npm run verify:erc20Bridge:${network}` 38 | 39 | ``` 40 | npm run verify:erc20Bridge:goerli 41 | npm run verify:erc20Bridge:polygonZKEVMTestnet 42 | ``` 43 | 44 | ## Using the erc20 bridge 45 | 46 | In order to use the bridge, some scripts are provided: 47 | 48 | ``` 49 | npm run bridge:MockERC20:goerli 50 | ``` 51 | 52 | - Now we have to wait until the message is forwarded to L2, there is a final script that will check it if the claim is ready. If it is ready, it will actually claim the erc20 in the other layer: 53 | 54 | ``` 55 | npm run claim:MockERC20:polygonZKEVMTestnet 56 | ``` 57 | 58 | ## Example erc20 bridge transaction 59 | 60 | bridge: https://goerli.etherscan.io/tx/0x392827147f8883498cb8fbdaac96b2f453d1cc0ee9078c482d5606fa0058a32f 61 | claim: https://testnet-zkevm.polygonscan.com/tx/0xbeca97d41c250fece46673c297dbc448b64c4a1825d53f360ccfe0d09733e978 62 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/contracts/ERC20BridgeNativeChain.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "./base/PolygonERC20BridgeBase.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 7 | 8 | /** 9 | * ERC20BridgeNativeChain is an example contract to use the message layer of the PolygonZkEVMBridge to bridge custom ERC20 10 | * This contract will be deployed on the native erc20 network (usually will be mainnet) 11 | */ 12 | contract ERC20BridgeNativeChain is PolygonERC20BridgeBase { 13 | using SafeERC20 for IERC20; 14 | 15 | // Token address 16 | IERC20 public immutable token; 17 | 18 | /** 19 | * @param _polygonZkEVMBridge Polygon zkevm bridge address 20 | * @param _counterpartContract Couterpart contract 21 | * @param _counterpartNetwork Couterpart network 22 | * @param _token Token address 23 | */ 24 | constructor( 25 | IPolygonZkEVMBridge _polygonZkEVMBridge, 26 | address _counterpartContract, 27 | uint32 _counterpartNetwork, 28 | IERC20 _token 29 | ) 30 | PolygonERC20BridgeBase( 31 | _polygonZkEVMBridge, 32 | _counterpartContract, 33 | _counterpartNetwork 34 | ) 35 | { 36 | token = _token; 37 | } 38 | 39 | /** 40 | * @dev Handle the reception of the tokens 41 | * @param amount Token amount 42 | */ 43 | function _receiveTokens(uint256 amount) internal override { 44 | token.safeTransferFrom(msg.sender, address(this), amount); 45 | } 46 | 47 | /** 48 | * @dev Handle the transfer of the tokens 49 | * @param destinationAddress Address destination that will receive the tokens on the other network 50 | * @param amount Token amount 51 | */ 52 | function _transferTokens( 53 | address destinationAddress, 54 | uint256 amount 55 | ) internal override { 56 | token.safeTransfer(destinationAddress, amount); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/contracts/ERC20BridgeNonNativeChain.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "./base/PolygonERC20BridgeBase.sol"; 6 | import "./interfaces/IERC20Wrapped.sol"; 7 | 8 | /** 9 | * ERC20BridgeNonNativeChain is an example contract to use the message layer of the PolygonZkEVMBridge to bridge custom ERC20 10 | * This contract will be deployed on the non-native erc20 network (usually will be zk-EVM) 11 | */ 12 | contract ERC20BridgeNonNativeChain is PolygonERC20BridgeBase { 13 | // Token address 14 | IERC20Wrapped public immutable token; 15 | 16 | /** 17 | * @param _polygonZkEVMBridge Polygon zkevm bridge address 18 | * @param _counterpartContract Couterpart contract 19 | * @param _counterpartNetwork Couterpart network 20 | * @param _token Token address 21 | */ 22 | constructor( 23 | IPolygonZkEVMBridge _polygonZkEVMBridge, 24 | address _counterpartContract, 25 | uint32 _counterpartNetwork, 26 | IERC20Wrapped _token 27 | ) 28 | PolygonERC20BridgeBase( 29 | _polygonZkEVMBridge, 30 | _counterpartContract, 31 | _counterpartNetwork 32 | ) 33 | { 34 | token = _token; 35 | } 36 | 37 | /** 38 | * @dev Handle the reception of the tokens 39 | * @param amount Token amount 40 | */ 41 | function _receiveTokens(uint256 amount) internal override { 42 | token.bridgeBurn(msg.sender, amount); 43 | } 44 | 45 | /** 46 | * @dev Handle the transfer of the tokens 47 | * @param destinationAddress Address destination that will receive the tokens on the other network 48 | * @param amount Token amount 49 | */ 50 | function _transferTokens( 51 | address destinationAddress, 52 | uint256 amount 53 | ) internal override { 54 | token.bridgeMint(destinationAddress, amount); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/contracts/base/PolygonBridgeBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "../polygonZKEVMContracts/interfaces/IBasePolygonZkEVMGlobalExitRoot.sol"; 6 | import "../polygonZKEVMContracts/interfaces/IBridgeMessageReceiver.sol"; 7 | import "../polygonZKEVMContracts/interfaces/IPolygonZkEVMBridge.sol"; 8 | 9 | /** 10 | * This contract contains the logic to use the message layer of the bridge to send and receive messages 11 | * to a counterpart contract deployed on another network. 12 | * Is needed to deploy 1 contract on each layer that inherits this library. 13 | */ 14 | abstract contract PolygonBridgeBase { 15 | // Zk-EVM Bridge address 16 | IPolygonZkEVMBridge public immutable polygonZkEVMBridge; 17 | 18 | // Counterpart contract that will be deployed on the other network 19 | // Both contract will send messages to each other 20 | address public immutable counterpartContract; 21 | 22 | // Counterpart network 23 | uint32 public immutable counterpartNetwork; 24 | 25 | /** 26 | * @param _polygonZkEVMBridge Polygon zkevm bridge address 27 | * @param _counterpartContract Couterpart contract 28 | * @param _counterpartNetwork Couterpart network 29 | */ 30 | constructor( 31 | IPolygonZkEVMBridge _polygonZkEVMBridge, 32 | address _counterpartContract, 33 | uint32 _counterpartNetwork 34 | ) { 35 | polygonZkEVMBridge = _polygonZkEVMBridge; 36 | counterpartContract = _counterpartContract; 37 | counterpartNetwork = _counterpartNetwork; 38 | } 39 | 40 | /** 41 | * @notice Send a message to the bridge 42 | * @param messageData Message data 43 | * @param forceUpdateGlobalExitRoot Indicates if the global exit root is updated or not 44 | */ 45 | function _bridgeMessage( 46 | bytes memory messageData, 47 | bool forceUpdateGlobalExitRoot 48 | ) internal virtual { 49 | polygonZkEVMBridge.bridgeMessage( 50 | counterpartNetwork, 51 | counterpartContract, 52 | forceUpdateGlobalExitRoot, 53 | messageData 54 | ); 55 | } 56 | 57 | /** 58 | * @notice Function triggered by the bridge once a message is received by the other network 59 | * @param originAddress Origin address that the message was sended 60 | * @param originNetwork Origin network that the message was sended ( not usefull for this contract) 61 | * @param data Abi encoded metadata 62 | */ 63 | function onMessageReceived( 64 | address originAddress, 65 | uint32 originNetwork, 66 | bytes memory data 67 | ) external payable { 68 | // Can only be called by the bridge 69 | require( 70 | msg.sender == address(polygonZkEVMBridge), 71 | "TokenWrapped::PolygonBridgeBase: Not PolygonZkEVMBridge" 72 | ); 73 | 74 | require( 75 | counterpartContract == originAddress, 76 | "TokenWrapped::PolygonBridgeBase: Not counterpart contract" 77 | ); 78 | require( 79 | counterpartNetwork == originNetwork, 80 | "TokenWrapped::PolygonBridgeBase: Not counterpart network" 81 | ); 82 | 83 | _onMessageReceived(data); 84 | } 85 | 86 | /** 87 | * @dev Handle the data of the message received 88 | * Must be implemented in parent contracts 89 | */ 90 | function _onMessageReceived(bytes memory data) internal virtual; 91 | } 92 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/contracts/base/PolygonERC20BridgeBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "./PolygonBridgeBase.sol"; 6 | 7 | /** 8 | * This contract contains the common logic to interact with the message layer of the bridge 9 | * to build a custom erc20 bridge. Is needed to deploy 1 contract on each layer that inherits 10 | * this library. 11 | */ 12 | abstract contract PolygonERC20BridgeBase is PolygonBridgeBase { 13 | /** 14 | * @param _polygonZkEVMBridge Polygon zkevm bridge address 15 | * @param _counterpartContract Couterpart contract 16 | * @param _counterpartNetwork Couterpart network 17 | */ 18 | constructor( 19 | IPolygonZkEVMBridge _polygonZkEVMBridge, 20 | address _counterpartContract, 21 | uint32 _counterpartNetwork 22 | ) 23 | PolygonBridgeBase( 24 | _polygonZkEVMBridge, 25 | _counterpartContract, 26 | _counterpartNetwork 27 | ) 28 | {} 29 | 30 | /** 31 | * @dev Emitted when bridge tokens to the counterpart network 32 | */ 33 | event BridgeTokens(address destinationAddress, uint256 amount); 34 | 35 | /** 36 | * @dev Emitted when claim tokens from the counterpart network 37 | */ 38 | event ClaimTokens(address destinationAddress, uint256 amount); 39 | 40 | /** 41 | * @notice Send a message to the bridge that contains the destination address and the token amount 42 | * The parent contract should implement the receive token protocol and afterwards call this function 43 | * @param destinationAddress Address destination that will receive the tokens on the other network 44 | * @param amount Token amount 45 | * @param forceUpdateGlobalExitRoot Indicates if the global exit root is updated or not 46 | */ 47 | function bridgeToken( 48 | address destinationAddress, 49 | uint256 amount, 50 | bool forceUpdateGlobalExitRoot 51 | ) external { 52 | _receiveTokens(amount); 53 | 54 | // Encode message data 55 | bytes memory messageData = abi.encode(destinationAddress, amount); 56 | 57 | // Send message data through the bridge 58 | _bridgeMessage(messageData, forceUpdateGlobalExitRoot); 59 | 60 | emit BridgeTokens(destinationAddress, amount); 61 | } 62 | 63 | /** 64 | * @notice Internal function triggered when receive a message 65 | * @param data message data containing the destination address and the token amount 66 | */ 67 | function _onMessageReceived(bytes memory data) internal override { 68 | // Decode message data 69 | (address destinationAddress, uint256 amount) = abi.decode( 70 | data, 71 | (address, uint256) 72 | ); 73 | 74 | _transferTokens(destinationAddress, amount); 75 | emit ClaimTokens(destinationAddress, amount); 76 | } 77 | 78 | /** 79 | * @dev Handle the reception of the tokens 80 | * Must be implemented in parent contracts 81 | */ 82 | function _receiveTokens(uint256 amount) internal virtual; 83 | 84 | /** 85 | * @dev Handle the transfer of the tokens 86 | * Must be implemented in parent contracts 87 | */ 88 | function _transferTokens( 89 | address destinationAddress, 90 | uint256 amount 91 | ) internal virtual; 92 | } 93 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/contracts/interfaces/IERC20Wrapped.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | /** 6 | * @dev Define interface for erc20 wrapped 7 | */ 8 | interface IERC20Wrapped { 9 | function bridgeMint(address to, uint256 value) external; 10 | 11 | function bridgeBurn(address account, uint256 value) external; 12 | } 13 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/contracts/mocks/CustomERC20Mainnet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol"; 6 | import "@openzeppelin/contracts/access/Ownable.sol"; 7 | 8 | contract CustomERC20Mainnet is ERC20Pausable, Ownable { 9 | constructor( 10 | string memory name, 11 | string memory symbol, 12 | address initialAccount, 13 | uint256 initialBalance 14 | ) ERC20(name, symbol) { 15 | _mint(initialAccount, initialBalance); 16 | } 17 | 18 | /** 19 | * @notice This function is used to pause the transferability of the token. 20 | * Only the owner can call this function 21 | */ 22 | function pause() public onlyOwner { 23 | _pause(); 24 | } 25 | 26 | /** 27 | * @notice This function is used to unpause the transferability of the token. 28 | * Only the owner can call this function 29 | */ 30 | function unpause() public onlyOwner { 31 | _unpause(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/contracts/mocks/CustomERC20Wrapped.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "./CustomERC20Mainnet.sol"; 6 | import "../interfaces/IERC20Wrapped.sol"; 7 | import "@openzeppelin/contracts/access/AccessControl.sol"; 8 | 9 | contract CustomERC20Wrapped is CustomERC20Mainnet, AccessControl, IERC20Wrapped { 10 | // PolygonZkEVM Bridge address 11 | address public immutable ERC20bridgeAddress; 12 | 13 | /// @notice Blacklister role 14 | bytes32 public constant BLACKLISTER_ROLE = keccak256("BLACKLISTER_ROLE"); 15 | 16 | /// @notice Map address to whitelist status 17 | mapping(address => bool) public isBlacklisted; 18 | 19 | /// @notice Event emitted when account is blacklisted 20 | event BlacklistAdded(address account); 21 | 22 | /// @notice Event emitted when account is removed from blacklist 23 | event BlacklistRemoved(address account); 24 | 25 | // Notice that can inherit any erc20 contract with ANY custom logic 26 | constructor( 27 | string memory name, 28 | string memory symbol, 29 | address initialAccount, 30 | uint256 initialBalance, 31 | address _ERC20bridgeAddress, 32 | address _blacklister 33 | ) CustomERC20Mainnet(name, symbol, initialAccount, initialBalance) { 34 | ERC20bridgeAddress = _ERC20bridgeAddress; 35 | _grantRole(BLACKLISTER_ROLE, _blacklister); 36 | } 37 | 38 | modifier onlyBridge() { 39 | require( 40 | msg.sender == ERC20bridgeAddress, 41 | "CustomERC20Wrapped::onlyBridge: Not PolygonZkEVMBridge" 42 | ); 43 | _; 44 | } 45 | 46 | modifier whenNotBlacklisted(address account) { 47 | require( 48 | !isBlacklisted[account], 49 | "CustomERC20Wrapped::whenNotBlacklisted: Account blacklisted" 50 | ); 51 | _; 52 | } 53 | 54 | function bridgeMint(address to, uint256 value) external onlyBridge { 55 | _mint(to, value); 56 | } 57 | 58 | // Notice that is not require to approve wrapped tokens to use the bridge 59 | function bridgeBurn(address account, uint256 value) external onlyBridge { 60 | _burn(account, value); 61 | } 62 | 63 | function addBlacklist(address account) external onlyRole(BLACKLISTER_ROLE) { 64 | isBlacklisted[account] = true; 65 | emit BlacklistAdded(account); 66 | } 67 | 68 | function removeBlacklist(address account) external onlyRole(BLACKLISTER_ROLE) { 69 | isBlacklisted[account] = false; 70 | emit BlacklistRemoved(account); 71 | } 72 | 73 | /** 74 | * @notice _beforeTokenTransfer hook to pause the transfer 75 | */ 76 | function _beforeTokenTransfer(address from, address to, uint256 amount) 77 | internal 78 | virtual 79 | override 80 | whenNotBlacklisted(from) 81 | whenNotBlacklisted(to) 82 | { 83 | super._beforeTokenTransfer(from, to, amount); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/contracts/polygonZKEVMContracts/PolygonZkEVMGlobalExitRoot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "./interfaces/IPolygonZkEVMGlobalExitRoot.sol"; 6 | import "./lib/GlobalExitRootLib.sol"; 7 | 8 | /** 9 | * Contract responsible for managing the exit roots across multiple networks 10 | */ 11 | contract PolygonZkEVMGlobalExitRoot is IPolygonZkEVMGlobalExitRoot { 12 | // PolygonZkEVMBridge address 13 | address public immutable bridgeAddress; 14 | 15 | // Rollup contract address 16 | address public immutable rollupAddress; 17 | 18 | // Rollup exit root, this will be updated every time a batch is verified 19 | bytes32 public lastRollupExitRoot; 20 | 21 | // Mainnet exit root, this will be updated every time a deposit is made in mainnet 22 | bytes32 public lastMainnetExitRoot; 23 | 24 | // Store every global exit root: Root --> timestamp 25 | mapping(bytes32 => uint256) public globalExitRootMap; 26 | 27 | /** 28 | * @dev Emitted when the global exit root is updated 29 | */ 30 | event UpdateGlobalExitRoot( 31 | bytes32 indexed mainnetExitRoot, 32 | bytes32 indexed rollupExitRoot 33 | ); 34 | 35 | /** 36 | * @param _rollupAddress Rollup contract address 37 | * @param _bridgeAddress PolygonZkEVMBridge contract address 38 | */ 39 | constructor(address _rollupAddress, address _bridgeAddress) { 40 | rollupAddress = _rollupAddress; 41 | bridgeAddress = _bridgeAddress; 42 | } 43 | 44 | /** 45 | * @notice Update the exit root of one of the networks and the global exit root 46 | * @param newRoot new exit tree root 47 | */ 48 | function updateExitRoot(bytes32 newRoot) external { 49 | // Store storage variables into temporal variables since will be used multiple times 50 | bytes32 cacheLastRollupExitRoot = lastRollupExitRoot; 51 | bytes32 cacheLastMainnetExitRoot = lastMainnetExitRoot; 52 | 53 | if (msg.sender == bridgeAddress) { 54 | lastMainnetExitRoot = newRoot; 55 | cacheLastMainnetExitRoot = newRoot; 56 | } else if (msg.sender == rollupAddress) { 57 | lastRollupExitRoot = newRoot; 58 | cacheLastRollupExitRoot = newRoot; 59 | } else { 60 | revert OnlyAllowedContracts(); 61 | } 62 | 63 | bytes32 newGlobalExitRoot = GlobalExitRootLib.calculateGlobalExitRoot( 64 | cacheLastMainnetExitRoot, 65 | cacheLastRollupExitRoot 66 | ); 67 | 68 | // If it already exists, do not modify the timestamp 69 | if (globalExitRootMap[newGlobalExitRoot] == 0) { 70 | globalExitRootMap[newGlobalExitRoot] = block.timestamp; 71 | emit UpdateGlobalExitRoot( 72 | cacheLastMainnetExitRoot, 73 | cacheLastRollupExitRoot 74 | ); 75 | } 76 | } 77 | 78 | /** 79 | * @notice Return last global exit root 80 | */ 81 | function getLastGlobalExitRoot() public view returns (bytes32) { 82 | return 83 | GlobalExitRootLib.calculateGlobalExitRoot( 84 | lastMainnetExitRoot, 85 | lastRollupExitRoot 86 | ); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/contracts/polygonZKEVMContracts/interfaces/IBasePolygonZkEVMGlobalExitRoot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | interface IBasePolygonZkEVMGlobalExitRoot { 6 | /** 7 | * @dev Thrown when the caller is not the allowed contracts 8 | */ 9 | error OnlyAllowedContracts(); 10 | 11 | function updateExitRoot(bytes32 newRollupExitRoot) external; 12 | 13 | function globalExitRootMap( 14 | bytes32 globalExitRootNum 15 | ) external returns (uint256); 16 | } 17 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/contracts/polygonZKEVMContracts/interfaces/IBridgeMessageReceiver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | /** 6 | * @dev Define interface for PolygonZkEVM Bridge message receiver 7 | */ 8 | interface IBridgeMessageReceiver { 9 | function onMessageReceived( 10 | address originAddress, 11 | uint32 originNetwork, 12 | bytes memory data 13 | ) external payable; 14 | } 15 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/contracts/polygonZKEVMContracts/interfaces/IPolygonZkEVMBridge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | interface IPolygonZkEVMBridge { 6 | /** 7 | * @dev Thrown when sender is not the PolygonZkEVM address 8 | */ 9 | error OnlyPolygonZkEVM(); 10 | 11 | /** 12 | * @dev Thrown when the destination network is invalid 13 | */ 14 | error DestinationNetworkInvalid(); 15 | 16 | /** 17 | * @dev Thrown when the amount does not match msg.value 18 | */ 19 | error AmountDoesNotMatchMsgValue(); 20 | 21 | /** 22 | * @dev Thrown when user is bridging tokens and is also sending a value 23 | */ 24 | error MsgValueNotZero(); 25 | 26 | /** 27 | * @dev Thrown when the Ether transfer on claimAsset fails 28 | */ 29 | error EtherTransferFailed(); 30 | 31 | /** 32 | * @dev Thrown when the message transaction on claimMessage fails 33 | */ 34 | error MessageFailed(); 35 | 36 | /** 37 | * @dev Thrown when the global exit root does not exist 38 | */ 39 | error GlobalExitRootInvalid(); 40 | 41 | /** 42 | * @dev Thrown when the smt proof does not match 43 | */ 44 | error InvalidSmtProof(); 45 | 46 | /** 47 | * @dev Thrown when an index is already claimed 48 | */ 49 | error AlreadyClaimed(); 50 | 51 | /** 52 | * @dev Thrown when the owner of permit does not match the sender 53 | */ 54 | error NotValidOwner(); 55 | 56 | /** 57 | * @dev Thrown when the spender of the permit does not match this contract address 58 | */ 59 | error NotValidSpender(); 60 | 61 | /** 62 | * @dev Thrown when the amount of the permit does not match 63 | */ 64 | error NotValidAmount(); 65 | 66 | /** 67 | * @dev Thrown when the permit data contains an invalid signature 68 | */ 69 | error NotValidSignature(); 70 | 71 | function bridgeAsset( 72 | uint32 destinationNetwork, 73 | address destinationAddress, 74 | uint256 amount, 75 | address token, 76 | bool forceUpdateGlobalExitRoot, 77 | bytes calldata permitData 78 | ) external payable; 79 | 80 | function bridgeMessage( 81 | uint32 destinationNetwork, 82 | address destinationAddress, 83 | bool forceUpdateGlobalExitRoot, 84 | bytes calldata metadata 85 | ) external payable; 86 | 87 | function claimAsset( 88 | bytes32[32] calldata smtProof, 89 | uint32 index, 90 | bytes32 mainnetExitRoot, 91 | bytes32 rollupExitRoot, 92 | uint32 originNetwork, 93 | address originTokenAddress, 94 | uint32 destinationNetwork, 95 | address destinationAddress, 96 | uint256 amount, 97 | bytes calldata metadata 98 | ) external; 99 | 100 | function claimMessage( 101 | bytes32[32] calldata smtProof, 102 | uint32 index, 103 | bytes32 mainnetExitRoot, 104 | bytes32 rollupExitRoot, 105 | uint32 originNetwork, 106 | address originAddress, 107 | uint32 destinationNetwork, 108 | address destinationAddress, 109 | uint256 amount, 110 | bytes calldata metadata 111 | ) external; 112 | 113 | function updateGlobalExitRoot() external; 114 | 115 | function activateEmergencyState() external; 116 | 117 | function deactivateEmergencyState() external; 118 | 119 | function networkID() external returns(uint32); 120 | } 121 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/contracts/polygonZKEVMContracts/interfaces/IPolygonZkEVMGlobalExitRoot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | import "./IBasePolygonZkEVMGlobalExitRoot.sol"; 5 | 6 | interface IPolygonZkEVMGlobalExitRoot is IBasePolygonZkEVMGlobalExitRoot { 7 | function getLastGlobalExitRoot() external view returns (bytes32); 8 | } 9 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/contracts/polygonZKEVMContracts/lib/EmergencyManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | /** 6 | * @dev Contract helper responsible to manage the emergency state 7 | */ 8 | contract EmergencyManager { 9 | /** 10 | * @dev Thrown when emergency state is active, and the function requires otherwise 11 | */ 12 | error OnlyNotEmergencyState(); 13 | 14 | /** 15 | * @dev Thrown when emergency state is not active, and the function requires otherwise 16 | */ 17 | error OnlyEmergencyState(); 18 | 19 | /** 20 | * @dev This empty reserved space is put in place to allow future versions to add new 21 | * variables without shifting down storage in the inheritance chain. 22 | */ 23 | uint256[10] private _gap; 24 | 25 | // Indicates whether the emergency state is active or not 26 | bool public isEmergencyState; 27 | 28 | /** 29 | * @dev Emitted when emergency state is activated 30 | */ 31 | event EmergencyStateActivated(); 32 | 33 | /** 34 | * @dev Emitted when emergency state is deactivated 35 | */ 36 | event EmergencyStateDeactivated(); 37 | 38 | /** 39 | * @notice Only allows a function to be callable if emergency state is unactive 40 | */ 41 | modifier ifNotEmergencyState() { 42 | if (isEmergencyState) { 43 | revert OnlyNotEmergencyState(); 44 | } 45 | _; 46 | } 47 | 48 | /** 49 | * @notice Only allows a function to be callable if emergency state is active 50 | */ 51 | modifier ifEmergencyState() { 52 | if (!isEmergencyState) { 53 | revert OnlyEmergencyState(); 54 | } 55 | _; 56 | } 57 | 58 | /** 59 | * @notice Activate emergency state 60 | */ 61 | function _activateEmergencyState() internal virtual ifNotEmergencyState { 62 | isEmergencyState = true; 63 | emit EmergencyStateActivated(); 64 | } 65 | 66 | /** 67 | * @notice Deactivate emergency state 68 | */ 69 | function _deactivateEmergencyState() internal virtual ifEmergencyState { 70 | isEmergencyState = false; 71 | emit EmergencyStateDeactivated(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/contracts/polygonZKEVMContracts/lib/GlobalExitRootLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | /** 6 | * @dev A library that provides the necessary calculations to calculate the global exit root 7 | */ 8 | library GlobalExitRootLib { 9 | function calculateGlobalExitRoot( 10 | bytes32 mainnetExitRoot, 11 | bytes32 rollupExitRoot 12 | ) internal pure returns (bytes32) { 13 | return keccak256(abi.encodePacked(mainnetExitRoot, rollupExitRoot)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/contracts/polygonZKEVMContracts/lib/TokenWrapped.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | // Implementation of permit based on https://github.com/WETH10/WETH10/blob/main/contracts/WETH10.sol 3 | pragma solidity 0.8.17; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | 7 | contract TokenWrapped is ERC20 { 8 | // Domain typehash 9 | bytes32 public constant DOMAIN_TYPEHASH = 10 | keccak256( 11 | "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" 12 | ); 13 | // Permit typehash 14 | bytes32 public constant PERMIT_TYPEHASH = 15 | keccak256( 16 | "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" 17 | ); 18 | 19 | // Version 20 | string public constant VERSION = "1"; 21 | 22 | // Chain id on deployment 23 | uint256 public immutable deploymentChainId; 24 | 25 | // Domain separator calculated on deployment 26 | bytes32 private immutable _DEPLOYMENT_DOMAIN_SEPARATOR; 27 | 28 | // PolygonZkEVM Bridge address 29 | address public immutable bridgeAddress; 30 | 31 | // Decimals 32 | uint8 private immutable _decimals; 33 | 34 | // Permit nonces 35 | mapping(address => uint256) public nonces; 36 | 37 | modifier onlyBridge() { 38 | require( 39 | msg.sender == bridgeAddress, 40 | "TokenWrapped::onlyBridge: Not PolygonZkEVMBridge" 41 | ); 42 | _; 43 | } 44 | 45 | constructor( 46 | string memory name, 47 | string memory symbol, 48 | uint8 __decimals 49 | ) ERC20(name, symbol) { 50 | bridgeAddress = msg.sender; 51 | _decimals = __decimals; 52 | deploymentChainId = block.chainid; 53 | _DEPLOYMENT_DOMAIN_SEPARATOR = _calculateDomainSeparator(block.chainid); 54 | } 55 | 56 | function mint(address to, uint256 value) external onlyBridge { 57 | _mint(to, value); 58 | } 59 | 60 | // Notice that is not require to approve wrapped tokens to use the bridge 61 | function burn(address account, uint256 value) external onlyBridge { 62 | _burn(account, value); 63 | } 64 | 65 | function decimals() public view virtual override returns (uint8) { 66 | return _decimals; 67 | } 68 | 69 | // Permit relative functions 70 | function permit( 71 | address owner, 72 | address spender, 73 | uint256 value, 74 | uint256 deadline, 75 | uint8 v, 76 | bytes32 r, 77 | bytes32 s 78 | ) external { 79 | require( 80 | block.timestamp <= deadline, 81 | "TokenWrapped::permit: Expired permit" 82 | ); 83 | 84 | bytes32 hashStruct = keccak256( 85 | abi.encode( 86 | PERMIT_TYPEHASH, 87 | owner, 88 | spender, 89 | value, 90 | nonces[owner]++, 91 | deadline 92 | ) 93 | ); 94 | 95 | bytes32 digest = keccak256( 96 | abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), hashStruct) 97 | ); 98 | 99 | address signer = ecrecover(digest, v, r, s); 100 | require( 101 | signer != address(0) && signer == owner, 102 | "TokenWrapped::permit: Invalid signature" 103 | ); 104 | 105 | _approve(owner, spender, value); 106 | } 107 | 108 | /** 109 | * @notice Calculate domain separator, given a chainID. 110 | * @param chainId Current chainID 111 | */ 112 | function _calculateDomainSeparator( 113 | uint256 chainId 114 | ) private view returns (bytes32) { 115 | return 116 | keccak256( 117 | abi.encode( 118 | DOMAIN_TYPEHASH, 119 | keccak256(bytes(name())), 120 | keccak256(bytes(VERSION)), 121 | chainId, 122 | address(this) 123 | ) 124 | ); 125 | } 126 | 127 | /// @dev Return the DOMAIN_SEPARATOR. 128 | function DOMAIN_SEPARATOR() public view returns (bytes32) { 129 | return 130 | block.chainid == deploymentChainId 131 | ? _DEPLOYMENT_DOMAIN_SEPARATOR 132 | : _calculateDomainSeparator(block.chainid); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/deployment/verifyMainnetContracts.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | /* eslint-disable import/no-dynamic-require, no-await-in-loop, no-restricted-syntax, guard-for-in */ 3 | require('dotenv').config(); 4 | const path = require('path'); 5 | const hre = require('hardhat'); 6 | const { expect } = require('chai'); 7 | 8 | const mainnetBridgeAddress = '0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe'; 9 | const testnetBridgeAddress = '0xF6BEEeBB578e214CA9E23B0e9683454Ff88Ed2A7'; 10 | 11 | const networkIDMainnet = 0; 12 | const networkIDzkEVM = 1; 13 | 14 | async function main() { 15 | const networkName = process.env.HARDHAT_NETWORK; 16 | const pathDeployOutputParameters = path.join(__dirname, './ERC20Bridge_output.json'); 17 | const deployOutputParameters = require(pathDeployOutputParameters); 18 | 19 | let zkEVMBridgeContractAddress; 20 | // Use mainnet bridge address 21 | if (networkName === 'polygonZKEVMMainnet' || networkName === 'mainnet') { 22 | zkEVMBridgeContractAddress = mainnetBridgeAddress; 23 | } 24 | 25 | // Use testnet bridge address 26 | if (networkName === 'polygonZKEVMTestnet' || networkName === 'goerli') { 27 | zkEVMBridgeContractAddress = testnetBridgeAddress; 28 | } 29 | 30 | // Token params 31 | const name = deployOutputParameters.tokenName; 32 | const symbol = deployOutputParameters.tokenSymbol; 33 | const initialAccount = deployOutputParameters.deployerAddress; 34 | const initialBalance = deployOutputParameters.tokenInitialBalance; 35 | 36 | try { 37 | // verify token mainnet 38 | await hre.run( 39 | 'verify:verify', 40 | { 41 | address: deployOutputParameters.erc20MainnetToken, 42 | constructorArguments: [ 43 | name, 44 | symbol, 45 | initialAccount, 46 | initialBalance, 47 | ], 48 | }, 49 | ); 50 | } catch (error) { 51 | console.log(error); 52 | expect(error.message.toLowerCase().includes('already verified')).to.be.equal(true); 53 | } 54 | 55 | try { 56 | // verify ERC20BridgeNativeChain 57 | await hre.run( 58 | 'verify:verify', 59 | { 60 | address: deployOutputParameters.ERC20BridgeMainnet, 61 | constructorArguments: [ 62 | zkEVMBridgeContractAddress, 63 | deployOutputParameters.ERC20BridgezkEVM, 64 | networkIDzkEVM, 65 | deployOutputParameters.erc20MainnetToken, 66 | ], 67 | }, 68 | ); 69 | } catch (error) { 70 | expect(error.message.toLowerCase().includes('already verified')).to.be.equal(true); 71 | } 72 | } 73 | 74 | main() 75 | .then(() => process.exit(0)) 76 | .catch((error) => { 77 | console.error(error); 78 | process.exit(1); 79 | }); 80 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/deployment/verifyZkEVM.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | /* eslint-disable import/no-dynamic-require, no-await-in-loop, no-restricted-syntax, guard-for-in */ 3 | require('dotenv').config(); 4 | const path = require('path'); 5 | const hre = require('hardhat'); 6 | const { expect } = require('chai'); 7 | 8 | const mainnetBridgeAddress = '0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe'; 9 | const testnetBridgeAddress = '0xF6BEEeBB578e214CA9E23B0e9683454Ff88Ed2A7'; 10 | 11 | const networkIDMainnet = 0; 12 | 13 | async function main() { 14 | const networkName = process.env.HARDHAT_NETWORK; 15 | const pathDeployOutputParameters = path.join(__dirname, './ERC20Bridge_output.json'); 16 | const deployOutputParameters = require(pathDeployOutputParameters); 17 | 18 | let zkEVMBridgeContractAddress; 19 | // Use mainnet bridge address 20 | if (networkName === 'polygonZKEVMMainnet' || networkName === 'mainnet') { 21 | zkEVMBridgeContractAddress = mainnetBridgeAddress; 22 | } 23 | 24 | // Use testnet bridge address 25 | if (networkName === 'polygonZKEVMTestnet' || networkName === 'goerli') { 26 | zkEVMBridgeContractAddress = testnetBridgeAddress; 27 | } 28 | 29 | // Token params 30 | const name = deployOutputParameters.tokenName; 31 | const symbol = deployOutputParameters.tokenSymbol; 32 | const initialAccount = deployOutputParameters.deployerAddress; 33 | const initialBalance = deployOutputParameters.tokenInitialBalance; 34 | 35 | try { 36 | // verify token mainnet 37 | await hre.run( 38 | 'verify:verify', 39 | { 40 | address: deployOutputParameters.erc20zkEVMToken, 41 | constructorArguments: [ 42 | name, 43 | symbol, 44 | initialAccount, 45 | initialBalance, 46 | deployOutputParameters.ERC20BridgezkEVM, 47 | ], 48 | }, 49 | ); 50 | } catch (error) { 51 | expect(error.message.toLowerCase().includes('already verified')).to.be.equal(true); 52 | } 53 | 54 | try { 55 | // verify ERC20BridgeNonNativeChain 56 | await hre.run( 57 | 'verify:verify', 58 | { 59 | address: deployOutputParameters.ERC20BridgezkEVM, 60 | constructorArguments: [ 61 | zkEVMBridgeContractAddress, 62 | deployOutputParameters.ERC20BridgeMainnet, 63 | networkIDMainnet, 64 | deployOutputParameters.erc20zkEVMToken, 65 | ], 66 | }, 67 | ); 68 | } catch (error) { 69 | expect(error.message.toLowerCase().includes('already verified')).to.be.equal(true); 70 | } 71 | } 72 | 73 | main() 74 | .then(() => process.exit(0)) 75 | .catch((error) => { 76 | console.error(error); 77 | process.exit(1); 78 | }); 79 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | require('@nomiclabs/hardhat-waffle'); 3 | require('hardhat-gas-reporter'); 4 | require('solidity-coverage'); 5 | require('@nomiclabs/hardhat-etherscan'); 6 | require('@openzeppelin/hardhat-upgrades'); 7 | 8 | const DEFAULT_MNEMONIC = 'test test test test test test test test test test test junk'; 9 | 10 | /* 11 | * You need to export an object to set up your config 12 | * Go to https://hardhat.org/config/ to learn more 13 | */ 14 | 15 | /** 16 | * @type import('hardhat/config').HardhatUserConfig 17 | */ 18 | module.exports = { 19 | solidity: { 20 | compilers: [ 21 | { 22 | version: "0.8.17", 23 | settings: { 24 | optimizer: { 25 | enabled: true, 26 | runs: 999999 27 | } 28 | } 29 | }, 30 | { 31 | version: "0.6.11", 32 | settings: { 33 | optimizer: { 34 | enabled: true, 35 | runs: 999999 36 | } 37 | } 38 | }, 39 | { 40 | version: "0.5.12", 41 | settings: { 42 | optimizer: { 43 | enabled: true, 44 | runs: 999999 45 | } 46 | } 47 | }, 48 | { 49 | version: "0.5.16", 50 | settings: { 51 | optimizer: { 52 | enabled: true, 53 | runs: 999999 54 | } 55 | } 56 | }, 57 | { 58 | version: "0.4.19", 59 | settings: { 60 | optimizer: { 61 | enabled: false, 62 | } 63 | } 64 | } 65 | ] 66 | }, 67 | networks: { 68 | mainnet: { 69 | url: `https://mainnet.infura.io/v3/${process.env.INFURA_PROJECT_ID}`, 70 | accounts: { 71 | mnemonic: process.env.MNEMONIC || DEFAULT_MNEMONIC, 72 | path: "m/44'/60'/0'/0", 73 | initialIndex: 0, 74 | count: 20, 75 | }, 76 | }, 77 | ropsten: { 78 | url: `https://ropsten.infura.io/v3/${process.env.INFURA_PROJECT_ID}`, 79 | accounts: { 80 | mnemonic: process.env.MNEMONIC || DEFAULT_MNEMONIC, 81 | path: "m/44'/60'/0'/0", 82 | initialIndex: 0, 83 | count: 20, 84 | }, 85 | }, 86 | goerli: { 87 | url: `https://goerli.infura.io/v3/${process.env.INFURA_PROJECT_ID}`, 88 | accounts: { 89 | mnemonic: process.env.MNEMONIC || DEFAULT_MNEMONIC, 90 | path: "m/44'/60'/0'/0", 91 | initialIndex: 0, 92 | count: 20, 93 | }, 94 | }, 95 | rinkeby: { 96 | url: `https://rinkeby.infura.io/v3/${process.env.INFURA_PROJECT_ID}`, 97 | accounts: { 98 | mnemonic: process.env.MNEMONIC || DEFAULT_MNEMONIC, 99 | path: "m/44'/60'/0'/0", 100 | initialIndex: 0, 101 | count: 20, 102 | }, 103 | }, 104 | localhost: { 105 | url: 'http://127.0.0.1:8545', 106 | accounts: { 107 | mnemonic: process.env.MNEMONIC || DEFAULT_MNEMONIC, 108 | path: "m/44'/60'/0'/0", 109 | initialIndex: 0, 110 | count: 20, 111 | }, 112 | }, 113 | hardhat: { 114 | initialDate: '0', 115 | allowUnlimitedContractSize: true, 116 | accounts: { 117 | mnemonic: process.env.MNEMONIC || DEFAULT_MNEMONIC, 118 | path: "m/44'/60'/0'/0", 119 | initialIndex: 0, 120 | count: 20, 121 | }, 122 | }, 123 | polygonZKEVMTestnet: { 124 | url: "https://rpc.public.zkevm-test.net", 125 | accounts: { 126 | mnemonic: process.env.MNEMONIC || DEFAULT_MNEMONIC, 127 | path: "m/44'/60'/0'/0", 128 | initialIndex: 0, 129 | count: 20, 130 | }, 131 | }, 132 | polygonZKEVMMainnet: { 133 | url: "https://zkevm-rpc.com", 134 | accounts: { 135 | mnemonic: process.env.MNEMONIC || DEFAULT_MNEMONIC, 136 | path: "m/44'/60'/0'/0", 137 | initialIndex: 0, 138 | count: 20, 139 | }, 140 | }, 141 | }, 142 | gasReporter: { 143 | enabled: !!process.env.REPORT_GAS, 144 | outputFile: process.env.REPORT_GAS_FILE ? "./gas_report.md" : null, 145 | noColors: process.env.REPORT_GAS_FILE ? true : false 146 | }, 147 | etherscan: { 148 | apiKey: { 149 | polygonZKEVMTestnet: `${process.env.ETHERSCAN_ZKEVM_API_KEY}`, 150 | polygonZKEVMMainnet: `${process.env.ETHERSCAN_ZKEVM_API_KEY}`, 151 | goerli: `${process.env.ETHERSCAN_API_KEY}`, 152 | mainnet: `${process.env.ETHERSCAN_API_KEY}` 153 | }, 154 | customChains: [ 155 | { 156 | network: "polygonZKEVMMainnet", 157 | chainId: 1101, 158 | urls: { 159 | apiURL: "https://api-zkevm.polygonscan.com/api", 160 | browserURL: "https://zkevm.polygonscan.com/" 161 | } 162 | }, 163 | { 164 | network: "polygonZKEVMTestnet", 165 | chainId: 1442, 166 | urls: { 167 | apiURL: "https://api-testnet-zkevm.polygonscan.com/api", 168 | browserURL: "https://testnet-zkevm.polygonscan.com/" 169 | } 170 | } 171 | ] 172 | }, 173 | }; -------------------------------------------------------------------------------- /rwaERC20-bridge-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@0xpolygonhermez/code-examples", 3 | "description": "custom ERC20 bridge example for polygon zkEVM", 4 | "version": "1.0.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/0xPolygonHermez/code-examples.git" 8 | }, 9 | "main": "index.js", 10 | "keywords": [ 11 | "zkevm", 12 | "snark", 13 | "polygon", 14 | "hermez", 15 | "stark", 16 | "EVM", 17 | "ethereum", 18 | "blockchain" 19 | ], 20 | "author": "0xPolygonHermez", 21 | "dependencies": { 22 | "axios": "^1.3.5", 23 | "chai": "^4.3.7", 24 | "ethers": "^5.7.2" 25 | }, 26 | "devDependencies": { 27 | "@0xpolygonhermez/zkevm-commonjs": "github:0xPolygonHermez/zkevm-commonjs#develop", 28 | "@nomiclabs/hardhat-ethers": "^2.2.2", 29 | "@nomiclabs/hardhat-etherscan": "^3.1.7", 30 | "@nomiclabs/hardhat-waffle": "^2.0.5", 31 | "@openzeppelin/contracts": "4.8.2", 32 | "@openzeppelin/contracts-upgradeable": "4.8.2", 33 | "@openzeppelin/hardhat-upgrades": "1.22.1", 34 | "@openzeppelin/test-helpers": "0.5.16", 35 | "dotenv": "^8.6.0", 36 | "eslint": "^8.36.0", 37 | "eslint-config-airbnb-base": "^15.0.0", 38 | "eslint-plugin-mocha": "^9.0.0", 39 | "ethereum-waffle": "^3.4.4", 40 | "hardhat": "^2.13.0", 41 | "hardhat-gas-reporter": "^1.0.9", 42 | "prettier": "^2.8.4", 43 | "prettier-plugin-solidity": "^1.1.3", 44 | "solc-0.8": "npm:solc@0.8.17", 45 | "solidity-coverage": "^0.7.22", 46 | "solidity-docgen": "^0.5.17" 47 | }, 48 | "scripts": { 49 | "deploy:erc20Bridge:goerli": "npx hardhat run deployment/deployERC20Bridge.js --network goerli", 50 | "deploy:erc20Bridge:mainnet": "npx hardhat run deployment/deployERC20Bridge.js --network mainnet", 51 | "verify:erc20Bridge:goerli": "npx hardhat run deployment/verifyMainnetContracts.js --network goerli", 52 | "verify:erc20Bridge:mainnet": "npx hardhat run deployment/verifyMainnetContracts.js --network mainnet", 53 | "verify:erc20Bridge:polygonZKEVMTestnet": "npx hardhat run deployment/verifyZkEVM.js --network polygonZKEVMTestnet", 54 | "verify:erc20Bridge:polygonZKEVMMainnet": "npx hardhat run deployment/verifyZkEVM.js --network polygonZKEVMMainnet", 55 | "lint": "npx eslint ./test && npx eslint ./docker/scripts && npx eslint ./deployment && npx eslint ./src", 56 | "lint:fix": "npx eslint ./test --fix && npx eslint ./docker/scripts --fix && npx eslint ./deployment --fix && npx eslint ./src --fix", 57 | "compile": "npx hardhat compile", 58 | "gas:report": "REPORT_GAS=true npx hardhat test", 59 | "gas:report:file": "rm -f .openzeppelin/unknown-31337.json && REPORT_GAS=true REPORT_GAS_FILE=true npx hardhat test", 60 | "bridge:MockERC20:goerli": "npx hardhat run scripts/bridgeMockERC20.js --network goerli", 61 | "bridge:MockERC20:mainnet": "npx hardhat run scripts/bridgeMockERC20.js --network mainnet", 62 | "bridge:MockERC20:polygonZKEVMTestnet": "npx hardhat run scripts/bridgeMockERC20.js --network polygonZKEVMTestnet", 63 | "bridge:MockERC20:polygonZKEVMMainnet": "npx hardhat run scripts/bridgeMockERC20.js --network polygonZKEVMMainnet", 64 | "claim:MockERC20:goerli": "npx hardhat run scripts/claimMockERC20.js --network goerli", 65 | "claim:MockERC20:mainnet": "npx hardhat run scripts/claimMockERC20.js --network mainnet", 66 | "claim:MockERC20:polygonZKEVMTestnet": "npx hardhat run scripts/claimMockERC20.js --network polygonZKEVMTestnet", 67 | "claim:MockERC20:polygonZKEVMMainnet": "npx hardhat run scripts/claimMockERC20.js --network polygonZKEVMMainnet" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/scripts/bridgeMockERC20.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-await-in-loop, no-use-before-define, no-lonely-if, import/no-dynamic-require, global-require */ 2 | /* eslint-disable no-console, no-inner-declarations, no-undef, import/no-unresolved, no-restricted-syntax */ 3 | const path = require('path'); 4 | require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); 5 | const { ethers } = require('hardhat'); 6 | 7 | const networkIDMainnet = 0; 8 | const networkIDRollup = 1; 9 | 10 | const pathdeployeERC20Bridge = path.join(__dirname, '../deployment/ERC20Bridge_output.json'); 11 | const deploymentERC20Bridge = require(pathdeployeERC20Bridge); 12 | 13 | async function main() { 14 | // Load deployer 15 | let deployer; 16 | if (process.env.PVTKEY) { 17 | deployer = new ethers.Wallet(process.env.PVTKEY, ethers.provider); 18 | console.log('Using pvtKey deployer with address: ', deployer.address); 19 | } else if (process.env.MNEMONIC) { 20 | deployer = ethers.Wallet.fromMnemonic(process.env.MNEMONIC, 'm/44\'/60\'/0\'/0/0').connect(ethers.provider); 21 | console.log('Using MNEMONIC deployer with address: ', deployer.address); 22 | } else { 23 | [deployer] = (await ethers.getSigners()); 24 | } 25 | 26 | const networkName = process.env.HARDHAT_NETWORK; 27 | let destinationNetwork; let 28 | ERC20BridgeContractAddress; let erc20TokenAddress; 29 | 30 | if (networkName === 'polygonZKEVMTestnet' || networkName === 'polygonZKEVMMainnet') { 31 | destinationNetwork = networkIDMainnet; 32 | erc20TokenAddress = deploymentERC20Bridge.erc20zkEVMToken; 33 | ERC20BridgeContractAddress = deploymentERC20Bridge.ERC20BridgezkEVM; 34 | } 35 | 36 | if (networkName === 'mainnet' || networkName === 'goerli') { 37 | destinationNetwork = networkIDRollup; 38 | erc20TokenAddress = deploymentERC20Bridge.erc20MainnetToken; 39 | ERC20BridgeContractAddress = deploymentERC20Bridge.ERC20BridgeMainnet; 40 | } 41 | 42 | const erc20BridgeFactory = await ethers.getContractFactory('ERC20BridgeNativeChain', deployer); 43 | const erc20BridgeContract = await erc20BridgeFactory.attach( 44 | ERC20BridgeContractAddress, 45 | ); 46 | 47 | // Approve tokens 48 | const tokenFactory = await ethers.getContractFactory('CustomERC20Mainnet', deployer); 49 | const tokenContract = await tokenFactory.attach( 50 | erc20TokenAddress, 51 | ); 52 | 53 | const tokenAmount = ethers.utils.parseEther('1'); 54 | const destinationAddress = deployer.address; 55 | 56 | await (await tokenContract.approve(ERC20BridgeContractAddress, tokenAmount)).wait(); 57 | console.log('approved tokens'); 58 | 59 | const tx = await erc20BridgeContract.bridgeToken( 60 | destinationAddress, 61 | tokenAmount, 62 | true, 63 | ); 64 | 65 | console.log((await tx.wait()).transactionHash); 66 | 67 | console.log('Bridge done succesfully'); 68 | } 69 | 70 | main().catch((e) => { 71 | console.error(e); 72 | process.exit(1); 73 | }); 74 | -------------------------------------------------------------------------------- /rwaERC20-bridge-example/scripts/claimMockERC20.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-await-in-loop */ 2 | /* eslint-disable no-console, no-inner-declarations, no-undef, import/no-unresolved */ 3 | 4 | const path = require('path'); 5 | require('dotenv').config({ path: path.resolve(__dirname, '../../.env') }); 6 | const { ethers } = require('hardhat'); 7 | 8 | const mainnetBridgeAddress = '0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe'; 9 | const testnetBridgeAddress = '0xF6BEEeBB578e214CA9E23B0e9683454Ff88Ed2A7'; 10 | 11 | const mekrleProofString = '/merkle-proof'; 12 | const getClaimsFromAcc = '/bridges/'; 13 | 14 | const pathdeployeERC20Bridge = path.join(__dirname, '../deployment/ERC20Bridge_output.json'); 15 | const deploymentERC20Bridge = require(pathdeployeERC20Bridge); 16 | 17 | async function main() { 18 | const currentProvider = ethers.provider; 19 | let deployer; 20 | if (process.env.PVTKEY) { 21 | deployer = new ethers.Wallet(process.env.PVTKEY, currentProvider); 22 | console.log('Using pvtKey deployer with address: ', deployer.address); 23 | } else if (process.env.MNEMONIC) { 24 | deployer = ethers.Wallet.fromMnemonic(process.env.MNEMONIC, 'm/44\'/60\'/0\'/0/0').connect(currentProvider); 25 | console.log('Using MNEMONIC deployer with address: ', deployer.address); 26 | } else { 27 | [deployer] = (await ethers.getSigners()); 28 | } 29 | 30 | let zkEVMBridgeContractAddress; 31 | let baseURL; 32 | const networkName = process.env.HARDHAT_NETWORK; 33 | 34 | // Use mainnet bridge address 35 | if (networkName === 'polygonZKEVMMainnet' || networkName === 'mainnet') { 36 | zkEVMBridgeContractAddress = mainnetBridgeAddress; 37 | baseURL = 'https://bridge-api.zkevm-rpc.com'; 38 | } else if (networkName === 'polygonZKEVMTestnet' || networkName === 'goerli') { 39 | // Use testnet bridge address 40 | zkEVMBridgeContractAddress = testnetBridgeAddress; 41 | baseURL = 'https://bridge-api.public.zkevm-test.net'; 42 | } 43 | 44 | const axios = require('axios').create({ 45 | baseURL, 46 | }); 47 | 48 | const bridgeFactoryZkeEVm = await ethers.getContractFactory('PolygonZkEVMBridge', deployer); 49 | const bridgeContractZkeVM = bridgeFactoryZkeEVm.attach(zkEVMBridgeContractAddress); 50 | 51 | let ERC20BridgeContractAddress; 52 | if (networkName === 'polygonZKEVMTestnet' || networkName === 'polygonZKEVMMainnet') { 53 | ERC20BridgeContractAddress = deploymentERC20Bridge.ERC20BridgezkEVM; 54 | } 55 | 56 | if (networkName === 'mainnet' || networkName === 'goerli') { 57 | ERC20BridgeContractAddress = deploymentERC20Bridge.ERC20BridgeMainnet; 58 | } 59 | 60 | const depositAxions = await axios.get(getClaimsFromAcc + ERC20BridgeContractAddress, { params: { limit: 100, offset: 0 } }); 61 | const depositsArray = depositAxions.data.deposits; 62 | 63 | if (depositsArray.length === 0) { 64 | console.log('Not deposits yet!'); 65 | return; 66 | } 67 | 68 | for (let i = 0; i < depositsArray.length; i++) { 69 | const currentDeposit = depositsArray[i]; 70 | if (currentDeposit.ready_for_claim) { 71 | if (currentDeposit.claim_tx_hash != '') { 72 | console.log('already claimed: ', currentDeposit.claim_tx_hash); 73 | continue; 74 | } 75 | 76 | const proofAxios = await axios.get(mekrleProofString, { 77 | params: { deposit_cnt: currentDeposit.deposit_cnt, net_id: currentDeposit.orig_net }, 78 | }); 79 | 80 | const { proof } = proofAxios.data; 81 | const claimTx = await bridgeContractZkeVM.claimMessage( 82 | proof.merkle_proof, 83 | currentDeposit.deposit_cnt, 84 | proof.main_exit_root, 85 | proof.rollup_exit_root, 86 | currentDeposit.orig_net, 87 | currentDeposit.orig_addr, 88 | currentDeposit.dest_net, 89 | currentDeposit.dest_addr, 90 | currentDeposit.amount, 91 | currentDeposit.metadata, 92 | ); 93 | console.log('claim message succesfully send: ', claimTx.hash); 94 | await claimTx.wait(); 95 | console.log('claim message succesfully mined'); 96 | } else { 97 | console.log('Not ready yet!'); 98 | } 99 | } 100 | } 101 | 102 | main().catch((e) => { 103 | console.error(e); 104 | process.exit(1); 105 | }); 106 | -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/.env.example: -------------------------------------------------------------------------------- 1 | MNEMONIC="test test test test test test test test test test test junk" 2 | INFURA_PROJECT_ID="" 3 | ETHERSCAN_API_KEY="" 4 | ETHERSCAN_ZKEVM_API_KEY="" 5 | PVTKEY="" 6 | -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | 'mocha', 4 | ], 5 | env: { 6 | node: true, 7 | mocha: true, 8 | }, 9 | extends: 'airbnb-base', 10 | rules: { 11 | indent: ['error', 4], 12 | 'mocha/no-exclusive-tests': 'error', 13 | 'max-len': ['error', { 14 | code: 140, comments: 200, ignoreStrings: true, ignoreTemplateLiterals: true, 15 | }], 16 | 'no-unused-vars': [2, { varsIgnorePattern: 'export^' }], 17 | 'no-return-assign': [0], 18 | 'no-underscore-dangle': [0], 19 | 'no-plusplus': ['error', { allowForLoopAfterthoughts: true }], 20 | 'func-names': [0], 21 | 'class-methods-use-this': [0], 22 | 'no-bitwise': [0], 23 | 'no-param-reassign': 'off', 24 | 'global-require': 'off', 25 | 'import/no-dynamic-require': 'off', 26 | 'no-console': [2, { allow: ['warn', 'error'] }], 27 | 'import/prefer-default-export': [0], 28 | 'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }], 29 | 'multiline-comment-style': 'error', 30 | 'import/no-extraneous-dependencies': 'off', 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/.gitignore: -------------------------------------------------------------------------------- 1 | coverage.json 2 | .env 3 | cache 4 | artifacts 5 | build 6 | yarn.lock 7 | node_modules 8 | coverage.json 9 | package-lock.json 10 | coverage 11 | .coverage_* 12 | .openzeppelin 13 | .vscode/launch.json 14 | deployment/*_output.json 15 | scripts/*_output.json 16 | *.ignore/ -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": "*.sol", 5 | "options": { 6 | "printWidth": 80, 7 | "tabWidth": 4, 8 | "useTabs": false, 9 | "singleQuote": false, 10 | "bracketSpacing": false, 11 | "explicitTypes": "always" 12 | } 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: ['mocks', 'interfaces'] 3 | }; -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "mark-callable-contracts": "off", 5 | "no-empty-blocks": "off", 6 | "compiler-version": ["error", "0.8.17"], 7 | "private-vars-leading-underscore": "error", 8 | "bracket-align": "off", 9 | "reason-string": "off", 10 | "not-rely-on-time": "off", 11 | "no-inline-assembly": "off", 12 | "check-send-result": "off" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": false, 3 | "solidity.linter": "solhint", 4 | "solidity.compileUsingRemoteVersion": "v0.8.17+commit.e14f2714" 5 | } 6 | -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/README.md: -------------------------------------------------------------------------------- 1 | # zkEVM NFT bridge example 2 | This folder provides an example on how to **bridge NFTs** using the message layer that `polygonZKEVMBridge` implements 3 | 4 | ## Requirements 5 | - node version: >= 14.x 6 | - npm version: >= 7.x 7 | 8 | ## Deployment NFT Bridge 9 | ### Note 10 | There are two bridges already deployed on `goerli <--> polygonZkEVMTestnet` networks: 11 | - `0xd3b1d467694d4964E3d777e5f2baCcf9Aee930b0` 12 | - `0x6D792cb4d69cC3E1e9A2282106Cc0491E796655e` 13 | 14 | Deploying a new nft-bridge is not necessary in order to bridge a NFT. Therefore, you can skip this section and go directly to `Using the NFT bridge` section in this document. 15 | 16 | ### Deployment 17 | In project root execute: 18 | ``` 19 | npm i 20 | cp .env.example .env 21 | ``` 22 | 23 | Fill `.env` with your `MNEMONIC` or `PVTKEY` and `INFURA_PROJECT_ID` 24 | If you want to verify the contracts also fill in the `ETHERSCAN_API_KEY` and `ETHERSCAN_ZKEVM_API_KEY` 25 | 26 | > Deterministic deployment is used to have the same address in both networks 27 | > This would be performed using a `create2` schema using this typical create2 factory https://etherscan.io/address/0x4e59b44847b379578588920ca78fbf26c0b4956c 28 | More info in the repo: https://github.com/Arachnid/deterministic-deployment-proxy/tree/master 29 | 30 | > Script will detect automatically the `bridgeAddress` to be used depending on the network 31 | 32 | To deploy use:`npm run deploy:nftBridge:${network}` 33 | 34 | 35 | As example for `goerli`/`polygonZKEVMTestnet` testnets: 36 | This script will deploy on both networks the same contract using the deterministic deployment: 37 | ``` 38 | npm run deploy:nftBridge:goerli 39 | ``` 40 | 41 | Once the deployment is finished, we will find the results on `NFTBridge_output_output.json` 42 | 43 | To verify contracts use `npm run verify:nftBridge:${network}` 44 | ``` 45 | npm run verify:nftBridge:goerli 46 | npm run verify:nftBridge:polygonZKEVMTestnet 47 | ``` 48 | 49 | ## Using the NFT bridge 50 | In order to use the bridge, some scripts are provided: 51 | 52 | - Deploy an NFT using: 53 | ``` 54 | npm run deploy:mockNFT:goerli 55 | ``` 56 | 57 | - Optionally verify it on etherscan: 58 | ``` 59 | npm run verify:mockNFT:goerli 60 | ``` 61 | 62 | - To use the bridge you can use one of the already deployed ones or deploy one yourself following the `Deployment NFT Bridge:Deployment` section 63 | The address of `deployment/NFTBridge_output.json` is used for this examples 64 | 65 | ``` 66 | npm run bridge:mockNFT:goerli 67 | ``` 68 | 69 | - Now we have to wait until the message is forwarded to L2, there is a final script that will check it if the claim is ready. If it is ready, it will actually claim the NFT in the other layer: 70 | > The same way as the previous step, if you deploy your own nftBridge you will have the update the `deployedNftBridgeAddress` 71 | 72 | ``` 73 | npm run claim:mockNFT:polygonZKEVMTestnet 74 | ``` 75 | 76 | ## Example nft bridge transaction 77 | https://testnet-zkevm.polygonscan.com/tx/0x4aed03de03897bf0f54337825fa4c876eb12549039dc374bafb13ef47ca2fad1 -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/contracts/ERC721Wrapped.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 6 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; 7 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; 8 | import "@openzeppelin/contracts/access/Ownable.sol"; 9 | 10 | contract ERC721Wrapped is ERC721, ERC721Enumerable, ERC721URIStorage { 11 | // PolygonZkEVM Bridge address 12 | address public immutable bridgeAddress; 13 | 14 | constructor( 15 | string memory name, 16 | string memory symbol 17 | ) ERC721(name, symbol) { 18 | bridgeAddress = msg.sender; 19 | } 20 | 21 | modifier onlyBridge() { 22 | require( 23 | msg.sender == bridgeAddress, 24 | "TokenWrapped::onlyBridge: Not PolygonZkEVMBridge" 25 | ); 26 | _; 27 | } 28 | /** 29 | * @dev Allows owner to mint an nft for a receiver 30 | * @param receiver nft receiver 31 | * @param tokenId token Id 32 | * @param _tokenURI token uri 33 | */ 34 | function mint(address receiver, uint256 tokenId, string memory _tokenURI) public onlyBridge { 35 | // Mint NFT 36 | _mint(receiver, tokenId); 37 | _setTokenURI(tokenId, _tokenURI); 38 | } 39 | 40 | 41 | /** 42 | * @dev Override _burn, see {IERC165-supportsInterface}. 43 | */ 44 | function burn(uint256 tokenId) public onlyBridge { 45 | return _burn(tokenId); 46 | } 47 | 48 | /** 49 | * @dev Override _burn, see {IERC165-supportsInterface}. 50 | */ 51 | function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) { 52 | return super._burn(tokenId); 53 | } 54 | 55 | 56 | /** 57 | * @dev Override supportsInterface, see {IERC165-supportsInterface}. 58 | */ 59 | function tokenURI(uint256 tokenId) public view virtual override(ERC721, ERC721URIStorage) returns (string memory) { 60 | return super.tokenURI(tokenId); 61 | } 62 | 63 | 64 | /** 65 | * @dev Override supportsInterface, see {IERC165-supportsInterface}. 66 | */ 67 | function supportsInterface( 68 | bytes4 interfaceId 69 | ) public view virtual override(ERC721, ERC721Enumerable) returns (bool) { 70 | return super.supportsInterface(interfaceId); 71 | } 72 | 73 | /** 74 | * @dev Override _beforeTokenTransfer, see {ERC721-_beforeTokenTransfer}. 75 | */ 76 | function _beforeTokenTransfer( 77 | address from, 78 | address to, 79 | uint256 firstTokenId, 80 | uint256 batchSize 81 | ) internal virtual override(ERC721, ERC721Enumerable) { 82 | super._beforeTokenTransfer(from, to, firstTokenId, batchSize); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/contracts/mocks/ERC20PermitMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | 7 | contract ERC20PermitMock is ERC20 { 8 | constructor( 9 | string memory name, 10 | string memory symbol, 11 | address initialAccount, 12 | uint256 initialBalance 13 | ) payable ERC20(name, symbol) { 14 | _mint(initialAccount, initialBalance); 15 | NAME_HASH = keccak256(bytes(name)); 16 | } 17 | 18 | function mint(address account, uint256 amount) public { 19 | _mint(account, amount); 20 | } 21 | 22 | function burn(uint256 amount) public { 23 | _burn(msg.sender, amount); 24 | } 25 | 26 | function transferInternal(address from, address to, uint256 value) public { 27 | _transfer(from, to, value); 28 | } 29 | 30 | function approveInternal( 31 | address owner, 32 | address spender, 33 | uint256 value 34 | ) public { 35 | _approve(owner, spender, value); 36 | } 37 | 38 | // erc20 permit 39 | mapping(address => uint256) public nonces; 40 | 41 | bytes32 public constant PERMIT_TYPEHASH = 42 | keccak256( 43 | "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" 44 | ); 45 | 46 | bytes32 public NAME_HASH; 47 | 48 | // bytes32 public constant VERSION_HASH = 49 | // keccak256("1") 50 | bytes32 public constant VERSION_HASH = 51 | 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6; 52 | 53 | // bytes32 public constant EIP712DOMAIN_HASH = 54 | // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") 55 | bytes32 public constant EIP712DOMAIN_HASH = 56 | 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; 57 | 58 | function _validateSignedData( 59 | address signer, 60 | bytes32 encodeData, 61 | uint8 v, 62 | bytes32 r, 63 | bytes32 s 64 | ) internal view { 65 | bytes32 domainSeparator = keccak256( 66 | abi.encode( 67 | EIP712DOMAIN_HASH, 68 | NAME_HASH, 69 | VERSION_HASH, 70 | getChainId(), 71 | address(this) 72 | ) 73 | ); 74 | 75 | bytes32 digest = keccak256( 76 | abi.encodePacked("\x19\x01", domainSeparator, encodeData) 77 | ); 78 | address recoveredAddress = ecrecover(digest, v, r, s); 79 | // Explicitly disallow authorizations for address(0) as ecrecover returns address(0) on malformed messages 80 | require( 81 | recoveredAddress != address(0) && recoveredAddress == signer, 82 | "HEZ::_validateSignedData: INVALID_SIGNATURE" 83 | ); 84 | } 85 | 86 | function getChainId() public view returns (uint256 chainId) { 87 | assembly { 88 | chainId := chainid() 89 | } 90 | } 91 | 92 | function permit( 93 | address owner, 94 | address spender, 95 | uint256 value, 96 | uint256 deadline, 97 | uint8 v, 98 | bytes32 r, 99 | bytes32 s 100 | ) external { 101 | require(deadline >= block.timestamp, "HEZ::permit: AUTH_EXPIRED"); 102 | bytes32 encodeData = keccak256( 103 | abi.encode( 104 | PERMIT_TYPEHASH, 105 | owner, 106 | spender, 107 | value, 108 | nonces[owner]++, 109 | deadline 110 | ) 111 | ); 112 | _validateSignedData(owner, encodeData, v, r, s); 113 | _approve(owner, spender, value); 114 | } 115 | 116 | function DOMAIN_SEPARATOR() external view returns (bytes32) { 117 | return 118 | keccak256( 119 | abi.encode( 120 | EIP712DOMAIN_HASH, 121 | NAME_HASH, 122 | VERSION_HASH, 123 | getChainId(), 124 | address(this) 125 | ) 126 | ); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/contracts/mocks/ERC721Mock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 6 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; 7 | import "@openzeppelin/contracts/access/Ownable.sol"; 8 | 9 | contract ERC721Mock is ERC721, ERC721Enumerable, Ownable { 10 | // Base token URI, the tokenURI of every nft will be the concatenation of this with the tokenID 11 | string public baseTokenURI; 12 | 13 | constructor( 14 | string memory name, 15 | string memory symbol, 16 | string memory _baseTokenURI 17 | ) ERC721(name, symbol) { 18 | baseTokenURI = _baseTokenURI; 19 | } 20 | 21 | /** 22 | * @dev Allows owner to mint an nft for a receiver 23 | * @param receiver nft receiver 24 | */ 25 | function mint(address receiver) public onlyOwner { 26 | // Mint NFT 27 | _mint(receiver, totalSupply() + 1); 28 | } 29 | 30 | /** 31 | * @dev Allows owner to set baseTokenURI 32 | * @param newBaseTokenURI new baseTokenURI 33 | */ 34 | function setBaseTokenURI(string memory newBaseTokenURI) external onlyOwner { 35 | baseTokenURI = newBaseTokenURI; 36 | } 37 | 38 | /** 39 | * @dev Override _baseURI, see {ERC721-_baseURI}. 40 | */ 41 | function _baseURI() internal view override returns (string memory) { 42 | return baseTokenURI; 43 | } 44 | 45 | /** 46 | * @dev Override supportsInterface, see {IERC165-supportsInterface}. 47 | */ 48 | function supportsInterface( 49 | bytes4 interfaceId 50 | ) public view virtual override(ERC721, ERC721Enumerable) returns (bool) { 51 | return super.supportsInterface(interfaceId); 52 | } 53 | 54 | /** 55 | * @dev Override _beforeTokenTransfer, see {ERC721-_beforeTokenTransfer}. 56 | */ 57 | function _beforeTokenTransfer( 58 | address from, 59 | address to, 60 | uint256 firstTokenId, 61 | uint256 batchSize 62 | ) internal virtual override(ERC721, ERC721Enumerable) { 63 | super._beforeTokenTransfer(from, to, firstTokenId, batchSize); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/contracts/polygonZKEVMContracts/PolygonZkEVMGlobalExitRoot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | import "./interfaces/IPolygonZkEVMGlobalExitRoot.sol"; 6 | import "./lib/GlobalExitRootLib.sol"; 7 | 8 | /** 9 | * Contract responsible for managing the exit roots across multiple networks 10 | */ 11 | contract PolygonZkEVMGlobalExitRoot is IPolygonZkEVMGlobalExitRoot { 12 | // PolygonZkEVMBridge address 13 | address public immutable bridgeAddress; 14 | 15 | // Rollup contract address 16 | address public immutable rollupAddress; 17 | 18 | // Rollup exit root, this will be updated every time a batch is verified 19 | bytes32 public lastRollupExitRoot; 20 | 21 | // Mainnet exit root, this will be updated every time a deposit is made in mainnet 22 | bytes32 public lastMainnetExitRoot; 23 | 24 | // Store every global exit root: Root --> timestamp 25 | mapping(bytes32 => uint256) public globalExitRootMap; 26 | 27 | /** 28 | * @dev Emitted when the global exit root is updated 29 | */ 30 | event UpdateGlobalExitRoot( 31 | bytes32 indexed mainnetExitRoot, 32 | bytes32 indexed rollupExitRoot 33 | ); 34 | 35 | /** 36 | * @param _rollupAddress Rollup contract address 37 | * @param _bridgeAddress PolygonZkEVMBridge contract address 38 | */ 39 | constructor(address _rollupAddress, address _bridgeAddress) { 40 | rollupAddress = _rollupAddress; 41 | bridgeAddress = _bridgeAddress; 42 | } 43 | 44 | /** 45 | * @notice Update the exit root of one of the networks and the global exit root 46 | * @param newRoot new exit tree root 47 | */ 48 | function updateExitRoot(bytes32 newRoot) external { 49 | // Store storage variables into temporal variables since will be used multiple times 50 | bytes32 cacheLastRollupExitRoot = lastRollupExitRoot; 51 | bytes32 cacheLastMainnetExitRoot = lastMainnetExitRoot; 52 | 53 | if (msg.sender == bridgeAddress) { 54 | lastMainnetExitRoot = newRoot; 55 | cacheLastMainnetExitRoot = newRoot; 56 | } else if (msg.sender == rollupAddress) { 57 | lastRollupExitRoot = newRoot; 58 | cacheLastRollupExitRoot = newRoot; 59 | } else { 60 | revert OnlyAllowedContracts(); 61 | } 62 | 63 | bytes32 newGlobalExitRoot = GlobalExitRootLib.calculateGlobalExitRoot( 64 | cacheLastMainnetExitRoot, 65 | cacheLastRollupExitRoot 66 | ); 67 | 68 | // If it already exists, do not modify the timestamp 69 | if (globalExitRootMap[newGlobalExitRoot] == 0) { 70 | globalExitRootMap[newGlobalExitRoot] = block.timestamp; 71 | emit UpdateGlobalExitRoot( 72 | cacheLastMainnetExitRoot, 73 | cacheLastRollupExitRoot 74 | ); 75 | } 76 | } 77 | 78 | /** 79 | * @notice Return last global exit root 80 | */ 81 | function getLastGlobalExitRoot() public view returns (bytes32) { 82 | return 83 | GlobalExitRootLib.calculateGlobalExitRoot( 84 | lastMainnetExitRoot, 85 | lastRollupExitRoot 86 | ); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/contracts/polygonZKEVMContracts/interfaces/IBasePolygonZkEVMGlobalExitRoot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | interface IBasePolygonZkEVMGlobalExitRoot { 6 | /** 7 | * @dev Thrown when the caller is not the allowed contracts 8 | */ 9 | error OnlyAllowedContracts(); 10 | 11 | function updateExitRoot(bytes32 newRollupExitRoot) external; 12 | 13 | function globalExitRootMap( 14 | bytes32 globalExitRootNum 15 | ) external returns (uint256); 16 | } 17 | -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/contracts/polygonZKEVMContracts/interfaces/IBridgeMessageReceiver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | /** 6 | * @dev Define interface for PolygonZkEVM Bridge message receiver 7 | */ 8 | interface IBridgeMessageReceiver { 9 | function onMessageReceived( 10 | address originAddress, 11 | uint32 originNetwork, 12 | bytes memory data 13 | ) external payable; 14 | } 15 | -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/contracts/polygonZKEVMContracts/interfaces/IPolygonZkEVMBridge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | interface IPolygonZkEVMBridge { 6 | /** 7 | * @dev Thrown when sender is not the PolygonZkEVM address 8 | */ 9 | error OnlyPolygonZkEVM(); 10 | 11 | /** 12 | * @dev Thrown when the destination network is invalid 13 | */ 14 | error DestinationNetworkInvalid(); 15 | 16 | /** 17 | * @dev Thrown when the amount does not match msg.value 18 | */ 19 | error AmountDoesNotMatchMsgValue(); 20 | 21 | /** 22 | * @dev Thrown when user is bridging tokens and is also sending a value 23 | */ 24 | error MsgValueNotZero(); 25 | 26 | /** 27 | * @dev Thrown when the Ether transfer on claimAsset fails 28 | */ 29 | error EtherTransferFailed(); 30 | 31 | /** 32 | * @dev Thrown when the message transaction on claimMessage fails 33 | */ 34 | error MessageFailed(); 35 | 36 | /** 37 | * @dev Thrown when the global exit root does not exist 38 | */ 39 | error GlobalExitRootInvalid(); 40 | 41 | /** 42 | * @dev Thrown when the smt proof does not match 43 | */ 44 | error InvalidSmtProof(); 45 | 46 | /** 47 | * @dev Thrown when an index is already claimed 48 | */ 49 | error AlreadyClaimed(); 50 | 51 | /** 52 | * @dev Thrown when the owner of permit does not match the sender 53 | */ 54 | error NotValidOwner(); 55 | 56 | /** 57 | * @dev Thrown when the spender of the permit does not match this contract address 58 | */ 59 | error NotValidSpender(); 60 | 61 | /** 62 | * @dev Thrown when the amount of the permit does not match 63 | */ 64 | error NotValidAmount(); 65 | 66 | /** 67 | * @dev Thrown when the permit data contains an invalid signature 68 | */ 69 | error NotValidSignature(); 70 | 71 | function bridgeAsset( 72 | uint32 destinationNetwork, 73 | address destinationAddress, 74 | uint256 amount, 75 | address token, 76 | bool forceUpdateGlobalExitRoot, 77 | bytes calldata permitData 78 | ) external payable; 79 | 80 | function bridgeMessage( 81 | uint32 destinationNetwork, 82 | address destinationAddress, 83 | bool forceUpdateGlobalExitRoot, 84 | bytes calldata metadata 85 | ) external payable; 86 | 87 | function claimAsset( 88 | bytes32[32] calldata smtProof, 89 | uint32 index, 90 | bytes32 mainnetExitRoot, 91 | bytes32 rollupExitRoot, 92 | uint32 originNetwork, 93 | address originTokenAddress, 94 | uint32 destinationNetwork, 95 | address destinationAddress, 96 | uint256 amount, 97 | bytes calldata metadata 98 | ) external; 99 | 100 | function claimMessage( 101 | bytes32[32] calldata smtProof, 102 | uint32 index, 103 | bytes32 mainnetExitRoot, 104 | bytes32 rollupExitRoot, 105 | uint32 originNetwork, 106 | address originAddress, 107 | uint32 destinationNetwork, 108 | address destinationAddress, 109 | uint256 amount, 110 | bytes calldata metadata 111 | ) external; 112 | 113 | function updateGlobalExitRoot() external; 114 | 115 | function activateEmergencyState() external; 116 | 117 | function deactivateEmergencyState() external; 118 | 119 | function networkID() external returns(uint32); 120 | } 121 | -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/contracts/polygonZKEVMContracts/interfaces/IPolygonZkEVMGlobalExitRoot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | import "./IBasePolygonZkEVMGlobalExitRoot.sol"; 5 | 6 | interface IPolygonZkEVMGlobalExitRoot is IBasePolygonZkEVMGlobalExitRoot { 7 | function getLastGlobalExitRoot() external view returns (bytes32); 8 | } 9 | -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/contracts/polygonZKEVMContracts/lib/EmergencyManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | /** 6 | * @dev Contract helper responsible to manage the emergency state 7 | */ 8 | contract EmergencyManager { 9 | /** 10 | * @dev Thrown when emergency state is active, and the function requires otherwise 11 | */ 12 | error OnlyNotEmergencyState(); 13 | 14 | /** 15 | * @dev Thrown when emergency state is not active, and the function requires otherwise 16 | */ 17 | error OnlyEmergencyState(); 18 | 19 | /** 20 | * @dev This empty reserved space is put in place to allow future versions to add new 21 | * variables without shifting down storage in the inheritance chain. 22 | */ 23 | uint256[10] private _gap; 24 | 25 | // Indicates whether the emergency state is active or not 26 | bool public isEmergencyState; 27 | 28 | /** 29 | * @dev Emitted when emergency state is activated 30 | */ 31 | event EmergencyStateActivated(); 32 | 33 | /** 34 | * @dev Emitted when emergency state is deactivated 35 | */ 36 | event EmergencyStateDeactivated(); 37 | 38 | /** 39 | * @notice Only allows a function to be callable if emergency state is unactive 40 | */ 41 | modifier ifNotEmergencyState() { 42 | if (isEmergencyState) { 43 | revert OnlyNotEmergencyState(); 44 | } 45 | _; 46 | } 47 | 48 | /** 49 | * @notice Only allows a function to be callable if emergency state is active 50 | */ 51 | modifier ifEmergencyState() { 52 | if (!isEmergencyState) { 53 | revert OnlyEmergencyState(); 54 | } 55 | _; 56 | } 57 | 58 | /** 59 | * @notice Activate emergency state 60 | */ 61 | function _activateEmergencyState() internal virtual ifNotEmergencyState { 62 | isEmergencyState = true; 63 | emit EmergencyStateActivated(); 64 | } 65 | 66 | /** 67 | * @notice Deactivate emergency state 68 | */ 69 | function _deactivateEmergencyState() internal virtual ifEmergencyState { 70 | isEmergencyState = false; 71 | emit EmergencyStateDeactivated(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/contracts/polygonZKEVMContracts/lib/GlobalExitRootLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity 0.8.17; 4 | 5 | /** 6 | * @dev A library that provides the necessary calculations to calculate the global exit root 7 | */ 8 | library GlobalExitRootLib { 9 | function calculateGlobalExitRoot( 10 | bytes32 mainnetExitRoot, 11 | bytes32 rollupExitRoot 12 | ) internal pure returns (bytes32) { 13 | return keccak256(abi.encodePacked(mainnetExitRoot, rollupExitRoot)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/contracts/polygonZKEVMContracts/lib/TokenWrapped.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | // Implementation of permit based on https://github.com/WETH10/WETH10/blob/main/contracts/WETH10.sol 3 | pragma solidity 0.8.17; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | 7 | contract TokenWrapped is ERC20 { 8 | // Domain typehash 9 | bytes32 public constant DOMAIN_TYPEHASH = 10 | keccak256( 11 | "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" 12 | ); 13 | // Permit typehash 14 | bytes32 public constant PERMIT_TYPEHASH = 15 | keccak256( 16 | "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" 17 | ); 18 | 19 | // Version 20 | string public constant VERSION = "1"; 21 | 22 | // Chain id on deployment 23 | uint256 public immutable deploymentChainId; 24 | 25 | // Domain separator calculated on deployment 26 | bytes32 private immutable _DEPLOYMENT_DOMAIN_SEPARATOR; 27 | 28 | // PolygonZkEVM Bridge address 29 | address public immutable bridgeAddress; 30 | 31 | // Decimals 32 | uint8 private immutable _decimals; 33 | 34 | // Permit nonces 35 | mapping(address => uint256) public nonces; 36 | 37 | modifier onlyBridge() { 38 | require( 39 | msg.sender == bridgeAddress, 40 | "TokenWrapped::onlyBridge: Not PolygonZkEVMBridge" 41 | ); 42 | _; 43 | } 44 | 45 | constructor( 46 | string memory name, 47 | string memory symbol, 48 | uint8 __decimals 49 | ) ERC20(name, symbol) { 50 | bridgeAddress = msg.sender; 51 | _decimals = __decimals; 52 | deploymentChainId = block.chainid; 53 | _DEPLOYMENT_DOMAIN_SEPARATOR = _calculateDomainSeparator(block.chainid); 54 | } 55 | 56 | function mint(address to, uint256 value) external onlyBridge { 57 | _mint(to, value); 58 | } 59 | 60 | // Notice that is not require to approve wrapped tokens to use the bridge 61 | function burn(address account, uint256 value) external onlyBridge { 62 | _burn(account, value); 63 | } 64 | 65 | function decimals() public view virtual override returns (uint8) { 66 | return _decimals; 67 | } 68 | 69 | // Permit relative functions 70 | function permit( 71 | address owner, 72 | address spender, 73 | uint256 value, 74 | uint256 deadline, 75 | uint8 v, 76 | bytes32 r, 77 | bytes32 s 78 | ) external { 79 | require( 80 | block.timestamp <= deadline, 81 | "TokenWrapped::permit: Expired permit" 82 | ); 83 | 84 | bytes32 hashStruct = keccak256( 85 | abi.encode( 86 | PERMIT_TYPEHASH, 87 | owner, 88 | spender, 89 | value, 90 | nonces[owner]++, 91 | deadline 92 | ) 93 | ); 94 | 95 | bytes32 digest = keccak256( 96 | abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), hashStruct) 97 | ); 98 | 99 | address signer = ecrecover(digest, v, r, s); 100 | require( 101 | signer != address(0) && signer == owner, 102 | "TokenWrapped::permit: Invalid signature" 103 | ); 104 | 105 | _approve(owner, spender, value); 106 | } 107 | 108 | /** 109 | * @notice Calculate domain separator, given a chainID. 110 | * @param chainId Current chainID 111 | */ 112 | function _calculateDomainSeparator( 113 | uint256 chainId 114 | ) private view returns (bytes32) { 115 | return 116 | keccak256( 117 | abi.encode( 118 | DOMAIN_TYPEHASH, 119 | keccak256(bytes(name())), 120 | keccak256(bytes(VERSION)), 121 | chainId, 122 | address(this) 123 | ) 124 | ); 125 | } 126 | 127 | /// @dev Return the DOMAIN_SEPARATOR. 128 | function DOMAIN_SEPARATOR() public view returns (bytes32) { 129 | return 130 | block.chainid == deploymentChainId 131 | ? _DEPLOYMENT_DOMAIN_SEPARATOR 132 | : _calculateDomainSeparator(block.chainid); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/deployment/deployNFTBridge.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-await-in-loop, no-use-before-define, no-lonely-if, import/no-dynamic-require, global-require */ 2 | /* eslint-disable no-console, no-inner-declarations, no-undef, import/no-unresolved, no-restricted-syntax */ 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); 6 | const { ethers } = require('hardhat'); 7 | 8 | const mainnetBridgeAddress = '0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe'; 9 | const testnetBridgeAddress = '0xF6BEEeBB578e214CA9E23B0e9683454Ff88Ed2A7'; 10 | 11 | const create2Contract = '0x4e59b44847b379578588920ca78fbf26c0b4956c'; 12 | const saltCreate2 = '0x0000000000000000000000000000000000000000000000000000000000000000'; 13 | 14 | async function main() { 15 | let zkEVMProvider; 16 | let zkEVMBridgeContractAddress; 17 | 18 | const networkName = process.env.HARDHAT_NETWORK; 19 | // Use mainnet bridge address 20 | if (networkName === 'mainnet') { 21 | zkEVMBridgeContractAddress = mainnetBridgeAddress; 22 | zkEVMProvider = new ethers.providers.JsonRpcProvider('https://zkevm-rpc.com'); 23 | } else if (networkName === 'goerli') { 24 | // Use testnet bridge address 25 | zkEVMBridgeContractAddress = testnetBridgeAddress; 26 | zkEVMProvider = new ethers.providers.JsonRpcProvider('https://rpc.public.zkevm-test.net'); 27 | } else { 28 | throw new Error('Network not supported'); 29 | } 30 | 31 | // Load deployer 32 | let deployer; let deployerZkEVM; 33 | if (process.env.PVTKEY) { 34 | deployer = new ethers.Wallet(process.env.PVTKEY, ethers.provider); 35 | deployerZkEVM = new ethers.Wallet(process.env.PVTKEY, zkEVMProvider); 36 | console.log('Using pvtKey deployer with address: ', deployer.address); 37 | } else if (process.env.MNEMONIC) { 38 | deployer = ethers.Wallet.fromMnemonic(process.env.MNEMONIC, 'm/44\'/60\'/0\'/0/0').connect(ethers.provider); 39 | deployerZkEVM = ethers.Wallet.fromMnemonic(process.env.MNEMONIC, 'm/44\'/60\'/0\'/0/0').connect(zkEVMProvider); 40 | console.log('Using MNEMONIC deployer with address: ', deployer.address); 41 | } else { 42 | [deployer] = (await ethers.getSigners()); 43 | } 44 | 45 | const nftBridgeFactory = await ethers.getContractFactory('ZkEVMNFTBridge', deployer); 46 | const deployBridgeTxData = (nftBridgeFactory.getDeployTransaction( 47 | zkEVMBridgeContractAddress, 48 | )).data; 49 | 50 | // Encode deploy transaction 51 | const hashInitCode = ethers.utils.solidityKeccak256(['bytes'], [deployBridgeTxData]); 52 | // Precalculate create2 address 53 | const precalculatedAddressDeployed = ethers.utils.getCreate2Address(create2Contract, saltCreate2, hashInitCode); 54 | 55 | const txParams = { 56 | to: create2Contract, 57 | data: ethers.utils.hexConcat([saltCreate2, deployBridgeTxData]), 58 | }; 59 | 60 | // Deploy L1 61 | if (await deployer.provider.getCode(precalculatedAddressDeployed) === '0x') { 62 | await (await deployer.sendTransaction(txParams)).wait(); 63 | } else { 64 | console.log('Contract already deployed on L1'); 65 | } 66 | 67 | // Deploy zkEVM 68 | if (await deployerZkEVM.provider.getCode(precalculatedAddressDeployed) === '0x') { 69 | await (await deployerZkEVM.sendTransaction(txParams)).wait(); 70 | } else { 71 | console.log('Contract already deployed on L2'); 72 | } 73 | 74 | // Check succesfull deployment 75 | if (await deployer.provider.getCode(precalculatedAddressDeployed) === '0x' 76 | || await deployerZkEVM.provider.getCode(precalculatedAddressDeployed) === '0x' 77 | ) { 78 | throw new Error('Deployment failed'); 79 | } 80 | console.log('NFT bridge contract succesfully deployed on: ', precalculatedAddressDeployed); 81 | 82 | const outputJson = { 83 | nftBridgeContract: precalculatedAddressDeployed, 84 | }; 85 | const pathOutputJson = path.join(__dirname, './NFTBridge_output.json'); 86 | 87 | fs.writeFileSync(pathOutputJson, JSON.stringify(outputJson, null, 1)); 88 | } 89 | 90 | main().catch((e) => { 91 | console.error(e); 92 | process.exit(1); 93 | }); 94 | -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/deployment/verifyContracts.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | /* eslint-disable import/no-dynamic-require, no-await-in-loop, no-restricted-syntax, guard-for-in */ 3 | require('dotenv').config(); 4 | const path = require('path'); 5 | const hre = require('hardhat'); 6 | const { expect } = require('chai'); 7 | 8 | const mainnetBridgeAddress = '0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe'; 9 | const testnetBridgeAddress = '0xF6BEEeBB578e214CA9E23B0e9683454Ff88Ed2A7'; 10 | 11 | async function main() { 12 | const networkName = process.env.HARDHAT_NETWORK; 13 | const pathDeployOutputParameters = path.join(__dirname, './NFTBridge_output.json'); 14 | const deployOutputParameters = require(pathDeployOutputParameters); 15 | 16 | let zkEVMBridgeContractAddress; 17 | // Use mainnet bridge address 18 | if (networkName === 'polygonZKEVMMainnet' || networkName === 'mainnet') { 19 | zkEVMBridgeContractAddress = mainnetBridgeAddress; 20 | } 21 | 22 | // Use testnet bridge address 23 | if (networkName === 'polygonZKEVMTestnet' || networkName === 'goerli') { 24 | zkEVMBridgeContractAddress = testnetBridgeAddress; 25 | } 26 | 27 | try { 28 | // verify governance 29 | await hre.run( 30 | 'verify:verify', 31 | { 32 | address: deployOutputParameters.nftBridgeContract, 33 | constructorArguments: [ 34 | zkEVMBridgeContractAddress, 35 | ], 36 | }, 37 | ); 38 | } catch (error) { 39 | console.log(error); 40 | expect(error.message.toLowerCase().includes('already verified')).to.be.equal(true); 41 | } 42 | } 43 | 44 | main() 45 | .then(() => process.exit(0)) 46 | .catch((error) => { 47 | console.error(error); 48 | process.exit(1); 49 | }); 50 | -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@0xpolygonhermez/code-examples", 3 | "description": "nft bridge example for polygon zkEVM", 4 | "version": "1.0.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/0xPolygonHermez/code-examples.git" 8 | }, 9 | "main": "index.js", 10 | "keywords": [ 11 | "zkevm", 12 | "snark", 13 | "polygon", 14 | "hermez", 15 | "stark", 16 | "EVM", 17 | "ethereum", 18 | "blockchain" 19 | ], 20 | "author": "0xPolygonHermez", 21 | "dependencies": { 22 | "axios": "^1.3.5", 23 | "chai": "^4.3.7", 24 | "ethers": "^5.7.2" 25 | }, 26 | "devDependencies": { 27 | "@0xpolygonhermez/zkevm-commonjs": "github:0xPolygonHermez/zkevm-commonjs#develop", 28 | "@nomiclabs/hardhat-ethers": "^2.2.2", 29 | "@nomiclabs/hardhat-etherscan": "^3.1.7", 30 | "@nomiclabs/hardhat-waffle": "^2.0.5", 31 | "@openzeppelin/contracts": "4.8.2", 32 | "@openzeppelin/contracts-upgradeable": "4.8.2", 33 | "@openzeppelin/hardhat-upgrades": "1.22.1", 34 | "@openzeppelin/test-helpers": "0.5.16", 35 | "dotenv": "^8.6.0", 36 | "eslint": "^8.36.0", 37 | "eslint-config-airbnb-base": "^15.0.0", 38 | "eslint-plugin-mocha": "^9.0.0", 39 | "ethereum-waffle": "^3.4.4", 40 | "hardhat": "^2.13.0", 41 | "hardhat-gas-reporter": "^1.0.9", 42 | "prettier": "^2.8.4", 43 | "prettier-plugin-solidity": "^1.1.3", 44 | "solc-0.8": "npm:solc@0.8.17", 45 | "solidity-coverage": "^0.7.22", 46 | "solidity-docgen": "^0.5.17" 47 | }, 48 | "scripts": { 49 | "deploy:nftBridge:goerli": "npx hardhat run deployment/deployNFTBridge.js --network goerli", 50 | "deploy:nftBridge:mainnet": "npx hardhat run deployment/deployNFTBridge.js --network mainnet", 51 | "verify:nftBridge:goerli": "npx hardhat run deployment/verifyContracts.js --network goerli", 52 | "verify:nftBridge:mainnet": "npx hardhat run deployment/verifyContracts.js --network mainnet", 53 | "verify:nftBridge:polygonZKEVMTestnet": "npx hardhat run deployment/verifyContracts.js --network polygonZKEVMTestnet", 54 | "verify:nftBridge:polygonZKEVMMainnet": "npx hardhat run deployment/verifyContracts.js --network polygonZKEVMMainnet", 55 | "lint": "npx eslint ./test && npx eslint ./docker/scripts && npx eslint ./deployment && npx eslint ./src", 56 | "lint:fix": "npx eslint ./test --fix && npx eslint ./docker/scripts --fix && npx eslint ./deployment --fix && npx eslint ./src --fix", 57 | "compile": "npx hardhat compile", 58 | "gas:report": "REPORT_GAS=true npx hardhat test", 59 | "gas:report:file": "rm -f .openzeppelin/unknown-31337.json && REPORT_GAS=true REPORT_GAS_FILE=true npx hardhat test", 60 | "deploy:mockNFT:goerli": "npx hardhat run scripts/deployMockNFT.js --network goerli", 61 | "deploy:mockNFT:mainnet": "npx hardhat run scripts/deployMockNFT.js --network mainnet", 62 | "deploy:mockNFT:polygonZKEVMTestnet": "npx hardhat run scripts/deployMockNFT.js --network polygonZKEVMTestnet", 63 | "deploy:mockNFT:polygonZKEVMMainnet": "npx hardhat run scripts/deployMockNFT.js --network polygonZKEVMMainnet", 64 | "verify:mockNFT:goerli": "npx hardhat run scripts/verifyMockNFT.js --network goerli", 65 | "verify:mockNFT:mainnet": "npx hardhat run scripts/verifyMockNFT.js --network mainnet", 66 | "verify:mockNFT:polygonZKEVMTestnet": "npx hardhat run scripts/verifyMockNFT.js --network polygonZKEVMTestnet", 67 | "verify:mockNFT:polygonZKEVMMainnet": "npx hardhat run scripts/verifyMockNFT.js --network polygonZKEVMMainnet", 68 | "bridge:mockNFT:goerli": "npx hardhat run scripts/bridgeMockNFT.js --network goerli", 69 | "bridge:mockNFT:mainnet": "npx hardhat run scripts/bridgeMockNFT.js --network mainnet", 70 | "bridge:mockNFT:polygonZKEVMTestnet": "npx hardhat run scripts/bridgeMockNFT.js --network polygonZKEVMTestnet", 71 | "bridge:mockNFT:polygonZKEVMMainnet": "npx hardhat run scripts/bridgeMockNFT.js --network polygonZKEVMMainnet", 72 | "claim:mockNFT:goerli": "npx hardhat run scripts/claimMockNFT.js --network goerli", 73 | "claim:mockNFT:mainnet": "npx hardhat run scripts/claimMockNFT.js --network mainnet", 74 | "claim:mockNFT:polygonZKEVMTestnet": "npx hardhat run scripts/claimMockNFT.js --network polygonZKEVMTestnet", 75 | "claim:mockNFT:polygonZKEVMMainnet": "npx hardhat run scripts/claimMockNFT.js --network polygonZKEVMMainnet" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/scripts/bridgeMockNFT.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-await-in-loop, no-use-before-define, no-lonely-if, import/no-dynamic-require, global-require */ 2 | /* eslint-disable no-console, no-inner-declarations, no-undef, import/no-unresolved, no-restricted-syntax */ 3 | const path = require('path'); 4 | require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); 5 | const { ethers } = require('hardhat'); 6 | 7 | const networkIDMainnet = 0; 8 | const networkIDRollup = 1; 9 | 10 | const pathdeployedNFT = path.join(__dirname, './deployMockNFT_output.json'); 11 | const deployedNFT = require(pathdeployedNFT).nftMockcontract; 12 | 13 | const pathPingPongOutput = path.join(__dirname, '../deployment/NFTBridge_output.json'); 14 | const deployedNftBridgeAddress = require(pathPingPongOutput).nftBridgeContract; 15 | 16 | async function main() { 17 | // Load deployer 18 | let deployer; 19 | if (process.env.PVTKEY) { 20 | deployer = new ethers.Wallet(process.env.PVTKEY, ethers.provider); 21 | console.log('Using pvtKey deployer with address: ', deployer.address); 22 | } else if (process.env.MNEMONIC) { 23 | deployer = ethers.Wallet.fromMnemonic(process.env.MNEMONIC, 'm/44\'/60\'/0\'/0/0').connect(ethers.provider); 24 | console.log('Using MNEMONIC deployer with address: ', deployer.address); 25 | } else { 26 | [deployer] = (await ethers.getSigners()); 27 | } 28 | 29 | const nftBridgeFactory = await ethers.getContractFactory('ZkEVMNFTBridge', deployer); 30 | const nftBridgeContract = await nftBridgeFactory.attach( 31 | deployedNftBridgeAddress, 32 | ); 33 | 34 | const tokenId = 1; 35 | const destinationNetwork = networkIDRollup; 36 | const destinationAddress = deployer.address; 37 | 38 | // Approve tokens 39 | const nftFactory = await ethers.getContractFactory('ERC721Mock', deployer); 40 | const nftContract = nftFactory.attach(deployedNFT); 41 | await (await nftContract.approve(nftBridgeContract.address, tokenId)).wait(); 42 | console.log('approved token id'); 43 | 44 | const tx = await nftBridgeContract.bridgeNFT( 45 | destinationNetwork, 46 | destinationAddress, 47 | deployedNFT, 48 | tokenId, 49 | true, 50 | ); 51 | 52 | console.log((await tx.wait()).transactionHash); 53 | 54 | console.log('Bridge done succesfully'); 55 | } 56 | 57 | main().catch((e) => { 58 | console.error(e); 59 | process.exit(1); 60 | }); 61 | -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/scripts/claimMockNFT.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-await-in-loop */ 2 | /* eslint-disable no-console, no-inner-declarations, no-undef, import/no-unresolved */ 3 | 4 | const path = require('path'); 5 | require('dotenv').config({ path: path.resolve(__dirname, '../../.env') }); 6 | const { ethers } = require('hardhat'); 7 | 8 | const mainnetBridgeAddress = '0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe'; 9 | const testnetBridgeAddress = '0xF6BEEeBB578e214CA9E23B0e9683454Ff88Ed2A7'; 10 | 11 | const mekrleProofString = '/merkle-proof'; 12 | const getClaimsFromAcc = '/bridges/'; 13 | 14 | const pathPingPongOutput = path.join(__dirname, '../deployment/NFTBridge_output.json'); 15 | const deployedNftBridgeAddress = require(pathPingPongOutput).nftBridgeContract; 16 | 17 | async function main() { 18 | const currentProvider = ethers.provider; 19 | let deployer; 20 | if (process.env.PVTKEY) { 21 | deployer = new ethers.Wallet(process.env.PVTKEY, currentProvider); 22 | console.log('Using pvtKey deployer with address: ', deployer.address); 23 | } else if (process.env.MNEMONIC) { 24 | deployer = ethers.Wallet.fromMnemonic(process.env.MNEMONIC, 'm/44\'/60\'/0\'/0/0').connect(currentProvider); 25 | console.log('Using MNEMONIC deployer with address: ', deployer.address); 26 | } else { 27 | [deployer] = (await ethers.getSigners()); 28 | } 29 | 30 | let zkEVMBridgeContractAddress; 31 | let baseURL; 32 | const networkName = process.env.HARDHAT_NETWORK; 33 | 34 | // Use mainnet bridge address 35 | if (networkName === 'polygonZKEVMMainnet' || networkName === 'mainnet') { 36 | zkEVMBridgeContractAddress = mainnetBridgeAddress; 37 | baseURL = 'https://bridge-api.zkevm-rpc.com'; 38 | } else if (networkName === 'polygonZKEVMTestnet' || networkName === 'goerli') { 39 | // Use testnet bridge address 40 | zkEVMBridgeContractAddress = testnetBridgeAddress; 41 | baseURL = 'https://bridge-api.public.zkevm-test.net'; 42 | } 43 | 44 | const axios = require('axios').create({ 45 | baseURL, 46 | }); 47 | 48 | const bridgeFactoryZkeEVm = await ethers.getContractFactory('PolygonZkEVMBridge', deployer); 49 | const bridgeContractZkeVM = bridgeFactoryZkeEVm.attach(zkEVMBridgeContractAddress); 50 | 51 | const depositAxions = await axios.get(getClaimsFromAcc + deployedNftBridgeAddress, { params: { limit: 100, offset: 0 } }); 52 | const depositsArray = depositAxions.data.deposits; 53 | 54 | if (depositsArray.length === 0) { 55 | console.log('Not deposits yet!'); 56 | return; 57 | } 58 | 59 | for (let i = 0; i < depositsArray.length; i++) { 60 | const currentDeposit = depositsArray[i]; 61 | if (currentDeposit.ready_for_claim) { 62 | 63 | if (currentDeposit.claim_tx_hash != "") { 64 | console.log('already claimed: ', currentDeposit.claim_tx_hash); 65 | continue; 66 | } 67 | 68 | const proofAxios = await axios.get(mekrleProofString, { 69 | params: { deposit_cnt: currentDeposit.deposit_cnt, net_id: currentDeposit.orig_net }, 70 | }); 71 | 72 | const { proof } = proofAxios.data; 73 | const claimTx = await bridgeContractZkeVM.claimMessage( 74 | proof.merkle_proof, 75 | currentDeposit.deposit_cnt, 76 | proof.main_exit_root, 77 | proof.rollup_exit_root, 78 | currentDeposit.orig_net, 79 | currentDeposit.orig_addr, 80 | currentDeposit.dest_net, 81 | currentDeposit.dest_addr, 82 | currentDeposit.amount, 83 | currentDeposit.metadata, 84 | ); 85 | console.log('claim message succesfully send: ', claimTx.hash); 86 | await claimTx.wait(); 87 | console.log('claim message succesfully mined'); 88 | } else { 89 | console.log('Not ready yet!'); 90 | } 91 | } 92 | } 93 | 94 | main().catch((e) => { 95 | console.error(e); 96 | process.exit(1); 97 | }); 98 | -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/scripts/deployMockNFT.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-await-in-loop, no-use-before-define, no-lonely-if, import/no-dynamic-require, global-require */ 2 | /* eslint-disable no-console, no-inner-declarations, no-undef, import/no-unresolved, no-restricted-syntax */ 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); 6 | const { ethers } = require('hardhat'); 7 | 8 | async function main() { 9 | // Load deployer 10 | let deployer; 11 | if (process.env.PVTKEY) { 12 | deployer = new ethers.Wallet(process.env.PVTKEY, ethers.provider); 13 | console.log('Using pvtKey deployer with address: ', deployer.address); 14 | } else if (process.env.MNEMONIC) { 15 | deployer = ethers.Wallet.fromMnemonic(process.env.MNEMONIC, 'm/44\'/60\'/0\'/0/0').connect(ethers.provider); 16 | console.log('Using MNEMONIC deployer with address: ', deployer.address); 17 | } else { 18 | [deployer] = (await ethers.getSigners()); 19 | } 20 | 21 | // deploy erc721 token 22 | const tokenName = 'test NFT'; 23 | const tokenSymbol = 'TNFT'; 24 | const baseTokenURL = 'http://example'; 25 | 26 | const nftFactory = await ethers.getContractFactory('ERC721Mock', deployer); 27 | const nftContract = await nftFactory.deploy( 28 | tokenName, 29 | tokenSymbol, 30 | baseTokenURL, 31 | ); 32 | await nftContract.deployed(); 33 | 34 | console.log('nftMockContract contract succesfully deployed on: ', nftContract.address); 35 | 36 | // mint nft for owner 37 | await nftContract.mint(deployer.address); 38 | 39 | const outputJson = { 40 | tokenName, 41 | tokenSymbol, 42 | baseTokenURL, 43 | nftMockcontract: nftContract.address, 44 | }; 45 | 46 | const pathOutputJson = path.join(__dirname, './deployMockNFT_output.json'); 47 | 48 | fs.writeFileSync(pathOutputJson, JSON.stringify(outputJson, null, 1)); 49 | } 50 | 51 | main().catch((e) => { 52 | console.error(e); 53 | process.exit(1); 54 | }); 55 | -------------------------------------------------------------------------------- /zkevm-nft-bridge-example/scripts/verifyMockNFT.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-dynamic-require, no-await-in-loop, no-restricted-syntax, guard-for-in */ 2 | require('dotenv').config(); 3 | const path = require('path'); 4 | const hre = require('hardhat'); 5 | const {expect} = require('chai'); 6 | 7 | async function main() { 8 | const pathDeployOutputParameters = path.join(__dirname, './deployMockNFT_output.json'); 9 | const deployOutputParameters = require(pathDeployOutputParameters); 10 | 11 | try { 12 | // verify governance 13 | await hre.run( 14 | 'verify:verify', 15 | { 16 | address: deployOutputParameters.nftMockcontract, 17 | constructorArguments: [ 18 | deployOutputParameters.tokenName, 19 | deployOutputParameters.tokenSymbol, 20 | deployOutputParameters.baseTokenURL, 21 | ], 22 | }, 23 | ); 24 | } catch (error) { 25 | expect(error.message.toLowerCase().includes('already verified')).to.be.equal(true); 26 | } 27 | } 28 | 29 | main() 30 | .then(() => process.exit(0)) 31 | .catch((error) => { 32 | console.error(error); 33 | process.exit(1); 34 | }); 35 | --------------------------------------------------------------------------------