├── .gitignore ├── README.md ├── multisig-wallet ├── .gitignore ├── README.md ├── contracts │ ├── MultiSigWallet.sol │ └── Token.sol ├── demo-setup-scripts │ └── init-env-hardhat.sh ├── deployments.json ├── deployments │ ├── ropsten_3 │ │ ├── MultiSigWallet.json │ │ └── Token.json │ ├── tenderly_1 │ │ ├── MultiSigWallet.json │ │ └── Token.json │ └── tenderly_3 │ │ ├── MultiSigWallet.json │ │ └── Token.json ├── hardhat.config.ts ├── infra.json ├── lib │ ├── api │ │ ├── t-forks-clean-recreate.ts │ │ └── tenderly-api.ts │ └── deployment-utils.ts ├── package-lock.json ├── package.json ├── scripts │ ├── multisig-deploy.ts │ ├── multisig-play.ts │ └── token-deploy.ts ├── tenderly.yaml ├── test │ └── multisig.ts ├── tsconfig.json ├── web3-actions-local │ ├── multisig-run-local.ts │ └── payload │ │ ├── payload-confirm.json │ │ ├── payload-execute.json │ │ └── payload-submit.json ├── web3-actions │ ├── .gitignore │ ├── abi │ │ ├── MultiSigWallet.json │ │ └── Token.json │ ├── multisig.ts │ ├── package-lock.json │ ├── package.json │ ├── tenderly-api.ts │ └── tsconfig.json └── yarn.lock ├── simple-coin-oracle ├── CoinOracle.sol ├── README.md ├── actions │ ├── .gitignore │ ├── CoinOracleContract.json │ ├── coinOracle.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json ├── package-lock.json └── tenderly.yaml ├── tic-tac-toe ├── README.md ├── TicTacToe.sol ├── actions │ ├── .gitignore │ ├── TicTacToe.json │ ├── example.ts │ ├── package-lock.json │ ├── package.json │ ├── tests │ │ └── ticTacToe.spec.ts │ ├── ticTacToeActions.ts │ └── tsconfig.json └── tenderly.yaml └── uniswap-notify-discord ├── README.md ├── actions ├── .gitignore ├── UniswapV3FactoryAbi.json ├── emulate-action-execution.ts ├── package-lock.json ├── package.json ├── tests │ ├── fixtures │ │ └── createPoolEventPayload.json │ └── swap.test.ts ├── tsconfig.json └── uniswapActions.ts └── tenderly.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | **/node_modules 3 | 4 | # Tenderly's actions out dir 5 | out 6 | .env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Examples of Tenderly Web3 Actions 3 | 4 | Example repository for web3 actions, accompanying tutorials and how-to guides. 5 | 6 | ## Running the examples 7 | To run these examples which deploy Web3 Actions, you need to have Tenderly CLI. 8 | 9 | ## Install Tenderly CLI 10 | If you haven't already, install [Tenderly CLI](https://github.com/Tenderly/tenderly-cli#installation). 11 | 12 | Before you go on, you need to login with CLI, using your Tenderly credentials: 13 | 14 | ```bash 15 | tenderly login 16 | ``` 17 | 18 | ### Install dependencies for Web3 Actions project: 19 | 20 | First `cd` into an example you're interested in and `cd actions`. Then run `npm i`: 21 | 22 | ``` bash 23 | cd tic-tac-toe/actions 24 | npm install 25 | cd .. 26 | ``` 27 | 28 | ### Build and Run 29 | 30 | To build/deploy run, `cd` into example you're interested in and then run the CLI. 31 | 32 | ``` bash 33 | cd tic-tac-toe 34 | tenderly build # stays on your machine 35 | tenderly deploy # deploys and activates the action 36 | ``` 37 | 38 | 39 | ## On examples structure 40 | 41 | All examples were generated via 42 | 43 | ```bash 44 | tenderly actions init 45 | ``` 46 | 47 | Each example root has the following structure: 48 | - It contains `tenderly.yaml` 49 | - It contains relevant smart contracts and a corresponding JSON file (if any are present). 50 | - It contains `actions` directory you'd get when you initialize Web3 Actions project using Tenderly CLI 51 | - `actions` is a node project (`package.json`) 52 | - `actions` is a typescript project (`tsconfig.json`) 53 | - Examples use `ethers`, which is an arbitrary decision. 54 | 55 | ## Common plumbing: 56 | What doesn't come out of the box with CLI setup is: 57 | - Support for [ethers.js](https://github.com/ethers-io/ethers.js/) or any other library, you need to add it yourself: 58 | 59 | ```bash 60 | cd actions 61 | npm install --save-dev axios ethers @types/node 62 | cd .. 63 | ``` 64 | 65 | - Importing contract's JSON file. You have to configure TS for this: 66 | ```diff 67 | { 68 | "compileOnSave": true, 69 | "compilerOptions": { 70 | "module": "commonjs", 71 | "noImplicitReturns": true, 72 | "noUnusedLocals": true, 73 | "outDir": "out", 74 | "sourceMap": true, 75 | "strict": true, 76 | "target": "es2020", 77 | + "resolveJsonModule": true, 78 | + "esModuleInterop": true 79 | }, 80 | "include": [ 81 | "**/*" 82 | ] 83 | } 84 | ``` 85 | -------------------------------------------------------------------------------- /multisig-wallet/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | 8 | #Hardhat files 9 | cache 10 | artifacts 11 | 12 | -------------------------------------------------------------------------------- /multisig-wallet/README.md: -------------------------------------------------------------------------------- 1 | # Web3 Actions + Multisig wallet 2 | 3 | This repository shows usage of Web3 Actions running with a simple [multisig wallet](contracts/MultiSigWallet.sol). 4 | 5 | The Web3 Action `multisig` is ran when a transaction emits one of following events: 6 | 7 | - **TxSubmission**: run a simulation on a fork and send a message to Discord about the results 8 | - **TxConfirmation**: send a message to Discord 9 | - **ExecuteTransaction**: send a message to Discord 10 | 11 | # Prerequisites: 12 | 13 | - Tenderly [CLI installed](https://github.com/Tenderly/tenderly-cli#installation) 14 | and [logged in via CLI](https://github.com/Tenderly/tenderly-cli#login). 15 | 16 | # Running the example 17 | 18 | The Web3 Actions code is located in `web3-actions` folder. You can specify this when running `tenderly actions init`. 19 | 20 | To run the example, you can either use [pre-deployed and Tenderly-verified contracts](#1a-using-pre-deployed-contracts) 21 | or spare some time to [deploy your own contracts](#1b-deploy-your-own-contracts). Follow the rest of instructions to run 22 | the example. 23 | 24 | Before you run the examples, you need to change the `REPLACE_ME` placeholders with your own values. 25 | 26 | ## 0: Configuration for Web3 Actions project 27 | 28 | **▶️ Action** First thing's first, install npm dependencies and ⏳. 29 | 30 | ```bash 31 | yarn 32 | cd web3-actions && yarn && cd .. 33 | ``` 34 | 35 | Note: The Web3 Actions project is an NPM project. 36 | 37 | ## 1A: Using pre-deployed contracts 38 | 39 | To save some time and to manually run Web 3 Actions from Tenderly Dashboard, you can use pre-deployed contracts listed 40 | below. 41 | 42 | These include a single approved and executed transaction you can use for running [Web3](https://google.com) Actions. 43 | 44 | The contracts are deployed on **Ropsten** at the follwing addresses: 45 | 46 | | Contract | Address | 47 | | ----------------------------------------------------------------------------------------------------------- | -------------------------------------------- | 48 | | [MultisigWallet](https://dashboard.tenderly.co/contract/ropsten/0x6794b4635e094982ed890f8b5fd57a45227d4a98) | `0x6794b4635e094982ed890f8b5fd57a45227d4a98` | 49 | | [Token](https://dashboard.tenderly.co/contract/ropsten/0x945b7c7178abd5936db09186b58a789d8308b876) | `0x945b7c7178abd5936db09186b58a789d8308b876` | 50 | 51 | **▶️ Action**: Add the contracts (addresses and links below) to your project in Tenderly. Click the links in the table to open the Dashboard and then click "Add to Project". As soon as you add the 52 | contracts, the transactions will be shown in **Transactions** page. 53 | 54 | Transactions executed on these contracts: 55 | 56 | | Action | TX Hash | 57 | | --------- | -------------------------------------------------------------------- | 58 | | Submit | `0xa71c273fd5e3b92b4ade35d284995504aed53fb3e6673fe4f5fcec045c75e77a` | 59 | | Confirm 1 | `0x6f08c6803a470039b73072a9dc34d69dcd0261fab29ac4cbcb02bb87daf96aa9` | 60 | | Confirm 2 | `0xf4c527efb784ac58b8c7a4483f00a07a687ecc4b8f38c6cbf9f95dd0f695cfb5` | 61 | | Execute | `0x9ae0ec902fa209c60ffa10ecaf0446dbccd36a02e33d18f16bc182e57de94ac4` | 62 | 63 | You can use these TX hashes to run Web3 Actions manually from the Dashboard. Skip 64 | to [Deploying Web3 Actions](#3-deploying-web3-actions). 65 | 66 | Note: since the contracts are deployed with a pre-defined list of owners, you can simulate transactions, but you can't 67 | submit, approve or execute any of these transactions on Ropsten. 68 | 69 | ## 1B: Deploy your own contracts 70 | 71 | If you want to run Web3 Actions on your own contracts, you can deploy them manually. For this example, we will use 72 | the [Ropsten testnet](https://ropsten.etherscan.io/). 73 | 74 | 0. **▶️ Action**: Set up a hardhat project .env file and replace placeholders. Source 75 | the [demo-setup-scripts/init-env-hardhat.sh](demo-setup-scripts/init-env-hardhat.sh) script and replace the 76 | placeholders. 77 | 78 | ```bash 79 | source demo-setup-scripts/init-env-hardhat.sh 80 | ``` 81 | 82 | 1. **▶️ Action**: Deploy the Contracts to Ropsten. Use shorthand script `yarn deploy:ropsten`. Hardhat project is set 83 | up 84 | with [Tenderly Hardhat Plugin automatic contract verification](https://github.com/Tenderly/hardhat-tenderly#automatic-verification) 85 | so your contracts are verified automatically as they're deployed 86 | 2. **▶️ Action**: Run a first set of transactions: submit 1 tx to multisig, do two approvals and optionally execute it. 87 | Use the script `yarn multisig-play:ropsten`. 88 | 3. **▶️ Action**: Copy the address of Multisig from deployment step (also found in `deployments.json`) and paste it 89 | into `tenderly.yaml` in place of `MULTISIG_ADDRESS` placeholder. 90 | 91 | ## 2: Run it locally 92 | 93 | Before deploying the action to Tenderly, you can test it out locally. 94 | 95 | ```bash 96 | source demo-setup-scripts/init-env-hardhat.sh 97 | yarn multisig-local 98 | ``` 99 | 100 | 👾 You should get several Discord messages and see some meaningful output. If all went well, you can go on and deploy the 101 | action to Tenderly. 102 | 103 | The file `web3-actions/multisig-local.js` is a simple script that runs Web3 Actions locally, 104 | using [@tenderly/actions-test](https://github.com/Tenderly/tenderly-actions). 105 | 106 | It requires Transaction payload to be passed as an argument. In the [web3-actions/payload](web3-actions/payload/) 107 | directory you'll find JSON file corresponding to an example transactions that submit, approve and execute a multisig 108 | transaction. For running locally, it's fine to use the provided ones. 109 | 110 | ## 3: Deploying Web3 Actions 111 | 112 | **▶️ Action**: Open `tenderly.yaml` and replace placeholders with your [Username](https://docs.tenderly.co/other/platform-access/how-to-find-the-project-slug-username-and-organization-name) 113 | and [Project Slug](https://docs.tenderly.co/other/platform-access/how-to-find-the-project-slug-username-and-organization-name). 114 | 115 | **▶️ Action**: Go to Tenderly Dashboard, open **Actions** and click **Secrets**. Add: 116 | 117 | 1. `TENDERLY_ACCESS_KEY` [following these instructions](https://docs.tenderly.co/other/platform-access/how-to-generate-api-access-tokens). 118 | 2. `multisig.DISCORD_URL` 119 | 120 | **▶️ Action**: Go to [web3-actions/multisig.ts](multisig.ts) and replace the placeholders at the top with appropriate values. 121 | 122 | You can either use contract addresses of pre-deployed contracts or your own 123 | **▶️ Action**: From directory containing `tenderly.yaml` (the root of this example project), run 124 | 125 | ```bash 126 | tenderly actions deploy 127 | ``` 128 | 129 | ## 4: Manually triggering Web3 Actions: 130 | 131 | **▶️ Action**: From the Web3 Actions page: 132 | 133 | 1. open the `multisig` action 134 | 2. click **Manual Run** 135 | 3. copy TX hash either from Tenderly Dashboard or from the table 136 | in [1A: Using pre-deployed contracts](#1a-using-pre-deployed-contracts) 137 | 4. paste the hash of transaction 138 | 5. select **Copy on write** storage option 139 | 6. click **⚡️ Trigger** button at the bottom of the page. Your Web3 Action should run, your Discord should receive a 140 | message 141 | 142 | ## 5: Trying it out with real-time transactions 143 | 144 | If you deployed your own contracts in step [1B: Deploy your own contracts](#1b-deploy-your-own-contracts), you can run 145 | the `multisig-play` script. Your Web3 Actions should pick up on the new transactions and run accordingly. 146 | 147 | ```bash 148 | tenderly actions multisig-play:ropsten 149 | ``` 150 | 151 | ## Misc 152 | 153 | - the `deployments.json` keeps track of deployed contracts so these can be easier to find in run scripts: `multisig-play.ts`. 154 | - When re-deploying, clean the `deployments.json` so the array is empty: 155 | 156 | ```json 157 | { 158 | "latest": [] 159 | } 160 | ``` 161 | -------------------------------------------------------------------------------- /multisig-wallet/contracts/MultiSigWallet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | contract MultiSigWallet { 5 | // Events 6 | event Deposit(address indexed sender, uint amount, uint balance); 7 | event TxSubmission( 8 | address indexed owner, 9 | uint indexed txIndex, 10 | address indexed to, 11 | uint value, 12 | bytes data 13 | ); 14 | event TxConfirmation(address indexed owner, uint indexed txIndex); 15 | event ConfirmationRevocation(address indexed owner, uint indexed txIndex); 16 | event ExecuteTransaction(address indexed owner, uint indexed txIndex); 17 | 18 | // List of owners of the wallet 19 | address[] public owners; 20 | // Mapping to allow for easy checks if someone is an owner 21 | mapping(address => bool) public isOwner; 22 | // Number of confirmations required to execute a transaction 23 | uint public numConfirmationsRequired; 24 | 25 | // Tx object 26 | struct Transaction { 27 | address to; 28 | uint value; 29 | bytes data; 30 | bool executed; 31 | uint numConfirmations; 32 | } 33 | 34 | // mapping from tx index => owner => bool 35 | // use this to check if a transaction is confirmed by an owner 36 | mapping(uint => mapping(address => bool)) public isConfirmed; 37 | 38 | // List of all tracked transactions 39 | Transaction[] public transactions; 40 | 41 | // Helper functions 42 | modifier onlyOwner() { 43 | require(isOwner[msg.sender], "not owner"); 44 | _; 45 | } 46 | 47 | modifier txExists(uint _txIndex) { 48 | require(_txIndex < transactions.length, "tx does not exist"); 49 | _; 50 | } 51 | 52 | modifier notExecuted(uint _txIndex) { 53 | require(!transactions[_txIndex].executed, "tx already executed"); 54 | _; 55 | } 56 | 57 | modifier notConfirmed(uint _txIndex) { 58 | require(!isConfirmed[_txIndex][msg.sender], "tx already confirmed"); 59 | _; 60 | } 61 | 62 | constructor(address[] memory _owners, uint _numConfirmationsRequired) { 63 | require(_owners.length > 0, "owners required"); 64 | require( 65 | _numConfirmationsRequired > 0 && 66 | _numConfirmationsRequired <= _owners.length, 67 | "invalid number of required confirmations" 68 | ); 69 | 70 | for (uint i = 0; i < _owners.length; i++) { 71 | address owner = _owners[i]; 72 | 73 | require(owner != address(0), "invalid owner"); 74 | require(!isOwner[owner], "owner not unique"); 75 | 76 | isOwner[owner] = true; 77 | owners.push(owner); 78 | } 79 | 80 | numConfirmationsRequired = _numConfirmationsRequired; 81 | } 82 | 83 | receive() external payable { 84 | emit Deposit(msg.sender, msg.value, address(this).balance); 85 | } 86 | 87 | function submitTransaction( 88 | address _to, 89 | uint _value, 90 | bytes memory _data 91 | ) public onlyOwner { 92 | uint txIndex = transactions.length; 93 | 94 | transactions.push( 95 | Transaction({ 96 | to: _to, 97 | value: _value, 98 | data: _data, 99 | executed: false, 100 | numConfirmations: 0 101 | }) 102 | ); 103 | 104 | emit TxSubmission(msg.sender, txIndex, _to, _value, _data); 105 | } 106 | 107 | function confirmTransaction(uint _txIndex) 108 | public 109 | onlyOwner 110 | txExists(_txIndex) 111 | notExecuted(_txIndex) 112 | notConfirmed(_txIndex) 113 | { 114 | Transaction storage transaction = transactions[_txIndex]; 115 | transaction.numConfirmations += 1; 116 | isConfirmed[_txIndex][msg.sender] = true; 117 | 118 | emit TxConfirmation(msg.sender, _txIndex); 119 | } 120 | 121 | function executeTransaction(uint _txIndex) 122 | public 123 | onlyOwner 124 | txExists(_txIndex) 125 | notExecuted(_txIndex) 126 | { 127 | Transaction storage transaction = transactions[_txIndex]; 128 | 129 | require( 130 | transaction.numConfirmations >= numConfirmationsRequired, 131 | "cannot execute tx" 132 | ); 133 | 134 | transaction.executed = true; 135 | 136 | (bool success, ) = transaction.to.call{value: transaction.value}( 137 | transaction.data 138 | ); 139 | require(success, "tx failed"); 140 | 141 | emit ExecuteTransaction(msg.sender, _txIndex); 142 | } 143 | 144 | function revokeConfirmation(uint _txIndex) 145 | public 146 | onlyOwner 147 | txExists(_txIndex) 148 | notExecuted(_txIndex) 149 | { 150 | Transaction storage transaction = transactions[_txIndex]; 151 | 152 | require(isConfirmed[_txIndex][msg.sender], "tx not confirmed"); 153 | 154 | transaction.numConfirmations -= 1; 155 | isConfirmed[_txIndex][msg.sender] = false; 156 | 157 | emit ConfirmationRevocation(msg.sender, _txIndex); 158 | } 159 | 160 | function getOwners() public view returns (address[] memory) { 161 | return owners; 162 | } 163 | 164 | function getTransactionCount() public view returns (uint) { 165 | return transactions.length; 166 | } 167 | 168 | function getTransaction(uint _txIndex) 169 | public 170 | view 171 | returns ( 172 | address to, 173 | uint value, 174 | bytes memory data, 175 | bool executed, 176 | uint numConfirmations 177 | ) 178 | { 179 | Transaction storage transaction = transactions[_txIndex]; 180 | 181 | return ( 182 | transaction.to, 183 | transaction.value, 184 | transaction.data, 185 | transaction.executed, 186 | transaction.numConfirmations 187 | ); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /multisig-wallet/contracts/Token.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity >=0.5.0 <0.9.0; 3 | 4 | library Balances { 5 | function move( 6 | mapping(address => uint256) storage balances, 7 | address from, 8 | address to, 9 | uint amount 10 | ) internal { 11 | require(balances[from] >= amount, "Not enough balance"); 12 | require(balances[to] + amount >= balances[to], "Balance Overflow"); 13 | balances[from] -= amount; 14 | balances[to] += amount; 15 | } 16 | } 17 | 18 | contract Token { 19 | mapping(address => uint256) balances; 20 | using Balances for *; 21 | mapping(address => mapping(address => uint256)) allowed; 22 | uint32 TokensPerEther = 20; 23 | 24 | event Transfer(address from, address to, uint amount); 25 | event Approval(address owner, address spender, uint amount); 26 | 27 | function transfer(address to, uint amount) external returns (bool success) { 28 | balances.move(msg.sender, to, amount); 29 | emit Transfer(msg.sender, to, amount); 30 | return true; 31 | } 32 | 33 | function stake() public payable { 34 | uint256 tokens = msg.value * TokensPerEther; 35 | balances[msg.sender] += tokens; 36 | } 37 | 38 | function transferFrom( 39 | address from, 40 | address to, 41 | uint amount 42 | ) external returns (bool success) { 43 | require(allowed[from][msg.sender] >= amount, "Not enough allowance"); 44 | allowed[from][msg.sender] -= amount; 45 | balances.move(from, to, amount); 46 | emit Transfer(from, to, amount); 47 | return true; 48 | } 49 | 50 | function approve(address spender, uint tokens) 51 | external 52 | returns (bool success) 53 | { 54 | require( 55 | allowed[msg.sender][spender] == 0, 56 | "Has allowance, can't approve more" 57 | ); 58 | allowed[msg.sender][spender] = tokens; 59 | emit Approval(msg.sender, spender, tokens); 60 | return true; 61 | } 62 | 63 | function balanceOf(address tokenOwner) 64 | external 65 | view 66 | returns (uint balance) 67 | { 68 | return balances[tokenOwner]; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /multisig-wallet/demo-setup-scripts/init-env-hardhat.sh: -------------------------------------------------------------------------------- 1 | cat < .env 2 | # Replace placeholders ??? 3 | 4 | ## Needed to run the Web3 Actions Locally 5 | #https://docs.tenderly.co/other/platform-access/how-to-find-the-project-slug-username-and-organization-name 6 | TENDERLY_PROJECT_SLUG=??? 7 | TENDERLY_USERNAME=??? 8 | 9 | # https://docs.tenderly.co/other/platform-access/how-to-generate-api-access-tokens 10 | TENDERLY_ACCESS_KEY=??? 11 | 12 | # https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks 13 | DISCORD_URL=??? 14 | 15 | ## needed if planning to deploy your own contracts, otherwise skip 16 | ROPSTEN_URL= 17 | ROPSTEN_PRIVATE_KEY_1=??? 18 | ROPSTEN_PRIVATE_KEY_2=??? 19 | ROPSTEN_PRIVATE_KEY_3=??? 20 | 21 | EOENV -------------------------------------------------------------------------------- /multisig-wallet/deployments.json: -------------------------------------------------------------------------------- 1 | { 2 | "latest": [ 3 | { 4 | "name": "Token", 5 | "address": "0x945B7C7178abd5936DB09186B58A789D8308B876", 6 | "network": "ropsten", 7 | "timestamp": "2022-09-09T14:29:14.477Z" 8 | }, 9 | { 10 | "name": "MultiSigWallet", 11 | "address": "0x6794B4635e094982Ed890F8b5fD57a45227d4a98", 12 | "network": "ropsten", 13 | "timestamp": "2022-09-09T14:29:37.429Z" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /multisig-wallet/deployments/ropsten_3/Token.json: -------------------------------------------------------------------------------- 1 | {"metadata":"{\"defaultCompiler\":{\"version\":\"0.8.9\"},\"sources\":{\"contracts/Token.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0\\npragma solidity >=0.5.0 <0.9.0;\\n\\nlibrary Balances {\\n function move(\\n mapping(address => uint256) storage balances,\\n address from,\\n address to,\\n uint amount\\n ) internal {\\n require(balances[from] >= amount, \\\"Not enough balance\\\");\\n require(balances[to] + amount >= balances[to], \\\"Balance Overflow\\\");\\n balances[from] -= amount;\\n balances[to] += amount;\\n }\\n}\\n\\ncontract Token {\\n mapping(address => uint256) balances;\\n using Balances for *;\\n mapping(address => mapping(address => uint256)) allowed;\\n uint32 TokensPerEther = 20;\\n\\n event Transfer(address from, address to, uint amount);\\n event Approval(address owner, address spender, uint amount);\\n\\n function transfer(address to, uint amount) external returns (bool success) {\\n balances.move(msg.sender, to, amount);\\n emit Transfer(msg.sender, to, amount);\\n return true;\\n }\\n\\n function stake() public payable {\\n uint256 tokens = msg.value * TokensPerEther;\\n balances[msg.sender] += tokens;\\n }\\n\\n function transferFrom(\\n address from,\\n address to,\\n uint amount\\n ) external returns (bool success) {\\n require(allowed[from][msg.sender] >= amount, \\\"Not enough allowance\\\");\\n allowed[from][msg.sender] -= amount;\\n balances.move(from, to, amount);\\n emit Transfer(from, to, amount);\\n return true;\\n }\\n\\n function approve(address spender, uint tokens)\\n external\\n returns (bool success)\\n {\\n require(\\n allowed[msg.sender][spender] == 0,\\n \\\"Has allowance, can't approve more\\\"\\n );\\n allowed[msg.sender][spender] = tokens;\\n emit Approval(msg.sender, spender, tokens);\\n return true;\\n }\\n\\n function balanceOf(address tokenOwner)\\n external\\n view\\n returns (uint balance)\\n {\\n return balances[tokenOwner];\\n }\\n}\\n\",\"versionPragma\":\">=0.5.0 <0.9.0\"}}}","address":"0xAF13dD3beA734678A333380BBb2b2a3e41BD10F3","bytecode":"0x60806040526014600260006101000a81548163ffffffff021916908363ffffffff16021790555034801561003257600080fd5b50610cd6806100426000396000f3fe60806040526004361061004a5760003560e01c8063095ea7b31461004f57806323b872dd1461008c5780633a4b66f1146100c957806370a08231146100d3578063a9059cbb14610110575b600080fd5b34801561005b57600080fd5b5061007660048036038101906100719190610840565b61014d565b604051610083919061089b565b60405180910390f35b34801561009857600080fd5b506100b360048036038101906100ae91906108b6565b6102d3565b6040516100c0919061089b565b60405180910390f35b6100d1610485565b005b3480156100df57600080fd5b506100fa60048036038101906100f59190610909565b610505565b6040516101079190610945565b60405180910390f35b34801561011c57600080fd5b5061013760048036038101906101329190610840565b61054d565b604051610144919061089b565b60405180910390f35b600080600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541461020d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610204906109e3565b60405180910390fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055507f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9253384846040516102c193929190610a12565b60405180910390a16001905092915050565b600081600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610394576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161038b90610a95565b60405180910390fd5b81600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546104209190610ae4565b9250508190555061043f84848460006105ac909392919063ffffffff16565b7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84848460405161047293929190610a12565b60405180910390a1600190509392505050565b6000600260009054906101000a900463ffffffff1663ffffffff16346104ab9190610b18565b9050806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546104fb9190610b72565b9250508190555050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600061056733848460006105ac909392919063ffffffff16565b7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef33848460405161059a93929190610a12565b60405180910390a16001905092915050565b808460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561062d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161062490610c14565b60405180910390fd5b8360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054818560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546106b69190610b72565b10156106f7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106ee90610c80565b60405180910390fd5b808460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546107459190610ae4565b92505081905550808460008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461079a9190610b72565b9250508190555050505050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006107d7826107ac565b9050919050565b6107e7816107cc565b81146107f257600080fd5b50565b600081359050610804816107de565b92915050565b6000819050919050565b61081d8161080a565b811461082857600080fd5b50565b60008135905061083a81610814565b92915050565b60008060408385031215610857576108566107a7565b5b6000610865858286016107f5565b92505060206108768582860161082b565b9150509250929050565b60008115159050919050565b61089581610880565b82525050565b60006020820190506108b0600083018461088c565b92915050565b6000806000606084860312156108cf576108ce6107a7565b5b60006108dd868287016107f5565b93505060206108ee868287016107f5565b92505060406108ff8682870161082b565b9150509250925092565b60006020828403121561091f5761091e6107a7565b5b600061092d848285016107f5565b91505092915050565b61093f8161080a565b82525050565b600060208201905061095a6000830184610936565b92915050565b600082825260208201905092915050565b7f48617320616c6c6f77616e63652c2063616e277420617070726f7665206d6f7260008201527f6500000000000000000000000000000000000000000000000000000000000000602082015250565b60006109cd602183610960565b91506109d882610971565b604082019050919050565b600060208201905081810360008301526109fc816109c0565b9050919050565b610a0c816107cc565b82525050565b6000606082019050610a276000830186610a03565b610a346020830185610a03565b610a416040830184610936565b949350505050565b7f4e6f7420656e6f75676820616c6c6f77616e6365000000000000000000000000600082015250565b6000610a7f601483610960565b9150610a8a82610a49565b602082019050919050565b60006020820190508181036000830152610aae81610a72565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610aef8261080a565b9150610afa8361080a565b925082821015610b0d57610b0c610ab5565b5b828203905092915050565b6000610b238261080a565b9150610b2e8361080a565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615610b6757610b66610ab5565b5b828202905092915050565b6000610b7d8261080a565b9150610b888361080a565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610bbd57610bbc610ab5565b5b828201905092915050565b7f4e6f7420656e6f7567682062616c616e63650000000000000000000000000000600082015250565b6000610bfe601283610960565b9150610c0982610bc8565b602082019050919050565b60006020820190508181036000830152610c2d81610bf1565b9050919050565b7f42616c616e6365204f766572666c6f7700000000000000000000000000000000600082015250565b6000610c6a601083610960565b9150610c7582610c34565b602082019050919050565b60006020820190508181036000830152610c9981610c5d565b905091905056fea26469706673582212206a919534a5f2b8939885541b3e3df8b3ee48535823a8cfa339be7dec1a57b61864736f6c63430008090033","deployedBytecode":"0x60806040526004361061004a5760003560e01c8063095ea7b31461004f57806323b872dd1461008c5780633a4b66f1146100c957806370a08231146100d3578063a9059cbb14610110575b600080fd5b34801561005b57600080fd5b5061007660048036038101906100719190610840565b61014d565b604051610083919061089b565b60405180910390f35b34801561009857600080fd5b506100b360048036038101906100ae91906108b6565b6102d3565b6040516100c0919061089b565b60405180910390f35b6100d1610485565b005b3480156100df57600080fd5b506100fa60048036038101906100f59190610909565b610505565b6040516101079190610945565b60405180910390f35b34801561011c57600080fd5b5061013760048036038101906101329190610840565b61054d565b604051610144919061089b565b60405180910390f35b600080600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541461020d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610204906109e3565b60405180910390fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055507f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9253384846040516102c193929190610a12565b60405180910390a16001905092915050565b600081600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610394576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161038b90610a95565b60405180910390fd5b81600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546104209190610ae4565b9250508190555061043f84848460006105ac909392919063ffffffff16565b7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84848460405161047293929190610a12565b60405180910390a1600190509392505050565b6000600260009054906101000a900463ffffffff1663ffffffff16346104ab9190610b18565b9050806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546104fb9190610b72565b9250508190555050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600061056733848460006105ac909392919063ffffffff16565b7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef33848460405161059a93929190610a12565b60405180910390a16001905092915050565b808460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561062d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161062490610c14565b60405180910390fd5b8360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054818560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546106b69190610b72565b10156106f7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106ee90610c80565b60405180910390fd5b808460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546107459190610ae4565b92505081905550808460008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461079a9190610b72565b9250508190555050505050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006107d7826107ac565b9050919050565b6107e7816107cc565b81146107f257600080fd5b50565b600081359050610804816107de565b92915050565b6000819050919050565b61081d8161080a565b811461082857600080fd5b50565b60008135905061083a81610814565b92915050565b60008060408385031215610857576108566107a7565b5b6000610865858286016107f5565b92505060206108768582860161082b565b9150509250929050565b60008115159050919050565b61089581610880565b82525050565b60006020820190506108b0600083018461088c565b92915050565b6000806000606084860312156108cf576108ce6107a7565b5b60006108dd868287016107f5565b93505060206108ee868287016107f5565b92505060406108ff8682870161082b565b9150509250925092565b60006020828403121561091f5761091e6107a7565b5b600061092d848285016107f5565b91505092915050565b61093f8161080a565b82525050565b600060208201905061095a6000830184610936565b92915050565b600082825260208201905092915050565b7f48617320616c6c6f77616e63652c2063616e277420617070726f7665206d6f7260008201527f6500000000000000000000000000000000000000000000000000000000000000602082015250565b60006109cd602183610960565b91506109d882610971565b604082019050919050565b600060208201905081810360008301526109fc816109c0565b9050919050565b610a0c816107cc565b82525050565b6000606082019050610a276000830186610a03565b610a346020830185610a03565b610a416040830184610936565b949350505050565b7f4e6f7420656e6f75676820616c6c6f77616e6365000000000000000000000000600082015250565b6000610a7f601483610960565b9150610a8a82610a49565b602082019050919050565b60006020820190508181036000830152610aae81610a72565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610aef8261080a565b9150610afa8361080a565b925082821015610b0d57610b0c610ab5565b5b828203905092915050565b6000610b238261080a565b9150610b2e8361080a565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615610b6757610b66610ab5565b5b828202905092915050565b6000610b7d8261080a565b9150610b888361080a565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610bbd57610bbc610ab5565b5b828201905092915050565b7f4e6f7420656e6f7567682062616c616e63650000000000000000000000000000600082015250565b6000610bfe601283610960565b9150610c0982610bc8565b602082019050919050565b60006020820190508181036000830152610c2d81610bf1565b9050919050565b7f42616c616e6365204f766572666c6f7700000000000000000000000000000000600082015250565b6000610c6a601083610960565b9150610c7582610c34565b602082019050919050565b60006020820190508181036000830152610c9981610c5d565b905091905056fea26469706673582212206a919534a5f2b8939885541b3e3df8b3ee48535823a8cfa339be7dec1a57b61864736f6c63430008090033","abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"tokens","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenOwner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stake","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]} -------------------------------------------------------------------------------- /multisig-wallet/deployments/tenderly_1/Token.json: -------------------------------------------------------------------------------- 1 | {"metadata":"{\"compiler\":{\"version\":\"0.8.9\"},\"sources\":{\"contracts/Token.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0\\npragma solidity >=0.5.0 <0.9.0;\\n\\nlibrary Balances {\\n function move(\\n mapping(address => uint256) storage balances,\\n address from,\\n address to,\\n uint amount\\n ) internal {\\n require(balances[from] >= amount, \\\"Not enough balance\\\");\\n require(balances[to] + amount >= balances[to], \\\"Balance Overflow\\\");\\n balances[from] -= amount;\\n balances[to] += amount;\\n }\\n}\\n\\ncontract Token {\\n mapping(address => uint256) balances;\\n using Balances for *;\\n mapping(address => mapping(address => uint256)) allowed;\\n uint32 TokensPerEther = 20;\\n\\n event Transfer(address from, address to, uint amount);\\n event Approval(address owner, address spender, uint amount);\\n\\n function transfer(address to, uint amount) external returns (bool success) {\\n balances.move(msg.sender, to, amount);\\n emit Transfer(msg.sender, to, amount);\\n return true;\\n }\\n\\n function stake() public payable {\\n uint256 tokens = msg.value * TokensPerEther;\\n balances[msg.sender] += tokens;\\n }\\n\\n function transferFrom(\\n address from,\\n address to,\\n uint amount\\n ) external returns (bool success) {\\n require(allowed[from][msg.sender] >= amount, \\\"Not enough allowance\\\");\\n allowed[from][msg.sender] -= amount;\\n balances.move(from, to, amount);\\n emit Transfer(from, to, amount);\\n return true;\\n }\\n\\n function approve(address spender, uint tokens)\\n external\\n returns (bool success)\\n {\\n require(\\n allowed[msg.sender][spender] == 0,\\n \\\"Has allowance, can't approve more\\\"\\n );\\n allowed[msg.sender][spender] = tokens;\\n emit Approval(msg.sender, spender, tokens);\\n return true;\\n }\\n\\n function balanceOf(address tokenOwner)\\n external\\n view\\n returns (uint balance)\\n {\\n return balances[tokenOwner];\\n }\\n}\\n\"}}}","address":"0x2D6582D761b0Fd0c74e06Ec914bac0657741e42e","bytecode":"0x60806040526014600260006101000a81548163ffffffff021916908363ffffffff16021790555034801561003257600080fd5b50610cd6806100426000396000f3fe60806040526004361061004a5760003560e01c8063095ea7b31461004f57806323b872dd1461008c5780633a4b66f1146100c957806370a08231146100d3578063a9059cbb14610110575b600080fd5b34801561005b57600080fd5b5061007660048036038101906100719190610840565b61014d565b604051610083919061089b565b60405180910390f35b34801561009857600080fd5b506100b360048036038101906100ae91906108b6565b6102d3565b6040516100c0919061089b565b60405180910390f35b6100d1610485565b005b3480156100df57600080fd5b506100fa60048036038101906100f59190610909565b610505565b6040516101079190610945565b60405180910390f35b34801561011c57600080fd5b5061013760048036038101906101329190610840565b61054d565b604051610144919061089b565b60405180910390f35b600080600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541461020d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610204906109e3565b60405180910390fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055507f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9253384846040516102c193929190610a12565b60405180910390a16001905092915050565b600081600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610394576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161038b90610a95565b60405180910390fd5b81600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546104209190610ae4565b9250508190555061043f84848460006105ac909392919063ffffffff16565b7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84848460405161047293929190610a12565b60405180910390a1600190509392505050565b6000600260009054906101000a900463ffffffff1663ffffffff16346104ab9190610b18565b9050806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546104fb9190610b72565b9250508190555050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600061056733848460006105ac909392919063ffffffff16565b7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef33848460405161059a93929190610a12565b60405180910390a16001905092915050565b808460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561062d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161062490610c14565b60405180910390fd5b8360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054818560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546106b69190610b72565b10156106f7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106ee90610c80565b60405180910390fd5b808460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546107459190610ae4565b92505081905550808460008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461079a9190610b72565b9250508190555050505050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006107d7826107ac565b9050919050565b6107e7816107cc565b81146107f257600080fd5b50565b600081359050610804816107de565b92915050565b6000819050919050565b61081d8161080a565b811461082857600080fd5b50565b60008135905061083a81610814565b92915050565b60008060408385031215610857576108566107a7565b5b6000610865858286016107f5565b92505060206108768582860161082b565b9150509250929050565b60008115159050919050565b61089581610880565b82525050565b60006020820190506108b0600083018461088c565b92915050565b6000806000606084860312156108cf576108ce6107a7565b5b60006108dd868287016107f5565b93505060206108ee868287016107f5565b92505060406108ff8682870161082b565b9150509250925092565b60006020828403121561091f5761091e6107a7565b5b600061092d848285016107f5565b91505092915050565b61093f8161080a565b82525050565b600060208201905061095a6000830184610936565b92915050565b600082825260208201905092915050565b7f48617320616c6c6f77616e63652c2063616e277420617070726f7665206d6f7260008201527f6500000000000000000000000000000000000000000000000000000000000000602082015250565b60006109cd602183610960565b91506109d882610971565b604082019050919050565b600060208201905081810360008301526109fc816109c0565b9050919050565b610a0c816107cc565b82525050565b6000606082019050610a276000830186610a03565b610a346020830185610a03565b610a416040830184610936565b949350505050565b7f4e6f7420656e6f75676820616c6c6f77616e6365000000000000000000000000600082015250565b6000610a7f601483610960565b9150610a8a82610a49565b602082019050919050565b60006020820190508181036000830152610aae81610a72565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610aef8261080a565b9150610afa8361080a565b925082821015610b0d57610b0c610ab5565b5b828203905092915050565b6000610b238261080a565b9150610b2e8361080a565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615610b6757610b66610ab5565b5b828202905092915050565b6000610b7d8261080a565b9150610b888361080a565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610bbd57610bbc610ab5565b5b828201905092915050565b7f4e6f7420656e6f7567682062616c616e63650000000000000000000000000000600082015250565b6000610bfe601283610960565b9150610c0982610bc8565b602082019050919050565b60006020820190508181036000830152610c2d81610bf1565b9050919050565b7f42616c616e6365204f766572666c6f7700000000000000000000000000000000600082015250565b6000610c6a601083610960565b9150610c7582610c34565b602082019050919050565b60006020820190508181036000830152610c9981610c5d565b905091905056fea26469706673582212206a919534a5f2b8939885541b3e3df8b3ee48535823a8cfa339be7dec1a57b61864736f6c63430008090033","deployedBytecode":"0x60806040526004361061004a5760003560e01c8063095ea7b31461004f57806323b872dd1461008c5780633a4b66f1146100c957806370a08231146100d3578063a9059cbb14610110575b600080fd5b34801561005b57600080fd5b5061007660048036038101906100719190610840565b61014d565b604051610083919061089b565b60405180910390f35b34801561009857600080fd5b506100b360048036038101906100ae91906108b6565b6102d3565b6040516100c0919061089b565b60405180910390f35b6100d1610485565b005b3480156100df57600080fd5b506100fa60048036038101906100f59190610909565b610505565b6040516101079190610945565b60405180910390f35b34801561011c57600080fd5b5061013760048036038101906101329190610840565b61054d565b604051610144919061089b565b60405180910390f35b600080600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541461020d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610204906109e3565b60405180910390fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055507f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9253384846040516102c193929190610a12565b60405180910390a16001905092915050565b600081600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610394576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161038b90610a95565b60405180910390fd5b81600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546104209190610ae4565b9250508190555061043f84848460006105ac909392919063ffffffff16565b7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84848460405161047293929190610a12565b60405180910390a1600190509392505050565b6000600260009054906101000a900463ffffffff1663ffffffff16346104ab9190610b18565b9050806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546104fb9190610b72565b9250508190555050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600061056733848460006105ac909392919063ffffffff16565b7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef33848460405161059a93929190610a12565b60405180910390a16001905092915050565b808460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561062d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161062490610c14565b60405180910390fd5b8360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054818560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546106b69190610b72565b10156106f7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106ee90610c80565b60405180910390fd5b808460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546107459190610ae4565b92505081905550808460008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461079a9190610b72565b9250508190555050505050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006107d7826107ac565b9050919050565b6107e7816107cc565b81146107f257600080fd5b50565b600081359050610804816107de565b92915050565b6000819050919050565b61081d8161080a565b811461082857600080fd5b50565b60008135905061083a81610814565b92915050565b60008060408385031215610857576108566107a7565b5b6000610865858286016107f5565b92505060206108768582860161082b565b9150509250929050565b60008115159050919050565b61089581610880565b82525050565b60006020820190506108b0600083018461088c565b92915050565b6000806000606084860312156108cf576108ce6107a7565b5b60006108dd868287016107f5565b93505060206108ee868287016107f5565b92505060406108ff8682870161082b565b9150509250925092565b60006020828403121561091f5761091e6107a7565b5b600061092d848285016107f5565b91505092915050565b61093f8161080a565b82525050565b600060208201905061095a6000830184610936565b92915050565b600082825260208201905092915050565b7f48617320616c6c6f77616e63652c2063616e277420617070726f7665206d6f7260008201527f6500000000000000000000000000000000000000000000000000000000000000602082015250565b60006109cd602183610960565b91506109d882610971565b604082019050919050565b600060208201905081810360008301526109fc816109c0565b9050919050565b610a0c816107cc565b82525050565b6000606082019050610a276000830186610a03565b610a346020830185610a03565b610a416040830184610936565b949350505050565b7f4e6f7420656e6f75676820616c6c6f77616e6365000000000000000000000000600082015250565b6000610a7f601483610960565b9150610a8a82610a49565b602082019050919050565b60006020820190508181036000830152610aae81610a72565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610aef8261080a565b9150610afa8361080a565b925082821015610b0d57610b0c610ab5565b5b828203905092915050565b6000610b238261080a565b9150610b2e8361080a565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615610b6757610b66610ab5565b5b828202905092915050565b6000610b7d8261080a565b9150610b888361080a565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610bbd57610bbc610ab5565b5b828201905092915050565b7f4e6f7420656e6f7567682062616c616e63650000000000000000000000000000600082015250565b6000610bfe601283610960565b9150610c0982610bc8565b602082019050919050565b60006020820190508181036000830152610c2d81610bf1565b9050919050565b7f42616c616e6365204f766572666c6f7700000000000000000000000000000000600082015250565b6000610c6a601083610960565b9150610c7582610c34565b602082019050919050565b60006020820190508181036000830152610c9981610c5d565b905091905056fea26469706673582212206a919534a5f2b8939885541b3e3df8b3ee48535823a8cfa339be7dec1a57b61864736f6c63430008090033","abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"tokens","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenOwner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stake","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]} -------------------------------------------------------------------------------- /multisig-wallet/deployments/tenderly_3/Token.json: -------------------------------------------------------------------------------- 1 | {"metadata":"{\"compiler\":{\"version\":\"0.8.9\"},\"sources\":{\"contracts/Token.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0\\npragma solidity >=0.5.0 <0.9.0;\\n\\nlibrary Balances {\\n function move(\\n mapping(address => uint256) storage balances,\\n address from,\\n address to,\\n uint amount\\n ) internal {\\n require(balances[from] >= amount, \\\"Not enough balance\\\");\\n require(balances[to] + amount >= balances[to], \\\"Balance Overflow\\\");\\n balances[from] -= amount;\\n balances[to] += amount;\\n }\\n}\\n\\ncontract Token {\\n mapping(address => uint256) balances;\\n using Balances for *;\\n mapping(address => mapping(address => uint256)) allowed;\\n uint32 TokensPerEther = 20;\\n\\n event Transfer(address from, address to, uint amount);\\n event Approval(address owner, address spender, uint amount);\\n\\n function transfer(address to, uint amount) external returns (bool success) {\\n balances.move(msg.sender, to, amount);\\n emit Transfer(msg.sender, to, amount);\\n return true;\\n }\\n\\n function stake() public payable {\\n uint256 tokens = msg.value * TokensPerEther;\\n balances[msg.sender] += tokens;\\n }\\n\\n function transferFrom(\\n address from,\\n address to,\\n uint amount\\n ) external returns (bool success) {\\n require(allowed[from][msg.sender] >= amount, \\\"Not enough allowance\\\");\\n allowed[from][msg.sender] -= amount;\\n balances.move(from, to, amount);\\n emit Transfer(from, to, amount);\\n return true;\\n }\\n\\n function approve(address spender, uint tokens)\\n external\\n returns (bool success)\\n {\\n require(\\n allowed[msg.sender][spender] == 0,\\n \\\"Has allowance, can't approve more\\\"\\n );\\n allowed[msg.sender][spender] = tokens;\\n emit Approval(msg.sender, spender, tokens);\\n return true;\\n }\\n\\n function balanceOf(address tokenOwner)\\n external\\n view\\n returns (uint balance)\\n {\\n return balances[tokenOwner];\\n }\\n}\\n\"}}}","address":"0x1Fb6c0d9248c929161320317392042C1E79D6aD6","bytecode":"0x60806040526014600260006101000a81548163ffffffff021916908363ffffffff16021790555034801561003257600080fd5b50610cd6806100426000396000f3fe60806040526004361061004a5760003560e01c8063095ea7b31461004f57806323b872dd1461008c5780633a4b66f1146100c957806370a08231146100d3578063a9059cbb14610110575b600080fd5b34801561005b57600080fd5b5061007660048036038101906100719190610840565b61014d565b604051610083919061089b565b60405180910390f35b34801561009857600080fd5b506100b360048036038101906100ae91906108b6565b6102d3565b6040516100c0919061089b565b60405180910390f35b6100d1610485565b005b3480156100df57600080fd5b506100fa60048036038101906100f59190610909565b610505565b6040516101079190610945565b60405180910390f35b34801561011c57600080fd5b5061013760048036038101906101329190610840565b61054d565b604051610144919061089b565b60405180910390f35b600080600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541461020d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610204906109e3565b60405180910390fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055507f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9253384846040516102c193929190610a12565b60405180910390a16001905092915050565b600081600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610394576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161038b90610a95565b60405180910390fd5b81600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546104209190610ae4565b9250508190555061043f84848460006105ac909392919063ffffffff16565b7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84848460405161047293929190610a12565b60405180910390a1600190509392505050565b6000600260009054906101000a900463ffffffff1663ffffffff16346104ab9190610b18565b9050806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546104fb9190610b72565b9250508190555050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600061056733848460006105ac909392919063ffffffff16565b7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef33848460405161059a93929190610a12565b60405180910390a16001905092915050565b808460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561062d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161062490610c14565b60405180910390fd5b8360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054818560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546106b69190610b72565b10156106f7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106ee90610c80565b60405180910390fd5b808460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546107459190610ae4565b92505081905550808460008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461079a9190610b72565b9250508190555050505050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006107d7826107ac565b9050919050565b6107e7816107cc565b81146107f257600080fd5b50565b600081359050610804816107de565b92915050565b6000819050919050565b61081d8161080a565b811461082857600080fd5b50565b60008135905061083a81610814565b92915050565b60008060408385031215610857576108566107a7565b5b6000610865858286016107f5565b92505060206108768582860161082b565b9150509250929050565b60008115159050919050565b61089581610880565b82525050565b60006020820190506108b0600083018461088c565b92915050565b6000806000606084860312156108cf576108ce6107a7565b5b60006108dd868287016107f5565b93505060206108ee868287016107f5565b92505060406108ff8682870161082b565b9150509250925092565b60006020828403121561091f5761091e6107a7565b5b600061092d848285016107f5565b91505092915050565b61093f8161080a565b82525050565b600060208201905061095a6000830184610936565b92915050565b600082825260208201905092915050565b7f48617320616c6c6f77616e63652c2063616e277420617070726f7665206d6f7260008201527f6500000000000000000000000000000000000000000000000000000000000000602082015250565b60006109cd602183610960565b91506109d882610971565b604082019050919050565b600060208201905081810360008301526109fc816109c0565b9050919050565b610a0c816107cc565b82525050565b6000606082019050610a276000830186610a03565b610a346020830185610a03565b610a416040830184610936565b949350505050565b7f4e6f7420656e6f75676820616c6c6f77616e6365000000000000000000000000600082015250565b6000610a7f601483610960565b9150610a8a82610a49565b602082019050919050565b60006020820190508181036000830152610aae81610a72565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610aef8261080a565b9150610afa8361080a565b925082821015610b0d57610b0c610ab5565b5b828203905092915050565b6000610b238261080a565b9150610b2e8361080a565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615610b6757610b66610ab5565b5b828202905092915050565b6000610b7d8261080a565b9150610b888361080a565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610bbd57610bbc610ab5565b5b828201905092915050565b7f4e6f7420656e6f7567682062616c616e63650000000000000000000000000000600082015250565b6000610bfe601283610960565b9150610c0982610bc8565b602082019050919050565b60006020820190508181036000830152610c2d81610bf1565b9050919050565b7f42616c616e6365204f766572666c6f7700000000000000000000000000000000600082015250565b6000610c6a601083610960565b9150610c7582610c34565b602082019050919050565b60006020820190508181036000830152610c9981610c5d565b905091905056fea26469706673582212206a919534a5f2b8939885541b3e3df8b3ee48535823a8cfa339be7dec1a57b61864736f6c63430008090033","deployedBytecode":"0x60806040526004361061004a5760003560e01c8063095ea7b31461004f57806323b872dd1461008c5780633a4b66f1146100c957806370a08231146100d3578063a9059cbb14610110575b600080fd5b34801561005b57600080fd5b5061007660048036038101906100719190610840565b61014d565b604051610083919061089b565b60405180910390f35b34801561009857600080fd5b506100b360048036038101906100ae91906108b6565b6102d3565b6040516100c0919061089b565b60405180910390f35b6100d1610485565b005b3480156100df57600080fd5b506100fa60048036038101906100f59190610909565b610505565b6040516101079190610945565b60405180910390f35b34801561011c57600080fd5b5061013760048036038101906101329190610840565b61054d565b604051610144919061089b565b60405180910390f35b600080600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541461020d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610204906109e3565b60405180910390fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055507f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9253384846040516102c193929190610a12565b60405180910390a16001905092915050565b600081600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610394576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161038b90610a95565b60405180910390fd5b81600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546104209190610ae4565b9250508190555061043f84848460006105ac909392919063ffffffff16565b7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84848460405161047293929190610a12565b60405180910390a1600190509392505050565b6000600260009054906101000a900463ffffffff1663ffffffff16346104ab9190610b18565b9050806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546104fb9190610b72565b9250508190555050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600061056733848460006105ac909392919063ffffffff16565b7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef33848460405161059a93929190610a12565b60405180910390a16001905092915050565b808460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561062d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161062490610c14565b60405180910390fd5b8360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054818560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546106b69190610b72565b10156106f7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106ee90610c80565b60405180910390fd5b808460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546107459190610ae4565b92505081905550808460008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461079a9190610b72565b9250508190555050505050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006107d7826107ac565b9050919050565b6107e7816107cc565b81146107f257600080fd5b50565b600081359050610804816107de565b92915050565b6000819050919050565b61081d8161080a565b811461082857600080fd5b50565b60008135905061083a81610814565b92915050565b60008060408385031215610857576108566107a7565b5b6000610865858286016107f5565b92505060206108768582860161082b565b9150509250929050565b60008115159050919050565b61089581610880565b82525050565b60006020820190506108b0600083018461088c565b92915050565b6000806000606084860312156108cf576108ce6107a7565b5b60006108dd868287016107f5565b93505060206108ee868287016107f5565b92505060406108ff8682870161082b565b9150509250925092565b60006020828403121561091f5761091e6107a7565b5b600061092d848285016107f5565b91505092915050565b61093f8161080a565b82525050565b600060208201905061095a6000830184610936565b92915050565b600082825260208201905092915050565b7f48617320616c6c6f77616e63652c2063616e277420617070726f7665206d6f7260008201527f6500000000000000000000000000000000000000000000000000000000000000602082015250565b60006109cd602183610960565b91506109d882610971565b604082019050919050565b600060208201905081810360008301526109fc816109c0565b9050919050565b610a0c816107cc565b82525050565b6000606082019050610a276000830186610a03565b610a346020830185610a03565b610a416040830184610936565b949350505050565b7f4e6f7420656e6f75676820616c6c6f77616e6365000000000000000000000000600082015250565b6000610a7f601483610960565b9150610a8a82610a49565b602082019050919050565b60006020820190508181036000830152610aae81610a72565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610aef8261080a565b9150610afa8361080a565b925082821015610b0d57610b0c610ab5565b5b828203905092915050565b6000610b238261080a565b9150610b2e8361080a565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615610b6757610b66610ab5565b5b828202905092915050565b6000610b7d8261080a565b9150610b888361080a565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610bbd57610bbc610ab5565b5b828201905092915050565b7f4e6f7420656e6f7567682062616c616e63650000000000000000000000000000600082015250565b6000610bfe601283610960565b9150610c0982610bc8565b602082019050919050565b60006020820190508181036000830152610c2d81610bf1565b9050919050565b7f42616c616e6365204f766572666c6f7700000000000000000000000000000000600082015250565b6000610c6a601083610960565b9150610c7582610c34565b602082019050919050565b60006020820190508181036000830152610c9981610c5d565b905091905056fea26469706673582212206a919534a5f2b8939885541b3e3df8b3ee48535823a8cfa339be7dec1a57b61864736f6c63430008090033","abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"tokens","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenOwner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stake","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]} -------------------------------------------------------------------------------- /multisig-wallet/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig } from "hardhat/config"; 2 | import "@nomicfoundation/hardhat-toolbox"; 3 | import * as dotenv from "dotenv"; 4 | import * as tdly from "@tenderly/hardhat-tenderly"; 5 | import { tenderlyNetwork } from "hardhat"; 6 | import { readFileSync } from "fs"; 7 | import { Infra as ForkInfra } from "./lib/api/t-forks-clean-recreate"; 8 | 9 | tdly.setup({ automaticVerifications: true }); 10 | 11 | const loadInfra = () => 12 | JSON.parse(readFileSync("infra.json").toString()) as unknown as ForkInfra; 13 | 14 | dotenv.config(); 15 | 16 | const infra = loadInfra(); 17 | const fork = infra.fork; 18 | 19 | const ACCOUNTS = [ 20 | process.env.ROPSTEN_PRIVATE_KEY_1 || "", 21 | process.env.ROPSTEN_PRIVATE_KEY_2 || "", 22 | process.env.ROPSTEN_PRIVATE_KEY_3 || "", 23 | ]; 24 | 25 | const config: HardhatUserConfig = { 26 | solidity: "0.8.9", 27 | networks: { 28 | ropsten: { 29 | url: process.env.ROPSTEN_URL, 30 | accounts: ACCOUNTS, 31 | }, 32 | tenderly: { 33 | url: fork?.url || "", 34 | chainId: fork?.chainId, 35 | }, 36 | }, 37 | tenderly: { 38 | project: "REPLACE_ME", 39 | username: "REPLACE_ME", 40 | privateVerification: true, 41 | }, 42 | }; 43 | 44 | export default config; 45 | -------------------------------------------------------------------------------- /multisig-wallet/infra.json: -------------------------------------------------------------------------------- 1 | { 2 | "fork": { 3 | "name": "???", 4 | "id": "99aca64d-c20c-42c6-af1b-a1c16c2771ec", 5 | "url": "https://rpc.tenderly.co/fork/99aca64d-c20c-42c6-af1b-a1c16c2771ec", 6 | "chainId": 3 7 | } 8 | } -------------------------------------------------------------------------------- /multisig-wallet/lib/api/t-forks-clean-recreate.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from "fs"; 2 | import { tenderlyApi } from "./tenderly-api"; 3 | import * as dotenv from "dotenv"; 4 | 5 | const { aTenderlyFork, removeFork } = tenderlyApi( 6 | process.env.TENDERLY_PROJECT_SLUG || "", 7 | process.env.TENDERLY_USERNAME || "", 8 | process.env.TENDERLY_ACCESS_KEY || "" 9 | ); 10 | 11 | export type Infra = { 12 | fork?: { 13 | name: string; 14 | id: string; 15 | url: string; 16 | chainId: number; 17 | }; 18 | }; 19 | 20 | (async () => { 21 | const currentInfra = JSON.parse( 22 | readFileSync("infra.json").toString() 23 | ) as unknown as Infra; 24 | 25 | if (currentInfra.fork) { 26 | await removeFork(currentInfra.fork.id).catch((err) => { 27 | console.error(err); 28 | }); 29 | } 30 | 31 | const fork = await aTenderlyFork({ 32 | network_id: currentInfra.fork?.chainId.toString() || "3", 33 | }); 34 | 35 | console.log("Created the fork", fork.id); 36 | 37 | writeFileSync( 38 | "infra.json", 39 | JSON.stringify( 40 | { 41 | fork: { 42 | ...currentInfra.fork, 43 | name: "???", 44 | id: fork.id, 45 | url: `https://rpc.tenderly.co/fork/${fork.id}`, 46 | }, 47 | }, 48 | null, 49 | 2 50 | ) 51 | ); 52 | 53 | writeFileSync("deployments.json", JSON.stringify({ latest: [] }, null, 2)); 54 | })().catch(console.error); 55 | -------------------------------------------------------------------------------- /multisig-wallet/lib/api/tenderly-api.ts: -------------------------------------------------------------------------------- 1 | import { JsonRpcProvider } from "@ethersproject/providers"; 2 | import axios, { AxiosResponse } from "axios"; 3 | 4 | type TenderlyForkRequest = { 5 | block_number?: number; 6 | network_id: string; 7 | transaction_index?: number; 8 | initial_balance?: number; 9 | chain_config?: { 10 | chain_id: number; 11 | homestead_block: number; 12 | dao_fork_support: boolean; 13 | eip_150_block: number; 14 | eip_150_hash: string; 15 | eip_155_block: number; 16 | eip_158_block: number; 17 | byzantium_block: number; 18 | constantinople_block: number; 19 | petersburg_block: number; 20 | istanbul_block: number; 21 | berlin_block: number; 22 | }; 23 | }; 24 | 25 | export type TenderlyForkProvider = { 26 | provider: JsonRpcProvider; 27 | id: number; 28 | blockNumber: number; 29 | /** 30 | * map from address to given address' balance 31 | */ 32 | removeFork: () => Promise>; 33 | }; 34 | 35 | export const tenderlyApi = ( 36 | projectSlug: string, 37 | username: string, 38 | accessKey: string 39 | ) => { 40 | const somewhereInTenderly = (where: string = projectSlug || "") => 41 | axios.create({ 42 | baseURL: `https://api.tenderly.co/api/v1/${where}`, 43 | headers: { 44 | "X-Access-Key": accessKey || "", 45 | "Content-Type": "application/json", 46 | }, 47 | }); 48 | 49 | const inProject = (...path: any[]) => 50 | [`account/${username}/project/${projectSlug}`, ...path] 51 | .join("/") 52 | .replace("//", ""); 53 | 54 | const anAxiosOnTenderly = () => somewhereInTenderly(""); 55 | const axiosOnTenderly = anAxiosOnTenderly(); 56 | 57 | // todo: cache these poor axioses 58 | const axiosInProject = somewhereInTenderly(inProject()); 59 | 60 | const removeFork = async (forkId: string) => { 61 | console.log("Removing test fork", forkId); 62 | return await axiosOnTenderly.delete(inProject(`fork/${forkId}`)); 63 | }; 64 | 65 | async function aTenderlyFork( 66 | fork: TenderlyForkRequest 67 | ): Promise { 68 | const forkResponse = await axiosInProject.post(`/fork`, fork); 69 | const forkId = forkResponse.data.root_transaction.fork_id; 70 | 71 | const forkProviderUrl = `https://rpc.tenderly.co/fork/${forkId}`; 72 | const forkProvider = new JsonRpcProvider(forkProviderUrl); 73 | 74 | const bn = ( 75 | forkResponse.data.root_transaction.receipt.blockNumber as string 76 | ).replace("0x", ""); 77 | const blockNumber: number = Number.parseInt(bn, 16); 78 | 79 | console.info( 80 | `\nForked with fork id ${forkId} at block number ${blockNumber}` 81 | ); 82 | 83 | console.info(`https://dashboard.tenderly.co/${inProject("fork", forkId)}`); 84 | 85 | console.info("JSON-RPC:", forkProviderUrl); 86 | 87 | return { 88 | provider: forkProvider, 89 | blockNumber, 90 | id: forkId, 91 | removeFork: () => removeFork(forkId), 92 | }; 93 | } 94 | 95 | return { 96 | aTenderlyFork, 97 | removeFork, 98 | }; 99 | }; 100 | -------------------------------------------------------------------------------- /multisig-wallet/lib/deployment-utils.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from "ethers"; 2 | import { readFileSync, writeFileSync } from "fs"; 3 | 4 | export type DeploymentRecord = { 5 | name: string; 6 | address: string; 7 | network: string; 8 | timestamp?: Date; 9 | }; 10 | 11 | function readDeployments() { 12 | type Deployments = Record; 13 | const deployments = JSON.parse( 14 | readFileSync("deployments.json", "utf8").toString() 15 | ) as unknown as Deployments; 16 | return deployments; 17 | } 18 | 19 | /** 20 | * Call to register you deployed a contract to a particular network. 21 | * @param deployment the deployment to register 22 | * @param tag optional tag 23 | * @returns 24 | */ 25 | export function deployed(deployment: DeploymentRecord, tag: string = "latest") { 26 | if (findDeployment(deployed.name, deployment.network, tag)) { 27 | return; 28 | } 29 | const deployments = readDeployments(); 30 | deployment.timestamp = new Date(); 31 | deployments[tag] = [...deployments[tag], deployment]; 32 | writeFileSync("deployments.json", JSON.stringify(deployments, null, 2)); 33 | } 34 | 35 | /** 36 | * 37 | * @param name the name of the contract 38 | * @param network the network the contract was deployed to 39 | * @param tag optional tag 40 | * @returns a deployment object 41 | */ 42 | export function findDeployment(name: string, network: string, tag = "latest") { 43 | return readDeployments()[tag].filter( 44 | (d) => d.name === name && d.network == network 45 | )[0]; 46 | } 47 | -------------------------------------------------------------------------------- /multisig-wallet/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "fork:restart": "ts-node ./lib/api/t-forks-clean-recreate.ts", 4 | "deploy:ropsten": "npx hardhat run scripts/token-deploy.ts --network ropsten && npx hardhat run scripts/multisig-deploy.ts --network ropsten", 5 | "deploy:tenderly-fork": "npx hardhat run scripts/token-deploy.ts --network tenderly && npx hardhat run scripts/multisig-deploy.ts --network tenderly", 6 | "multisig-play:ropsten": "npx hardhat run scripts/multisig-play.ts --network ropsten", 7 | "multisig-play:tenderly-fork": "npx hardhat run scripts/multisig-play.ts --network tenderly", 8 | "actions:run:local": "ts-node web3-actions-local/multisig-run-local.ts" 9 | }, 10 | "devDependencies": { 11 | "@nomicfoundation/hardhat-toolbox": "^1.0.2", 12 | "@tenderly/actions-test": "^0.0.9", 13 | "@tenderly/hardhat-tenderly": "^1.1.4", 14 | "@types/node": "^18.6.3", 15 | "axios": "^0.27.2", 16 | "chai": "^4.3.6", 17 | "dotenv": "^16.0.1", 18 | "ethers": "^5.6.9", 19 | "hardhat": "^2.10.1", 20 | "mocha": "^10.0.0", 21 | "ts-node": "^10.9.1", 22 | "typescript": "^4.7.4" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /multisig-wallet/scripts/multisig-deploy.ts: -------------------------------------------------------------------------------- 1 | import { ethers, network } from "hardhat"; 2 | import { deployed, findDeployment } from "../lib/deployment-utils"; 3 | 4 | async function main() { 5 | const foundMultiSig = findDeployment("MultiSigWallet", network.name); 6 | if (!!foundMultiSig) { 7 | console.log("Multisig wallet already deployed", foundMultiSig); 8 | return; 9 | } 10 | 11 | const owners = (await ethers.provider.listAccounts()).slice(0, 3); 12 | // deploy the contract MultiSigWallet 13 | const MultiSigWallet = await ethers.getContractFactory("MultiSigWallet"); 14 | const multiSigWallet = await MultiSigWallet.deploy(owners, 1); 15 | await multiSigWallet.deployed(); 16 | deployed({ 17 | name: "MultiSigWallet", 18 | address: multiSigWallet.address, 19 | network: network.name, 20 | }); 21 | console.log("MultiSigWallet deployed to:", multiSigWallet.address); 22 | } 23 | 24 | // We recommend this pattern to be able to use async/await everywhere 25 | // and properly handle errors. 26 | main().catch((error) => { 27 | console.error(error); 28 | process.exitCode = 1; 29 | }); 30 | -------------------------------------------------------------------------------- /multisig-wallet/scripts/multisig-play.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, BytesLike } from "ethers"; 2 | import { ethers, network } from "hardhat"; 3 | import { findDeployment } from "../lib/deployment-utils"; 4 | 5 | async function main() { 6 | const deployedMultiSig = findDeployment("MultiSigWallet", network.name); 7 | const deployedToken = findDeployment("Token", network.name); 8 | if (!deployedMultiSig) { 9 | console.log("Multi Sig not deployed, can't play, run multisig-deploy.ts"); 10 | return; 11 | } 12 | console.log({ deployedMultiSig, deployedToken }); 13 | const multisigWallet = await ethers.getContractAt( 14 | "MultiSigWallet", 15 | deployedMultiSig.address, 16 | ethers.provider.getSigner("0xf7ddedc66b1d482e5c38e4730b3357d32411e5dd") 17 | ); 18 | 19 | console.log("Default signer", await ethers.provider.getSigner().getAddress()); 20 | 21 | const token = await ethers.getContractAt("Token", deployedToken.address); 22 | const VALUE = ethers.utils.hexValue(ethers.utils.parseEther("0.1")); 23 | const tx = await token.populateTransaction.stake({ 24 | value: VALUE, 25 | }); 26 | 27 | console.log("Internal TX", tx); 28 | 29 | const msig = await multisigWallet.submitTransaction( 30 | token.address, 31 | (tx.value as BigNumber).toHexString(), 32 | tx.data as BytesLike 33 | ); 34 | 35 | const reciept = await msig.wait(); 36 | 37 | const subm = reciept.events?.filter((e) => e.event == "TxSubmission"); 38 | 39 | // @ts-ignore 40 | const latestTxIdx = (subm[0].args[1] as BigNumber).toNumber(); 41 | 42 | console.log( 43 | `Submitted (IDX: ${latestTxIdx}, TX: ${reciept.transactionHash})` 44 | ); 45 | 46 | // Get signers 47 | const [first, second, third] = [ 48 | "0xF7dDedc66B1d482e5C38E4730B3357d32411e5Dd", 49 | "0x3a55A1e7cf75dD8374de8463530dF721816F1411", 50 | "0xeDed260BFDCDf6Dc0f564b3e4AB5CeA805bBA10B", 51 | ].map((addr) => ethers.provider.getSigner(addr)); 52 | 53 | // Confirm 1 54 | const conf1tx = await multisigWallet 55 | .connect(second) 56 | .confirmTransaction(latestTxIdx); 57 | await conf1tx.wait(); 58 | console.log("Confirmation 1"); 59 | 60 | // Confirm 2 61 | const conf2tx = await multisigWallet 62 | .connect(third) 63 | .confirmTransaction(latestTxIdx); 64 | await conf2tx.wait(); 65 | console.log("Confirmation 2"); 66 | 67 | // Fund the wallet before executing the TX 68 | const fundingWalletTx = await second.sendTransaction({ 69 | to: multisigWallet.address, 70 | value: VALUE, 71 | }); 72 | await fundingWalletTx.wait(); 73 | 74 | // Execute the TX 75 | const execTx = await multisigWallet 76 | .connect(first) 77 | .executeTransaction(latestTxIdx, { gasLimit: 100000 }); 78 | await execTx.wait(); 79 | 80 | console.log(`Executed (IDX: ${latestTxIdx}, TX: ${reciept.transactionHash})`); 81 | } 82 | 83 | // We recommend this pattern to be able to use async/await everywhere 84 | // and properly handle errors. 85 | main().catch((error) => { 86 | console.error(error); 87 | process.exitCode = 1; 88 | }); 89 | -------------------------------------------------------------------------------- /multisig-wallet/scripts/token-deploy.ts: -------------------------------------------------------------------------------- 1 | import { ethers, network } from "hardhat"; 2 | import { deployed, findDeployment } from "../lib/deployment-utils"; 3 | 4 | async function main() { 5 | const foundToken = findDeployment("Token", network.name); 6 | 7 | if (!!foundToken) { 8 | console.log( 9 | "Token already deployed", 10 | foundToken.network, 11 | foundToken.address 12 | ); 13 | return; 14 | } 15 | 16 | const Token = await ethers.getContractFactory("Token"); 17 | const token = await Token.deploy(); 18 | 19 | await token.deployed(); 20 | console.log("Token deployed to:", token.address); 21 | deployed({ 22 | name: "Token", 23 | address: token.address, 24 | network: network.name, 25 | }); 26 | } 27 | // We recommend this pattern to be able to use async/await everywhere 28 | // and properly handle errors. 29 | main().catch((error) => { 30 | console.error(error); 31 | process.exitCode = 1; 32 | }); 33 | -------------------------------------------------------------------------------- /multisig-wallet/tenderly.yaml: -------------------------------------------------------------------------------- 1 | account_id: "" 2 | actions: 3 | nenad/demo-encode-eth-safari: 4 | runtime: v1 5 | sources: web3-actions 6 | specs: 7 | multisig: 8 | description: Acts when multisig event takes place 9 | function: multisig:onAnEvent 10 | trigger: 11 | transaction: 12 | filters: 13 | - eventEmitted: 14 | contract: 15 | address: 0xceA103d9D4040aCd684E0E2f2d992ff7d3Ea62c4 16 | name: TxSubmission 17 | network: 3 18 | - eventEmitted: 19 | contract: 20 | address: 0xceA103d9D4040aCd684E0E2f2d992ff7d3Ea62c4 21 | name: TxConfirmation 22 | network: 3 23 | - eventEmitted: 24 | contract: 25 | address: 0xceA103d9D4040aCd684E0E2f2d992ff7d3Ea62c4 26 | name: ConfirmationRevocation 27 | network: 3 28 | - eventEmitted: 29 | contract: 30 | address: 0xceA103d9D4040aCd684E0E2f2d992ff7d3Ea62c4 31 | name: ExecuteTransaction 32 | network: 3 33 | status: 34 | - mined 35 | type: transaction 36 | # multisig-periodic: 37 | # description: Reminders 38 | # function: multisig:everyDay 39 | # trigger: 40 | # periodic: 41 | # interval: 1d 42 | # type: periodic 43 | multisig-webhook: 44 | description: Via Webhook 45 | function: multisig:onHook 46 | trigger: 47 | type: webhook 48 | wehbook: 49 | authenticated: true 50 | project_slug: "" 51 | -------------------------------------------------------------------------------- /multisig-wallet/test/multisig.ts: -------------------------------------------------------------------------------- 1 | import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers"; 2 | import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs"; 3 | import { expect } from "chai"; 4 | import { ethers } from "hardhat"; 5 | 6 | import { MultiSigWallet } from "../typechain-types"; 7 | const multisigAddress = "0xb071fef2e0f6fbe5ea3267205655d038af22b903"; 8 | 9 | const attach = async () => { 10 | const multisigWallet = await ethers.getContractAt( 11 | "MultiSigWallet", 12 | multisigAddress 13 | ); 14 | 15 | return multisigWallet; 16 | }; 17 | 18 | describe("multisig scenario", async () => { 19 | let multisigWallet: MultiSigWallet; 20 | 21 | before(async () => { 22 | multisigWallet = await loadFixture(attach); 23 | }); 24 | 25 | it("", async () => { 26 | console.log("Test"); 27 | expect(await multisigWallet.getOwners()).to.length(3, "!3 owners"); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /multisig-wallet/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "forceConsistentCasingInFileNames": true, 5 | "module": "commonjs", 6 | "outDir": "./dist", 7 | "resolveJsonModule": true, 8 | "skipLibCheck": true, 9 | "strict": true, 10 | "target": "es2020" 11 | }, 12 | "exclude": [ 13 | "actions", 14 | "actions" 15 | ] 16 | } -------------------------------------------------------------------------------- /multisig-wallet/web3-actions-local/multisig-run-local.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TestPeriodicEvent, 3 | TestRuntime, 4 | TestWebhookEvent, 5 | } from "@tenderly/actions-test"; 6 | import { everyDay, onAnEvent, onHook } from "../web3-actions/multisig"; 7 | 8 | import * as dotenv from "dotenv"; 9 | dotenv.config(); 10 | 11 | /* 12 | * This is the local run of Action Functions. 13 | * TestRuntime is a helper class that allows you to run Action Functions locally. 14 | **/ 15 | const main = async () => { 16 | const testRuntime = new TestRuntime(); 17 | 18 | testRuntime.context.secrets.put( 19 | "multisig.DISCORD_URL", 20 | process.env.DISCORD_URL || "" 21 | ); 22 | 23 | testRuntime.context.secrets.put( 24 | "TENDERLY_ACCESS_KEY", 25 | process.env.TENDERLY_ACCESS_KEY || "" 26 | ); 27 | 28 | await testRuntime.execute( 29 | onAnEvent, 30 | require("./payload/payload-submit.json") 31 | ); 32 | 33 | await testRuntime.execute( 34 | onAnEvent, 35 | require("./payload/payload-confirm.json") 36 | ); 37 | 38 | await testRuntime.execute( 39 | onHook, 40 | new TestWebhookEvent({ txId: 2, who: "reminder0x" }) 41 | ); 42 | 43 | await testRuntime.execute(everyDay, new TestPeriodicEvent()); 44 | 45 | await testRuntime.execute( 46 | onAnEvent, 47 | require("./payload/payload-execute.json") 48 | ); 49 | }; 50 | 51 | (async () => await main())(); 52 | -------------------------------------------------------------------------------- /multisig-wallet/web3-actions-local/payload/payload-confirm.json: -------------------------------------------------------------------------------- 1 | { 2 | "network": "3", 3 | "blockHash": "0x84f3ec6712b02ba288d40514ba4be00e04a96ca20db9881dfaf990e6dbb7e8d8", 4 | "blockNumber": 12766262, 5 | "hash": "0xad91a78d2f544371237ec63d5a47f2d6775784266eeb81ed7ea304d1f146c46c", 6 | "from": "0x3a55A1e7cf75dD8374de8463530dF721816F1411", 7 | "to": "0x418EBb95EAa40c119408143056CaD984C6129d45", 8 | "logs": [ 9 | { 10 | "address": "0x418EBb95EAa40c119408143056CaD984C6129d45", 11 | "topics": [ 12 | "0x52a3954c699324c8e90e7e2f7cb9f57cd61af1902197bafcc5085b6c2a57b823", 13 | "0x0000000000000000000000003a55a1e7cf75dd8374de8463530df721816f1411", 14 | "0x0000000000000000000000000000000000000000000000000000000000000000" 15 | ], 16 | "data": "0x" 17 | } 18 | ], 19 | "input": "0xc01a8c840000000000000000000000000000000000000000000000000000000000000000", 20 | "value": "0x0", 21 | "nonce": "0x329", 22 | "gas": "0x12565", 23 | "gasUsed": "0x12565", 24 | "cumulativeGasUsed": "0x56a97", 25 | "gasPrice": "0x832155e2", 26 | "gasTipCap": "0x832155db", 27 | "gasFeeCap": "0x832155e3", 28 | "alertId": null 29 | } 30 | -------------------------------------------------------------------------------- /multisig-wallet/web3-actions-local/payload/payload-execute.json: -------------------------------------------------------------------------------- 1 | { 2 | "network": "3", 3 | "blockHash": "0xb1f0d051f2deef91cdec639898af1288f5f17d3ace60f34e16e88c86ceac6a40", 4 | "blockNumber": 12766265, 5 | "hash": "0x7faeb8641d16ad5179ed0d4fb7b478311b85ab41e186c8180d23357f5e9a675f", 6 | "from": "0xF7dDedc66B1d482e5C38E4730B3357d32411e5Dd", 7 | "to": "0x418EBb95EAa40c119408143056CaD984C6129d45", 8 | "logs": [ 9 | { 10 | "address": "0x418EBb95EAa40c119408143056CaD984C6129d45", 11 | "topics": [ 12 | "0x5445f318f4f5fcfb66592e68e0cc5822aa15664039bd5f0ffde24c5a8142b1ac", 13 | "0x000000000000000000000000f7ddedc66b1d482e5c38e4730b3357d32411e5dd", 14 | "0x0000000000000000000000000000000000000000000000000000000000000000" 15 | ], 16 | "data": "0x" 17 | } 18 | ], 19 | "input": "0xee22610b0000000000000000000000000000000000000000000000000000000000000000", 20 | "value": "0x0", 21 | "nonce": "0x1e6", 22 | "gas": "0x11067", 23 | "gasUsed": "0x10dc6", 24 | "cumulativeGasUsed": "0xe88ce", 25 | "gasPrice": "0x4190aaf1", 26 | "gasTipCap": "0x4190aaea", 27 | "gasFeeCap": "0x4190aaf2", 28 | "alertId": null 29 | } 30 | -------------------------------------------------------------------------------- /multisig-wallet/web3-actions-local/payload/payload-submit.json: -------------------------------------------------------------------------------- 1 | { 2 | "network": "3", 3 | "blockHash": "0xdd524d760f70a42fe940560ef6732b3692ace41ad38a6c516c6681ca19ff4a36", 4 | "blockNumber": 12773572, 5 | "hash": "0xd4f72faf1421f5ca96480378ea65565da3602c0f4ad1c61ad46d7cf2a3f29b1d", 6 | "from": "0xF7dDedc66B1d482e5C38E4730B3357d32411e5Dd", 7 | "to": "0x418EBb95EAa40c119408143056CaD984C6129d45", 8 | "logs": [ 9 | { 10 | "address": "0x418EBb95EAa40c119408143056CaD984C6129d45", 11 | "topics": [ 12 | "0x52e5c631bb2fd786d6b1c26f68548e07c61d087bf325770dd88d072cf0a5e8b0", 13 | "0x000000000000000000000000f7ddedc66b1d482e5c38e4730b3357d32411e5dd", 14 | "0x000000000000000000000000000000000000000000000000000000000000000a", 15 | "0x0000000000000000000000004e97c71a44ef1b78a7caee159aa016be53e04c32" 16 | ], 17 | "data": "0x00000000000000000000000000000000000000000000000016345785d8a00000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000043a4b66f100000000000000000000000000000000000000000000000000000000" 18 | } 19 | ], 20 | "input": "0xc64274740000000000000000000000004e97c71a44ef1b78a7caee159aa016be53e04c3200000000000000000000000000000000000000000000000016345785d8a00000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000043a4b66f100000000000000000000000000000000000000000000000000000000", 21 | "value": "0x0", 22 | "nonce": "0x1f1", 23 | "gas": "0x19e34", 24 | "gasUsed": "0x19e34", 25 | "cumulativeGasUsed": "0x480640", 26 | "gasPrice": "0x3b9aca00", 27 | "gasTipCap": "0x3b9ac9f9", 28 | "gasFeeCap": "0x3b9aca01", 29 | "alertId": null 30 | } 31 | -------------------------------------------------------------------------------- /multisig-wallet/web3-actions/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directories 2 | node_modules/ 3 | 4 | # Ignore tsc output 5 | out/**/* 6 | -------------------------------------------------------------------------------- /multisig-wallet/web3-actions/abi/Token.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "Token", 4 | "sourceName": "contracts/Token.sol", 5 | "abi": [ 6 | { 7 | "anonymous": false, 8 | "inputs": [ 9 | { 10 | "indexed": false, 11 | "internalType": "address", 12 | "name": "owner", 13 | "type": "address" 14 | }, 15 | { 16 | "indexed": false, 17 | "internalType": "address", 18 | "name": "spender", 19 | "type": "address" 20 | }, 21 | { 22 | "indexed": false, 23 | "internalType": "uint256", 24 | "name": "amount", 25 | "type": "uint256" 26 | } 27 | ], 28 | "name": "Approval", 29 | "type": "event" 30 | }, 31 | { 32 | "anonymous": false, 33 | "inputs": [ 34 | { 35 | "indexed": false, 36 | "internalType": "address", 37 | "name": "from", 38 | "type": "address" 39 | }, 40 | { 41 | "indexed": false, 42 | "internalType": "address", 43 | "name": "to", 44 | "type": "address" 45 | }, 46 | { 47 | "indexed": false, 48 | "internalType": "uint256", 49 | "name": "amount", 50 | "type": "uint256" 51 | } 52 | ], 53 | "name": "Transfer", 54 | "type": "event" 55 | }, 56 | { 57 | "inputs": [ 58 | { 59 | "internalType": "address", 60 | "name": "spender", 61 | "type": "address" 62 | }, 63 | { 64 | "internalType": "uint256", 65 | "name": "tokens", 66 | "type": "uint256" 67 | } 68 | ], 69 | "name": "approve", 70 | "outputs": [ 71 | { 72 | "internalType": "bool", 73 | "name": "success", 74 | "type": "bool" 75 | } 76 | ], 77 | "stateMutability": "nonpayable", 78 | "type": "function" 79 | }, 80 | { 81 | "inputs": [ 82 | { 83 | "internalType": "address", 84 | "name": "tokenOwner", 85 | "type": "address" 86 | } 87 | ], 88 | "name": "balanceOf", 89 | "outputs": [ 90 | { 91 | "internalType": "uint256", 92 | "name": "balance", 93 | "type": "uint256" 94 | } 95 | ], 96 | "stateMutability": "view", 97 | "type": "function" 98 | }, 99 | { 100 | "inputs": [], 101 | "name": "stake", 102 | "outputs": [], 103 | "stateMutability": "payable", 104 | "type": "function" 105 | }, 106 | { 107 | "inputs": [ 108 | { 109 | "internalType": "address", 110 | "name": "to", 111 | "type": "address" 112 | }, 113 | { 114 | "internalType": "uint256", 115 | "name": "amount", 116 | "type": "uint256" 117 | } 118 | ], 119 | "name": "transfer", 120 | "outputs": [ 121 | { 122 | "internalType": "bool", 123 | "name": "success", 124 | "type": "bool" 125 | } 126 | ], 127 | "stateMutability": "nonpayable", 128 | "type": "function" 129 | }, 130 | { 131 | "inputs": [ 132 | { 133 | "internalType": "address", 134 | "name": "from", 135 | "type": "address" 136 | }, 137 | { 138 | "internalType": "address", 139 | "name": "to", 140 | "type": "address" 141 | }, 142 | { 143 | "internalType": "uint256", 144 | "name": "amount", 145 | "type": "uint256" 146 | } 147 | ], 148 | "name": "transferFrom", 149 | "outputs": [ 150 | { 151 | "internalType": "bool", 152 | "name": "success", 153 | "type": "bool" 154 | } 155 | ], 156 | "stateMutability": "nonpayable", 157 | "type": "function" 158 | } 159 | ], 160 | "bytecode": "0x60806040526014600260006101000a81548163ffffffff021916908363ffffffff16021790555034801561003257600080fd5b50610cd6806100426000396000f3fe60806040526004361061004a5760003560e01c8063095ea7b31461004f57806323b872dd1461008c5780633a4b66f1146100c957806370a08231146100d3578063a9059cbb14610110575b600080fd5b34801561005b57600080fd5b5061007660048036038101906100719190610840565b61014d565b604051610083919061089b565b60405180910390f35b34801561009857600080fd5b506100b360048036038101906100ae91906108b6565b6102d3565b6040516100c0919061089b565b60405180910390f35b6100d1610485565b005b3480156100df57600080fd5b506100fa60048036038101906100f59190610909565b610505565b6040516101079190610945565b60405180910390f35b34801561011c57600080fd5b5061013760048036038101906101329190610840565b61054d565b604051610144919061089b565b60405180910390f35b600080600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541461020d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610204906109e3565b60405180910390fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055507f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9253384846040516102c193929190610a12565b60405180910390a16001905092915050565b600081600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610394576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161038b90610a95565b60405180910390fd5b81600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546104209190610ae4565b9250508190555061043f84848460006105ac909392919063ffffffff16565b7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84848460405161047293929190610a12565b60405180910390a1600190509392505050565b6000600260009054906101000a900463ffffffff1663ffffffff16346104ab9190610b18565b9050806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546104fb9190610b72565b9250508190555050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600061056733848460006105ac909392919063ffffffff16565b7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef33848460405161059a93929190610a12565b60405180910390a16001905092915050565b808460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561062d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161062490610c14565b60405180910390fd5b8360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054818560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546106b69190610b72565b10156106f7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106ee90610c80565b60405180910390fd5b808460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546107459190610ae4565b92505081905550808460008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461079a9190610b72565b9250508190555050505050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006107d7826107ac565b9050919050565b6107e7816107cc565b81146107f257600080fd5b50565b600081359050610804816107de565b92915050565b6000819050919050565b61081d8161080a565b811461082857600080fd5b50565b60008135905061083a81610814565b92915050565b60008060408385031215610857576108566107a7565b5b6000610865858286016107f5565b92505060206108768582860161082b565b9150509250929050565b60008115159050919050565b61089581610880565b82525050565b60006020820190506108b0600083018461088c565b92915050565b6000806000606084860312156108cf576108ce6107a7565b5b60006108dd868287016107f5565b93505060206108ee868287016107f5565b92505060406108ff8682870161082b565b9150509250925092565b60006020828403121561091f5761091e6107a7565b5b600061092d848285016107f5565b91505092915050565b61093f8161080a565b82525050565b600060208201905061095a6000830184610936565b92915050565b600082825260208201905092915050565b7f48617320616c6c6f77616e63652c2063616e277420617070726f7665206d6f7260008201527f6500000000000000000000000000000000000000000000000000000000000000602082015250565b60006109cd602183610960565b91506109d882610971565b604082019050919050565b600060208201905081810360008301526109fc816109c0565b9050919050565b610a0c816107cc565b82525050565b6000606082019050610a276000830186610a03565b610a346020830185610a03565b610a416040830184610936565b949350505050565b7f4e6f7420656e6f75676820616c6c6f77616e6365000000000000000000000000600082015250565b6000610a7f601483610960565b9150610a8a82610a49565b602082019050919050565b60006020820190508181036000830152610aae81610a72565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610aef8261080a565b9150610afa8361080a565b925082821015610b0d57610b0c610ab5565b5b828203905092915050565b6000610b238261080a565b9150610b2e8361080a565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615610b6757610b66610ab5565b5b828202905092915050565b6000610b7d8261080a565b9150610b888361080a565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610bbd57610bbc610ab5565b5b828201905092915050565b7f4e6f7420656e6f7567682062616c616e63650000000000000000000000000000600082015250565b6000610bfe601283610960565b9150610c0982610bc8565b602082019050919050565b60006020820190508181036000830152610c2d81610bf1565b9050919050565b7f42616c616e6365204f766572666c6f7700000000000000000000000000000000600082015250565b6000610c6a601083610960565b9150610c7582610c34565b602082019050919050565b60006020820190508181036000830152610c9981610c5d565b905091905056fea26469706673582212206a919534a5f2b8939885541b3e3df8b3ee48535823a8cfa339be7dec1a57b61864736f6c63430008090033", 161 | "deployedBytecode": "0x60806040526004361061004a5760003560e01c8063095ea7b31461004f57806323b872dd1461008c5780633a4b66f1146100c957806370a08231146100d3578063a9059cbb14610110575b600080fd5b34801561005b57600080fd5b5061007660048036038101906100719190610840565b61014d565b604051610083919061089b565b60405180910390f35b34801561009857600080fd5b506100b360048036038101906100ae91906108b6565b6102d3565b6040516100c0919061089b565b60405180910390f35b6100d1610485565b005b3480156100df57600080fd5b506100fa60048036038101906100f59190610909565b610505565b6040516101079190610945565b60405180910390f35b34801561011c57600080fd5b5061013760048036038101906101329190610840565b61054d565b604051610144919061089b565b60405180910390f35b600080600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541461020d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610204906109e3565b60405180910390fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055507f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9253384846040516102c193929190610a12565b60405180910390a16001905092915050565b600081600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610394576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161038b90610a95565b60405180910390fd5b81600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546104209190610ae4565b9250508190555061043f84848460006105ac909392919063ffffffff16565b7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84848460405161047293929190610a12565b60405180910390a1600190509392505050565b6000600260009054906101000a900463ffffffff1663ffffffff16346104ab9190610b18565b9050806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546104fb9190610b72565b9250508190555050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600061056733848460006105ac909392919063ffffffff16565b7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef33848460405161059a93929190610a12565b60405180910390a16001905092915050565b808460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561062d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161062490610c14565b60405180910390fd5b8360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054818560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546106b69190610b72565b10156106f7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106ee90610c80565b60405180910390fd5b808460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546107459190610ae4565b92505081905550808460008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461079a9190610b72565b9250508190555050505050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006107d7826107ac565b9050919050565b6107e7816107cc565b81146107f257600080fd5b50565b600081359050610804816107de565b92915050565b6000819050919050565b61081d8161080a565b811461082857600080fd5b50565b60008135905061083a81610814565b92915050565b60008060408385031215610857576108566107a7565b5b6000610865858286016107f5565b92505060206108768582860161082b565b9150509250929050565b60008115159050919050565b61089581610880565b82525050565b60006020820190506108b0600083018461088c565b92915050565b6000806000606084860312156108cf576108ce6107a7565b5b60006108dd868287016107f5565b93505060206108ee868287016107f5565b92505060406108ff8682870161082b565b9150509250925092565b60006020828403121561091f5761091e6107a7565b5b600061092d848285016107f5565b91505092915050565b61093f8161080a565b82525050565b600060208201905061095a6000830184610936565b92915050565b600082825260208201905092915050565b7f48617320616c6c6f77616e63652c2063616e277420617070726f7665206d6f7260008201527f6500000000000000000000000000000000000000000000000000000000000000602082015250565b60006109cd602183610960565b91506109d882610971565b604082019050919050565b600060208201905081810360008301526109fc816109c0565b9050919050565b610a0c816107cc565b82525050565b6000606082019050610a276000830186610a03565b610a346020830185610a03565b610a416040830184610936565b949350505050565b7f4e6f7420656e6f75676820616c6c6f77616e6365000000000000000000000000600082015250565b6000610a7f601483610960565b9150610a8a82610a49565b602082019050919050565b60006020820190508181036000830152610aae81610a72565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610aef8261080a565b9150610afa8361080a565b925082821015610b0d57610b0c610ab5565b5b828203905092915050565b6000610b238261080a565b9150610b2e8361080a565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615610b6757610b66610ab5565b5b828202905092915050565b6000610b7d8261080a565b9150610b888361080a565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610bbd57610bbc610ab5565b5b828201905092915050565b7f4e6f7420656e6f7567682062616c616e63650000000000000000000000000000600082015250565b6000610bfe601283610960565b9150610c0982610bc8565b602082019050919050565b60006020820190508181036000830152610c2d81610bf1565b9050919050565b7f42616c616e6365204f766572666c6f7700000000000000000000000000000000600082015250565b6000610c6a601083610960565b9150610c7582610c34565b602082019050919050565b60006020820190508181036000830152610c9981610c5d565b905091905056fea26469706673582212206a919534a5f2b8939885541b3e3df8b3ee48535823a8cfa339be7dec1a57b61864736f6c63430008090033", 162 | "linkReferences": {}, 163 | "deployedLinkReferences": {} 164 | } 165 | -------------------------------------------------------------------------------- /multisig-wallet/web3-actions/multisig.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ActionFn, 3 | Context, 4 | Event, 5 | TransactionEvent, 6 | WebhookEvent, 7 | } from "@tenderly/actions"; 8 | import axios from "axios"; 9 | import { BigNumber, BytesLike, ethers } from "ethers"; 10 | import { LogDescription } from "ethers/lib/utils"; 11 | import { abi as MultisigAbi } from "./abi/MultiSigWallet.json"; 12 | import { abi as TokenAbi } from "./abi/Token.json"; 13 | import { tenderlyApi } from "./tenderly-api"; 14 | 15 | const KEY_STORE_SUBMITTED_TX = "multisig.submitted"; 16 | 17 | /** TODO: Update with proper username, project etc */ 18 | const TENDERLY_USERNAME = "REPLACE_ME"; 19 | const TENDERLY_PROJECT_SLUG = "REPLACE_ME"; 20 | 21 | const MULTISIG_WALLET_ADDRESS = "0x6794B4635e094982Ed890F8b5fD57a45227d4a98"; 22 | const MULTISIG_TOKEN_ADDRESS = "0x945B7C7178abd5936DB09186B58A789D8308B876"; 23 | 24 | type SubmittedTx = { 25 | owner: string; 26 | txIndex: BigNumber; 27 | from: string; 28 | to: string; 29 | value: BigNumber; 30 | data: BytesLike; 31 | input: BytesLike; 32 | }; 33 | 34 | /** 35 | * Web3 Action responding to all events from the multisig contract. 36 | * It stores every submitted transaction in Action storage. 37 | * Other actions can use this to simulate the transaction later on. 38 | * 39 | * @param context the context of the action function (provided by Tenderly Web3 Actions Runtime) 40 | * @param event The Action Event that triggered this action function - TransactionEvent. 41 | * @returns nothing 42 | */ 43 | export const onAnEvent: ActionFn = async (context: Context, event: Event) => { 44 | const transactionEvent = event as TransactionEvent; 45 | 46 | // obtain the Event from TX 47 | let iface = new ethers.utils.Interface(MultisigAbi); 48 | const parsedLogs = transactionEvent.logs.map((log) => iface.parseLog(log)); 49 | const txEvent = parsedLogs[0]; 50 | 51 | // tracks all submitted transactions 52 | const submittedTxs = await loadSubmittedTransactions(context); 53 | 54 | switch (txEvent.name) { 55 | case "TxSubmission": 56 | const submitted = extractSubmittedTx(txEvent, transactionEvent); 57 | 58 | submittedTxs.txs.push(submitted); 59 | await context.storage.putJson(KEY_STORE_SUBMITTED_TX, submittedTxs); 60 | 61 | await simulateAndSendToDiscord(context, submitted); 62 | 63 | break; 64 | 65 | case "TxConfirmation": 66 | await toDiscord(context, "🛎", "confirmed by" + transactionEvent.from); 67 | break; 68 | 69 | case "ExecuteTransaction": 70 | const txIndex = txEvent.args.txIndex; 71 | console.log("TX Executed " + txIndex); 72 | submittedTxs.txs = submittedTxs.txs.filter( 73 | (tx) => tx.txIndex == txEvent.args.txIndex 74 | ); 75 | 76 | await context.storage.putJson(KEY_STORE_SUBMITTED_TX, submittedTxs); 77 | await toDiscord(context, "💸", "confirmed by" + transactionEvent.from); 78 | break; 79 | } 80 | return; 81 | }; 82 | 83 | /** 84 | * Simulates the transaction submitted to Multisig on the Tenderly fork. 85 | * @param submitted the submitted transaction to simulate 86 | * @returns difference in token balance prior and after the transaction 87 | */ 88 | async function simulateOnFork(context: Context, submitted: SubmittedTx) { 89 | // create a fork new fork with the freshest data from the network - using the DIY lib until an official SDK jumps in 90 | const myApi = tenderlyApi( 91 | TENDERLY_PROJECT_SLUG, 92 | TENDERLY_USERNAME, 93 | // TODO: add the TENDERLY_ACCESS_KEY to the secrets in the dashboard 94 | await context.secrets.get("TENDERLY_ACCESS_KEY") 95 | ); 96 | const fork = await myApi.aTenderlyFork({ network_id: "3" }); 97 | 98 | const ethersOnFork = fork.provider; // just grab the provider 99 | 100 | const tokenOnFork = new ethers.Contract( 101 | MULTISIG_TOKEN_ADDRESS, 102 | TokenAbi, 103 | ethersOnFork.getSigner() 104 | ); 105 | 106 | const initialBalance = (await tokenOnFork.balanceOf( 107 | MULTISIG_WALLET_ADDRESS 108 | )) as BigNumber; 109 | 110 | await ethersOnFork.send("tenderly_setBalance", [ 111 | [MULTISIG_WALLET_ADDRESS], 112 | ethers.utils.hexValue(ethers.utils.parseUnits("10", "ether")), 113 | ]); 114 | 115 | const tx = { 116 | to: submitted.to, 117 | from: MULTISIG_WALLET_ADDRESS, 118 | data: submitted.data, 119 | value: BigNumber.from(submitted.value).toHexString(), 120 | }; 121 | 122 | // send the TX 123 | await ethersOnFork.send("eth_sendTransaction", [tx]); 124 | 125 | const balance = (await tokenOnFork.balanceOf( 126 | MULTISIG_WALLET_ADDRESS 127 | )) as BigNumber; 128 | 129 | console.log("Balances", { initialBalance, balance }); 130 | 131 | await fork.removeFork(); // remove the fork. For debugging purposes leave it in place 132 | 133 | return { changeInBalance: balance.sub(initialBalance) }; 134 | } 135 | 136 | /** 137 | * Web-hook based Web3 Action 138 | * @param context 139 | * @param event 140 | */ 141 | export const onHook: ActionFn = async (context: Context, event: Event) => { 142 | const evt = event as WebhookEvent; 143 | await toDiscord( 144 | context, 145 | "🛎", 146 | `${evt.payload.who} is reminding you to check out TX with IDX ${evt.payload.txId}` 147 | ); 148 | }; 149 | 150 | /** 151 | * Periodic Web3 Action: daily simulation of unexecuted transactions 152 | * @param context 153 | * @param event 154 | */ 155 | export const everyDay: ActionFn = async (context: Context, _: Event) => { 156 | const stored = await loadSubmittedTransactions(context); 157 | await toDiscord(context, "🛎", "Daily simulations"); 158 | await Promise.all( 159 | stored.txs.map((tx) => simulateAndSendToDiscord(context, tx)) 160 | ); 161 | }; 162 | 163 | function extractSubmittedTx( 164 | txEvent: LogDescription, 165 | actionEvent: TransactionEvent 166 | ): SubmittedTx { 167 | return { 168 | owner: txEvent.args.owner, 169 | txIndex: txEvent.args.txIndex, 170 | from: txEvent.args.from, 171 | to: txEvent.args.to, 172 | value: txEvent.args.value, 173 | data: txEvent.args.data, 174 | input: actionEvent.input, 175 | }; 176 | } 177 | 178 | const simulateAndSendToDiscord = async ( 179 | context: Context, 180 | submitted: SubmittedTx 181 | ) => { 182 | const simRes = await simulateOnFork(context, submitted); 183 | 184 | await toDiscord( 185 | context, 186 | simRes.changeInBalance.gt(0) ? "🟢" : "🔴", 187 | `W3A simulated TX idx = ${submitted.txIndex.toString()}`, 188 | simRes 189 | ); 190 | }; 191 | 192 | async function loadSubmittedTransactions(context: Context) { 193 | const stored: { txs: SubmittedTx[] } = await context.storage.getJson( 194 | KEY_STORE_SUBMITTED_TX 195 | ); 196 | if (!stored.txs) { 197 | stored.txs = []; 198 | } 199 | return { 200 | txs: stored.txs.map((tx) => ({ 201 | ...tx, 202 | value: BigNumber.from(tx.value), 203 | txIndex: BigNumber.from(tx.txIndex), 204 | })), 205 | }; 206 | } 207 | 208 | async function toDiscord( 209 | context: Context, 210 | howItWent: "🟢" | "🔴" | "🛎" | "💸", 211 | activity: string, 212 | data?: any 213 | ) { 214 | const body = !!data ? "```" + JSON.stringify(data, null, 2) + "```" : ""; 215 | 216 | // TODO: add multisig.DISCORD_URL to the secrets in the dashboard 217 | const discordUrl = await context.secrets.get("multisig.DISCORD_URL"); 218 | console.log(discordUrl); // ********** 219 | await axios.post(discordUrl, { 220 | content: `${howItWent} ${activity} ${body}`, 221 | }); 222 | } 223 | -------------------------------------------------------------------------------- /multisig-wallet/web3-actions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "actions", 3 | "scripts": { 4 | "build": "tsc" 5 | }, 6 | "devDependencies": { 7 | "@tenderly/actions-test": "^0.0.9", 8 | "axios": "^0.27.2", 9 | "typescript": "^4.3.5" 10 | }, 11 | "dependencies": { 12 | "@tenderly/actions": "^0.0.7", 13 | "ethers": "^5.6.9" 14 | }, 15 | "private": true 16 | } 17 | -------------------------------------------------------------------------------- /multisig-wallet/web3-actions/tenderly-api.ts: -------------------------------------------------------------------------------- 1 | import { JsonRpcProvider } from "@ethersproject/providers"; 2 | import axios, { AxiosResponse } from "axios"; 3 | 4 | type TenderlyForkRequest = { 5 | block_number?: number; 6 | network_id: string; 7 | transaction_index?: number; 8 | initial_balance?: number; 9 | chain_config?: { 10 | chain_id: number; 11 | homestead_block: number; 12 | dao_fork_support: boolean; 13 | eip_150_block: number; 14 | eip_150_hash: string; 15 | eip_155_block: number; 16 | eip_158_block: number; 17 | byzantium_block: number; 18 | constantinople_block: number; 19 | petersburg_block: number; 20 | istanbul_block: number; 21 | berlin_block: number; 22 | }; 23 | }; 24 | 25 | export type TenderlyForkProvider = { 26 | provider: JsonRpcProvider; 27 | id: number; 28 | blockNumber: number; 29 | /** 30 | * map from address to given address' balance 31 | */ 32 | removeFork: () => Promise>; 33 | }; 34 | 35 | export const tenderlyApi = ( 36 | projectSlug: string, 37 | username: string, 38 | accessKey: string 39 | ) => { 40 | const somewhereInTenderly = (where: string = projectSlug || "") => 41 | axios.create({ 42 | baseURL: `https://api.tenderly.co/api/v1/${where}`, 43 | headers: { 44 | "X-Access-Key": accessKey || "", 45 | "Content-Type": "application/json", 46 | }, 47 | }); 48 | 49 | const inProject = (...path: any[]) => 50 | [`account/${username}/project/${projectSlug}`, ...path] 51 | .join("/") 52 | .replace("//", ""); 53 | 54 | const anAxiosOnTenderly = () => somewhereInTenderly(""); 55 | const axiosOnTenderly = anAxiosOnTenderly(); 56 | 57 | // todo: cache these poor axioses 58 | const axiosInProject = somewhereInTenderly(inProject()); 59 | 60 | const removeFork = async (forkId: string) => { 61 | console.log("Removing test fork", forkId); 62 | return await axiosOnTenderly.delete(inProject(`fork/${forkId}`)); 63 | }; 64 | 65 | async function aTenderlyFork( 66 | fork: TenderlyForkRequest 67 | ): Promise { 68 | const forkResponse = await axiosInProject.post(`/fork`, fork); 69 | const forkId = forkResponse.data.root_transaction.fork_id; 70 | 71 | const forkProviderUrl = `https://rpc.tenderly.co/fork/${forkId}`; 72 | const forkProvider = new JsonRpcProvider(forkProviderUrl); 73 | 74 | const bn = ( 75 | forkResponse.data.root_transaction.receipt.blockNumber as string 76 | ).replace("0x", ""); 77 | const blockNumber: number = Number.parseInt(bn, 16); 78 | 79 | console.info( 80 | `\nForked with fork id ${forkId} at block number ${blockNumber}` 81 | ); 82 | 83 | console.info(`https://dashboard.tenderly.co/${inProject("fork", forkId)}`); 84 | 85 | console.info("JSON-RPC:", forkProviderUrl); 86 | 87 | return { 88 | provider: forkProvider, 89 | blockNumber, 90 | id: forkId, 91 | removeFork: () => removeFork(forkId), 92 | }; 93 | } 94 | 95 | return { 96 | aTenderlyFork, 97 | }; 98 | }; 99 | -------------------------------------------------------------------------------- /multisig-wallet/web3-actions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "esModuleInterop": true, 5 | "module": "commonjs", 6 | "noImplicitReturns": true, 7 | "noUnusedLocals": false, 8 | "outDir": "out", 9 | "resolveJsonModule": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "target": "es2020", 13 | "rootDir": "" 14 | }, 15 | "exclude": [ 16 | "actions" 17 | ], 18 | "include": [ 19 | "**/*" 20 | ] 21 | } -------------------------------------------------------------------------------- /simple-coin-oracle/CoinOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.14; 3 | 4 | contract CoinOracle { 5 | address private owner; 6 | mapping(string => uint256) private coinPrices; 7 | mapping(uint256 => SimpleCoinConsumer) public consumers; 8 | /** W3A listens to this event and then pushes the info */ 9 | event RequestCoinPrice(string name, address consumer, uint256 reqId); 10 | 11 | constructor() { 12 | owner = msg.sender; 13 | } 14 | 15 | function requestPrice(string memory _name, uint256 _reqId) public payable { 16 | consumers[_reqId] = SimpleCoinConsumer(msg.sender); 17 | emit RequestCoinPrice(_name, msg.sender, _reqId); 18 | } 19 | 20 | function update(uint256 _reqId, uint256 priceInCents) public onlyOwner { 21 | consumers[_reqId].coinPrice(priceInCents, _reqId); 22 | } 23 | 24 | modifier onlyOwner() { 25 | require(msg.sender == owner, "owner only"); 26 | _; 27 | } 28 | } 29 | 30 | contract SimpleCoinConsumer { 31 | CoinOracle private coinOracle; 32 | uint public nonce = 0; 33 | 34 | struct ConvertRequest { 35 | string coin; 36 | uint amount; 37 | } 38 | 39 | mapping(uint => ConvertRequest) public amountsToConvert; 40 | 41 | constructor(address _coinOracleAddress) { 42 | coinOracle = CoinOracle(_coinOracleAddress); 43 | } 44 | 45 | function doCleverStuff(string memory coin, uint amountInCents) public { 46 | //... 47 | nonce += 1; 48 | coinOracle.requestPrice(coin, nonce); 49 | amountsToConvert[nonce] = ConvertRequest(coin, amountInCents); 50 | //... 51 | } 52 | 53 | modifier onlyOracle() { 54 | require(msg.sender == address(coinOracle), "only the code oracle"); 55 | _; 56 | } 57 | 58 | function coinPrice(uint _priceInCents, uint _nonce) external onlyOracle { 59 | ConvertRequest memory req = amountsToConvert[_nonce]; 60 | delete amountsToConvert[_nonce]; 61 | emit Exchange(req.coin, req.amount, req.amount * _priceInCents); 62 | } 63 | 64 | event Exchange(string coin, uint amountInCents, uint amountInCoins); 65 | } 66 | -------------------------------------------------------------------------------- /simple-coin-oracle/README.md: -------------------------------------------------------------------------------- 1 | # How to Build Your Own Oracle Using Web3 Actions? 2 | 3 | ## Running the examples 4 | To run these examples which deploy Web3 Actions, you need to have Tenderly CLI. 5 | 6 | ## Install Tenderly CLI 7 | If you haven't already, install [Tenderly CLI](https://github.com/Tenderly/tenderly-cli#installation). 8 | 9 | Before you go on, you need to login with CLI, using your Tenderly credentials: 10 | 11 | ```bash 12 | tenderly login 13 | ``` 14 | 15 | ### Install dependencies for Web3 Actions project: 16 | 17 | First `cd` into an example you're interested in and `cd actions`. Then run `npm i`: 18 | 19 | ``` bash 20 | npm install 21 | ``` 22 | 23 | ### Build and Run 24 | 25 | To build/deploy the actions, `cd` into example you're interested in and then run the CLI. 26 | 27 | ``` bash 28 | tenderly build # local build only 29 | tenderly deploy # deploys and activates the action 30 | ``` -------------------------------------------------------------------------------- /simple-coin-oracle/actions/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directories 2 | node_modules/ 3 | 4 | # Ignore tsc output 5 | out/**/* 6 | -------------------------------------------------------------------------------- /simple-coin-oracle/actions/coinOracle.ts: -------------------------------------------------------------------------------- 1 | import { ActionFn, Context, Event, TransactionEvent } from "@tenderly/actions"; 2 | 3 | import { ethers } from "ethers"; 4 | import axios from "axios"; 5 | import CoinOracleContract from "./CoinOracleContract.json"; 6 | 7 | const CONTRACT_ADDRESS = "0x15aa485ba52ddb7ceb269b2090e45b6a0c42cc5f"; 8 | 9 | export const coinPrice: ActionFn = async (context: Context, event: Event) => { 10 | let transactionEvent = event as TransactionEvent; 11 | 12 | const ifc = new ethers.utils.Interface(CoinOracleContract.abi); 13 | 14 | const { data, topics } = transactionEvent.logs[0]; 15 | const priceRequest = ifc.decodeEventLog("RequestCoinPrice", data, topics); 16 | const price = await getPrice(priceRequest.name); 17 | 18 | const oc = await oracleContract(context, ifc); 19 | 20 | await oc.update(priceRequest.reqId, price, { 21 | gasLimit: 250000, 22 | gasPrice: ethers.utils.parseUnits("100", "gwei"), 23 | }); 24 | console.log( 25 | `Processed: ${priceRequest.reqId} with price in cents: ${price}` 26 | ); 27 | }; 28 | 29 | const getPrice = async (coin: string) => { 30 | const coinInfo = await axios.get( 31 | `https://api.coingecko.com/api/v3/coins/${coin}` 32 | ); 33 | return coinInfo.data.market_data.current_price.usd * 100; 34 | }; 35 | 36 | const oracleContract = async ( 37 | context: Context, 38 | contractInterface: ethers.utils.Interface 39 | ) => { 40 | const etherscanApiKey = await context.secrets.get("oracle.providerApiKey"); 41 | 42 | const provider = ethers.getDefaultProvider(ethers.providers.getNetwork(3), { 43 | etherscan: etherscanApiKey, 44 | }); 45 | 46 | const oracleWallet = new ethers.Wallet( 47 | await context.secrets.get("oracle.addressPrivateKey"), 48 | provider 49 | ); 50 | 51 | const contract = new ethers.Contract( 52 | CONTRACT_ADDRESS, 53 | contractInterface, 54 | oracleWallet 55 | ); 56 | return contract; 57 | }; 58 | -------------------------------------------------------------------------------- /simple-coin-oracle/actions/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "actions", 3 | "requires": true, 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "@ethersproject/abi": { 7 | "version": "5.6.3", 8 | "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.6.3.tgz", 9 | "integrity": "sha512-CxKTdoZY4zDJLWXG6HzNH6znWK0M79WzzxHegDoecE3+K32pzfHOzuXg2/oGSTecZynFgpkjYXNPOqXVJlqClw==", 10 | "dev": true, 11 | "requires": { 12 | "@ethersproject/address": "^5.6.1", 13 | "@ethersproject/bignumber": "^5.6.2", 14 | "@ethersproject/bytes": "^5.6.1", 15 | "@ethersproject/constants": "^5.6.1", 16 | "@ethersproject/hash": "^5.6.1", 17 | "@ethersproject/keccak256": "^5.6.1", 18 | "@ethersproject/logger": "^5.6.0", 19 | "@ethersproject/properties": "^5.6.0", 20 | "@ethersproject/strings": "^5.6.1" 21 | } 22 | }, 23 | "@ethersproject/abstract-provider": { 24 | "version": "5.6.1", 25 | "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.6.1.tgz", 26 | "integrity": "sha512-BxlIgogYJtp1FS8Muvj8YfdClk3unZH0vRMVX791Z9INBNT/kuACZ9GzaY1Y4yFq+YSy6/w4gzj3HCRKrK9hsQ==", 27 | "dev": true, 28 | "requires": { 29 | "@ethersproject/bignumber": "^5.6.2", 30 | "@ethersproject/bytes": "^5.6.1", 31 | "@ethersproject/logger": "^5.6.0", 32 | "@ethersproject/networks": "^5.6.3", 33 | "@ethersproject/properties": "^5.6.0", 34 | "@ethersproject/transactions": "^5.6.2", 35 | "@ethersproject/web": "^5.6.1" 36 | } 37 | }, 38 | "@ethersproject/abstract-signer": { 39 | "version": "5.6.2", 40 | "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.6.2.tgz", 41 | "integrity": "sha512-n1r6lttFBG0t2vNiI3HoWaS/KdOt8xyDjzlP2cuevlWLG6EX0OwcKLyG/Kp/cuwNxdy/ous+R/DEMdTUwWQIjQ==", 42 | "dev": true, 43 | "requires": { 44 | "@ethersproject/abstract-provider": "^5.6.1", 45 | "@ethersproject/bignumber": "^5.6.2", 46 | "@ethersproject/bytes": "^5.6.1", 47 | "@ethersproject/logger": "^5.6.0", 48 | "@ethersproject/properties": "^5.6.0" 49 | } 50 | }, 51 | "@ethersproject/address": { 52 | "version": "5.6.1", 53 | "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.6.1.tgz", 54 | "integrity": "sha512-uOgF0kS5MJv9ZvCz7x6T2EXJSzotiybApn4XlOgoTX0xdtyVIJ7pF+6cGPxiEq/dpBiTfMiw7Yc81JcwhSYA0Q==", 55 | "dev": true, 56 | "requires": { 57 | "@ethersproject/bignumber": "^5.6.2", 58 | "@ethersproject/bytes": "^5.6.1", 59 | "@ethersproject/keccak256": "^5.6.1", 60 | "@ethersproject/logger": "^5.6.0", 61 | "@ethersproject/rlp": "^5.6.1" 62 | } 63 | }, 64 | "@ethersproject/base64": { 65 | "version": "5.6.1", 66 | "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.6.1.tgz", 67 | "integrity": "sha512-qB76rjop6a0RIYYMiB4Eh/8n+Hxu2NIZm8S/Q7kNo5pmZfXhHGHmS4MinUainiBC54SCyRnwzL+KZjj8zbsSsw==", 68 | "dev": true, 69 | "requires": { 70 | "@ethersproject/bytes": "^5.6.1" 71 | } 72 | }, 73 | "@ethersproject/basex": { 74 | "version": "5.6.1", 75 | "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.6.1.tgz", 76 | "integrity": "sha512-a52MkVz4vuBXR06nvflPMotld1FJWSj2QT0985v7P/emPZO00PucFAkbcmq2vpVU7Ts7umKiSI6SppiLykVWsA==", 77 | "dev": true, 78 | "requires": { 79 | "@ethersproject/bytes": "^5.6.1", 80 | "@ethersproject/properties": "^5.6.0" 81 | } 82 | }, 83 | "@ethersproject/bignumber": { 84 | "version": "5.6.2", 85 | "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.6.2.tgz", 86 | "integrity": "sha512-v7+EEUbhGqT3XJ9LMPsKvXYHFc8eHxTowFCG/HgJErmq4XHJ2WR7aeyICg3uTOAQ7Icn0GFHAohXEhxQHq4Ubw==", 87 | "dev": true, 88 | "requires": { 89 | "@ethersproject/bytes": "^5.6.1", 90 | "@ethersproject/logger": "^5.6.0", 91 | "bn.js": "^5.2.1" 92 | } 93 | }, 94 | "@ethersproject/bytes": { 95 | "version": "5.6.1", 96 | "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.6.1.tgz", 97 | "integrity": "sha512-NwQt7cKn5+ZE4uDn+X5RAXLp46E1chXoaMmrxAyA0rblpxz8t58lVkrHXoRIn0lz1joQElQ8410GqhTqMOwc6g==", 98 | "dev": true, 99 | "requires": { 100 | "@ethersproject/logger": "^5.6.0" 101 | } 102 | }, 103 | "@ethersproject/constants": { 104 | "version": "5.6.1", 105 | "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.6.1.tgz", 106 | "integrity": "sha512-QSq9WVnZbxXYFftrjSjZDUshp6/eKp6qrtdBtUCm0QxCV5z1fG/w3kdlcsjMCQuQHUnAclKoK7XpXMezhRDOLg==", 107 | "dev": true, 108 | "requires": { 109 | "@ethersproject/bignumber": "^5.6.2" 110 | } 111 | }, 112 | "@ethersproject/contracts": { 113 | "version": "5.6.2", 114 | "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.6.2.tgz", 115 | "integrity": "sha512-hguUA57BIKi6WY0kHvZp6PwPlWF87MCeB4B7Z7AbUpTxfFXFdn/3b0GmjZPagIHS+3yhcBJDnuEfU4Xz+Ks/8g==", 116 | "dev": true, 117 | "requires": { 118 | "@ethersproject/abi": "^5.6.3", 119 | "@ethersproject/abstract-provider": "^5.6.1", 120 | "@ethersproject/abstract-signer": "^5.6.2", 121 | "@ethersproject/address": "^5.6.1", 122 | "@ethersproject/bignumber": "^5.6.2", 123 | "@ethersproject/bytes": "^5.6.1", 124 | "@ethersproject/constants": "^5.6.1", 125 | "@ethersproject/logger": "^5.6.0", 126 | "@ethersproject/properties": "^5.6.0", 127 | "@ethersproject/transactions": "^5.6.2" 128 | } 129 | }, 130 | "@ethersproject/hash": { 131 | "version": "5.6.1", 132 | "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.6.1.tgz", 133 | "integrity": "sha512-L1xAHurbaxG8VVul4ankNX5HgQ8PNCTrnVXEiFnE9xoRnaUcgfD12tZINtDinSllxPLCtGwguQxJ5E6keE84pA==", 134 | "dev": true, 135 | "requires": { 136 | "@ethersproject/abstract-signer": "^5.6.2", 137 | "@ethersproject/address": "^5.6.1", 138 | "@ethersproject/bignumber": "^5.6.2", 139 | "@ethersproject/bytes": "^5.6.1", 140 | "@ethersproject/keccak256": "^5.6.1", 141 | "@ethersproject/logger": "^5.6.0", 142 | "@ethersproject/properties": "^5.6.0", 143 | "@ethersproject/strings": "^5.6.1" 144 | } 145 | }, 146 | "@ethersproject/hdnode": { 147 | "version": "5.6.2", 148 | "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.6.2.tgz", 149 | "integrity": "sha512-tERxW8Ccf9CxW2db3WsN01Qao3wFeRsfYY9TCuhmG0xNpl2IO8wgXU3HtWIZ49gUWPggRy4Yg5axU0ACaEKf1Q==", 150 | "dev": true, 151 | "requires": { 152 | "@ethersproject/abstract-signer": "^5.6.2", 153 | "@ethersproject/basex": "^5.6.1", 154 | "@ethersproject/bignumber": "^5.6.2", 155 | "@ethersproject/bytes": "^5.6.1", 156 | "@ethersproject/logger": "^5.6.0", 157 | "@ethersproject/pbkdf2": "^5.6.1", 158 | "@ethersproject/properties": "^5.6.0", 159 | "@ethersproject/sha2": "^5.6.1", 160 | "@ethersproject/signing-key": "^5.6.2", 161 | "@ethersproject/strings": "^5.6.1", 162 | "@ethersproject/transactions": "^5.6.2", 163 | "@ethersproject/wordlists": "^5.6.1" 164 | } 165 | }, 166 | "@ethersproject/json-wallets": { 167 | "version": "5.6.1", 168 | "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.6.1.tgz", 169 | "integrity": "sha512-KfyJ6Zwz3kGeX25nLihPwZYlDqamO6pfGKNnVMWWfEVVp42lTfCZVXXy5Ie8IZTN0HKwAngpIPi7gk4IJzgmqQ==", 170 | "dev": true, 171 | "requires": { 172 | "@ethersproject/abstract-signer": "^5.6.2", 173 | "@ethersproject/address": "^5.6.1", 174 | "@ethersproject/bytes": "^5.6.1", 175 | "@ethersproject/hdnode": "^5.6.2", 176 | "@ethersproject/keccak256": "^5.6.1", 177 | "@ethersproject/logger": "^5.6.0", 178 | "@ethersproject/pbkdf2": "^5.6.1", 179 | "@ethersproject/properties": "^5.6.0", 180 | "@ethersproject/random": "^5.6.1", 181 | "@ethersproject/strings": "^5.6.1", 182 | "@ethersproject/transactions": "^5.6.2", 183 | "aes-js": "3.0.0", 184 | "scrypt-js": "3.0.1" 185 | } 186 | }, 187 | "@ethersproject/keccak256": { 188 | "version": "5.6.1", 189 | "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.6.1.tgz", 190 | "integrity": "sha512-bB7DQHCTRDooZZdL3lk9wpL0+XuG3XLGHLh3cePnybsO3V0rdCAOQGpn/0R3aODmnTOOkCATJiD2hnL+5bwthA==", 191 | "dev": true, 192 | "requires": { 193 | "@ethersproject/bytes": "^5.6.1", 194 | "js-sha3": "0.8.0" 195 | } 196 | }, 197 | "@ethersproject/logger": { 198 | "version": "5.6.0", 199 | "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.6.0.tgz", 200 | "integrity": "sha512-BiBWllUROH9w+P21RzoxJKzqoqpkyM1pRnEKG69bulE9TSQD8SAIvTQqIMZmmCO8pUNkgLP1wndX1gKghSpBmg==", 201 | "dev": true 202 | }, 203 | "@ethersproject/networks": { 204 | "version": "5.6.3", 205 | "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.6.3.tgz", 206 | "integrity": "sha512-QZxRH7cA5Ut9TbXwZFiCyuPchdWi87ZtVNHWZd0R6YFgYtes2jQ3+bsslJ0WdyDe0i6QumqtoYqvY3rrQFRZOQ==", 207 | "dev": true, 208 | "requires": { 209 | "@ethersproject/logger": "^5.6.0" 210 | } 211 | }, 212 | "@ethersproject/pbkdf2": { 213 | "version": "5.6.1", 214 | "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.6.1.tgz", 215 | "integrity": "sha512-k4gRQ+D93zDRPNUfmduNKq065uadC2YjMP/CqwwX5qG6R05f47boq6pLZtV/RnC4NZAYOPH1Cyo54q0c9sshRQ==", 216 | "dev": true, 217 | "requires": { 218 | "@ethersproject/bytes": "^5.6.1", 219 | "@ethersproject/sha2": "^5.6.1" 220 | } 221 | }, 222 | "@ethersproject/properties": { 223 | "version": "5.6.0", 224 | "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.6.0.tgz", 225 | "integrity": "sha512-szoOkHskajKePTJSZ46uHUWWkbv7TzP2ypdEK6jGMqJaEt2sb0jCgfBo0gH0m2HBpRixMuJ6TBRaQCF7a9DoCg==", 226 | "dev": true, 227 | "requires": { 228 | "@ethersproject/logger": "^5.6.0" 229 | } 230 | }, 231 | "@ethersproject/providers": { 232 | "version": "5.6.8", 233 | "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.6.8.tgz", 234 | "integrity": "sha512-Wf+CseT/iOJjrGtAOf3ck9zS7AgPmr2fZ3N97r4+YXN3mBePTG2/bJ8DApl9mVwYL+RpYbNxMEkEp4mPGdwG/w==", 235 | "dev": true, 236 | "requires": { 237 | "@ethersproject/abstract-provider": "^5.6.1", 238 | "@ethersproject/abstract-signer": "^5.6.2", 239 | "@ethersproject/address": "^5.6.1", 240 | "@ethersproject/base64": "^5.6.1", 241 | "@ethersproject/basex": "^5.6.1", 242 | "@ethersproject/bignumber": "^5.6.2", 243 | "@ethersproject/bytes": "^5.6.1", 244 | "@ethersproject/constants": "^5.6.1", 245 | "@ethersproject/hash": "^5.6.1", 246 | "@ethersproject/logger": "^5.6.0", 247 | "@ethersproject/networks": "^5.6.3", 248 | "@ethersproject/properties": "^5.6.0", 249 | "@ethersproject/random": "^5.6.1", 250 | "@ethersproject/rlp": "^5.6.1", 251 | "@ethersproject/sha2": "^5.6.1", 252 | "@ethersproject/strings": "^5.6.1", 253 | "@ethersproject/transactions": "^5.6.2", 254 | "@ethersproject/web": "^5.6.1", 255 | "bech32": "1.1.4", 256 | "ws": "7.4.6" 257 | } 258 | }, 259 | "@ethersproject/random": { 260 | "version": "5.6.1", 261 | "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.6.1.tgz", 262 | "integrity": "sha512-/wtPNHwbmng+5yi3fkipA8YBT59DdkGRoC2vWk09Dci/q5DlgnMkhIycjHlavrvrjJBkFjO/ueLyT+aUDfc4lA==", 263 | "dev": true, 264 | "requires": { 265 | "@ethersproject/bytes": "^5.6.1", 266 | "@ethersproject/logger": "^5.6.0" 267 | } 268 | }, 269 | "@ethersproject/rlp": { 270 | "version": "5.6.1", 271 | "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.6.1.tgz", 272 | "integrity": "sha512-uYjmcZx+DKlFUk7a5/W9aQVaoEC7+1MOBgNtvNg13+RnuUwT4F0zTovC0tmay5SmRslb29V1B7Y5KCri46WhuQ==", 273 | "dev": true, 274 | "requires": { 275 | "@ethersproject/bytes": "^5.6.1", 276 | "@ethersproject/logger": "^5.6.0" 277 | } 278 | }, 279 | "@ethersproject/sha2": { 280 | "version": "5.6.1", 281 | "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.6.1.tgz", 282 | "integrity": "sha512-5K2GyqcW7G4Yo3uenHegbXRPDgARpWUiXc6RiF7b6i/HXUoWlb7uCARh7BAHg7/qT/Q5ydofNwiZcim9qpjB6g==", 283 | "dev": true, 284 | "requires": { 285 | "@ethersproject/bytes": "^5.6.1", 286 | "@ethersproject/logger": "^5.6.0", 287 | "hash.js": "1.1.7" 288 | } 289 | }, 290 | "@ethersproject/signing-key": { 291 | "version": "5.6.2", 292 | "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.6.2.tgz", 293 | "integrity": "sha512-jVbu0RuP7EFpw82vHcL+GP35+KaNruVAZM90GxgQnGqB6crhBqW/ozBfFvdeImtmb4qPko0uxXjn8l9jpn0cwQ==", 294 | "dev": true, 295 | "requires": { 296 | "@ethersproject/bytes": "^5.6.1", 297 | "@ethersproject/logger": "^5.6.0", 298 | "@ethersproject/properties": "^5.6.0", 299 | "bn.js": "^5.2.1", 300 | "elliptic": "6.5.4", 301 | "hash.js": "1.1.7" 302 | } 303 | }, 304 | "@ethersproject/solidity": { 305 | "version": "5.6.1", 306 | "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.6.1.tgz", 307 | "integrity": "sha512-KWqVLkUUoLBfL1iwdzUVlkNqAUIFMpbbeH0rgCfKmJp0vFtY4AsaN91gHKo9ZZLkC4UOm3cI3BmMV4N53BOq4g==", 308 | "dev": true, 309 | "requires": { 310 | "@ethersproject/bignumber": "^5.6.2", 311 | "@ethersproject/bytes": "^5.6.1", 312 | "@ethersproject/keccak256": "^5.6.1", 313 | "@ethersproject/logger": "^5.6.0", 314 | "@ethersproject/sha2": "^5.6.1", 315 | "@ethersproject/strings": "^5.6.1" 316 | } 317 | }, 318 | "@ethersproject/strings": { 319 | "version": "5.6.1", 320 | "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.6.1.tgz", 321 | "integrity": "sha512-2X1Lgk6Jyfg26MUnsHiT456U9ijxKUybz8IM1Vih+NJxYtXhmvKBcHOmvGqpFSVJ0nQ4ZCoIViR8XlRw1v/+Cw==", 322 | "dev": true, 323 | "requires": { 324 | "@ethersproject/bytes": "^5.6.1", 325 | "@ethersproject/constants": "^5.6.1", 326 | "@ethersproject/logger": "^5.6.0" 327 | } 328 | }, 329 | "@ethersproject/transactions": { 330 | "version": "5.6.2", 331 | "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.6.2.tgz", 332 | "integrity": "sha512-BuV63IRPHmJvthNkkt9G70Ullx6AcM+SDc+a8Aw/8Yew6YwT51TcBKEp1P4oOQ/bP25I18JJr7rcFRgFtU9B2Q==", 333 | "dev": true, 334 | "requires": { 335 | "@ethersproject/address": "^5.6.1", 336 | "@ethersproject/bignumber": "^5.6.2", 337 | "@ethersproject/bytes": "^5.6.1", 338 | "@ethersproject/constants": "^5.6.1", 339 | "@ethersproject/keccak256": "^5.6.1", 340 | "@ethersproject/logger": "^5.6.0", 341 | "@ethersproject/properties": "^5.6.0", 342 | "@ethersproject/rlp": "^5.6.1", 343 | "@ethersproject/signing-key": "^5.6.2" 344 | } 345 | }, 346 | "@ethersproject/units": { 347 | "version": "5.6.1", 348 | "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.6.1.tgz", 349 | "integrity": "sha512-rEfSEvMQ7obcx3KWD5EWWx77gqv54K6BKiZzKxkQJqtpriVsICrktIQmKl8ReNToPeIYPnFHpXvKpi068YFZXw==", 350 | "dev": true, 351 | "requires": { 352 | "@ethersproject/bignumber": "^5.6.2", 353 | "@ethersproject/constants": "^5.6.1", 354 | "@ethersproject/logger": "^5.6.0" 355 | } 356 | }, 357 | "@ethersproject/wallet": { 358 | "version": "5.6.2", 359 | "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.6.2.tgz", 360 | "integrity": "sha512-lrgh0FDQPuOnHcF80Q3gHYsSUODp6aJLAdDmDV0xKCN/T7D99ta1jGVhulg3PY8wiXEngD0DfM0I2XKXlrqJfg==", 361 | "dev": true, 362 | "requires": { 363 | "@ethersproject/abstract-provider": "^5.6.1", 364 | "@ethersproject/abstract-signer": "^5.6.2", 365 | "@ethersproject/address": "^5.6.1", 366 | "@ethersproject/bignumber": "^5.6.2", 367 | "@ethersproject/bytes": "^5.6.1", 368 | "@ethersproject/hash": "^5.6.1", 369 | "@ethersproject/hdnode": "^5.6.2", 370 | "@ethersproject/json-wallets": "^5.6.1", 371 | "@ethersproject/keccak256": "^5.6.1", 372 | "@ethersproject/logger": "^5.6.0", 373 | "@ethersproject/properties": "^5.6.0", 374 | "@ethersproject/random": "^5.6.1", 375 | "@ethersproject/signing-key": "^5.6.2", 376 | "@ethersproject/transactions": "^5.6.2", 377 | "@ethersproject/wordlists": "^5.6.1" 378 | } 379 | }, 380 | "@ethersproject/web": { 381 | "version": "5.6.1", 382 | "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.6.1.tgz", 383 | "integrity": "sha512-/vSyzaQlNXkO1WV+RneYKqCJwualcUdx/Z3gseVovZP0wIlOFcCE1hkRhKBH8ImKbGQbMl9EAAyJFrJu7V0aqA==", 384 | "dev": true, 385 | "requires": { 386 | "@ethersproject/base64": "^5.6.1", 387 | "@ethersproject/bytes": "^5.6.1", 388 | "@ethersproject/logger": "^5.6.0", 389 | "@ethersproject/properties": "^5.6.0", 390 | "@ethersproject/strings": "^5.6.1" 391 | } 392 | }, 393 | "@ethersproject/wordlists": { 394 | "version": "5.6.1", 395 | "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.6.1.tgz", 396 | "integrity": "sha512-wiPRgBpNbNwCQFoCr8bcWO8o5I810cqO6mkdtKfLKFlLxeCWcnzDi4Alu8iyNzlhYuS9npCwivMbRWF19dyblw==", 397 | "dev": true, 398 | "requires": { 399 | "@ethersproject/bytes": "^5.6.1", 400 | "@ethersproject/hash": "^5.6.1", 401 | "@ethersproject/logger": "^5.6.0", 402 | "@ethersproject/properties": "^5.6.0", 403 | "@ethersproject/strings": "^5.6.1" 404 | } 405 | }, 406 | "@tenderly/actions": { 407 | "version": "0.0.7", 408 | "resolved": "https://registry.npmjs.org/@tenderly/actions/-/actions-0.0.7.tgz", 409 | "integrity": "sha512-wCEG80wEs8ONy0TAl7pohFV7KUUJU/3nAnBO9H6YGk6lN+tTJscNZ7n8umd5nisIR4HZ/mXiotYfbenO+KaiDQ==" 410 | }, 411 | "@types/node": { 412 | "version": "17.0.38", 413 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.38.tgz", 414 | "integrity": "sha512-5jY9RhV7c0Z4Jy09G+NIDTsCZ5G0L5n+Z+p+Y7t5VJHM30bgwzSjVtlcBxqAj+6L/swIlvtOSzr8rBk/aNyV2g==", 415 | "dev": true 416 | }, 417 | "aes-js": { 418 | "version": "3.0.0", 419 | "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", 420 | "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", 421 | "dev": true 422 | }, 423 | "asynckit": { 424 | "version": "0.4.0", 425 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 426 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", 427 | "dev": true 428 | }, 429 | "axios": { 430 | "version": "0.27.2", 431 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", 432 | "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", 433 | "dev": true, 434 | "requires": { 435 | "follow-redirects": "^1.14.9", 436 | "form-data": "^4.0.0" 437 | } 438 | }, 439 | "bech32": { 440 | "version": "1.1.4", 441 | "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", 442 | "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", 443 | "dev": true 444 | }, 445 | "bn.js": { 446 | "version": "5.2.1", 447 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", 448 | "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", 449 | "dev": true 450 | }, 451 | "brorand": { 452 | "version": "1.1.0", 453 | "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", 454 | "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", 455 | "dev": true 456 | }, 457 | "combined-stream": { 458 | "version": "1.0.8", 459 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 460 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 461 | "dev": true, 462 | "requires": { 463 | "delayed-stream": "~1.0.0" 464 | } 465 | }, 466 | "delayed-stream": { 467 | "version": "1.0.0", 468 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 469 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 470 | "dev": true 471 | }, 472 | "elliptic": { 473 | "version": "6.5.4", 474 | "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", 475 | "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", 476 | "dev": true, 477 | "requires": { 478 | "bn.js": "^4.11.9", 479 | "brorand": "^1.1.0", 480 | "hash.js": "^1.0.0", 481 | "hmac-drbg": "^1.0.1", 482 | "inherits": "^2.0.4", 483 | "minimalistic-assert": "^1.0.1", 484 | "minimalistic-crypto-utils": "^1.0.1" 485 | }, 486 | "dependencies": { 487 | "bn.js": { 488 | "version": "4.12.0", 489 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", 490 | "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", 491 | "dev": true 492 | } 493 | } 494 | }, 495 | "ethers": { 496 | "version": "5.6.8", 497 | "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.6.8.tgz", 498 | "integrity": "sha512-YxIGaltAOdvBFPZwIkyHnXbW40f1r8mHUgapW6dxkO+6t7H6wY8POUn0Kbxrd/N7I4hHxyi7YCddMAH/wmho2w==", 499 | "dev": true, 500 | "requires": { 501 | "@ethersproject/abi": "5.6.3", 502 | "@ethersproject/abstract-provider": "5.6.1", 503 | "@ethersproject/abstract-signer": "5.6.2", 504 | "@ethersproject/address": "5.6.1", 505 | "@ethersproject/base64": "5.6.1", 506 | "@ethersproject/basex": "5.6.1", 507 | "@ethersproject/bignumber": "5.6.2", 508 | "@ethersproject/bytes": "5.6.1", 509 | "@ethersproject/constants": "5.6.1", 510 | "@ethersproject/contracts": "5.6.2", 511 | "@ethersproject/hash": "5.6.1", 512 | "@ethersproject/hdnode": "5.6.2", 513 | "@ethersproject/json-wallets": "5.6.1", 514 | "@ethersproject/keccak256": "5.6.1", 515 | "@ethersproject/logger": "5.6.0", 516 | "@ethersproject/networks": "5.6.3", 517 | "@ethersproject/pbkdf2": "5.6.1", 518 | "@ethersproject/properties": "5.6.0", 519 | "@ethersproject/providers": "5.6.8", 520 | "@ethersproject/random": "5.6.1", 521 | "@ethersproject/rlp": "5.6.1", 522 | "@ethersproject/sha2": "5.6.1", 523 | "@ethersproject/signing-key": "5.6.2", 524 | "@ethersproject/solidity": "5.6.1", 525 | "@ethersproject/strings": "5.6.1", 526 | "@ethersproject/transactions": "5.6.2", 527 | "@ethersproject/units": "5.6.1", 528 | "@ethersproject/wallet": "5.6.2", 529 | "@ethersproject/web": "5.6.1", 530 | "@ethersproject/wordlists": "5.6.1" 531 | } 532 | }, 533 | "follow-redirects": { 534 | "version": "1.15.1", 535 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", 536 | "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", 537 | "dev": true 538 | }, 539 | "form-data": { 540 | "version": "4.0.0", 541 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 542 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 543 | "dev": true, 544 | "requires": { 545 | "asynckit": "^0.4.0", 546 | "combined-stream": "^1.0.8", 547 | "mime-types": "^2.1.12" 548 | } 549 | }, 550 | "hash.js": { 551 | "version": "1.1.7", 552 | "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", 553 | "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", 554 | "dev": true, 555 | "requires": { 556 | "inherits": "^2.0.3", 557 | "minimalistic-assert": "^1.0.1" 558 | } 559 | }, 560 | "hmac-drbg": { 561 | "version": "1.0.1", 562 | "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", 563 | "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", 564 | "dev": true, 565 | "requires": { 566 | "hash.js": "^1.0.3", 567 | "minimalistic-assert": "^1.0.0", 568 | "minimalistic-crypto-utils": "^1.0.1" 569 | } 570 | }, 571 | "inherits": { 572 | "version": "2.0.4", 573 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 574 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 575 | "dev": true 576 | }, 577 | "js-sha3": { 578 | "version": "0.8.0", 579 | "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", 580 | "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", 581 | "dev": true 582 | }, 583 | "mime-db": { 584 | "version": "1.52.0", 585 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 586 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 587 | "dev": true 588 | }, 589 | "mime-types": { 590 | "version": "2.1.35", 591 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 592 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 593 | "dev": true, 594 | "requires": { 595 | "mime-db": "1.52.0" 596 | } 597 | }, 598 | "minimalistic-assert": { 599 | "version": "1.0.1", 600 | "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", 601 | "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", 602 | "dev": true 603 | }, 604 | "minimalistic-crypto-utils": { 605 | "version": "1.0.1", 606 | "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", 607 | "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", 608 | "dev": true 609 | }, 610 | "scrypt-js": { 611 | "version": "3.0.1", 612 | "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", 613 | "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", 614 | "dev": true 615 | }, 616 | "typescript": { 617 | "version": "4.7.2", 618 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.2.tgz", 619 | "integrity": "sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==", 620 | "dev": true 621 | }, 622 | "ws": { 623 | "version": "7.4.6", 624 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", 625 | "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", 626 | "dev": true 627 | } 628 | } 629 | } 630 | -------------------------------------------------------------------------------- /simple-coin-oracle/actions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "actions", 3 | "scripts": { 4 | "build": "tsc" 5 | }, 6 | "devDependencies": { 7 | "@types/node": "^17.0.38", 8 | "typescript": "^4.3.5", 9 | "axios": "^0.27.2", 10 | "ethers": "^5.6.8" 11 | }, 12 | "dependencies": { 13 | "@tenderly/actions": "^0.0.7" 14 | }, 15 | "private": true 16 | } 17 | -------------------------------------------------------------------------------- /simple-coin-oracle/actions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "noImplicitReturns": true, 6 | "noUnusedLocals": true, 7 | "outDir": "out", 8 | "sourceMap": true, 9 | "strict": true, 10 | "target": "es2020", 11 | "resolveJsonModule": true, 12 | "esModuleInterop": true, 13 | "rootDir": "" 14 | }, 15 | "include": [ 16 | "**/*" 17 | ] 18 | } -------------------------------------------------------------------------------- /simple-coin-oracle/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1 3 | } 4 | -------------------------------------------------------------------------------- /simple-coin-oracle/tenderly.yaml: -------------------------------------------------------------------------------- 1 | account_id: "" 2 | actions: 3 | nenad/w3a-examples: 4 | runtime: v1 5 | sources: actions 6 | specs: 7 | coinOracle: 8 | description: Swapit 9 | function: coinOracle:coinPrice 10 | trigger: 11 | type: transaction 12 | transaction: 13 | status: 14 | - mined 15 | filters: 16 | - network: 3 17 | eventEmitted: 18 | contract: 19 | address: 0x15AA485Ba52ddB7Ceb269b2090E45b6A0C42cC5f 20 | name: RequestCoinPrice 21 | project_slug: "" -------------------------------------------------------------------------------- /tic-tac-toe/README.md: -------------------------------------------------------------------------------- 1 | 2 | # How to send a Discord message about a new Uniswap Pool? 3 | 4 | 5 | ## Running the examples 6 | To run these examples, you need to have Tenderly CLI. If you haven't already, install [Tenderly CLI](https://github.com/Tenderly/tenderly-cli#installation). 7 | 8 | Before you go on, you need to login with CLI, using your Tenderly credentials: 9 | 10 | ```bash 11 | tenderly login 12 | ``` 13 | 14 | ### Install dependencies for Web3 Actions project: 15 | 16 | First `cd` into an example you're interested in and `cd actions`. Then run `npm i`: 17 | 18 | ``` bash 19 | npm install 20 | ``` 21 | 22 | ### Test, Build, Deploy 23 | 24 | To build/deploy the actions, `cd` into example you're interested in and then run the CLI. 25 | 26 | ``` bash 27 | npm test 28 | tenderly build # local build only 29 | tenderly deploy # deploys and activates the action 30 | ``` 31 | -------------------------------------------------------------------------------- /tic-tac-toe/TicTacToe.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.7; 2 | 3 | //SPDX-License-Identifier: MIT 4 | 5 | // TicTacToe is a solidity implementation of the tic tac toe game. 6 | // You can find the rules at https://en.wikipedia.org/wiki/Tic-tac-toe 7 | contract TicTacToe { 8 | // Players enumerates all possible players 9 | enum Players { 10 | None, 11 | PlayerOne, 12 | PlayerTwo 13 | } 14 | // Winners enumerates all possible winners 15 | enum Winners { 16 | None, 17 | PlayerOne, 18 | PlayerTwo, 19 | Draw 20 | } 21 | 22 | // Game stores the state of a round of tic tac toe. 23 | // As long as `winner` is `None`, the game is not over. 24 | // `playerTurn` defines who may go next. 25 | // Player one must make the first move. 26 | // The `board` has the size 3x3 and in each cell, a player 27 | // can be listed. Initializes as `None` player, as that is the 28 | // first element in the enumeration. 29 | // That means that players are free to fill in any cell at the 30 | // start of the game. 31 | struct Game { 32 | address playerOne; 33 | address playerTwo; 34 | Winners winner; 35 | Players playerTurn; 36 | Players[3][3] board; 37 | } 38 | 39 | // games stores all the games. 40 | // Games that are already over as well as games that are still running. 41 | // It is possible to iterate over all games, as the keys of the mapping 42 | // are known to be the integers from `1` to `nrOfGames`. 43 | mapping(uint256 => Game) private games; 44 | // nrOfGames stores the total number of games in this contract. 45 | uint256 private nrOfGames; 46 | 47 | // GameCreated signals that `creator` created a new game with this `gameId`. 48 | event GameCreated(uint256 gameId, address creator); 49 | // PlayerJoinedGame signals that `player` joined the game with the id `gameId`. 50 | // That player has the player number `playerNumber` in that game. 51 | event PlayerJoinedGame(uint256 gameId, address player, uint8 playerNumber); 52 | // PlayerMadeMove signals that `player` filled in the board of the game with 53 | // the id `gameId`. She did so at the coordinates `boardRow`, `boardCol`. 54 | event PlayerMadeMove( 55 | uint256 gameId, 56 | address player, 57 | uint256 boardRow, 58 | uint256 boardCol 59 | ); 60 | // GameOver signals that the game with the id `gameId` is over. 61 | // The winner is indicated by `winner`. No more moves are allowed in this game. 62 | event GameOver(uint256 gameId, Winners winner); 63 | 64 | // newGame creates a new game and returns the new game's `gameId`. 65 | // The `gameId` is required in subsequent calls to identify the game. 66 | function newGame() public returns (uint256 gameId) { 67 | Game memory game; 68 | game.playerTurn = Players.PlayerOne; 69 | 70 | nrOfGames += 1; 71 | games[nrOfGames] = game; 72 | 73 | emit GameCreated(nrOfGames, msg.sender); 74 | 75 | return nrOfGames; 76 | } 77 | 78 | // joinGame lets the sender of the message join the game with the id `gameId`. 79 | // It returns `success = true` when joining the game was possible and 80 | // `false` otherwise. 81 | // `reason` indicates why a game was joined or not joined. 82 | function joinGame(uint256 _gameId) public { 83 | require(_gameId <= nrOfGames, "No such game exists."); 84 | 85 | address player = msg.sender; 86 | Game storage game = games[_gameId]; 87 | 88 | require( 89 | game.playerOne == address(0) || game.playerTwo == address(0), 90 | "Game already started." 91 | ); 92 | 93 | // Assign the new player to slot 1 if it is still available. 94 | if (game.playerOne == address(0)) { 95 | game.playerOne = player; 96 | emit PlayerJoinedGame(_gameId, player, uint8(Players.PlayerOne)); 97 | return; 98 | } 99 | 100 | // If slot 1 is taken, assign the new player to slot 2 if it is still available. 101 | if (game.playerTwo == address(0)) { 102 | game.playerTwo = player; 103 | emit PlayerJoinedGame(_gameId, player, uint8(Players.PlayerTwo)); 104 | return; 105 | } 106 | } 107 | 108 | // makeMove inserts a player on the game board. 109 | // The player is identified as the sender of the message. 110 | function makeMove( 111 | uint256 _gameId, 112 | uint256 _boardRow, 113 | uint256 _boardCol 114 | ) public { 115 | require(_gameId <= nrOfGames, "No such game exists."); 116 | 117 | Game storage game = games[_gameId]; 118 | 119 | // Any winner other than `None` means that no more moves are allowed. 120 | 121 | require(game.winner == Winners.None, "The game has already ended."); 122 | 123 | // Only the player whose turn it is may make a move. 124 | require(msg.sender == getCurrentPlayer(game), "It is not your turn."); 125 | 126 | // Players can only make moves in cells on the board that have not been played before. 127 | require( 128 | game.board[_boardRow][_boardCol] == Players.None, 129 | "There is already a mark at the given coordinates." 130 | ); 131 | 132 | // Now the move is recorded and the according event emitted. 133 | game.board[_boardRow][_boardCol] = game.playerTurn; 134 | emit PlayerMadeMove(_gameId, msg.sender, _boardRow, _boardCol); 135 | 136 | // Check if there is a winner now that we have a new move. 137 | Winners winner = calculateWinner(game.board); 138 | if (winner != Winners.None) { 139 | // If there is a winner (can be a `Draw`) it must be recorded in the game and 140 | // the corresponding event must be emitted. 141 | game.winner = winner; 142 | emit GameOver(_gameId, winner); 143 | 144 | return; 145 | } 146 | 147 | // A move was made and there is no winner yet. 148 | // The next player should make her move. 149 | nextPlayer(game); 150 | } 151 | 152 | // getCurrentPlayer returns the address of the player that should make the next move. 153 | // Returns the `0x0` address if it is no player's turn. 154 | function getCurrentPlayer(Game storage _game) 155 | private 156 | view 157 | returns (address player) 158 | { 159 | if (_game.playerTurn == Players.PlayerOne) { 160 | return _game.playerOne; 161 | } 162 | 163 | if (_game.playerTurn == Players.PlayerTwo) { 164 | return _game.playerTwo; 165 | } 166 | 167 | return address(0); 168 | } 169 | 170 | // calculateWinner returns the winner on the given board. 171 | // The returned winner can be `None` in which case there is no winner and no draw. 172 | function calculateWinner(Players[3][3] memory _board) 173 | private 174 | pure 175 | returns (Winners winner) 176 | { 177 | // First we check if there is a victory in a row. 178 | // If so, convert `Players` to `Winners` 179 | // Subsequently we do the same for columns and diagonals. 180 | Players player = winnerInRow(_board); 181 | if (player == Players.PlayerOne) { 182 | return Winners.PlayerOne; 183 | } 184 | if (player == Players.PlayerTwo) { 185 | return Winners.PlayerTwo; 186 | } 187 | 188 | player = winnerInColumn(_board); 189 | if (player == Players.PlayerOne) { 190 | return Winners.PlayerOne; 191 | } 192 | if (player == Players.PlayerTwo) { 193 | return Winners.PlayerTwo; 194 | } 195 | 196 | player = winnerInDiagonal(_board); 197 | if (player == Players.PlayerOne) { 198 | return Winners.PlayerOne; 199 | } 200 | if (player == Players.PlayerTwo) { 201 | return Winners.PlayerTwo; 202 | } 203 | 204 | // If there is no winner and no more space on the board, 205 | // then it is a draw. 206 | if (isBoardFull(_board)) { 207 | return Winners.Draw; 208 | } 209 | 210 | return Winners.None; 211 | } 212 | 213 | // winnerInRow returns the player that wins in any row. 214 | // To win in a row, all cells in the row must belong to the same player 215 | // and that player must not be the `None` player. 216 | function winnerInRow(Players[3][3] memory _board) 217 | private 218 | pure 219 | returns (Players winner) 220 | { 221 | for (uint8 x = 0; x < 3; x++) { 222 | if ( 223 | _board[x][0] == _board[x][1] && 224 | _board[x][1] == _board[x][2] && 225 | _board[x][0] != Players.None 226 | ) { 227 | return _board[x][0]; 228 | } 229 | } 230 | 231 | return Players.None; 232 | } 233 | 234 | // winnerInColumn returns the player that wins in any column. 235 | // To win in a column, all cells in the column must belong to the same player 236 | // and that player must not be the `None` player. 237 | function winnerInColumn(Players[3][3] memory _board) 238 | private 239 | pure 240 | returns (Players winner) 241 | { 242 | for (uint8 y = 0; y < 3; y++) { 243 | if ( 244 | _board[0][y] == _board[1][y] && 245 | _board[1][y] == _board[2][y] && 246 | _board[0][y] != Players.None 247 | ) { 248 | return _board[0][y]; 249 | } 250 | } 251 | 252 | return Players.None; 253 | } 254 | 255 | // winnerInDiagoral returns the player that wins in any diagonal. 256 | // To win in a diagonal, all cells in the diaggonal must belong to the same player 257 | // and that player must not be the `None` player. 258 | function winnerInDiagonal(Players[3][3] memory _board) 259 | private 260 | pure 261 | returns (Players winner) 262 | { 263 | if ( 264 | _board[0][0] == _board[1][1] && 265 | _board[1][1] == _board[2][2] && 266 | _board[0][0] != Players.None 267 | ) { 268 | return _board[0][0]; 269 | } 270 | 271 | if ( 272 | _board[0][2] == _board[1][1] && 273 | _board[1][1] == _board[2][0] && 274 | _board[0][2] != Players.None 275 | ) { 276 | return _board[0][2]; 277 | } 278 | 279 | return Players.None; 280 | } 281 | 282 | // isBoardFull returns true if all cells of the board belong to a player other 283 | // than `None`. 284 | function isBoardFull(Players[3][3] memory _board) 285 | private 286 | pure 287 | returns (bool isFull) 288 | { 289 | for (uint8 x = 0; x < 3; x++) { 290 | for (uint8 y = 0; y < 3; y++) { 291 | if (_board[x][y] == Players.None) { 292 | return false; 293 | } 294 | } 295 | } 296 | 297 | return true; 298 | } 299 | 300 | // nextPlayer changes whose turn it is for the given `_game`. 301 | function nextPlayer(Game storage _game) private { 302 | if (_game.playerTurn == Players.PlayerOne) { 303 | _game.playerTurn = Players.PlayerTwo; 304 | } else { 305 | _game.playerTurn = Players.PlayerOne; 306 | } 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /tic-tac-toe/actions/.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | **/node_modules 3 | 4 | # Tenderly's actions out dir 5 | out -------------------------------------------------------------------------------- /tic-tac-toe/actions/example.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ActionFn, 3 | Context, 4 | Event, 5 | BlockEvent 6 | } from '@tenderly/actions' 7 | 8 | export const blockHelloWorldFn: ActionFn = async (context: Context, event: Event) => { 9 | let blockEvent = event as BlockEvent 10 | console.log(blockEvent) 11 | } 12 | -------------------------------------------------------------------------------- /tic-tac-toe/actions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "actions", 3 | "scripts": { 4 | "build": "tsc", 5 | "ts-node": "ts-node", 6 | "test": " ts-mocha **/*.spec.ts" 7 | }, 8 | "devDependencies": { 9 | "@tenderly/actions-test": "0.0.9", 10 | "@types/chai": "^4.3.1", 11 | "@types/mocha": "^9.1.1", 12 | "@types/node": "^17.0.40", 13 | "chai": "^4.3.6", 14 | "mocha": "^10.0.0", 15 | "npm": "^8.12.1", 16 | "ts-mocha": "^10.0.0", 17 | "ts-node": "^10.8.1", 18 | "typescript": "^4.3.5" 19 | }, 20 | "dependencies": { 21 | "@tenderly/actions": "^0.0.7", 22 | "ethers": "^5.6.5" 23 | }, 24 | "private": true 25 | } 26 | -------------------------------------------------------------------------------- /tic-tac-toe/actions/tests/ticTacToe.spec.ts: -------------------------------------------------------------------------------- 1 | import { TransactionEvent } from "@tenderly/actions"; 2 | import { TestRuntime } from "@tenderly/actions-test"; 3 | import { expect } from "chai"; 4 | import { 5 | newGameAction, 6 | playerJoinedAction, 7 | playerMadeMoveAction, 8 | } from "../ticTacToeActions"; 9 | const testEvents = { 10 | newGame: { 11 | network: "3", 12 | blockHash: 13 | "0xa2dd5faea09715bdd020ef9090e170fff12140bc97d7f4ec9f8bcf8b0aaf3cc2", 14 | blockNumber: 12339873, 15 | hash: "0x8ca1f4b306bba7a7fb5d8a81c789003d5b3a20ab1989c15cf8e1b569bd7b9a20", 16 | from: "0xeDed260BFDCDf6Dc0f564b3e4AB5CeA805bBA10B", 17 | to: "0x1eb7DBd296eB3FC08B2b6217d87E6Bf3CB6e42dF", 18 | logs: [ 19 | { 20 | address: "0x1eb7DBd296eB3FC08B2b6217d87E6Bf3CB6e42dF", 21 | topics: [ 22 | "0xc3e0f84839dc888c892a013d10c8f9d6dc05a21a879d0ce468ca558013e9121c", 23 | ], 24 | data: "0x000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000eded260bfdcdf6dc0f564b3e4ab5cea805bba10b", 25 | }, 26 | ], 27 | input: "0x7d03f5f3", 28 | value: "0x0", 29 | nonce: "0xd", 30 | gas: "0x17d41", 31 | gasUsed: "0x17d41", 32 | cumulativeGasUsed: "0x17d41", 33 | gasPrice: "0x9502f907", 34 | gasTipCap: "0x9502f900", 35 | gasFeeCap: "0x9502f90e", 36 | alertId: null, 37 | }, 38 | player1Joined: { 39 | network: "3", 40 | blockHash: 41 | "0x4a24ce7e8bc21bfa4ceebac947d2b179a3bfcf7c8e91f7fbd16cf968716cf924", 42 | blockNumber: 12339969, 43 | hash: "0x6809dc1b23dd55e51c45f9add0146fb6fb6a06656bf8325169769593432a081f", 44 | from: "0x3a55A1e7cf75dD8374de8463530dF721816F1411", 45 | to: "0x1eb7DBd296eB3FC08B2b6217d87E6Bf3CB6e42dF", 46 | logs: [ 47 | { 48 | address: "0x1eb7DBd296eB3FC08B2b6217d87E6Bf3CB6e42dF", 49 | topics: [ 50 | "0x8f5866c09e99941481e2be79d3a7698371543fe3ad3387b903553fd6043e8550", 51 | ], 52 | data: "0x000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000003a55a1e7cf75dd8374de8463530df721816f14110000000000000000000000000000000000000000000000000000000000000001", 53 | }, 54 | ], 55 | input: "0xefaa55a0000000000000000000000000000000000000000000000000000000000000000e", 56 | value: "0x0", 57 | nonce: "0x30a", 58 | gas: "0xbd19", 59 | gasUsed: "0xbd19", 60 | cumulativeGasUsed: "0xe75ec", 61 | gasPrice: "0x9502f907", 62 | gasTipCap: "0x9502f900", 63 | gasFeeCap: "0x9502f90e", 64 | alertId: null, 65 | }, 66 | player2Joined: { 67 | network: "3", 68 | blockHash: 69 | "0xee8abb99f9f9d5977c58f88a7d7087ab4f2a7e71281cb94a47327b3712f25681", 70 | blockNumber: 12340010, 71 | hash: "0x2df7a3fe134192ba17ef26bcb0fd2c8b5cea4282bc6b7434bcf8be6d00106e1c", 72 | from: "0xf7ddedc66b1d482e5c38e4730b3357d32411e5dd", 73 | to: "0x1eb7dbd296eb3fc08b2b6217d87e6bf3cb6e42df", 74 | logs: [ 75 | { 76 | address: "0x1eb7dbd296eb3fc08b2b6217d87e6bf3cb6e42df", 77 | topics: [ 78 | "0x8f5866c09e99941481e2be79d3a7698371543fe3ad3387b903553fd6043e8550", 79 | ], 80 | data: "0x000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000f7ddedc66b1d482e5c38e4730b3357d32411e5dd0000000000000000000000000000000000000000000000000000000000000002", 81 | }, 82 | ], 83 | input: "0xefaa55a0000000000000000000000000000000000000000000000000000000000000000e", 84 | value: "0x", 85 | nonce: "0xd1", 86 | gas: "0x8376", 87 | gasUsed: "0x8376", 88 | cumulativeGasUsed: "0xe96b", 89 | gasPrice: "0x9502f908", 90 | gasTipCap: "0x9502f900", 91 | gasFeeCap: "0x9502f90e", 92 | alertId: null, 93 | }, 94 | moves: [ 95 | { 96 | network: "3", 97 | blockHash: 98 | "0xd2bdb9623b4a18c5165890882b175a54b73b11e2d6abf6b90e778fe1c83a9245", 99 | blockNumber: 12340129, 100 | hash: "0x03e871341fe7c177b65d8389a50916129ae3754353f66245720a4e116474f1da", 101 | from: "0x3a55a1e7cf75dd8374de8463530df721816f1411", 102 | to: "0x1eb7dbd296eb3fc08b2b6217d87e6bf3cb6e42df", 103 | logs: [ 104 | { 105 | address: "0x1eb7dbd296eb3fc08b2b6217d87e6bf3cb6e42df", 106 | topics: [ 107 | "0xaa03b0eb53c70f6640eba4234ad2c58782c8927e7abf7d3a6e2c45d07ca9d583", 108 | ], 109 | data: "0x000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000003a55a1e7cf75dd8374de8463530df721816f141100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 110 | }, 111 | ], 112 | input: "0xcfc2f5ff000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 113 | value: "0x", 114 | nonce: "0x30b", 115 | gas: "0x118fb", 116 | gasUsed: "0x118fb", 117 | cumulativeGasUsed: "0x9175ca", 118 | gasPrice: "0x9502f907", 119 | gasTipCap: "0x9502f900", 120 | gasFeeCap: "0x9502f90e", 121 | alertId: null, 122 | }, 123 | ], 124 | }; 125 | describe("TicTacToeActions", () => { 126 | it("new game", async () => { 127 | const testRuntime = new TestRuntime(); 128 | await testRuntime.execute( 129 | newGameAction, 130 | testEvents.newGame as TransactionEvent 131 | ); 132 | console.log(testRuntime.context.storage); 133 | }); 134 | 135 | it("playerJoined", async () => { 136 | const testRuntime = new TestRuntime(); 137 | testRuntime.context.storage.putJson("14", { 138 | players: {}, 139 | board: [ 140 | [0, 0, 0], 141 | [0, 0, 0], 142 | [0, 0, 0], 143 | ], 144 | }); 145 | 146 | await testRuntime.execute( 147 | playerJoinedAction, 148 | testEvents.player1Joined as TransactionEvent 149 | ); 150 | 151 | const p1JoinedState = Object.entries( 152 | (await testRuntime.context.storage.getJson("14")).players 153 | )[0]; 154 | expect(p1JoinedState[0]).to.eq( 155 | "0x3a55a1e7cf75dd8374de8463530df721816f1411" 156 | ); 157 | expect(p1JoinedState[1]).to.eq(1); 158 | 159 | await testRuntime.execute( 160 | playerJoinedAction, 161 | testEvents.player2Joined as TransactionEvent 162 | ); 163 | 164 | const p2JoinedState = Object.entries( 165 | (await testRuntime.context.storage.getJson("14")).players 166 | )[1]; 167 | expect(p2JoinedState[0]).to.eq( 168 | "0xf7ddedc66b1d482e5c38e4730b3357d32411e5dd" 169 | ); 170 | expect(p2JoinedState[1]).to.eq(2); 171 | console.log(testRuntime.context.storage); 172 | }); 173 | 174 | it("player makes move", async () => { 175 | const testRuntime = new TestRuntime(); 176 | testRuntime.context.storage.putJson("14", { 177 | players: { 178 | "0x3a55a1e7cf75dd8374de8463530df721816f1411": 1, 179 | "0xf7ddedc66b1d482e5c38e4730b3357d32411e5dd": 2, 180 | }, 181 | board: [ 182 | [0, 0, 0], 183 | [0, 0, 0], 184 | [0, 0, 0], 185 | ], 186 | }); 187 | await testRuntime.execute(playerMadeMoveAction, testEvents.moves[0]); 188 | expect((await testRuntime.context.storage.getJson("14")).board).deep.eq( 189 | [ 190 | [1, 0, 0], 191 | [0, 0, 0], 192 | [0, 0, 0], 193 | ] 194 | ); 195 | }); 196 | }); 197 | -------------------------------------------------------------------------------- /tic-tac-toe/actions/ticTacToeActions.ts: -------------------------------------------------------------------------------- 1 | import { ActionFn, Context, Event, TransactionEvent } from "@tenderly/actions"; 2 | import { ethers } from "ethers"; 3 | import TicTacToe from "./TicTacToe.json"; 4 | 5 | export type Game = { 6 | players: { [address: string]: number }; 7 | board: number[][]; 8 | }; 9 | 10 | export const newGameAction: ActionFn = async ( 11 | context: Context, 12 | event: Event 13 | ) => { 14 | let txEvent = event as TransactionEvent; 15 | 16 | let iface = new ethers.utils.Interface(TicTacToe.abi); 17 | 18 | const result = iface.decodeEventLog( 19 | "GameCreated", 20 | txEvent.logs[0].data, 21 | txEvent.logs[0].topics 22 | ); 23 | 24 | const { gameId, playerNumber, player } = result; 25 | console.log("Game Created Event:", { 26 | gameId: gameId.toString(), 27 | playerNumber, 28 | player, 29 | }); 30 | 31 | const game: Game = createNewGame(); 32 | await context.storage.putJson(gameId.toString(), game); 33 | }; 34 | 35 | const createNewGame = (): Game => { 36 | return { 37 | players: {}, 38 | board: [ 39 | [0, 0, 0], 40 | [0, 0, 0], 41 | [0, 0, 0], 42 | ], 43 | }; 44 | }; 45 | 46 | export const playerJoinedAction: ActionFn = async ( 47 | context: Context, 48 | event: Event 49 | ) => { 50 | let txEvent = event as TransactionEvent; 51 | let iface = new ethers.utils.Interface(TicTacToe.abi); 52 | const result = iface.decodeEventLog( 53 | "PlayerJoinedGame", 54 | txEvent.logs[0].data, 55 | txEvent.logs[0].topics 56 | ); 57 | 58 | const gameId = result.gameId.toString(); 59 | const playerAddress = result.player.toLowerCase() as string; 60 | const playerNumber = result.playerNumber; 61 | 62 | console.log("Player joined event:", { 63 | gameId, 64 | playerAddress, 65 | playerNumber, 66 | }); 67 | 68 | const game: Game = (await context.storage.getJson(gameId)) as Game; 69 | game.players[playerAddress] = playerNumber; 70 | 71 | await context.storage.putJson(result.gameId.toString(), game); 72 | }; 73 | 74 | export const playerMadeMoveAction: ActionFn = async ( 75 | context: Context, 76 | event: Event 77 | ) => { 78 | let txEvent = event as TransactionEvent; 79 | let iface = new ethers.utils.Interface(TicTacToe.abi); 80 | const result = iface.decodeEventLog( 81 | "PlayerMadeMove", 82 | txEvent.logs[0].data, 83 | txEvent.logs[0].topics 84 | ); 85 | 86 | const gameId = result.gameId.toString(); 87 | const game = (await context.storage.getJson(gameId)) as Game; 88 | const player = result.player.toLowerCase() as string; 89 | const { boardRow, boardCol } = result; 90 | 91 | console.log("Player's move event log:", { 92 | gameId, 93 | player, 94 | boardRow: boardRow.toString(), 95 | boardCol: boardCol.toString(), 96 | }); 97 | 98 | game.board[boardRow][boardCol] = game.players[player]; 99 | 100 | await context.storage.putJson(gameId, game); 101 | 102 | processNewGameState(game); 103 | }; 104 | 105 | export const gameOverAction: ActionFn = async ( 106 | context: Context, 107 | event: Event 108 | ) => { 109 | let txEvent = event as TransactionEvent; 110 | let iface = new ethers.utils.Interface(TicTacToe.abi); 111 | 112 | const gameOverTopic = iface.getEventTopic("GameOver"); 113 | const gameOverLog = txEvent.logs.find( 114 | (log) => 115 | log.topics.find((topic) => topic == gameOverTopic) !== undefined 116 | ); 117 | 118 | if (gameOverLog == undefined) { 119 | // impossible 120 | throw Error("GameOver log present."); 121 | } 122 | 123 | const result = iface.decodeEventLog( 124 | "GameOver", 125 | gameOverLog.data, 126 | gameOverLog.topics 127 | ); 128 | 129 | console.log(result); 130 | 131 | const gameId = result.gameId.toString(); 132 | const winner = result.winner as number; 133 | 134 | const winnerMessage = getWinnerMessage(winner); 135 | console.info(`Game ${gameId}: ${winnerMessage}`); 136 | }; 137 | 138 | const getWinnerMessage = (winner: number) => { 139 | if (winner == 3) { 140 | return "⚖️ It's a draw."; 141 | } 142 | return `🎉 Winner of the game is ${winner}`; 143 | }; 144 | 145 | const processNewGameState = (game: Game) => { 146 | let board = "\n"; 147 | game.board.forEach((row) => { 148 | row.forEach((field) => { 149 | if (field == 1) { 150 | board += "❎ "; 151 | return; 152 | } 153 | 154 | if (field == 2) { 155 | board += "🅾️ "; 156 | return; 157 | } 158 | 159 | board += "💜 "; 160 | }); 161 | 162 | board += "\n"; 163 | }); 164 | 165 | console.log(board); 166 | }; 167 | -------------------------------------------------------------------------------- /tic-tac-toe/actions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "noImplicitReturns": true, 6 | "noUnusedLocals": true, 7 | "outDir": "out", 8 | "sourceMap": true, 9 | "strict": true, 10 | "target": "es2020", 11 | "resolveJsonModule": true, 12 | "esModuleInterop": true, 13 | "rootDir": "" 14 | }, 15 | "include": [ 16 | "**/*" 17 | ] 18 | } -------------------------------------------------------------------------------- /tic-tac-toe/tenderly.yaml: -------------------------------------------------------------------------------- 1 | account_id: "nenad" 2 | project_slug: "w3a-examples" 3 | actions: 4 | nenad/w3a-examples: 5 | runtime: v1 6 | sources: actions 7 | specs: 8 | newGame: 9 | description: Respond to newGame event 10 | function: ticTacToeActions:newGameAction 11 | trigger: 12 | type: transaction 13 | transaction: 14 | status: 15 | - mined 16 | filters: 17 | - network: 3 18 | eventEmitted: 19 | contract: 20 | address: 0x1eb7dbd296eb3fc08b2b6217d87e6bf3cb6e42df 21 | name: GameCreated 22 | playerJoined: 23 | description: Respond to player joining game 24 | function: ticTacToeActions:playerJoinedAction 25 | trigger: 26 | type: transaction 27 | transaction: 28 | status: 29 | - mined 30 | filters: 31 | - network: 3 32 | eventEmitted: 33 | contract: 34 | address: 0x1eb7dbd296eb3fc08b2b6217d87e6bf3cb6e42df 35 | name: PlayerJoinedGame 36 | playerMadeMove: 37 | description: Respond to player making a move event 38 | function: ticTacToeActions:playerMadeMoveAction 39 | trigger: 40 | type: transaction 41 | transaction: 42 | status: 43 | - mined 44 | filters: 45 | - network: 3 46 | eventEmitted: 47 | contract: 48 | address: 0x1eb7dbd296eb3fc08b2b6217d87e6bf3cb6e42df 49 | name: PlayerMadeMove 50 | gameOver: 51 | description: Respond to game over 52 | function: ticTacToeActions:gameOverAction 53 | trigger: 54 | type: transaction 55 | transaction: 56 | status: 57 | - mined 58 | filters: 59 | - network: 3 60 | eventEmitted: 61 | contract: 62 | address: 0x1eb7dbd296eb3fc08b2b6217d87e6bf3cb6e42df 63 | name: GameOver 64 | -------------------------------------------------------------------------------- /uniswap-notify-discord/README.md: -------------------------------------------------------------------------------- 1 | 2 | # How to send a Discord message about a new Uniswap Pool? 3 | 4 | 5 | ## Running the examples 6 | To run these examples which deploy Web3 Actions, you need to have Tenderly CLI. 7 | 8 | ## Install Tenderly CLI 9 | If you haven't already, install [Tenderly CLI](https://github.com/Tenderly/tenderly-cli#installation). 10 | 11 | Before you go on, you need to login with CLI, using your Tenderly credentials: 12 | 13 | ```bash 14 | tenderly login 15 | ``` 16 | 17 | ### Install dependencies for Web3 Actions project: 18 | 19 | First `cd` into an example you're interested in and `cd actions`. Then run `npm i`: 20 | 21 | ``` bash 22 | npm install 23 | ``` 24 | 25 | ### Build and Run 26 | 27 | To build/deploy the actions, `cd` into example you're interested in and then run the CLI. 28 | 29 | ``` bash 30 | npm test 31 | tenderly build # local build only 32 | tenderly deploy # deploys and activates the action 33 | ``` -------------------------------------------------------------------------------- /uniswap-notify-discord/actions/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directories 2 | node_modules/ 3 | 4 | # Ignore tsc output 5 | out/**/* 6 | -------------------------------------------------------------------------------- /uniswap-notify-discord/actions/UniswapV3FactoryAbi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [], 4 | "stateMutability": "nonpayable", 5 | "type": "constructor" 6 | }, 7 | { 8 | "anonymous": false, 9 | "inputs": [ 10 | { 11 | "indexed": true, 12 | "internalType": "uint24", 13 | "name": "fee", 14 | "type": "uint24" 15 | }, 16 | { 17 | "indexed": true, 18 | "internalType": "int24", 19 | "name": "tickSpacing", 20 | "type": "int24" 21 | } 22 | ], 23 | "name": "FeeAmountEnabled", 24 | "type": "event" 25 | }, 26 | { 27 | "anonymous": false, 28 | "inputs": [ 29 | { 30 | "indexed": true, 31 | "internalType": "address", 32 | "name": "oldOwner", 33 | "type": "address" 34 | }, 35 | { 36 | "indexed": true, 37 | "internalType": "address", 38 | "name": "newOwner", 39 | "type": "address" 40 | } 41 | ], 42 | "name": "OwnerChanged", 43 | "type": "event" 44 | }, 45 | { 46 | "anonymous": false, 47 | "inputs": [ 48 | { 49 | "indexed": true, 50 | "internalType": "address", 51 | "name": "token0", 52 | "type": "address" 53 | }, 54 | { 55 | "indexed": true, 56 | "internalType": "address", 57 | "name": "token1", 58 | "type": "address" 59 | }, 60 | { 61 | "indexed": true, 62 | "internalType": "uint24", 63 | "name": "fee", 64 | "type": "uint24" 65 | }, 66 | { 67 | "indexed": false, 68 | "internalType": "int24", 69 | "name": "tickSpacing", 70 | "type": "int24" 71 | }, 72 | { 73 | "indexed": false, 74 | "internalType": "address", 75 | "name": "pool", 76 | "type": "address" 77 | } 78 | ], 79 | "name": "PoolCreated", 80 | "type": "event" 81 | }, 82 | { 83 | "inputs": [ 84 | { 85 | "internalType": "address", 86 | "name": "tokenA", 87 | "type": "address" 88 | }, 89 | { 90 | "internalType": "address", 91 | "name": "tokenB", 92 | "type": "address" 93 | }, 94 | { 95 | "internalType": "uint24", 96 | "name": "fee", 97 | "type": "uint24" 98 | } 99 | ], 100 | "name": "createPool", 101 | "outputs": [ 102 | { 103 | "internalType": "address", 104 | "name": "pool", 105 | "type": "address" 106 | } 107 | ], 108 | "stateMutability": "nonpayable", 109 | "type": "function" 110 | }, 111 | { 112 | "inputs": [ 113 | { 114 | "internalType": "uint24", 115 | "name": "fee", 116 | "type": "uint24" 117 | }, 118 | { 119 | "internalType": "int24", 120 | "name": "tickSpacing", 121 | "type": "int24" 122 | } 123 | ], 124 | "name": "enableFeeAmount", 125 | "outputs": [], 126 | "stateMutability": "nonpayable", 127 | "type": "function" 128 | }, 129 | { 130 | "inputs": [ 131 | { 132 | "internalType": "uint24", 133 | "name": "", 134 | "type": "uint24" 135 | } 136 | ], 137 | "name": "feeAmountTickSpacing", 138 | "outputs": [ 139 | { 140 | "internalType": "int24", 141 | "name": "", 142 | "type": "int24" 143 | } 144 | ], 145 | "stateMutability": "view", 146 | "type": "function" 147 | }, 148 | { 149 | "inputs": [ 150 | { 151 | "internalType": "address", 152 | "name": "", 153 | "type": "address" 154 | }, 155 | { 156 | "internalType": "address", 157 | "name": "", 158 | "type": "address" 159 | }, 160 | { 161 | "internalType": "uint24", 162 | "name": "", 163 | "type": "uint24" 164 | } 165 | ], 166 | "name": "getPool", 167 | "outputs": [ 168 | { 169 | "internalType": "address", 170 | "name": "", 171 | "type": "address" 172 | } 173 | ], 174 | "stateMutability": "view", 175 | "type": "function" 176 | }, 177 | { 178 | "inputs": [], 179 | "name": "owner", 180 | "outputs": [ 181 | { 182 | "internalType": "address", 183 | "name": "", 184 | "type": "address" 185 | } 186 | ], 187 | "stateMutability": "view", 188 | "type": "function" 189 | }, 190 | { 191 | "inputs": [], 192 | "name": "parameters", 193 | "outputs": [ 194 | { 195 | "internalType": "address", 196 | "name": "factory", 197 | "type": "address" 198 | }, 199 | { 200 | "internalType": "address", 201 | "name": "token0", 202 | "type": "address" 203 | }, 204 | { 205 | "internalType": "address", 206 | "name": "token1", 207 | "type": "address" 208 | }, 209 | { 210 | "internalType": "uint24", 211 | "name": "fee", 212 | "type": "uint24" 213 | }, 214 | { 215 | "internalType": "int24", 216 | "name": "tickSpacing", 217 | "type": "int24" 218 | } 219 | ], 220 | "stateMutability": "view", 221 | "type": "function" 222 | }, 223 | { 224 | "inputs": [ 225 | { 226 | "internalType": "address", 227 | "name": "_owner", 228 | "type": "address" 229 | } 230 | ], 231 | "name": "setOwner", 232 | "outputs": [], 233 | "stateMutability": "nonpayable", 234 | "type": "function" 235 | } 236 | ] -------------------------------------------------------------------------------- /uniswap-notify-discord/actions/emulate-action-execution.ts: -------------------------------------------------------------------------------- 1 | import * as TActions from "@tenderly/actions-test"; 2 | import { onPoolCreatedEventEmitted } from "./uniswapActions"; 3 | import CreatePoolEventPayload from "./tests/fixtures/createPoolEventPayload.json"; 4 | 5 | const runtime = new TActions.TestRuntime(); 6 | const txEventCreatePool = 7 | CreatePoolEventPayload as TActions.TestTransactionEvent; 8 | 9 | const DISCORD_WEBHOOK = process.env.DISCORD_WEBHOOK || ""; 10 | runtime.context.secrets.put("discord.uniswapChannelWebhook", DISCORD_WEBHOOK); 11 | runtime.execute(onPoolCreatedEventEmitted, txEventCreatePool); 12 | -------------------------------------------------------------------------------- /uniswap-notify-discord/actions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "actions", 3 | "scripts": { 4 | "build": "tsc", 5 | "test": "ts-mocha tests/**/*.test.ts --timeout 15000" 6 | }, 7 | "devDependencies": { 8 | "@tenderly/actions-test": "0.0.9", 9 | "@types/chai": "^4.3.1", 10 | "@types/mocha": "^9.1.1", 11 | "@types/moxios": "^0.4.15", 12 | "@types/node": "^17.0.39", 13 | "axios": "^0.27.2", 14 | "chai": "^4.3.6", 15 | "ethers": "^5.6.8", 16 | "mocha": "^10.0.0", 17 | "moxios": "^0.4.0", 18 | "ts-mocha": "^10.0.0", 19 | "ts-node": "^10.8.0", 20 | "typescript": "^4.3.5" 21 | }, 22 | "dependencies": { 23 | "@tenderly/actions": "^0.0.7" 24 | }, 25 | "private": true 26 | } 27 | -------------------------------------------------------------------------------- /uniswap-notify-discord/actions/tests/fixtures/createPoolEventPayload.json: -------------------------------------------------------------------------------- 1 | { 2 | "network": "1", 3 | "blockHash": "0x3496d03e6efd9a02417c713fa0de00915b78581a2eaf0e8b3fce435a96ab02c7", 4 | "blockNumber": 12376729, 5 | "hash": "0x125e0b641d4a4b08806bf52c0c6757648c9963bcda8681e4f996f09e00d4c2cc", 6 | "from": "0xb2ef52180d1e5f4835F4e343251286fa84743456", 7 | "to": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88", 8 | "logs": [ 9 | { 10 | "address": "0x1F98431c8aD98523631AE4a59f267346ea31F984", 11 | "topics": [ 12 | "0x783cca1c0412dd0d695e784568c96da2e9c22ff989357a2e8b1d9b2b4e6b7118", 13 | "0x000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", 14 | "0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", 15 | "0x00000000000000000000000000000000000000000000000000000000000001f4" 16 | ], 17 | "data": "0x000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000088e6a0c2ddd26feeb64f039a2c41296fcb3f5640" 18 | }, 19 | { 20 | "address": "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640", 21 | "topics": [ 22 | "0x98636036cb66a9c19a37435efc1e90142190214e8abeb821bdba3f2990dd4c95" 23 | ], 24 | "data": "0x00000000000000000000000000000000000042919a3b4e1f2e279ab5fe196328000000000000000000000000000000000000000000000000000000000002f93e" 25 | }, 26 | { 27 | "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 28 | "topics": [ 29 | "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", 30 | "0x000000000000000000000000b2ef52180d1e5f4835f4e343251286fa84743456", 31 | "0x00000000000000000000000088e6a0c2ddd26feeb64f039a2c41296fcb3f5640" 32 | ], 33 | "data": "0x00000000000000000000000000000000000000000000000000000000b28bd217" 34 | }, 35 | { 36 | "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", 37 | "topics": [ 38 | "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c", 39 | "0x000000000000000000000000c36442b4a4522e871399cd717abdd847ab11fe88" 40 | ], 41 | "data": "0x0000000000000000000000000000000000000000000000000de0b6b39fbba6a3" 42 | }, 43 | { 44 | "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", 45 | "topics": [ 46 | "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", 47 | "0x000000000000000000000000c36442b4a4522e871399cd717abdd847ab11fe88", 48 | "0x00000000000000000000000088e6a0c2ddd26feeb64f039a2c41296fcb3f5640" 49 | ], 50 | "data": "0x0000000000000000000000000000000000000000000000000de0b6b39fbba6a3" 51 | }, 52 | { 53 | "address": "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640", 54 | "topics": [ 55 | "0x7a53080ba414158be7ec69b987b5fb7d07dee101fe85488f0853ae16239d0bde", 56 | "0x000000000000000000000000c36442b4a4522e871399cd717abdd847ab11fe88", 57 | "0x000000000000000000000000000000000000000000000000000000000002eaae", 58 | "0x00000000000000000000000000000000000000000000000000000000000305c0" 59 | ], 60 | "data": "0x000000000000000000000000c36442b4a4522e871399cd717abdd847ab11fe88000000000000000000000000000000000000000000000000000139d797d3bfe000000000000000000000000000000000000000000000000000000000b28bd2170000000000000000000000000000000000000000000000000de0b6b39fbba6a3" 61 | }, 62 | { 63 | "address": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88", 64 | "topics": [ 65 | "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", 66 | "0x0000000000000000000000000000000000000000000000000000000000000000", 67 | "0x000000000000000000000000b2ef52180d1e5f4835f4e343251286fa84743456", 68 | "0x00000000000000000000000000000000000000000000000000000000000003d1" 69 | ], 70 | "data": "0x" 71 | }, 72 | { 73 | "address": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88", 74 | "topics": [ 75 | "0x3067048beee31b25b2f1681f88dac838c8bba36af25bfb2b7cf7473a5847e35f", 76 | "0x00000000000000000000000000000000000000000000000000000000000003d1" 77 | ], 78 | "data": "0x000000000000000000000000000000000000000000000000000139d797d3bfe000000000000000000000000000000000000000000000000000000000b28bd2170000000000000000000000000000000000000000000000000de0b6b39fbba6a3" 79 | } 80 | ], 81 | "input": "0xac9650d8000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000008413ead562000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000001f400000000000000000000000000000000000042919a3b4e1f2e279ab5fe19632800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016488316456000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000000000000000000000000000000000000002eaae00000000000000000000000000000000000000000000000000000000000305c000000000000000000000000000000000000000000000000000000000b28bd2170000000000000000000000000000000000000000000000000de0b6b39fbba6a300000000000000000000000000000000000000000000000000000000a99e6dfc0000000000000000000000000000000000000000000000000d2f13f77158ab1a000000000000000000000000b2ef52180d1e5f4835f4e343251286fa84743456000000000000000000000000000000000000000000000000000000006093159300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000412210e8a00000000000000000000000000000000000000000000000000000000", 82 | "value": "0xde0b6b39fbba6a3", 83 | "nonce": "0x10a", 84 | "gas": "0x58c199", 85 | "gasUsed": "0x4f5dfd", 86 | "cumulativeGasUsed": "0x8c95fc", 87 | "gasPrice": "0xee6b28000", 88 | "gasTipCap": "0x", 89 | "gasFeeCap": "0x", 90 | "alertId": null, 91 | "transactionHash": "0x125e0b641d4a4b08806bf52c0c6757648c9963bcda8681e4f996f09e00d4c2cc" 92 | } -------------------------------------------------------------------------------- /uniswap-notify-discord/actions/tests/swap.test.ts: -------------------------------------------------------------------------------- 1 | import * as TActions from "@tenderly/actions-test"; 2 | import { onPoolCreatedEventEmitted } from "../uniswapActions"; 3 | import moxios from "moxios"; 4 | import CreatePoolEventPayload from "./fixtures/createPoolEventPayload.json"; 5 | import { expect } from "chai"; 6 | 7 | describe("Uniswap", () => { 8 | beforeEach(() => { 9 | moxios.install(); 10 | }); 11 | 12 | afterEach(() => { 13 | moxios.uninstall(); 14 | }); 15 | 16 | it("sends to discord when a pair created event happens on chain", (done) => { 17 | const runtime = new TActions.TestRuntime(); 18 | const txEventCreatePool = 19 | CreatePoolEventPayload as TActions.TestTransactionEvent; 20 | const DISCORD_WEBHOOK = "https://mock.discord.webhook"; // any unique URI will do, we'll stub this request 21 | 22 | runtime.context.secrets.put( 23 | "discord.uniswapChannelWebhook", 24 | DISCORD_WEBHOOK 25 | ); 26 | 27 | stubTheGraphResponse(); 28 | 29 | // invoke the runtime 30 | runtime.execute(onPoolCreatedEventEmitted, txEventCreatePool); 31 | 32 | // wait a bit and then check if Discord got the expected message 33 | setTimeout(() => { 34 | // 35 | const discordData = JSON.parse( 36 | moxios.requests.get("post", DISCORD_WEBHOOK).config.data 37 | ); 38 | expect(discordData).to.deep.equal({ 39 | content: "🐥 USD Coin IN TEST ↔️ Wrapped Ether IN TEST", 40 | }); 41 | done(); 42 | }, 500); 43 | }); 44 | 45 | function stubTheGraphResponse() { 46 | // stub the uniswap response. The content is really irrelevant here because we're just asserting 47 | // the right data is sent to Discord 48 | moxios.stubRequest( 49 | "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3", 50 | { 51 | response: { 52 | data: { 53 | token0: { 54 | id: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", 55 | name: "USD Coin IN TEST", // added this to ensure (mocked) response is properly used 56 | symbol: "USDC", 57 | totalValueLocked: "978550777.498939", 58 | totalSupply: "19312", 59 | derivedETH: "0.0005595934721935152103857830042930448", 60 | }, 61 | token1: { 62 | id: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", 63 | name: "Wrapped Ether IN TEST", 64 | symbol: "WETH", 65 | totalValueLocked: "597555.62451452980569386", 66 | totalSupply: "19848", 67 | derivedETH: "1", 68 | }, 69 | }, 70 | }, 71 | status: 200, 72 | } 73 | ); 74 | } 75 | }); 76 | -------------------------------------------------------------------------------- /uniswap-notify-discord/actions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "noImplicitReturns": true, 6 | "noUnusedLocals": true, 7 | "outDir": "out", 8 | "sourceMap": true, 9 | "strict": true, 10 | "target": "es2020", 11 | "resolveJsonModule": true, 12 | "esModuleInterop": true, 13 | "rootDir": "" 14 | }, 15 | "include": [ 16 | "**/*", 17 | "../manualTrigger.ts" 18 | ] 19 | } -------------------------------------------------------------------------------- /uniswap-notify-discord/actions/uniswapActions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ActionFn, 3 | Context, 4 | Event, 5 | TransactionEvent, 6 | } from '@tenderly/actions' 7 | import axios from 'axios'; 8 | import { ethers } from 'ethers'; 9 | 10 | import UniswapV3FactoryAbi from './UniswapV3FactoryAbi.json' 11 | 12 | export const onPoolCreatedEventEmitted: ActionFn = async (context: Context, event: Event) => { 13 | try { 14 | const txEvent = event as TransactionEvent; 15 | 16 | const eventLog = await getPoolCreatedEvent(txEvent); 17 | 18 | const tokensData = await getTokensData(eventLog.token0, eventLog.token1); 19 | console.log("Received Tokens Data: ", tokensData); 20 | 21 | await notifyDiscord(`${tokensData.token0.name} ↔️ ${tokensData.token1.name}`, context); 22 | 23 | } catch (error) { 24 | console.error(error); 25 | } 26 | } 27 | 28 | const getPoolCreatedEvent = async (txEvent: TransactionEvent) => { 29 | const ifc = new ethers.utils.Interface(UniswapV3FactoryAbi); 30 | const poolCreatedTopic = ifc.getEventTopic("PoolCreated"); 31 | 32 | const poolCreatedEventLog = txEvent.logs.find(log => { 33 | return log.topics.find(topic => topic == poolCreatedTopic) !== undefined 34 | }) 35 | 36 | if (poolCreatedEventLog == undefined) { 37 | throw Error("PoolCreatedEvent missing") 38 | } 39 | return ifc.decodeEventLog("PoolCreated", 40 | poolCreatedEventLog.data, 41 | poolCreatedEventLog.topics) as unknown as UniswapPool; 42 | } 43 | 44 | const getTokensData = async (token0: string, token1: string) => { 45 | const tokenFields = `id, name, symbol, totalValueLocked, totalSupply, derivedETH`; 46 | const theGraphQuery = ` 47 | { 48 | token0: token(id:"${token0.toLowerCase()}"){${tokenFields}}, 49 | token1: token(id:"${token1.toLowerCase()}"){${tokenFields}} 50 | }`; 51 | 52 | const UNISWAP_THE_GRAPH = "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3"; 53 | const resp = await axios.post(UNISWAP_THE_GRAPH, { 54 | query: theGraphQuery 55 | }); 56 | 57 | return resp.data.data as unknown as { 58 | token0: UniwswapToken, 59 | token1: UniwswapToken 60 | } 61 | } 62 | 63 | const notifyDiscord = async (text: string, context: Context) => { 64 | console.log('Sending to Discord:', `🐥 ${text}`) 65 | const webhookLink = await context.secrets.get("discord.uniswapChannelWebhook"); 66 | await axios.post( 67 | webhookLink, 68 | { 69 | 'content': `🐥 ${text}` 70 | }, 71 | { 72 | headers: { 73 | 'Accept': 'application/json', 74 | 'Content-Type': 'application/json' 75 | } 76 | } 77 | ); 78 | 79 | } 80 | 81 | type UniswapPool = { 82 | token0: string, 83 | token1: string, 84 | fee: number, 85 | tickSpacing: number, 86 | pool: number 87 | } 88 | 89 | type UniwswapToken = { 90 | id: string, 91 | name: string, 92 | symbol: string, 93 | tokenValueLocked: number, 94 | totalSupply: number, 95 | derivedEth: number 96 | } -------------------------------------------------------------------------------- /uniswap-notify-discord/tenderly.yaml: -------------------------------------------------------------------------------- 1 | account_id: "" 2 | actions: 3 | nenad/w3a-examples: 4 | runtime: v1 5 | sources: actions 6 | specs: 7 | uniswapNewPool: 8 | description: Runs when a new swap is made on Uniswap 9 | function: uniswapActions:onPoolCreatedEventEmitted 10 | trigger: 11 | type: transaction 12 | transaction: 13 | status: 14 | - mined 15 | filters: 16 | - network: 1 17 | eventEmitted: 18 | contract: 19 | address: 0xd849b2af570ffa3033973ea11be6e01b7ba661d9 20 | name: PoolCreated 21 | project_slug: "" 22 | --------------------------------------------------------------------------------