master #1
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,3 +4,4 @@ node_modules
|
||||
backup-tornado-*
|
||||
backup-tornadoInvoice-*
|
||||
test
|
||||
parseTool.js
|
||||
|
||||
@ -5,8 +5,8 @@ RUN apt update && apt install --yes --no-install-recommends wget git apt-transpo
|
||||
|
||||
WORKDIR /home/root/tornado-cli
|
||||
|
||||
ENV GIT_REPOSITORY=https://git.tornado.ws/tornadocash/tornado-cli
|
||||
ENV GIT_COMMIT_HASH=7e0d8d9183f1f29409e1ec0b3c94a44354ae30fa
|
||||
ENV GIT_REPOSITORY=https://git.tornado.ws/tornadosto/tornado-cli.git
|
||||
ENV GIT_COMMIT_HASH=5efae744be6c1d3269d697a032fae389f8ddf0cb
|
||||
|
||||
RUN git init && \
|
||||
git remote add origin $GIT_REPOSITORY && \
|
||||
@ -18,3 +18,7 @@ RUN npm ci
|
||||
RUN npm install -g pkg@5.8.1
|
||||
|
||||
RUN node scripts/createDeterministicExecutable.js
|
||||
|
||||
RUN printf '#!/bin/sh\ncp /home/root/tornado-cli/tornado-cli.exe /output/' > /copy_out.sh && chmod +x /copy_out.sh
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
25
README.md
25
README.md
@ -53,12 +53,7 @@ Also, you can use VPN for CLI without Tor or even combine it.
|
||||
|
||||
All code logic located in `cli.js`, other files is just testing scripts or static files. You can check or change any part of the code and at next `node cli.js` run changes will apply.
|
||||
|
||||
Any user can check that the precompiled `tornado-cli.exe` matches the source code. To verify **tornado-cli.exe**:
|
||||
|
||||
1. Build docker image from latest commit: `docker build -t tornado-cli .`
|
||||
2. Create container`docker create --name tornado-cli tornado-cli:latest`
|
||||
3. Copy executable from container `docker cp tornado-cli:/home/root/tornado-cli/tornado-cli.exe tornado-cli-verify.exe`
|
||||
4. Compare hashes of original tornado-cli.exe and freshly generated executable with two commands: on Windows use `CertUtil -hashfile tornado-cli.exe SHA256` and `CertUtil -hashfile tornado-cli-verify.exe SHA256`, on Linux - `sha256sum tornado-cli.exe` and `sha256sum tornado-cli-verify.exe`. Hashes should be identical.
|
||||
Any user can check that the precompiled `tornado-cli.exe` matches the source code. To build `tornado-cli.exe`, [pkg](https://www.npmjs.com/package/pkg) package is used, which allows creating deterministic and reproducible executable from source. Creating script is located at `./scripts/createDeterministicExecutable.js`, and you can rebuild executable with command `npm run createExe`, or you can verify that sha1 hash of existing executable matches new executable, verification script located at `./scripts/verifyExecutable` and you can call it with command `npm run verifyExe` and compare hashes.
|
||||
|
||||
### Commands and usage
|
||||
|
||||
@ -168,3 +163,21 @@ View transaction on block explorer https://goerli.etherscan.io/tx/0x6ded443caed8
|
||||
Tornado contract balance is xxx.x ETH
|
||||
Sender account balance is x.xxxxxxx ETH
|
||||
```
|
||||
|
||||
#### To verify:
|
||||
|
||||
```bash
|
||||
$ docker build -t tornado-cli:latest .
|
||||
```
|
||||
wait for docker to build
|
||||
|
||||
```bash
|
||||
$ docker run --rm -v %cd%/output:/output tornado-cli:latest /copy_out.sh
|
||||
```
|
||||
copy exe to current folder in windows
|
||||
|
||||
```bash
|
||||
CertUtil -hashfile output/tornado-cli.exe SHA256
|
||||
CertUtil -hashfile tornado-cli.exe SHA256
|
||||
```
|
||||
compare with the exe in git
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,290 +0,0 @@
|
||||
[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "governanceAddress",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "tornAddress",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_relayerRegistry",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "rewardsClaimed",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "RewardsClaimed",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "rewards",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "RewardsUpdated",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "Governance",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "contract ITornadoGovernance",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "accumulatedRewardPerTorn",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "accumulatedRewardRateOnLastUpdate",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "accumulatedRewards",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "addBurnRewards",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "bytes32[]",
|
||||
"name": "domains",
|
||||
"type": "bytes32[]"
|
||||
}
|
||||
],
|
||||
"name": "bulkResolve",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "result",
|
||||
"type": "address[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "checkReward",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "rewards",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getReward",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "ratioConstant",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "relayerRegistry",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "node",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "resolve",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "setReward",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "torn",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "contract IERC20",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountLockedBeforehand",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "updateRewardsOnLockedBalanceChange",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "withdrawTorn",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
110
cache/ethereumclassic/deposits_etc_1.json
vendored
Normal file
110
cache/ethereumclassic/deposits_etc_1.json
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
[
|
||||
{
|
||||
"blockNumber": 22385840,
|
||||
"transactionHash": "0x63fa83cf4127ef4147d507b23501179cfc6c037731e8dfb4fd2cc49c09b9eae8",
|
||||
"commitment": "0x03f8ca1f7d166d11531313ef8b15acb22b19ae5dd98ceea6886b16a320a517cd",
|
||||
"leafIndex": 0
|
||||
},
|
||||
{
|
||||
"blockNumber": 22385872,
|
||||
"transactionHash": "0xf55784219dc9d71c142b42f4099e6b791ef5fb2035ee9d14c8e82879a37eae2c",
|
||||
"commitment": "0x1be521b0899780c2dbc5ce84dfcf90b5635692b23f6ea1042c9312d742e990d8",
|
||||
"leafIndex": 1
|
||||
},
|
||||
{
|
||||
"blockNumber": 22392807,
|
||||
"transactionHash": "0x9c7c0202da743a351aafc60c5e62f46c920b7ef8ab0fbd9a437ee5df2f273a52",
|
||||
"commitment": "0x2c6e62f51b8251c9ebdeccbcdd58efe15b35326cb3f1e0ddd4b99eed3c9fccb0",
|
||||
"leafIndex": 2
|
||||
},
|
||||
{
|
||||
"blockNumber": 22392987,
|
||||
"transactionHash": "0x81505022469948ef6d700738463cbfa30aab567897a7bcd7c5b9ac34c25f9b14",
|
||||
"commitment": "0x1fc417281a28562a41f921786f147ac95e77323304c5cdb9a9359d2dd971056f",
|
||||
"leafIndex": 3
|
||||
},
|
||||
{
|
||||
"blockNumber": 22393172,
|
||||
"transactionHash": "0x764550f81da7ba82bb609bd45bba1f0e61deb14fa1a30a320ac8fa7042f6f383",
|
||||
"commitment": "0x29b11c7da6fcaeb3c6248472677a65fb55daec2a110b90ff0d5c7ad95d4bd05e",
|
||||
"leafIndex": 4
|
||||
},
|
||||
{
|
||||
"blockNumber": 22393172,
|
||||
"transactionHash": "0x207a4a95a0bd67e8d7bfb35f81a02d80465b467467fd3097bcf9877a5d1dd712",
|
||||
"commitment": "0x0a29115a2af3acbb0820056604fd123ed365a3b8e856901430b0c5609ed03897",
|
||||
"leafIndex": 5
|
||||
},
|
||||
{
|
||||
"blockNumber": 22393172,
|
||||
"transactionHash": "0x6afd92cf8f9f0afec38179f6c223e49b69fd414354f382c87a0f3e23b596dab3",
|
||||
"commitment": "0x167ece4de25842f445e5005a525f20f86f7edc8b2a65a923184b052f43a894d5",
|
||||
"leafIndex": 6
|
||||
},
|
||||
{
|
||||
"blockNumber": 22393172,
|
||||
"transactionHash": "0xd4c4bae2a66fb7aa192d81144459f1e9030a5211f68a644919fbad722a644b0c",
|
||||
"commitment": "0x15902671ae98cbd40887e395eeef6e9d66972c671fdd688cdecbc6b7c89b3bc0",
|
||||
"leafIndex": 7
|
||||
},
|
||||
{
|
||||
"blockNumber": 22393172,
|
||||
"transactionHash": "0x84fb0853a93fb412f3570a115e56f97677df99762f741c6c281df411703ea14b",
|
||||
"commitment": "0x146518004a7639391f39833de66bce38ccb23f7d24ae072f2ea7e3ad349fbcb0",
|
||||
"leafIndex": 8
|
||||
},
|
||||
{
|
||||
"blockNumber": 22393287,
|
||||
"transactionHash": "0x9c9d005fb758fdc3810d26923573e27c6149cec3d3f0e219ea7182010f8279a0",
|
||||
"commitment": "0x1c7e098f281b291f47ffdbe734bd7762638692765aa10ae79d00296c683b4f64",
|
||||
"leafIndex": 9
|
||||
},
|
||||
{
|
||||
"blockNumber": 22393298,
|
||||
"transactionHash": "0xa145a95807fb52c42dcaa478f5784755feedbc06948871066f2c4b63a674e583",
|
||||
"commitment": "0x0e874573d767d81c8e04427b38ef7ee88732c9c92c9534f3b42965091d244d95",
|
||||
"leafIndex": 10
|
||||
},
|
||||
{
|
||||
"blockNumber": 22393384,
|
||||
"transactionHash": "0xaefad2d9200f27c1ff2dc6731563753fd87ffd5abb16f2610f9d8408e5173643",
|
||||
"commitment": "0x109040cd8b4f5073949d62c7923cc8030fcc92e3675cc35932a4cbbacb8ce6be",
|
||||
"leafIndex": 11
|
||||
},
|
||||
{
|
||||
"blockNumber": 22396258,
|
||||
"transactionHash": "0x6135e90e4c74bf5b3271a44850a10f5556bb3ca57d8f2850c0eddfb1375a1280",
|
||||
"commitment": "0x034e30e5e1c21deaa71cfedea0fa153c25d3ff380bed56f83a78ed5a307a0c36",
|
||||
"leafIndex": 12
|
||||
},
|
||||
{
|
||||
"blockNumber": 22396797,
|
||||
"transactionHash": "0x82dd8478501366f3d7bc82694af2bdbeb5fd3878baf4ddb9d02f1eaf7d31ce63",
|
||||
"commitment": "0x1469fb2b9bed134e8cff2ada483a8f416b2a44629c7a388dd713b9dc05b87d02",
|
||||
"leafIndex": 13
|
||||
},
|
||||
{
|
||||
"blockNumber": 22396930,
|
||||
"transactionHash": "0x56d1744e17e907f0a33ac774eed8dcf1ddb003e14d1e52bf37fdbcf41b0a8cb2",
|
||||
"commitment": "0x10199e8b32f29fdc6410cac40d9db40302e0902afd71e7a86f94a33af9a6c6d0",
|
||||
"leafIndex": 14
|
||||
},
|
||||
{
|
||||
"blockNumber": 22396986,
|
||||
"transactionHash": "0xd3f697cbe6384d33e3751e222e75ed868adc2175bacd1f3bf91cdd9fa2332a5e",
|
||||
"commitment": "0x0e13899823c0e21680f21de2e1ffd74d899ebfebbd767c8dd3a9b665fb9a2ed4",
|
||||
"leafIndex": 15
|
||||
},
|
||||
{
|
||||
"blockNumber": 22399955,
|
||||
"transactionHash": "0xbe98adfe86b0492cd85da8c684e53a4adc2b0b62eae2495c7a5962da635c80e9",
|
||||
"commitment": "0x227deb325a01d3d83b36b03f2c31b761cf8c20e6f722c2f7c2ea8be02284fce1",
|
||||
"leafIndex": 16
|
||||
},
|
||||
{
|
||||
"blockNumber": 22410370,
|
||||
"transactionHash": "0x99194cba56f82b27b6928cb37a72a441d83a990e9f8072b12d0c31cce79eb6af",
|
||||
"commitment": "0x143e37303e307f5367ba3fc59a066fca1be31cdb714d622ab8ffa4246faafc05",
|
||||
"leafIndex": 17
|
||||
}
|
||||
]
|
||||
79
cache/ethereumclassic/withdrawals_etc_1.json
vendored
Normal file
79
cache/ethereumclassic/withdrawals_etc_1.json
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
[
|
||||
{
|
||||
"blockNumber": 22385858,
|
||||
"transactionHash": "0x1a8cb44a9d03d2e1248deed7c65d86b30e2ae0f6ccd1e26dfcdd026e6e7ba7e2",
|
||||
"nullifierHash": "0x01de4b0b2bee6944f771806e61dcb8f223f77ebcb86140040e4b18532dcfa642",
|
||||
"to": "0xc2f667db796e82913160c1e01030aabbd5ead47e",
|
||||
"fee": "0"
|
||||
},
|
||||
{
|
||||
"blockNumber": 22385878,
|
||||
"transactionHash": "0xef83a1582639c13b2ccf90d10305d2c873eedc04346563988d0175cb2e9cf876",
|
||||
"nullifierHash": "0x0466c43a2ba160630a9929a1e9ef956610ab9fd8038fadd0d80dd6dab498ee37",
|
||||
"to": "0xc2f667db796e82913160c1e01030aabbd5ead47e",
|
||||
"fee": "0"
|
||||
},
|
||||
{
|
||||
"blockNumber": 22393007,
|
||||
"transactionHash": "0x397d7d92ca3aaa571bb10f8f8cb1dc272ecb7be35bc4e8131ce086d36c2b5f5f",
|
||||
"nullifierHash": "0x11e974a721874186ce971f36873c013bc93c464ed26a6a82ed96319573f9c673",
|
||||
"to": "0xb4ef209ccee95de23a8e1f7627ac7e676ff0739d",
|
||||
"fee": "7182616000000000"
|
||||
},
|
||||
{
|
||||
"blockNumber": 22393369,
|
||||
"transactionHash": "0x8d5d7de4fcd014b050b9feff398fafd88112c04b0fccd420944ecabfe339dd44",
|
||||
"nullifierHash": "0x22af5c5cd73087a65567b25a3da887d3dc192806691a3694fa868f9ea63ef7d2",
|
||||
"to": "0xc0f12799b8d3fa8810dfe1616095170c72117f8f",
|
||||
"fee": "1986919520000000"
|
||||
},
|
||||
{
|
||||
"blockNumber": 22393391,
|
||||
"transactionHash": "0xfdcb3c4521394b1871243e1a6ad3ee1c5c0a18e118c571f4206e6a7b48e96a34",
|
||||
"nullifierHash": "0x0add34f677a5e2faccda12a32698418222f07075983b205bd990e68c0b96255e",
|
||||
"to": "0xc0f12799b8d3fa8810dfe1616095170c72117f8f",
|
||||
"fee": "4974722720000000"
|
||||
},
|
||||
{
|
||||
"blockNumber": 22393496,
|
||||
"transactionHash": "0x840dcdc3982c47ca46b088bd9d63977682a6eb2461df832ee1a85d41d5e8475b",
|
||||
"nullifierHash": "0x1fca9aec5973b7cefbd632df53224c32a5bdb7e0c5516b1bfbcf7819f3f1c1b3",
|
||||
"to": "0xc2f667db796e82913160c1e01030aabbd5ead47e",
|
||||
"fee": "0"
|
||||
},
|
||||
{
|
||||
"blockNumber": 22396800,
|
||||
"transactionHash": "0x306a40c939450320a433399435e93b99f6de9c835196cb5aef94e97829e7a165",
|
||||
"nullifierHash": "0x1664ce55a6e2520b562b8d2a9e4a965b4e72478bb423bdcd671e89ca9083b515",
|
||||
"to": "0xb4ef209ccee95de23a8e1f7627ac7e676ff0739d",
|
||||
"fee": "1574701600000000"
|
||||
},
|
||||
{
|
||||
"blockNumber": 22396811,
|
||||
"transactionHash": "0x9728fab8b35a5a4fd0e6a67bb0e90963ade1262f5bb49a80a736253c27c3017e",
|
||||
"nullifierHash": "0x19aa705bf1b01bf5a304b46f5f4f960766f8191068750373b4585ebb0d5f0dee",
|
||||
"to": "0x9b5fbedaa0c2474b43e4f96f58e62f702614c8b8",
|
||||
"fee": "1575916000000000"
|
||||
},
|
||||
{
|
||||
"blockNumber": 22397057,
|
||||
"transactionHash": "0x45a369f482fc98f39eb55c4bd2e113ebf8436f93a01211609881acdab5a87d06",
|
||||
"nullifierHash": "0x1f9d9874d23cbd163d31b8f2b936ba18d5b3a42991519970bdea29712e1f0ff0",
|
||||
"to": "0xc0f12799b8d3fa8810dfe1616095170c72117f8f",
|
||||
"fee": "4986919520000000"
|
||||
},
|
||||
{
|
||||
"blockNumber": 22397185,
|
||||
"transactionHash": "0x55ece3a9554848a6042ce8cbd4ff22ddd64d51d4b63347d15c661ba8345a68ff",
|
||||
"nullifierHash": "0x1934703fb8829f2e2764b8621e208a9c0a51396a338ea1d098591c7b9a30c991",
|
||||
"to": "0x9b5fbedaa0c2474b43e4f96f58e62f702614c8b8",
|
||||
"fee": "4975916000000000"
|
||||
},
|
||||
{
|
||||
"blockNumber": 22399965,
|
||||
"transactionHash": "0xd881c6f30fc6486559dc7bfd870b753895a7bab0d101bec8ecb3c1d60a4bd68e",
|
||||
"nullifierHash": "0x21edacf1834d5506c88be35371f7b328f89f3ed3947b58df5ce34a741adc96f2",
|
||||
"to": "0x754e3d0f4473d2bd7b6bcb62a4c61706d1be9545",
|
||||
"fee": "0"
|
||||
}
|
||||
]
|
||||
2894
cache/sepolia/deposits_eth_0.1.json
vendored
Normal file
2894
cache/sepolia/deposits_eth_0.1.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
18
cache/sepolia/deposits_eth_1.json
vendored
18
cache/sepolia/deposits_eth_1.json
vendored
@ -550,23 +550,5 @@
|
||||
"transactionHash": "0xf810858c8d70168efa0c4db912ed7caf191b1379612c6e4ec0adc7272d48e599",
|
||||
"commitment": "0x05cef42e5d479a19a6d6dfc3e2a59efee92ab350875aa77c84bec4b5d7f04546",
|
||||
"leafIndex": 91
|
||||
},
|
||||
{
|
||||
"blockNumber": 8548903,
|
||||
"transactionHash": "0xd00051d1b3d58e8abdf2e333b84a80e48722342e2e60371d4ca5fc74b4812968",
|
||||
"commitment": "0x2820eab529e832cb5550001ffbb336b11000cbaf7d7e444283858df83ec752e7",
|
||||
"leafIndex": 92
|
||||
},
|
||||
{
|
||||
"blockNumber": 8554220,
|
||||
"transactionHash": "0xe9d72cb832e7e45fb43d3c3f5f4485b759dd298e1488e37bcf7f0f2e14340cf1",
|
||||
"commitment": "0x1bc58d9aa06b54578c59cf5503bf5f9824a46463976cdcc750bcc789d1f17827",
|
||||
"leafIndex": 93
|
||||
},
|
||||
{
|
||||
"blockNumber": 8582499,
|
||||
"transactionHash": "0xb5b3bf519294917588501dedeaff90f75274a34e38c02dd93640847cee3a2a61",
|
||||
"commitment": "0x305c2a56eb349ca8910cdce13ac0ff86a607521423481a315a40c9b80e30c484",
|
||||
"leafIndex": 94
|
||||
}
|
||||
]
|
||||
86
cache/sepolia/deposits_eth_10.json
vendored
86
cache/sepolia/deposits_eth_10.json
vendored
@ -1,86 +0,0 @@
|
||||
[
|
||||
{
|
||||
"blockNumber": 6808552,
|
||||
"transactionHash": "0x7d7f9d83dc671ddac7f68fbd1643f35ee388f74c049339a40c92bca9120f303b",
|
||||
"commitment": "0x1fabbaf0879a9691cf5d7d0cfab8411ac18b59cde8aa6336647c55b5b0364756",
|
||||
"leafIndex": 0
|
||||
},
|
||||
{
|
||||
"blockNumber": 6841965,
|
||||
"transactionHash": "0x894a12cdc48d3c2f1c0e41403014c750eee232fc0942aa6a1b7b45e8ffe01c25",
|
||||
"commitment": "0x28554b45d48c8b4defc13874c0bed9a3a2508947de91664be901b747985b8c66",
|
||||
"leafIndex": 1
|
||||
},
|
||||
{
|
||||
"blockNumber": 6841972,
|
||||
"transactionHash": "0x13f1e212b49d814066afa79a7ec9c0ebfdb19cd49ab1a4f54753d2bea0707eba",
|
||||
"commitment": "0x1c3378f8e955fe66f5d6f2621ddb812bbbfd53d25f2a1efc3aaaf7539639109b",
|
||||
"leafIndex": 2
|
||||
},
|
||||
{
|
||||
"blockNumber": 6841977,
|
||||
"transactionHash": "0x128193940812eee01b874336378215c02446b9022671c19b516a5a47c4943094",
|
||||
"commitment": "0x11dd59ccfcf8b8f95a8d488b6b92dfef7271426b2d8a80724aa458a949d2d616",
|
||||
"leafIndex": 3
|
||||
},
|
||||
{
|
||||
"blockNumber": 6841996,
|
||||
"transactionHash": "0x91abeea9495392b3e86fad1de78a02ab98c7ec7309f764de513cc5da27c6fb7b",
|
||||
"commitment": "0x1ebe8c66bb44d730e96a0c3030deaf099eadeb7c49f3fc558e66484d5a59e479",
|
||||
"leafIndex": 4
|
||||
},
|
||||
{
|
||||
"blockNumber": 6842009,
|
||||
"transactionHash": "0x643c36ca6a6770c0838bd03da4424f41eba4ae0010bbfe6c2db3fc82e8ee3f77",
|
||||
"commitment": "0x09b3231821f0cc8b957051feafa73796a43567c16802a59b8d355abad013d302",
|
||||
"leafIndex": 5
|
||||
},
|
||||
{
|
||||
"blockNumber": 6842010,
|
||||
"transactionHash": "0x7e3bd713be872432cf0fd25aad30f5607b7346754a4d55040811b3ed24623a97",
|
||||
"commitment": "0x1a63b64f8687b587c159aede8bcf1baf34a4bf9aa4d5d10d6bc5959a62a61f8e",
|
||||
"leafIndex": 6
|
||||
},
|
||||
{
|
||||
"blockNumber": 6859777,
|
||||
"transactionHash": "0xa7e5cd6440fb27401c2065562ad003e27f56bf5e3deb1113c11f91e9a5fe5a14",
|
||||
"commitment": "0x2ec46d07c14093a5b59061e647b2780d7b7d43495ae175c414a40e3bf3434aef",
|
||||
"leafIndex": 7
|
||||
},
|
||||
{
|
||||
"blockNumber": 6859779,
|
||||
"transactionHash": "0xcc0f337ac271d595cfac1e5f2ad3b5e12983dbbff945929a32e1f964c12ba8d4",
|
||||
"commitment": "0x2e6f47a8e1ccd23af268fb5d2ef8739b74cd486b480963e76493f73d3eca8ae7",
|
||||
"leafIndex": 8
|
||||
},
|
||||
{
|
||||
"blockNumber": 6859781,
|
||||
"transactionHash": "0x9384d9be1508d30b76d97fe1f83b29f91948ad6aae886c107bd8cc620b3c28d7",
|
||||
"commitment": "0x2827191f0a60a6867114fe38db2adbf96543e23d821979f5825a07de76f06edd",
|
||||
"leafIndex": 9
|
||||
},
|
||||
{
|
||||
"blockNumber": 8142964,
|
||||
"transactionHash": "0x3637a59d43ded88e11a1e33a47e9ac25d588f1a645d73ce463d32c8f5ee20c41",
|
||||
"commitment": "0x25e1e0db5f9fee5f34a6bf177bb102ae3bb55ddbb850a1394bb788a0f83402e7",
|
||||
"leafIndex": 10
|
||||
},
|
||||
{
|
||||
"blockNumber": 8414361,
|
||||
"transactionHash": "0x9db998ec88fc6569d8707a09d9b556bd3332b84037ed05b0ce73e97e37dc637d",
|
||||
"commitment": "0x030d556c506fac8d8420e8583c8683af885761e08c68647c51300836c5d061c3",
|
||||
"leafIndex": 11
|
||||
},
|
||||
{
|
||||
"blockNumber": 8415769,
|
||||
"transactionHash": "0xae54c08901cc31f66e5f2aa559b65b4ad41d335e2247f4fdd71bc99b3588e7e4",
|
||||
"commitment": "0x1cdc8de34b0a461e6486770f97bc623b1f34ea10f79b6e98f8f47ef33dc02a20",
|
||||
"leafIndex": 12
|
||||
},
|
||||
{
|
||||
"blockNumber": 8589745,
|
||||
"transactionHash": "0xdabfe58e8c2beb13e636b751c36efde634f62f38ac65ad05e52ab2cc5009b8bf",
|
||||
"commitment": "0x0cbe58b8e9002532253174ff8f0e4574d968760f4fb35c948a9a947850739639",
|
||||
"leafIndex": 13
|
||||
}
|
||||
]
|
||||
1626
cache/sepolia/withdrawals_eth_0.1.json
vendored
Normal file
1626
cache/sepolia/withdrawals_eth_0.1.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
21
cache/sepolia/withdrawals_eth_1.json
vendored
21
cache/sepolia/withdrawals_eth_1.json
vendored
@ -446,26 +446,5 @@
|
||||
"nullifierHash": "0x00fac88aeaaf4cdfe15e4f0e246ccf7e5b64adca4a0699989f9eaeb47d0a32bd",
|
||||
"to": "0xc53c51af207c224a865d8bb5e1948ac5c2cb8442",
|
||||
"fee": "0"
|
||||
},
|
||||
{
|
||||
"blockNumber": 8554223,
|
||||
"transactionHash": "0x2a134c9ecacc558184d74b65b60fad388985de493e8542a43c24288f160c04c5",
|
||||
"nullifierHash": "0x2d5e889a9d3b27af4578e4df48e620de2673e551a444bd005c61692d93fd890c",
|
||||
"to": "0xb66596d2eab0204494dedaeffe0f86fc74714a0f",
|
||||
"fee": "0"
|
||||
},
|
||||
{
|
||||
"blockNumber": 8583310,
|
||||
"transactionHash": "0xf30b1b4064f561b103d2d54f4dfd8fd6c6b27e11d049b57ef128f02a0a77e67a",
|
||||
"nullifierHash": "0x27b4bc29a518df33ad035be4d580528bbf5447da01c1cd3619fafc0ffa8497bd",
|
||||
"to": "0xb67af20093837ab2d4fc826a729f30612c47ef2e",
|
||||
"fee": "0"
|
||||
},
|
||||
{
|
||||
"blockNumber": 8589727,
|
||||
"transactionHash": "0xaa1da6d30393434b4e7500e946129343676c954526864a890d0a6760c70cd67a",
|
||||
"nullifierHash": "0x2bccfd39f219e8acade8908e35e48711eef76edf24c221aa850bd1f8b07812f7",
|
||||
"to": "0x9ff3c1bea9ffb56a78824fe29f457f066257dd58",
|
||||
"fee": "0"
|
||||
}
|
||||
]
|
||||
44
cache/sepolia/withdrawals_eth_10.json
vendored
44
cache/sepolia/withdrawals_eth_10.json
vendored
@ -1,44 +0,0 @@
|
||||
[
|
||||
{
|
||||
"blockNumber": 6852128,
|
||||
"transactionHash": "0x1207bb7e71881f0585069c3d5460481bdd0b06b89769ecee6a4ea2721a458c66",
|
||||
"nullifierHash": "0x1f048c38e166e09fec79454a2b6d4169051ab640e26a28571036bffad9afd0f8",
|
||||
"to": "0x40c3d1656a26c9266f4a10fed0d87eff79f54e64",
|
||||
"fee": "88446881087520000"
|
||||
},
|
||||
{
|
||||
"blockNumber": 6895895,
|
||||
"transactionHash": "0x73bab36402644d065a6ff5bb805416538c302c2460f186302b27bb23f6e0cde1",
|
||||
"nullifierHash": "0x1e3df17b5fb58f51aa17b247c4ef4e201b8017e4633908f97cb8e885ba447657",
|
||||
"to": "0x7d98a740517586b3f2b369015585c21d02db117e",
|
||||
"fee": "0"
|
||||
},
|
||||
{
|
||||
"blockNumber": 6895899,
|
||||
"transactionHash": "0x6cbec76fc1d86a3d9a5ff9d72dcd09a117fc814d66867d35751c5f4a3bc6136e",
|
||||
"nullifierHash": "0x01044fd0ffb372aa0ce7b91c0b8d41a68142621f76653d0d466c69fa49f95658",
|
||||
"to": "0x7d98a740517586b3f2b369015585c21d02db117e",
|
||||
"fee": "0"
|
||||
},
|
||||
{
|
||||
"blockNumber": 6895903,
|
||||
"transactionHash": "0xd8693cb67c6f668008b4887736b638903b7fe3debed3f38b900424d1456230d4",
|
||||
"nullifierHash": "0x1998975018dfb4c3b946685aaf9557faea15152d4cade2255d4127c1aa0a614c",
|
||||
"to": "0x7d98a740517586b3f2b369015585c21d02db117e",
|
||||
"fee": "0"
|
||||
},
|
||||
{
|
||||
"blockNumber": 8414382,
|
||||
"transactionHash": "0x06fc1138e6bc20ec3d41fb35785810fdfd5160ab05b04b38a752158938431c62",
|
||||
"nullifierHash": "0x22b9b8581092ee88e8cf1d8c214403ba3008383e86f6425f780b562e11f5b236",
|
||||
"to": "0xa382e9af881ea84010847922cad4307a83fbfcc7",
|
||||
"fee": "0"
|
||||
},
|
||||
{
|
||||
"blockNumber": 8415797,
|
||||
"transactionHash": "0x10e1e7d474ec670dc05e040ab1a9349116d3faf2425c68a98eff6ccf8386fb21",
|
||||
"nullifierHash": "0x00763202337be87dc9e606012b3aa9a2dac9f210f54db613a91c75d89183426d",
|
||||
"to": "0x68356fd2f92a96040c312fed8ffd4569d1fcde59",
|
||||
"fee": "0"
|
||||
}
|
||||
]
|
||||
485
cli.js
485
cli.js
@ -29,16 +29,11 @@ const tornadoProxyAbi = require('./abis/TornadoProxy.abi.json');
|
||||
const tornadoInstanceAbi = require('./abis/Instance.abi.json');
|
||||
const relayerRegistryAbi = require("./abis/RelayerRegistry.abi.json");
|
||||
const relayerAggregatorAbi = require('./abis/Aggregator.abi');
|
||||
const tornadoGovernanceAbi = require("./abis/Governance.abi.json");
|
||||
const stakingRewardsAbi = require("./abis/StakingRewards.abi.json");
|
||||
|
||||
const relayerAggregatorAddress = config.deployments[`netId1`].relayerAggregator;
|
||||
const relayerRegistryAddress = config.deployments[`netId1`].relayerRegistry;
|
||||
const relayerRegistryDeployedBlockNumber = config.deployments["netId1"].relayerRegistryDeployedBlockNumber;
|
||||
const relayerSubdomains = Object.values(config.deployments).map(({ ensSubdomainKey }) => ensSubdomainKey);
|
||||
const tornTokenAddress = "0x77777FeDdddFfC19Ff86DB637967013e6C6A116C";
|
||||
const governanceAddress = "0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce";
|
||||
const stakingRewardsAddress = "0x5B3f656C80E8ddb9ec01Dd9018815576E9238c29";
|
||||
|
||||
/** @typedef {import ("web3-eth-contract").Contract} Web3Contract */
|
||||
/** @typedef {import ("web3-eth").Eth} Web3Eth */
|
||||
@ -73,9 +68,6 @@ const stakingRewardsAddress = "0x5B3f656C80E8ddb9ec01Dd9018815576E9238c29";
|
||||
* @property {Web3Contract} [tornadoInstanceContract] Tornado cash instance contract for selected pool (chain/currency/value)
|
||||
* @property {Web3Contract} [tornadoProxyContract] Tornado cash proxy contract for selected chain
|
||||
* @property {Web3Contract} [tornadoTokenInstanceContract] Tornado Cash instance contract for selected token pool (for ERC20 token mixing pools, e.g. DAI)
|
||||
* @property {Web3Contract} [governanceContract] Tornado Cash Governance contract instance to access staking/voting functionality
|
||||
* @property {Web3Contract} [tornTokenContract] TORN token contract instance to approve/send tokens
|
||||
* @property {Web3Contract} [stakingRewardsContract] Staking rewards contract to distribute TORN tokens earned by Tornado protocol between TORN stakers
|
||||
* @property {string} netName Network (chain) human-readable name
|
||||
* @property {string} netSymbol Network main token symbol (ETH for Ethereum mainnet and so on)
|
||||
* @property {string} netId Network (chain) ID
|
||||
@ -101,9 +93,6 @@ const globals =
|
||||
feeOracle: undefined,
|
||||
tornadoInstanceContract: undefined,
|
||||
tornadoProxyContract: undefined,
|
||||
governanceContract: undefined,
|
||||
tornTokenContract: undefined,
|
||||
stakingRewardsContract: undefined,
|
||||
netName: "Ethereum",
|
||||
netSymbol: "ETH",
|
||||
netId: 1
|
||||
@ -300,7 +289,8 @@ async function generateTransaction(to, encodedData, value = 0, txType = 'other')
|
||||
/**
|
||||
* Create deposit object from secret and nullifier
|
||||
*/
|
||||
function createDeposit({ nullifier, secret }) {
|
||||
function createDeposit({ nullifier, secret })
|
||||
{
|
||||
let deposit = { nullifier, secret };
|
||||
deposit.preimage = Buffer.concat([deposit.nullifier.leInt2Buff(31), deposit.secret.leInt2Buff(31)]);
|
||||
deposit.commitment = pedersenHash(deposit.preimage);
|
||||
@ -340,7 +330,8 @@ async function backupInvoice({ currency, amount, netId, commitmentNote, invoiceS
|
||||
* @param currency Сurrency
|
||||
* @param amount Deposit amount
|
||||
*/
|
||||
async function createInvoice({ currency, amount, chainId }) {
|
||||
async function createInvoice({ currency, amount, chainId })
|
||||
{
|
||||
const deposit = createDeposit({
|
||||
nullifier: rbigint(31),
|
||||
secret: rbigint(31)
|
||||
@ -367,7 +358,7 @@ async function createInvoice({ currency, amount, chainId }) {
|
||||
async function deposit({ currency, amount, commitmentNote }) {
|
||||
currency = currency.toLowerCase();
|
||||
|
||||
const { signerAddress, tornadoProxyAddress, tornadoInstanceAddress, tornadoProxyContract, instanceTokenAddress, tornadoTokenInstanceContract, netSymbol, netId } = globals;
|
||||
const { signerAddress, tornadoProxyAddress, tornadoInstanceAddress, tornadoProxyContract, instanceTokenAddress, tornadoTokenInstanceContract, netSymbol, netId } = globals;
|
||||
assert(signerAddress != null, 'Error! Private key not found. Please provide PRIVATE_KEY in .env file or as command argument, if you deposit');
|
||||
let commitment, noteString;
|
||||
if (!commitmentNote) {
|
||||
@ -397,14 +388,14 @@ async function deposit({ currency, amount, commitmentNote }) {
|
||||
// a token
|
||||
await printERC20Balance({ address: tornadoInstanceAddress, name: 'Tornado contract' });
|
||||
await printERC20Balance({ address: signerAddress, name: 'Sender account' });
|
||||
const decimals = config.deployments[`netId${netId}`]['tokens'][currency].decimals;
|
||||
const decimals = config.deployments[`netId${netId}`]['tokens'][currency].decimals;
|
||||
const tokenAmount = fromDecimals({ amount, decimals });
|
||||
|
||||
const allowance = await tornadoTokenInstanceContract.methods.allowance(signerAddress, tornadoProxyAddress).call({ from: signerAddress });
|
||||
console.log('Current allowance is', fromWei(allowance));
|
||||
if (toBN(allowance).lt(toBN(tokenAmount))) {
|
||||
console.log('Approving tokens for deposit');
|
||||
await generateTransaction(tornTokenAddress, tornadoTokenInstanceContract.methods.approve(tornadoProxyAddress, tokenAmount).call(), 0, 'send')
|
||||
await generateTransaction(instanceTokenAddress, tornadoTokenInstanceContract.methods.approve(tornadoProxyAddress, tokenAmount).encodeABI());
|
||||
}
|
||||
|
||||
console.log('Submitting deposit transaction');
|
||||
@ -434,7 +425,8 @@ async function deposit({ currency, amount, commitmentNote }) {
|
||||
* @param {number} amount Tornado instance amount, like 0.1 (ETH or BNB) or 10
|
||||
* @return {Promise<MerkleProof>} Calculated valid merkle tree (proof)
|
||||
*/
|
||||
async function generateMerkleProof(deposit, currency, amount) {
|
||||
async function generateMerkleProof(deposit, currency, amount)
|
||||
{
|
||||
const { web3Instance, multiCallAddress, tornadoInstanceContract } = globals;
|
||||
|
||||
// Get all deposit events from smart contract and assemble merkle tree from them
|
||||
@ -481,7 +473,8 @@ async function generateMerkleProof(deposit, currency, amount) {
|
||||
* @param {MerkleProof} [args.merkleProof] Valid merkle tree proof
|
||||
* @returns {Promise<ProofData>} Proof data
|
||||
*/
|
||||
async function generateProof({ deposit, currency, amount, recipient, relayerAddress = 0, fee = 0, refund = 0, merkleProof }) {
|
||||
async function generateProof({ deposit, currency, amount, recipient, relayerAddress = 0, fee = 0, refund = 0, merkleProof })
|
||||
{
|
||||
// Compute merkle proof of our commitment
|
||||
if (merkleProof === undefined)
|
||||
merkleProof = await generateMerkleProof(deposit, currency, amount);
|
||||
@ -532,9 +525,11 @@ async function generateProof({ deposit, currency, amount, recipient, relayerAddr
|
||||
* @param noteString Note to withdraw
|
||||
* @param recipient Recipient address
|
||||
*/
|
||||
async function withdraw({ deposit, currency, amount, recipient, relayerURL, refund, privateKey }) {
|
||||
async function withdraw({ deposit, currency, amount, recipient, relayerURL, refund, privateKey })
|
||||
{
|
||||
const { web3Instance, signerAddress, tornadoProxyAddress, requestOptions, feeOracle, tornadoInstanceAddress, tornadoProxyContract, netSymbol, netId, shouldPromptConfirmation } = globals;
|
||||
if (currency === netSymbol.toLowerCase() && refund && refund !== '0') {
|
||||
if (currency === netSymbol.toLowerCase() && refund && refund !== '0')
|
||||
{
|
||||
throw new Error('The ETH purchase is supposed to be 0 for ETH withdrawals');
|
||||
}
|
||||
|
||||
@ -543,66 +538,75 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu
|
||||
else
|
||||
refund = toBN(await feeOracle.fetchRefundInETH(currency.toLowerCase()));
|
||||
|
||||
if (!web3Utils.isAddress(recipient)) {
|
||||
if (!web3Utils.isAddress(recipient))
|
||||
{
|
||||
throw new Error('Recipient address is not valid');
|
||||
}
|
||||
|
||||
const depositInfo = await loadDepositData({ amount, currency, deposit });
|
||||
const allDeposits = loadCachedEvents({ type: "deposit", currency, amount });
|
||||
if ((depositInfo.leafIndex > allDeposits[allDeposits.length - 1].leafIndex - 10)
|
||||
&& allDeposits.length > 10) {
|
||||
&& allDeposits.length > 10)
|
||||
{
|
||||
console.log("\nWARNING: you're trying to withdraw your deposit too early, there are not enough subsequent deposits to ensure good anonymity level. Read: https://docs.tornado.ws/general/guides/opsec.html");
|
||||
if (shouldPromptConfirmation)
|
||||
await promptConfirmation("Continue withdrawal with risks to anonymity? [Y/n]: ")
|
||||
}
|
||||
const withdrawInfo = await loadWithdrawalData({ amount, currency, deposit });
|
||||
if (withdrawInfo) {
|
||||
if(withdrawInfo)
|
||||
{
|
||||
console.error("\nError: note has already been withdrawn. Use `compliance` command to check deposit and withdrawal info.\n");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (privateKey || globals.privateKey) {
|
||||
if (privateKey || globals.privateKey)
|
||||
{
|
||||
// using private key
|
||||
|
||||
// check if the address of recepient matches with the account of provided private key from environment to prevent accidental use of deposit address for withdrawal transaction.
|
||||
assert
|
||||
(
|
||||
recipient.toLowerCase() == signerAddress.toLowerCase(),
|
||||
'Withdrawal recepient mismatches with the account of provided private key from environment file'
|
||||
);
|
||||
(
|
||||
recipient.toLowerCase() == signerAddress.toLowerCase(),
|
||||
'Withdrawal recepient mismatches with the account of provided private key from environment file'
|
||||
);
|
||||
const checkBalance = await web3Instance.getBalance(signerAddress);
|
||||
assert
|
||||
(
|
||||
checkBalance !== 0,
|
||||
'You have 0 balance, make sure to fund account by withdrawing from tornado using relayer first'
|
||||
);
|
||||
(
|
||||
checkBalance !== 0,
|
||||
'You have 0 balance, make sure to fund account by withdrawing from tornado using relayer first'
|
||||
);
|
||||
|
||||
const { proof, args } = await generateProof({ deposit, currency, amount, recipient, refund });
|
||||
|
||||
console.log('Submitting withdraw transaction');
|
||||
await generateTransaction
|
||||
(
|
||||
tornadoProxyAddress,
|
||||
tornadoProxyContract.methods.withdraw(tornadoInstanceAddress, proof, ...args).encodeABI(),
|
||||
toBN(args[5]),
|
||||
'user_withdrawal'
|
||||
);
|
||||
(
|
||||
tornadoProxyAddress,
|
||||
tornadoProxyContract.methods.withdraw(tornadoInstanceAddress, proof, ...args).encodeABI(),
|
||||
toBN(args[5]),
|
||||
'user_withdrawal'
|
||||
);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
let relayerInfo;
|
||||
if (relayerURL) {
|
||||
try {
|
||||
if (relayerURL)
|
||||
{
|
||||
try
|
||||
{
|
||||
relayerURL = new URL(relayerURL).origin;
|
||||
res = await axios.get(relayerURL + '/status', requestOptions);
|
||||
relayerInfo = res.data;
|
||||
} catch (err) {
|
||||
} catch (err)
|
||||
{
|
||||
console.error(err);
|
||||
throw new Error('Cannot get relayer status');
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
const availableRelayers = await getRelayers(netId);
|
||||
if (availableRelayers.length === 0) throw new Error("Cannot automatically pick a relayer to withdraw your note. Provide relayer manually with `--relayer` cmd option or use private key withdrawal")
|
||||
if(availableRelayers.length === 0) throw new Error("Cannot automatically pick a relayer to withdraw your note. Provide relayer manually with `--relayer` cmd option or use private key withdrawal")
|
||||
relayerInfo = pickWeightedRandomRelayer(availableRelayers);
|
||||
relayerURL = "https://" + relayerInfo.hostname
|
||||
console.log(`Selected relayer: ${relayerURL}`)
|
||||
@ -691,10 +695,12 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu
|
||||
}
|
||||
}
|
||||
|
||||
if (currency === netSymbol.toLowerCase()) {
|
||||
if (currency === netSymbol.toLowerCase())
|
||||
{
|
||||
await printETHBalance({ address: recipient, name: 'Recipient' });
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
await printERC20Balance({ address: recipient, name: 'Recipient' });
|
||||
}
|
||||
console.log('Done withdrawal from Tornado Cash');
|
||||
@ -976,9 +982,9 @@ function getCurrentNetworkSymbol(chainId) {
|
||||
*/
|
||||
async function waitForTxReceipt({ txHash, attempts = 60, delay = 1000 }) {
|
||||
let retryAttempt = 0;
|
||||
while (retryAttempt < attempts) {
|
||||
while(retryAttempt < attempts){
|
||||
const result = await globals.web3Instance.getTransactionReceipt(txHash);
|
||||
if (!result?.blockNumber) {
|
||||
if(!result?.blockNumber){
|
||||
retryAttempt++;
|
||||
await sleep(delay);
|
||||
continue;
|
||||
@ -997,31 +1003,31 @@ async function waitForTxReceipt({ txHash, attempts = 60, delay = 1000 }) {
|
||||
* if we can fetch all deposit events from subgraph, we shouldn't require archive node)
|
||||
* @returns {Promise<string>} Full RPC link
|
||||
*/
|
||||
async function selectDefaultRpc(chainId, eventType, isSubgraphAvailable = false) {
|
||||
async function selectDefaultRpc(chainId, eventType, isSubgraphAvailable = false){
|
||||
const candidates = config.deployments[`netId${chainId}`].defaultRpcs;
|
||||
|
||||
for (const candidate of candidates) {
|
||||
for(const candidate of candidates){
|
||||
const localWeb3 = await createWeb3Instance(candidate);
|
||||
|
||||
try {
|
||||
try{
|
||||
if (!(await localWeb3.net.isListening())) throw new Error('Cannot connect to websocket provider');
|
||||
|
||||
if (eventType === "relayer") {
|
||||
if (eventType === "relayer"){
|
||||
const relayerRegistryContract = new localWeb3.Contract(relayerRegistryAbi, relayerRegistryAddress);
|
||||
const registeredRelayers = loadCachedEvents({ type: "relayer" });
|
||||
const registeredRelayers = loadCachedEvents({type: "relayer"});
|
||||
|
||||
if (registeredRelayers.length > 0) {
|
||||
if(registeredRelayers.length > 0){
|
||||
const relayerAggregatorContract = new localWeb3.Contract(relayerAggregatorAbi, relayerAggregatorAddress);
|
||||
const relayerNameHashes = registeredRelayers.map(r => r.ensHash);
|
||||
// Here it checks RPC returndata size limit: when getRelayer function aggregates onchain data for all relayers, returndata size will be big
|
||||
await relayerAggregatorContract.methods.relayersData(relayerNameHashes, relayerSubdomains).call();
|
||||
}
|
||||
|
||||
const lastBlock = await localWeb3.getBlockNumber();
|
||||
const lastCachedBlock = registeredRelayers.length > 0 ? registeredRelayers[registeredRelayers.length - 1].blockNumber : relayerRegistryDeployedBlockNumber;
|
||||
const fromBlock = isSubgraphAvailable ? lastBlock - 1000 : lastCachedBlock;
|
||||
const toBlock = isSubgraphAvailable ? lastBlock : lastCachedBlock + 1000;
|
||||
await relayerRegistryContract.getPastEvents("RelayerRegistered", { fromBlock, toBlock });
|
||||
const lastBlock = await localWeb3.getBlockNumber();
|
||||
const lastCachedBlock = registeredRelayers.length > 0 ? registeredRelayers[registeredRelayers.length - 1].blockNumber : relayerRegistryDeployedBlockNumber;
|
||||
const fromBlock = isSubgraphAvailable ? lastBlock - 1000 : lastCachedBlock;
|
||||
const toBlock = isSubgraphAvailable ? lastBlock : lastCachedBlock + 1000;
|
||||
await relayerRegistryContract.getPastEvents("RelayerRegistered", { fromBlock, toBlock });
|
||||
}
|
||||
else if (eventType === "withdrawal") {
|
||||
const oldTransactionHash = config.deployments[`netId${chainId}`].firstDeploymentTransaction;
|
||||
@ -1042,7 +1048,7 @@ async function selectDefaultRpc(chainId, eventType, isSubgraphAvailable = false)
|
||||
|
||||
console.log("Selected RPC: " + candidate);
|
||||
return candidate;
|
||||
} catch (e) {
|
||||
} catch(e){
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
@ -1056,24 +1062,24 @@ async function selectDefaultRpc(chainId, eventType, isSubgraphAvailable = false)
|
||||
* @param {EventType} eventType Possible type of events in Tornado: deposit, withdrawal or fetch relayers
|
||||
* @returns {Promise<string>} Full subgraph link
|
||||
*/
|
||||
async function selectDefaultGraph(chainId, eventType) {
|
||||
async function selectDefaultGraph(chainId, eventType){
|
||||
let candidates = config.deployments[`netId${chainId}`].subgraphs;
|
||||
if (eventType === "relayer") {
|
||||
if (eventType === "relayer"){
|
||||
if (chainId != 1) throw new Error("Relayer subgraph is available only for mainnet");
|
||||
candidates = config.deployments[`netId${chainId}`].relayerSubgraphs;
|
||||
query = '{ relayers(first: 10) { address, ensName, ensHash, blockRegistration } }'
|
||||
}
|
||||
else if (eventType === "deposit") query = `{ deposits(first: 1, orderBy: timestamp) { blockNumber, index } }`;
|
||||
else if (eventType === "deposit") query = `{ deposits(first: 1, orderBy: timestamp) { blockNumber, index } }`;
|
||||
else query = `{ withdrawals(first: 1, orderBy: timestamp) { timestamp } }`
|
||||
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
const response = await axios.post(candidate, { query }, globals.requestOptions);
|
||||
for(const candidate of candidates) {
|
||||
try{
|
||||
const response = await axios.post(candidate, {query}, globals.requestOptions);
|
||||
const result = response.data.data[`${eventType}s`];
|
||||
if (!result) throw new Error("Invalid response from subgraph");
|
||||
console.log(`Selected subgraph for ${eventType}s - ${candidate}`);
|
||||
return candidate;
|
||||
} catch (e) {
|
||||
} catch(e){
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
@ -1087,7 +1093,7 @@ async function selectDefaultGraph(chainId, eventType) {
|
||||
* @param {string | number} chainId
|
||||
* @returns {Promise<Array<Object>>} List of available relayers
|
||||
*/
|
||||
async function getRelayers(chainId) {
|
||||
async function getRelayers(chainId){
|
||||
console.log("Fetching relayers...");
|
||||
|
||||
const MIN_STAKE_LISTED_BALANCE = '0X1B1AE4D6E2EF500000'; // 500 TORN
|
||||
@ -1131,7 +1137,7 @@ async function getRelayers(chainId) {
|
||||
return validRelayers;
|
||||
}
|
||||
|
||||
async function getAvailableRelayersData(relayers) {
|
||||
async function getAvailableRelayersData(relayers){
|
||||
let statuses = [];
|
||||
for (const relayer of relayers) {
|
||||
try {
|
||||
@ -1144,14 +1150,14 @@ async function getRelayers(chainId) {
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// console.error(`Failed to fetch status for ${relayer.hostname}:`);
|
||||
// console.error(`Failed to fetch status for ${relayer.hostname}:`);
|
||||
}
|
||||
}
|
||||
|
||||
return statuses;
|
||||
}
|
||||
|
||||
const registeredRelayers = await fetchEvents({ type: "relayer" });
|
||||
const registeredRelayers = await fetchEvents({type: "relayer"});
|
||||
// Some relayers can be unregistered and then registrered again
|
||||
const deduplicatedRelayers = registeredRelayers.filter((relayer, index, relayers) => index === relayers.findIndex(r => relayer.ensName === r.ensName));
|
||||
const validRelayers = await getValidRelayers(deduplicatedRelayers, ensSubdomainKey);
|
||||
@ -1223,12 +1229,12 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
*/
|
||||
async function retryPostRequest(url, data, requestOptions, retryAttempts, waitingTimeIncrease = 2000) {
|
||||
let retryAttempt = 0;
|
||||
while (1) {
|
||||
while (1){
|
||||
await sleep(waitingTimeIncrease * retryAttempt);
|
||||
try {
|
||||
try{
|
||||
return await axios.post(url, data, requestOptions);
|
||||
} catch (e) {
|
||||
if (retryAttempt === retryAttempts) throw e;
|
||||
} catch(e){
|
||||
if(retryAttempt === retryAttempts) throw e;
|
||||
retryAttempt++;
|
||||
}
|
||||
}
|
||||
@ -1241,7 +1247,7 @@ async function retryPostRequest(url, data, requestOptions, retryAttempts, waitin
|
||||
* @param {number | string} amount Tornado instance currency amount
|
||||
* @returns {string} Path to cache file
|
||||
*/
|
||||
function getEventsFilePath(eventType, currency, amount) {
|
||||
function getEventsFilePath(eventType, currency, amount){
|
||||
return eventType === "relayer" ? "./cache/relayer/register.json" : `./cache/${globals.netName.toLowerCase()}/${eventType}s_${currency.toLowerCase()}_${amount}.json`;
|
||||
}
|
||||
|
||||
@ -1274,7 +1280,7 @@ function loadCachedEvents({ type, currency, amount }) {
|
||||
* @returns {Promise<Array<Object>>} All events (cached and newly fetched)
|
||||
*/
|
||||
async function fetchEvents({ type, currency, amount }) {
|
||||
if (currency) currency = currency.toLowerCase();
|
||||
if(currency) currency = currency.toLowerCase();
|
||||
if (type === 'withdraw') {
|
||||
type = 'withdrawal';
|
||||
}
|
||||
@ -1286,7 +1292,7 @@ async function fetchEvents({ type, currency, amount }) {
|
||||
const cachedEvents = loadCachedEvents({ type, currency, amount });
|
||||
const startBlock = cachedEvents.length ? cachedEvents[cachedEvents.length - 1].blockNumber + 1 : (type === "relayer" ? relayerRegistryDeployedBlockNumber : instanceDeployedBlockNumber);
|
||||
|
||||
if (type !== "relayer") {
|
||||
if(type !== "relayer"){
|
||||
console.log('Loaded cached', amount, currency.toUpperCase(), type, 'events for', startBlock, 'block');
|
||||
console.log('Fetching', amount, currency.toUpperCase(), type, 'events for', netName, 'network');
|
||||
}
|
||||
@ -1300,9 +1306,9 @@ async function fetchEvents({ type, currency, amount }) {
|
||||
if (type === 'relayer') fetchedEvents.sort((first, second) => first.blockRegistration - second.blockRegistration);
|
||||
|
||||
try {
|
||||
const cachedEvents = loadCachedEvents({ type, currency, amount });
|
||||
const cachedEvents = loadCachedEvents({type, currency, amount});
|
||||
const events = cachedEvents.concat(fetchedEvents);
|
||||
fs.writeFileSync(getEventsFilePath(type, currency, amount), JSON.stringify(events, null, 2), { flag: 'w+', encoding: 'utf-8' });
|
||||
fs.writeFileSync(getEventsFilePath(type, currency, amount), JSON.stringify(events, null, 2), 'utf8');
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
throw new Error('Writing cache file failed:', error);
|
||||
@ -1320,19 +1326,19 @@ async function fetchEvents({ type, currency, amount }) {
|
||||
for (let i = startBlock; i < targetBlock; i += chunks) {
|
||||
let mapFunction;
|
||||
if (type === "relayer")
|
||||
mapFunction = ({ blockNumber, returnValues: { relayer, relayerAddress, ensName } }) => ({ blockNumber, ensHash: relayer, ensName, address: relayerAddress });
|
||||
mapFunction = ({ blockNumber, returnValues: {relayer, relayerAddress, ensName} }) => ({blockNumber, ensHash: relayer, ensName, address: relayerAddress});
|
||||
else if (type === "deposit")
|
||||
mapFunction = ({ blockNumber, transactionHash, returnValues: { commitment, leafIndex, timestamp } }) =>
|
||||
({ blockNumber, transactionHash, commitment, leafIndex: Number(leafIndex), timestamp: Number(timestamp) });
|
||||
else mapFunction = ({ blockNumber, transactionHash, returnValues: { nullifierHash, to, fee } }) => ({ blockNumber, transactionHash, nullifierHash, to, fee });
|
||||
({blockNumber, transactionHash, commitment, leafIndex: Number(leafIndex), timestamp: Number(timestamp)});
|
||||
else mapFunction = ({ blockNumber, transactionHash, returnValues: {nullifierHash, to, fee} }) => ({blockNumber, transactionHash, nullifierHash, to, fee});
|
||||
|
||||
const finalBlock = Math.min(i + chunks - 1, targetBlock);
|
||||
try {
|
||||
const fetchedEvents = await contract.getPastEvents(eventNameInContract, { fromBlock: i, toBlock: finalBlock });
|
||||
try{
|
||||
const fetchedEvents = await contract.getPastEvents(eventNameInContract, {fromBlock: i, toBlock: finalBlock});
|
||||
console.log('Fetched', type === "relayer" ? type : `${amount} ${currency.toUpperCase()} ${type}`, 'events to block:', finalBlock);
|
||||
if (fetchedEvents.length === 0) continue;
|
||||
if(fetchedEvents.length === 0) continue;
|
||||
await updateCache(fetchedEvents.map(mapFunction));
|
||||
} catch (err) {
|
||||
} catch(err){
|
||||
console.error(`Failed fetching ${type} events from node on block ${i}: `, err)
|
||||
process.exit(1);
|
||||
}
|
||||
@ -1367,7 +1373,7 @@ async function fetchEvents({ type, currency, amount }) {
|
||||
address, ensName, ensHash, blockRegistration
|
||||
}
|
||||
}`;
|
||||
mapFunction = ({ blockRegistration, address, ensName, ensHash }) => ({ address, ensHash, ensName, blockNumber: Number(blockRegistration) });
|
||||
mapFunction = ({blockRegistration, address, ensName, ensHash}) => ({address, ensHash, ensName, blockNumber: Number(blockRegistration)});
|
||||
}
|
||||
else if (type === 'deposit') {
|
||||
query = `
|
||||
@ -1376,19 +1382,19 @@ async function fetchEvents({ type, currency, amount }) {
|
||||
blockNumber, transactionHash, commitment, index
|
||||
}
|
||||
}`;
|
||||
mapFunction = ({ blockNumber, transactionHash, commitment, index }) => ({ blockNumber: Number(blockNumber), transactionHash, commitment, leafIndex: Number(index) });
|
||||
mapFunction = ({ blockNumber, transactionHash, commitment, index }) => ({blockNumber: Number(blockNumber), transactionHash, commitment, leafIndex: Number(index)});
|
||||
}
|
||||
else if (type === "withdrawal") {
|
||||
query = `
|
||||
query = `
|
||||
query($currency: String, $amount: String, $blockNumber: Int){
|
||||
withdrawals(orderBy: blockNumber, first: 1000, where: {currency: $currency, amount: $amount, blockNumber${filter}: $blockNumber}) {
|
||||
blockNumber, transactionHash, nullifier, to, fee
|
||||
}
|
||||
}`;
|
||||
mapFunction = ({ blockNumber, transactionHash, nullifier, to, fee }) => ({ blockNumber: Number(blockNumber), transactionHash, nullifierHash: nullifier, to, fee });
|
||||
mapFunction = ({ blockNumber, transactionHash, nullifier, to, fee }) => ({blockNumber: Number(blockNumber), transactionHash, nullifierHash: nullifier, to, fee});
|
||||
}
|
||||
|
||||
const querySubgraph = await retryPostRequest(subgraph, { query, variables }, globals.requestOptions, 3);
|
||||
const querySubgraph = await retryPostRequest(subgraph, {query, variables}, globals.requestOptions, 3);
|
||||
const queryResult = querySubgraph.data.data[`${type}s`];
|
||||
return queryResult.map(mapFunction);
|
||||
} catch (error) {
|
||||
@ -1400,15 +1406,15 @@ async function fetchEvents({ type, currency, amount }) {
|
||||
console.log('Querying latest events from subgraph');
|
||||
const latestBlock = await web3Instance.getBlockNumber();
|
||||
try {
|
||||
for (let i = startBlock; i < latestBlock;) {
|
||||
for (let i = startBlock; i < latestBlock; ) {
|
||||
let result = await queryFromGraph(i);
|
||||
if (Object.keys(result).length === 0) break;
|
||||
const resultBlockNumber = result[result.length - 1].blockNumber;
|
||||
while (result.length > 0 && result[result.length - 1].blockNumber === resultBlockNumber) result.pop();
|
||||
while(result.length > 0 && result[result.length - 1].blockNumber === resultBlockNumber) result.pop();
|
||||
result = result.concat(await queryFromGraph(resultBlockNumber, ""));
|
||||
await updateCache(result);
|
||||
i = resultBlockNumber;
|
||||
console.log('Fetched', type === 'relayer' ? type : `${amount} ${currency.toUpperCase()} ${type}`, 'events to block:', Number(resultBlockNumber));
|
||||
console.log('Fetched', type === 'relayer' ? type : `${amount} ${currency.toUpperCase()} ${type}`, 'events to block:', Number(resultBlockNumber));
|
||||
}
|
||||
} catch {
|
||||
console.log('Fallback to web3 events');
|
||||
@ -1423,7 +1429,7 @@ async function fetchEvents({ type, currency, amount }) {
|
||||
await syncEvents();
|
||||
}
|
||||
|
||||
const updatedEvents = loadCachedEvents({ type, currency, amount })
|
||||
const updatedEvents = loadCachedEvents({type, currency, amount})
|
||||
const updatedBlock = updatedEvents[updatedEvents.length - 1].blockNumber;
|
||||
console.log('Cache updated for Tornado', type === 'relayer' ? type : `${amount} ${currency.toUpperCase()} instance to block`, updatedBlock, 'successfully');
|
||||
console.log(`Total ${type}s:`, updatedEvents.length - 1);
|
||||
@ -1434,10 +1440,12 @@ async function fetchEvents({ type, currency, amount }) {
|
||||
* Parses Tornado Cash note
|
||||
* @param {string} noteString the note
|
||||
*/
|
||||
function parseNote(noteString) {
|
||||
function parseNote(noteString)
|
||||
{
|
||||
const noteRegex = /tornado-(?<currency>\w+)-(?<amount>[\d.]+)-(?<netId>\d+)-0x(?<note>[0-9a-fA-F]{124})/g;
|
||||
const match = noteRegex.exec(noteString);
|
||||
if (!match) {
|
||||
if (!match)
|
||||
{
|
||||
throw new Error('The note has invalid format');
|
||||
}
|
||||
|
||||
@ -1480,7 +1488,7 @@ function parseInvoice(invoiceString) {
|
||||
|
||||
async function loadDepositData({ amount, currency, deposit }) {
|
||||
const { web3Instance, tornadoInstanceContract } = globals;
|
||||
const cachedEvents = await fetchEvents({ type: 'deposit', currency, amount });
|
||||
const cachedEvents = await fetchEvents({ type: 'deposit', currency, amount});
|
||||
const depositEvent = cachedEvents.find(event => event.commitment === deposit.commitmentHex);
|
||||
if (!depositEvent) throw new Error('There is no related deposit, the note is invalid');
|
||||
|
||||
@ -1537,119 +1545,13 @@ async function promptConfirmation(query) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stake TORN tokens in Governance contract to earn rewards from withdrawals and perticate in Governance with voting
|
||||
* @param {number | string} amount Amount of tokens. Can be fractional, for example stake 100.8432 TORN
|
||||
*/
|
||||
async function stakeTorn(amount) {
|
||||
const { tornTokenContract, governanceContract, signerAddress } = globals;
|
||||
const tokenAmount = fromDecimals({ amount, decimals: 18 });
|
||||
const allowance = await tornTokenContract.methods.allowance(signerAddress, governanceAddress).call();
|
||||
console.log('Current TORN allowance is', fromWei(allowance));
|
||||
if (toBN(allowance).lt(toBN(tokenAmount))) {
|
||||
console.log('Approving tokens for stake');
|
||||
await generateTransaction(tornTokenAddress, tornTokenContract.methods.approve(governanceAddress, tokenAmount).encodeABI(), 0, 'send');
|
||||
}
|
||||
console.log("Sending stake transaction...");
|
||||
await generateTransaction(governanceAddress, governanceContract.methods.lockWithApproval(tokenAmount).encodeABI(), 0, 'send');
|
||||
|
||||
const stakedAmount = await governanceContract.methods.lockedBalance(signerAddress).call();
|
||||
console.log("Staked successfull: your current stake balance is", fromWei(stakedAmount), "TORN");
|
||||
}
|
||||
|
||||
/**
|
||||
* Withdraw TORN tokens from Governance staking (without rewards)
|
||||
* @param {number | string} amount Amount of TORn tokens to withdraw, can be fractional
|
||||
*/
|
||||
async function unstakeTorn(amount) {
|
||||
const { governanceContract, signerAddress } = globals;
|
||||
const tokenAmount = fromDecimals({ amount, decimals: 18 });
|
||||
const stakedAmount = await governanceContract.methods.lockedBalance(signerAddress).call();
|
||||
if (toBN(stakedAmount).lt(toBN(tokenAmount))) throw new Error(`Not enough tokens in stake. You have ${fromWei(stakedAmount)} tokens, but you're trying to withdraw ${amount}.`);
|
||||
|
||||
console.log("Sending unstake transaction...");
|
||||
await generateTransaction(governanceAddress, governanceContract.methods.unlock(tokenAmount).encodeABI(), 0, 'send');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate voting power in Tornado Cash governance to another address: delegatee can vote and create proposals on your behalf
|
||||
* @param {string} address Delegatee address
|
||||
*/
|
||||
async function delegate(address) {
|
||||
if (!web3Utils.isAddress(address)) throw new Error("Cannot delegate: invalid delegatee address provided");
|
||||
|
||||
await generateTransaction(governanceAddress, globals.governanceContract.methods.delegate(address).encodeABI(), 0, 'send');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove Tornado Cash governance delegation. After doing it, nobody can vote or create proposals on your behalf
|
||||
*/
|
||||
async function undelegate() {
|
||||
const { governanceContract, signerAddress } = globals;
|
||||
const currentDelegatee = await governanceContract.methods.delegatedTo(signerAddress).call();
|
||||
if (currentDelegatee.toLowerCase() === "0x0000000000000000000000000000000000000000") {
|
||||
console.log("No actual delegatee: already undelegated");
|
||||
return;
|
||||
}
|
||||
|
||||
await generateTransaction(governanceAddress, governanceContract.methods.undelegate().encodeABI(), 0, 'send');
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads actual data from contract and prints information about Tornado Cash staking for specified address: stake amount, unclaimed staking rewards and voting power delegation
|
||||
* @param {string} address
|
||||
*/
|
||||
async function printStakeInfo(address) {
|
||||
if (!web3Utils.isAddress(address)) throw new Error("Cannot check stake info: invalid address provided");
|
||||
|
||||
const { governanceContract, stakingRewardsContract } = globals;
|
||||
|
||||
const stakedAmount = await governanceContract.methods.lockedBalance(address).call();
|
||||
const rewardsAmount = await stakingRewardsContract.methods.checkReward(address).call();
|
||||
const delegatee = await governanceContract.methods.delegatedTo(address).call();
|
||||
|
||||
console.log('\n====================Staking info====================');
|
||||
console.log('Account :', address);
|
||||
console.log('Staked balance :', `${fromWei(stakedAmount)} TORN`);
|
||||
console.log('Unclaimed rewards :', `${fromWei(rewardsAmount)} TORN`);
|
||||
console.log('Delegation status :', delegatee.toLowerCase() === "0x0000000000000000000000000000000000000000" ? "not delegated" : `voting power delegated to ${delegatee}`);
|
||||
console.log('====================================================', '\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Vote in governance from signer address with all staked tokens for or against specified proposal by ID
|
||||
* @param {string | number} proposalId Proposal ID
|
||||
* @param {string} decision For or against proposal ("yes" is for, "no" is against, true is for, false is against)
|
||||
*/
|
||||
async function vote(proposalId, decision){
|
||||
const { governanceContract, signerAddress } = globals;
|
||||
|
||||
const signerStakedBalance = await governanceContract.methods.lockedBalance(signerAddress).call();
|
||||
if (signerStakedBalance.lte(0)) throw new Error("You have no staked balance, therefore you cannot vote.");
|
||||
|
||||
let support;
|
||||
if (decision === "yes" || decision === "for") support = true;
|
||||
else if (decision === "no" || decision === "against") support = false;
|
||||
else throw new Error("Invalid user decision: cannot vote for or against proposal");
|
||||
|
||||
|
||||
await generateTransaction(governanceAddress, governanceContract.methods.castVote(Number(proposalId), support).encodeABI(), 0, 'send');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate transaction to claim staking rewards from Tornado Cash staking
|
||||
*/
|
||||
async function claimStakingRewards(){
|
||||
await generateTransaction(stakingRewardsAddress, globals.stakingRewardsContract.methods.getReward().encodeABI(), 0, 'send');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create web3 eth instance with provider using RPC link
|
||||
* @param {string} rpc Full RPC link
|
||||
* @returns {Promise<Web3Eth>} Initialized Web3 instance object
|
||||
*/
|
||||
async function createWeb3Instance(rpc) {
|
||||
const { torPort } = globals;
|
||||
async function createWeb3Instance(rpc){
|
||||
const { torPort }= globals;
|
||||
|
||||
let web3;
|
||||
if (torPort && rpc.startsWith('https')) {
|
||||
@ -1670,26 +1572,12 @@ async function createWeb3Instance(rpc) {
|
||||
web3 = new Web3(new Web3.providers.WebsocketProvider(rpc, web3Options), null, { transactionConfirmationBlocks: 1 });
|
||||
} else {
|
||||
console.log(`Connecting to remote node ${rpc}`);
|
||||
web3 = new Web3(new Web3.providers.HttpProvider(rpc, { timeout: 10000, keepAlive: true }), null, { transactionConfirmationBlocks: 1 });
|
||||
web3 = new Web3(new Web3.providers.HttpProvider(rpc, {timeout: 10000, keepAlive: true}), null, { transactionConfirmationBlocks: 1 });
|
||||
}
|
||||
|
||||
return web3.eth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize TORN contracts and then call initNetwork
|
||||
* @param {Object} args Arguments to pass to initNetwork function
|
||||
*/
|
||||
async function initTorn(args) {
|
||||
initPreferences(args);
|
||||
await initNetwork({ ...args, chainId: 1, onlyRpc: true });
|
||||
const { web3Instance } = globals;
|
||||
|
||||
globals.governanceContract = new web3Instance.Contract(tornadoGovernanceAbi, governanceAddress);
|
||||
globals.tornTokenContract = new web3Instance.Contract(erc20Abi, tornTokenAddress);
|
||||
globals.stakingRewardsContract = new web3Instance.Contract(stakingRewardsAbi, stakingRewardsAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init web3 network from user parameters for all program
|
||||
* @param {Object} args Arguments
|
||||
@ -1701,7 +1589,7 @@ async function initTorn(args) {
|
||||
* @param {EventType} [args.eventType] Applicable event type for user actions
|
||||
* @param {string} [relayer] User-provided relayer link
|
||||
*/
|
||||
async function initNetwork({ rpc, chainId, privateKey, torPort, onlyRpc, eventType, relayer }) {
|
||||
async function initNetwork({rpc, chainId, privateKey, torPort, onlyRpc, eventType, relayer}) {
|
||||
|
||||
if (torPort) {
|
||||
globals.torPort = torPort;
|
||||
@ -1714,7 +1602,7 @@ async function initNetwork({ rpc, chainId, privateKey, torPort, onlyRpc, eventTy
|
||||
|
||||
if (chainId && !rpc) {
|
||||
const subgraphUrl = onlyRpc ? null : await selectDefaultGraph(chainId, eventType)
|
||||
rpc = await selectDefaultRpc(chainId, eventType, !!subgraphUrl)
|
||||
rpc = await selectDefaultRpc(chainId, eventType, subgraphUrl)
|
||||
}
|
||||
|
||||
globals.web3Instance = await createWeb3Instance(rpc)
|
||||
@ -1725,7 +1613,7 @@ async function initNetwork({ rpc, chainId, privateKey, torPort, onlyRpc, eventTy
|
||||
globals.feeOracle = Number(globals.netId) === 1 ? new TornadoFeeOracleV4(globals.netId, rpc) : new TornadoFeeOracleV5(globals.netId, rpc);
|
||||
|
||||
// Create web3 instance to fetch relayer on mainnet, if user want to get relayers list or withdraw, but he didn't provide relayer link
|
||||
if (!relayer && !privateKey && (eventType === 'relayer' || eventType === 'withdrawal')) {
|
||||
if (!relayer && !privateKey && (eventType === 'relayer' || eventType === 'withdrawal')){
|
||||
if (globals.netId === 1) globals.relayerWeb3Instance = globals.web3Instance;
|
||||
else {
|
||||
const subgraphUrl = onlyRpc ? null : await selectDefaultGraph(1, 'relayer');
|
||||
@ -1769,7 +1657,7 @@ async function initNetwork({ rpc, chainId, privateKey, torPort, onlyRpc, eventTy
|
||||
* @param {boolean} [userPreferences.nonconfirmation] Don't ask for confirmation for crucial actions
|
||||
* @param {boolean} [userPreferences.localMode] Don't submit signed transactions to blockchain (remote nodes)
|
||||
*/
|
||||
function initPreferences({ nonconfirmation, localMode }) {
|
||||
function initPreferences({nonconfirmation, localMode}){
|
||||
if (nonconfirmation) {
|
||||
console.log("Non-confirmation mode detected: program won't ask confirmation for crucial actions")
|
||||
globals.shouldPromptConfirmation = false;
|
||||
@ -1783,11 +1671,11 @@ function initPreferences({ nonconfirmation, localMode }) {
|
||||
/**
|
||||
* Init web3, all Tornado contracts, and snark
|
||||
*/
|
||||
async function init({ rpc, chainId, currency = 'dai', amount = '100', privateKey, torPort, onlyRpc, nonconfirmation, localMode, eventType, relayer }) {
|
||||
async function init({ rpc, chainId, currency = 'dai', amount = '100', privateKey, torPort, onlyRpc, nonconfirmation, localMode, eventType, relayer}) {
|
||||
currency = currency.toLowerCase()
|
||||
|
||||
initPreferences({ nonconfirmation, localMode });
|
||||
await initNetwork({ rpc, chainId, privateKey, torPort, onlyRpc, eventType, relayer });
|
||||
initPreferences({nonconfirmation, localMode});
|
||||
await initNetwork({rpc, chainId, privateKey, torPort, onlyRpc, eventType, relayer});
|
||||
|
||||
const { netId, web3Instance } = globals;
|
||||
// console.log(netId, chainId);
|
||||
@ -1841,7 +1729,7 @@ async function main() {
|
||||
.action(async (noteString, recipient, refund) => {
|
||||
const { currency, amount, netId, deposit } = parseNote(noteString);
|
||||
|
||||
await init({ ...program, chainId: netId, currency, amount, eventType: 'withdrawal' });
|
||||
await init({...program, chainId: netId, currency, amount, eventType: 'withdrawal'});
|
||||
|
||||
await withdraw({
|
||||
deposit,
|
||||
@ -1853,94 +1741,6 @@ async function main() {
|
||||
relayerURL: program.relayer
|
||||
});
|
||||
});
|
||||
program
|
||||
.command('compliance <note>')
|
||||
.description(
|
||||
'Shows the deposit and withdrawal of the provided note. This might be necessary to show the origin of assets held in your withdrawal address.'
|
||||
)
|
||||
.action(async (noteString) => {
|
||||
|
||||
const { currency, amount, netId, deposit } = parseNote(noteString);
|
||||
|
||||
await init({ ...program, chainId: netId, currency, amount, eventType: 'withdrawal', relayer: "dummy" });
|
||||
|
||||
const depositInfo = await loadDepositData({ amount, currency, deposit });
|
||||
const withdrawInfo = await loadWithdrawalData({ amount, currency, deposit });
|
||||
|
||||
const depositDate = new Date(depositInfo.timestamp * 1000);
|
||||
console.log('\n=============Deposit=================');
|
||||
console.log('Deposit :', amount, currency.toUpperCase());
|
||||
console.log('Date :', depositDate.toLocaleDateString(), depositDate.toLocaleTimeString());
|
||||
console.log('From :', `https://${getExplorerLink()}/address/${depositInfo.from}`);
|
||||
console.log('Transaction :', `https://${getExplorerLink()}/tx/${depositInfo.txHash}`);
|
||||
console.log('Commitment :', depositInfo.commitment);
|
||||
console.log('Spent :', depositInfo.isSpent);
|
||||
console.log('=====================================', '\n');
|
||||
|
||||
if (!depositInfo.isSpent) {
|
||||
console.log('The note was not spent!');
|
||||
return;
|
||||
}
|
||||
|
||||
const withdrawalDate = new Date(withdrawInfo.timestamp * 1000);
|
||||
console.log('\n=============Withdrawal==============');
|
||||
console.log('Withdrawal :', withdrawInfo.amount, currency);
|
||||
console.log('Relayer Fee :', withdrawInfo.fee, currency);
|
||||
console.log('Date :', withdrawalDate.toLocaleDateString(), withdrawalDate.toLocaleTimeString());
|
||||
console.log('To :', `https://${getExplorerLink()}/address/${withdrawInfo.to}`);
|
||||
console.log('Transaction :', `https://${getExplorerLink()}/tx/${withdrawInfo.txHash}`);
|
||||
console.log('Nullifier :', withdrawInfo.nullifier);
|
||||
console.log('=====================================', '\n');
|
||||
});
|
||||
program
|
||||
.command("stake <amount>")
|
||||
.description("Stake TORN tokens in Governance contract to earn rewards and vote. Requires private key")
|
||||
.action(async (amount) => {
|
||||
await initTorn(program);
|
||||
await stakeTorn(Number(amount));
|
||||
})
|
||||
program
|
||||
.command("unstake <amount>")
|
||||
.description("Unstake TORN tokens (withdraw from Governance staking). Requires private key")
|
||||
.action(async (amount) => {
|
||||
await initTorn(program);
|
||||
await unstakeTorn(amount);
|
||||
})
|
||||
program
|
||||
.command("checkStake <address>")
|
||||
.description("Check Tornado Cash staking information about provided address: stake amount, unclaimed staking rewards and voting power delegation")
|
||||
.action(async (address) => {
|
||||
await initTorn(program);
|
||||
await printStakeInfo(address);
|
||||
});
|
||||
program
|
||||
.command("claim")
|
||||
.description("Claim staking rewards. Requires private key")
|
||||
.action(async () => {
|
||||
await initTorn(program);
|
||||
await claimStakingRewards();
|
||||
})
|
||||
program
|
||||
.command("vote <decision> <proposal_id>")
|
||||
.description("Vote for or against Tornado Cash governance proposal with all staked tokens. Decision can be `yes/for` or `no/against`. To change your vote, just use this function again with different decision")
|
||||
.action(async (decision, proposalId) => {
|
||||
await initTorn(program);
|
||||
await vote(proposalId, decision.toLowerCase())
|
||||
})
|
||||
program
|
||||
.command("delegate <address>")
|
||||
.description("Delegate voting power to another address. Requires private key")
|
||||
.action(async (address) => {
|
||||
await initTorn(program);
|
||||
await delegate(address);
|
||||
});
|
||||
program
|
||||
.command("undelegate")
|
||||
.description("Remove current delegatee (nobody can vote or create proposals on your behalf after it). Requires private key")
|
||||
.action(async () => {
|
||||
await initTorn(program);
|
||||
await undelegate();
|
||||
});
|
||||
program
|
||||
.command('createNote <currency> <amount> <chainId>')
|
||||
.description(
|
||||
@ -1970,15 +1770,15 @@ async function main() {
|
||||
.command("listRelayers <chain_id>")
|
||||
.description("Check available relayers on selected chain. If you wantue non-default RPC, you should provide ONLY mainnet RPC urls")
|
||||
.action(async (chainId) => {
|
||||
await initNetwork({ ...program, chainId: 1, eventType: 'relayer' })
|
||||
await initNetwork({...program, chainId: 1, eventType: 'relayer'})
|
||||
const availableRelayers = await getRelayers(chainId);
|
||||
console.log("There are " + availableRelayers.length + " available relayers")
|
||||
|
||||
for (const relayer of availableRelayers) {
|
||||
for(const relayer of availableRelayers){
|
||||
console.log({
|
||||
'hostname': 'https://' + relayer.hostname + '/',
|
||||
'ensName': relayer.ensName,
|
||||
'stakeBalance': Number(web3Utils.fromWei(relayer.stakeBalance, 'ether')).toFixed(2) + " TORN",
|
||||
'stakeBalance': Number(web3Utils.fromWei(relayer.stakeBalance, 'ether')).toFixed(2)+" TORN",
|
||||
'tornadoServiceFee': relayer.tornadoServiceFee + "%"
|
||||
});
|
||||
}
|
||||
@ -2014,13 +1814,52 @@ async function main() {
|
||||
await initNetwork(program);
|
||||
await submitTransaction(signedTX);
|
||||
});
|
||||
program
|
||||
.command('compliance <note>')
|
||||
.description(
|
||||
'Shows the deposit and withdrawal of the provided note. This might be necessary to show the origin of assets held in your withdrawal address.'
|
||||
)
|
||||
.action(async (noteString) => {
|
||||
|
||||
const { currency, amount, netId, deposit } = parseNote(noteString);
|
||||
|
||||
await init({ ...program, chainId: netId, currency, amount, eventType: 'withdrawal', relayer: "dummy"});
|
||||
|
||||
const depositInfo = await loadDepositData({ amount, currency, deposit });
|
||||
const withdrawInfo = await loadWithdrawalData({ amount, currency, deposit });
|
||||
|
||||
const depositDate = new Date(depositInfo.timestamp * 1000);
|
||||
console.log('\n=============Deposit=================');
|
||||
console.log('Deposit :', amount, currency.toUpperCase());
|
||||
console.log('Date :', depositDate.toLocaleDateString(), depositDate.toLocaleTimeString());
|
||||
console.log('From :', `https://${getExplorerLink()}/address/${depositInfo.from}`);
|
||||
console.log('Transaction :', `https://${getExplorerLink()}/tx/${depositInfo.txHash}`);
|
||||
console.log('Commitment :', depositInfo.commitment);
|
||||
console.log('Spent :', depositInfo.isSpent);
|
||||
console.log('=====================================', '\n');
|
||||
|
||||
if (!depositInfo.isSpent) {
|
||||
console.log('The note was not spent!');
|
||||
return;
|
||||
}
|
||||
|
||||
const withdrawalDate = new Date(withdrawInfo.timestamp * 1000);
|
||||
console.log('\n=============Withdrawal==============');
|
||||
console.log('Withdrawal :', withdrawInfo.amount, currency);
|
||||
console.log('Relayer Fee :', withdrawInfo.fee, currency);
|
||||
console.log('Date :', withdrawalDate.toLocaleDateString(), withdrawalDate.toLocaleTimeString());
|
||||
console.log('To :', `https://${getExplorerLink()}/address/${withdrawInfo.to}`);
|
||||
console.log('Transaction :', `https://${getExplorerLink()}/tx/${withdrawInfo.txHash}`);
|
||||
console.log('Nullifier :', withdrawInfo.nullifier);
|
||||
console.log('=====================================', '\n');
|
||||
});
|
||||
program
|
||||
.command('syncEvents <type> <currency> <amount> [chain_id]')
|
||||
.description('Sync the local cache file of deposit / withdrawal events for specific currency.')
|
||||
.action(async (type, currency, amount, chainId) => {
|
||||
console.log('Starting event sync command');
|
||||
|
||||
await init({ ...program, currency, amount, chainId });
|
||||
await init({ ...program, currency, amount, chainId});
|
||||
if (type === "withdraw") type === "withdrawal";
|
||||
|
||||
const cachedEvents = await fetchEvents({ type, currency, amount });
|
||||
@ -2060,7 +1899,7 @@ async function main() {
|
||||
netId = parse.netId;
|
||||
|
||||
console.log('\n=============Note=================');
|
||||
console.log('Network:', getCurrentNetworkName());
|
||||
console.log('Network:', getCurrentNetworkName());
|
||||
console.log('Denomination:', parse.amount, parse.currency.toUpperCase());
|
||||
console.log('Commitment: ', parse.deposit.commitmentHex);
|
||||
console.log('Nullifier Hash: ', parse.deposit.nullifierHex);
|
||||
|
||||
BIN
output/tornado-cli.exe
Normal file
BIN
output/tornado-cli.exe
Normal file
Binary file not shown.
BIN
tornado-cli.exe
BIN
tornado-cli.exe
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user