Compare commits
17 Commits
e724211f91
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| bb46f01dcf | |||
|
4bfb781eff
|
|||
|
41a9a75036
|
|||
|
8fcb9ed387
|
|||
|
8e84cd651c
|
|||
|
c57631ebfb
|
|||
|
355e1e88ce
|
|||
|
6dcf5cb78f
|
|||
|
b57a27a28f
|
|||
|
0a33404eb4
|
|||
| 212e37d847 | |||
| dbd8eaa654 | |||
| b5afb8d558 | |||
| c3bef6af31 | |||
| 0f44e768fe | |||
| b2270836e4 | |||
| e4b7ced4d3 |
2
.env.example
Normal file
2
.env.example
Normal file
@@ -0,0 +1,2 @@
|
||||
PINATA_API_KEY=
|
||||
PINATA_SECRET_API_KEY=
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@ dist
|
||||
node_modules
|
||||
.husky
|
||||
.nuxt
|
||||
dist.zip
|
||||
1
.npmrc
Normal file
1
.npmrc
Normal file
@@ -0,0 +1 @@
|
||||
@tornado:registry=https://git.tornado.ws/api/packages/tornado-packages/npm/
|
||||
6
.prettierrc
Normal file
6
.prettierrc
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"semi": false,
|
||||
"printWidth": 140
|
||||
}
|
||||
16
README.md
16
README.md
@@ -2,19 +2,29 @@
|
||||
|
||||
## Build Setup
|
||||
|
||||
If you use the latest Node.js version, you should modify your NODE_OPTIONS env
|
||||
|
||||
```bash
|
||||
export NODE_OPTIONS="--openssl-legacy-provider"
|
||||
```
|
||||
|
||||
```bash
|
||||
# install dependencies
|
||||
$ yarn install
|
||||
|
||||
# serve with hot reload at localhost:3000
|
||||
$ yarn dev
|
||||
|
||||
# build for production and launch server
|
||||
$ yarn build
|
||||
$ yarn start
|
||||
|
||||
# generate static project
|
||||
$ yarn generate
|
||||
|
||||
# serve with hot reload at localhost:3000
|
||||
# should do yarn build first if worker files are changed
|
||||
$ yarn dev
|
||||
|
||||
# update cached events from node & subgraphs
|
||||
$ yarn update:events
|
||||
```
|
||||
|
||||
For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org).
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
import { AES, HmacSHA256, enc } from 'crypto-js'
|
||||
import { isEmpty } from 'lodash'
|
||||
import { BigNumber, Contract } from 'ethers'
|
||||
import { poseidon } from '@tornado/circomlib'
|
||||
import { decrypt } from 'eth-sig-util'
|
||||
|
||||
const { AES, HmacSHA256, enc } = require('crypto-js')
|
||||
const { isEmpty } = require('lodash')
|
||||
const { BigNumber } = require('ethers')
|
||||
const { poseidon } = require('circomlib')
|
||||
const { decrypt } = require('eth-sig-util')
|
||||
const { IndexedDB } = require('../services/idb')
|
||||
const { sleep } = require('../utilities/helpers')
|
||||
const { workerEvents, numbers } = require('../constants/worker')
|
||||
const { ExtendedProvider } = require('../services/ether/ExtendedProvider')
|
||||
|
||||
const { POOL_CONTRACT, RPC_LIST, FALLBACK_RPC_LIST } = require('../constants/contracts')
|
||||
const { TornadoPool__factory: TornadoPoolFactory } = require('../_contracts')
|
||||
import { IndexedDB } from './services/idb'
|
||||
import { BatchEventsService } from './services/batch'
|
||||
import { getAllCommitments } from './services/graph'
|
||||
import { ExtendedProvider } from './services/provider'
|
||||
import { POOL_CONTRACT, RPC_LIST, FALLBACK_RPC_LIST, workerEvents, numbers } from './services/constants'
|
||||
import { sleep } from './services/utilities'
|
||||
import { poolAbi } from './services/pool'
|
||||
import { downloadEvents } from './services/downloadEvents'
|
||||
|
||||
const getProviderWithSigner = (chainId) => {
|
||||
return new ExtendedProvider(RPC_LIST[chainId], chainId, FALLBACK_RPC_LIST[chainId])
|
||||
@@ -61,22 +61,75 @@ const initWorker = (chainId) => {
|
||||
setTornadoPool(chainId, provider)
|
||||
}
|
||||
const setTornadoPool = (chainId, provider) => {
|
||||
self.poolContract = TornadoPoolFactory.connect(POOL_CONTRACT[chainId], provider)
|
||||
self.poolContract = new Contract(POOL_CONTRACT[chainId], poolAbi, provider)
|
||||
|
||||
self.BatchEventsService = new BatchEventsService({
|
||||
provider,
|
||||
contract: self.poolContract
|
||||
})
|
||||
}
|
||||
|
||||
const getCommitmentBatch = async ({ blockFrom, blockTo, cachedEvents, withCache }) => {
|
||||
const filter = self.poolContract.filters.NewCommitment()
|
||||
const events = await self.poolContract.queryFilter(filter, blockFrom, blockTo)
|
||||
const events = []
|
||||
|
||||
const commitmentEvents = events.map(({ blockNumber, transactionHash, args }) => ({
|
||||
blockNumber,
|
||||
transactionHash,
|
||||
index: Number(args.index),
|
||||
commitment: args.commitment,
|
||||
encryptedOutput: args.encryptedOutput,
|
||||
}))
|
||||
let { events: graphEvents, lastSyncBlock } = await getAllCommitments({
|
||||
fromBlock: blockFrom,
|
||||
toBlock: blockTo,
|
||||
chainId
|
||||
})
|
||||
|
||||
return commitmentEvents.filter((el) => {
|
||||
if (lastSyncBlock) {
|
||||
graphEvents = graphEvents
|
||||
.filter(({ blockNumber }) => {
|
||||
if (blockFrom && blockTo) {
|
||||
return Number(blockFrom) <= Number(blockNumber) && Number(blockNumber) <= Number(blockTo)
|
||||
} else if (blockTo) {
|
||||
return Number(blockNumber) <= Number(blockTo)
|
||||
}
|
||||
// does not filter by default
|
||||
return true
|
||||
})
|
||||
.map(({ blockNumber, transactionHash, index, commitment, encryptedOutput }) => ({
|
||||
blockNumber,
|
||||
transactionHash,
|
||||
index: Number(index),
|
||||
commitment,
|
||||
encryptedOutput,
|
||||
}))
|
||||
|
||||
console.log({
|
||||
graphEvents
|
||||
})
|
||||
|
||||
events.push(...graphEvents)
|
||||
blockFrom = lastSyncBlock
|
||||
}
|
||||
|
||||
if (!blockTo || blockTo > blockFrom) {
|
||||
let nodeEvents = await self.BatchEventsService.getBatchEvents({
|
||||
fromBlock: blockFrom,
|
||||
toBlock: blockTo,
|
||||
type: 'NewCommitment'
|
||||
})
|
||||
|
||||
if (nodeEvents && nodeEvents.length) {
|
||||
nodeEvents = nodeEvents.map(({ blockNumber, transactionHash, args }) => ({
|
||||
blockNumber,
|
||||
transactionHash,
|
||||
index: Number(args.index),
|
||||
commitment: args.commitment,
|
||||
encryptedOutput: args.encryptedOutput,
|
||||
}))
|
||||
|
||||
console.log({
|
||||
nodeEvents
|
||||
})
|
||||
|
||||
events.push(...nodeEvents)
|
||||
}
|
||||
}
|
||||
|
||||
return events.filter((el) => {
|
||||
if (!withCache && cachedEvents && cachedEvents.length) {
|
||||
return cachedEvents.find((cached) => {
|
||||
return el.transactionHash === cached.transactionHash && el.index === cached.index
|
||||
@@ -113,6 +166,14 @@ const getCommitments = async ({ withCache, lastSyncBlock }) => {
|
||||
return { commitmentEvents: cachedEvents }
|
||||
}
|
||||
blockFrom = newBlockFrom > currentBlock ? currentBlock : newBlockFrom
|
||||
} else {
|
||||
const downloadedEvents = await downloadEvents(`commitments_${self.chainId}.json`, blockFrom)
|
||||
|
||||
if (downloadedEvents.events.length) {
|
||||
cachedEvents.push(...downloadedEvents.events)
|
||||
|
||||
blockFrom = downloadedEvents.lastBlock
|
||||
}
|
||||
}
|
||||
|
||||
const commitmentEvents = await getCommitmentBatch({ blockFrom, blockTo: currentBlock, cachedEvents, withCache })
|
||||
@@ -213,10 +274,7 @@ const getCommitmentEvents = async ({ publicKey, lastSyncBlock, withCache = true
|
||||
}
|
||||
}
|
||||
|
||||
const getBatchCommitmentsEvents = async (
|
||||
{ blockFrom, blockTo, publicKey, privateKey, cachedEvents, withCache = true },
|
||||
[port],
|
||||
) => {
|
||||
const getBatchCommitmentsEvents = async ({ blockFrom, blockTo, publicKey, privateKey, cachedEvents, withCache = true }, [port]) => {
|
||||
try {
|
||||
const commitments = await getCommitmentBatch({ blockFrom, blockTo, publicKey, cachedEvents, withCache })
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
const { isEmpty } = require('lodash')
|
||||
const { BigNumber } = require('ethers')
|
||||
import { isEmpty } from 'lodash'
|
||||
import { BigNumber, Contract } from 'ethers'
|
||||
|
||||
const { IndexedDB } = require('../services/idb')
|
||||
const { sleep } = require('../utilities/helpers')
|
||||
const { workerEvents, numbers } = require('../constants/worker')
|
||||
const { ExtendedProvider } = require('../services/ether/ExtendedProvider')
|
||||
|
||||
const { POOL_CONTRACT, RPC_LIST, FALLBACK_RPC_LIST } = require('../constants/contracts')
|
||||
const { TornadoPool__factory: TornadoPoolFactory } = require('../_contracts')
|
||||
import { IndexedDB } from './services/idb'
|
||||
import { BatchEventsService } from './services/batch'
|
||||
import { getAllNullifiers } from './services/graph'
|
||||
import { ExtendedProvider } from './services/provider'
|
||||
import { POOL_CONTRACT, RPC_LIST, FALLBACK_RPC_LIST, workerEvents, numbers } from './services/constants'
|
||||
import { sleep } from './services/utilities'
|
||||
import { poolAbi } from './services/pool'
|
||||
import { downloadEvents } from './services/downloadEvents'
|
||||
|
||||
const getProviderWithSigner = (chainId) => {
|
||||
return new ExtendedProvider(RPC_LIST[chainId], chainId, FALLBACK_RPC_LIST[chainId])
|
||||
@@ -47,7 +47,12 @@ const initWorker = (chainId) => {
|
||||
}
|
||||
|
||||
const setTornadoPool = (chainId, provider) => {
|
||||
self.poolContract = TornadoPoolFactory.connect(POOL_CONTRACT[chainId], provider)
|
||||
self.poolContract = new Contract(POOL_CONTRACT[chainId], poolAbi, provider)
|
||||
|
||||
self.BatchEventsService = new BatchEventsService({
|
||||
provider,
|
||||
contract: self.poolContract
|
||||
})
|
||||
}
|
||||
|
||||
const saveEvents = async ({ events }) => {
|
||||
@@ -116,6 +121,14 @@ const getCachedEvents = async () => {
|
||||
return { blockFrom, cachedEvents }
|
||||
}
|
||||
blockFrom = newBlockFrom > currentBlock ? currentBlock : newBlockFrom
|
||||
} else {
|
||||
const downloadedEvents = await downloadEvents(`nullifiers_${self.chainId}.json`, blockFrom)
|
||||
|
||||
if (downloadedEvents.events.length) {
|
||||
cachedEvents.push(...downloadedEvents.events)
|
||||
|
||||
blockFrom = downloadedEvents.lastBlock
|
||||
}
|
||||
}
|
||||
|
||||
return { blockFrom, cachedEvents }
|
||||
@@ -123,14 +136,39 @@ const getCachedEvents = async () => {
|
||||
|
||||
const getNullifiers = async (blockFrom) => {
|
||||
try {
|
||||
const filter = self.poolContract.filters.NewNullifier()
|
||||
const events = await self.poolContract.queryFilter(filter, blockFrom)
|
||||
const events = []
|
||||
|
||||
return events.map(({ blockNumber, transactionHash, args }) => ({
|
||||
blockNumber,
|
||||
transactionHash,
|
||||
nullifier: args.nullifier,
|
||||
}))
|
||||
let { events: graphEvents, lastSyncBlock } = await getAllNullifiers({ fromBlock: blockFrom, chainId })
|
||||
|
||||
if (lastSyncBlock) {
|
||||
console.log({
|
||||
graphEvents
|
||||
})
|
||||
|
||||
events.push(...graphEvents)
|
||||
blockFrom = lastSyncBlock
|
||||
}
|
||||
|
||||
let nodeEvents = await self.BatchEventsService.getBatchEvents({
|
||||
fromBlock: blockFrom,
|
||||
type: 'NewNullifier'
|
||||
})
|
||||
|
||||
if (nodeEvents && nodeEvents.length) {
|
||||
nodeEvents = nodeEvents.map(({ blockNumber, transactionHash, args }) => ({
|
||||
blockNumber,
|
||||
transactionHash,
|
||||
nullifier: args.nullifier,
|
||||
}))
|
||||
|
||||
console.log({
|
||||
nodeEvents
|
||||
})
|
||||
|
||||
events.push(...nodeEvents)
|
||||
}
|
||||
|
||||
return events
|
||||
} catch (err) {
|
||||
console.error('getNullifiers', err.message)
|
||||
return []
|
||||
|
||||
85
assets/services/batch.js
Normal file
85
assets/services/batch.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import { sleep, getBatches } from './utilities'
|
||||
|
||||
export class BatchEventsService {
|
||||
constructor({
|
||||
provider,
|
||||
contract,
|
||||
concurrencySize = 10,
|
||||
blocksPerRequest = 2000,
|
||||
shouldRetry = true,
|
||||
retryMax = 5,
|
||||
retryOn = 500,
|
||||
}) {
|
||||
this.provider = provider;
|
||||
this.contract = contract;
|
||||
this.concurrencySize = concurrencySize;
|
||||
this.blocksPerRequest = blocksPerRequest;
|
||||
this.shouldRetry = shouldRetry;
|
||||
this.retryMax = retryMax;
|
||||
this.retryOn = retryOn;
|
||||
}
|
||||
|
||||
async getPastEvents({ fromBlock, toBlock, type }) {
|
||||
let err;
|
||||
let retries = 0;
|
||||
|
||||
// eslint-disable-next-line no-unmodified-loop-condition
|
||||
while ((!this.shouldRetry && retries === 0) || (this.shouldRetry && retries < this.retryMax)) {
|
||||
try {
|
||||
return (await this.contract.queryFilter(type, fromBlock, toBlock));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (e) {
|
||||
err = e;
|
||||
retries++;
|
||||
|
||||
// If provider.getBlockNumber returned last block that isn't accepted (happened on Avalanche/Gnosis),
|
||||
// get events to last accepted block
|
||||
if (e.message.includes('after last accepted block')) {
|
||||
const acceptedBlock = parseInt(e.message.split('after last accepted block ')[1]);
|
||||
toBlock = acceptedBlock;
|
||||
}
|
||||
|
||||
// retry on 0.5 seconds
|
||||
await sleep(this.retryOn);
|
||||
}
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
createBatchRequest(batchArray) {
|
||||
return batchArray.map(async (event, index) => {
|
||||
await sleep(20 * index);
|
||||
|
||||
return this.getPastEvents(event);
|
||||
});
|
||||
}
|
||||
|
||||
async getBatchEvents({ fromBlock, toBlock, type = '*' }) {
|
||||
if (!toBlock) {
|
||||
toBlock = await this.provider.getBlockNumber();
|
||||
}
|
||||
|
||||
const eventsToSync = [];
|
||||
|
||||
for (let i = fromBlock; i < toBlock; i += this.blocksPerRequest) {
|
||||
const j = i + this.blocksPerRequest - 1 > toBlock ? toBlock : i + this.blocksPerRequest - 1;
|
||||
|
||||
eventsToSync.push({ fromBlock: i, toBlock: j, type });
|
||||
}
|
||||
|
||||
const events = [];
|
||||
const eventChunk = getBatches(eventsToSync, this.concurrencySize);
|
||||
|
||||
let chunkCount = 0;
|
||||
|
||||
for (const chunk of eventChunk) {
|
||||
chunkCount++;
|
||||
|
||||
const fetchedEvents = (await Promise.all(this.createBatchRequest(chunk))).flat();
|
||||
events.push(...fetchedEvents);
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
}
|
||||
237
assets/services/bridgeHelper.js
Normal file
237
assets/services/bridgeHelper.js
Normal file
@@ -0,0 +1,237 @@
|
||||
export const bridgeAbi = [
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: "contract IOmnibridge",
|
||||
name: "_bridge",
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
internalType: "contract IWETH",
|
||||
name: "_weth",
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
internalType: "address",
|
||||
name: "_owner",
|
||||
type: "address",
|
||||
},
|
||||
],
|
||||
stateMutability: "nonpayable",
|
||||
type: "constructor",
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: true,
|
||||
internalType: "address",
|
||||
name: "owner",
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
internalType: "bytes",
|
||||
name: "key",
|
||||
type: "bytes",
|
||||
},
|
||||
],
|
||||
name: "PublicKey",
|
||||
type: "event",
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "WETH",
|
||||
outputs: [
|
||||
{
|
||||
internalType: "contract IWETH",
|
||||
name: "",
|
||||
type: "address",
|
||||
},
|
||||
],
|
||||
stateMutability: "view",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "bridge",
|
||||
outputs: [
|
||||
{
|
||||
internalType: "contract IOmnibridge",
|
||||
name: "",
|
||||
type: "address",
|
||||
},
|
||||
],
|
||||
stateMutability: "view",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: "address",
|
||||
name: "_token",
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
internalType: "address",
|
||||
name: "_to",
|
||||
type: "address",
|
||||
},
|
||||
],
|
||||
name: "claimTokens",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: "address",
|
||||
name: "_token",
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
internalType: "uint256",
|
||||
name: "_value",
|
||||
type: "uint256",
|
||||
},
|
||||
{
|
||||
internalType: "bytes",
|
||||
name: "_data",
|
||||
type: "bytes",
|
||||
},
|
||||
],
|
||||
name: "onTokenBridged",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "owner",
|
||||
outputs: [
|
||||
{
|
||||
internalType: "address",
|
||||
name: "",
|
||||
type: "address",
|
||||
},
|
||||
],
|
||||
stateMutability: "view",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
components: [
|
||||
{
|
||||
internalType: "address",
|
||||
name: "owner",
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
internalType: "bytes",
|
||||
name: "publicKey",
|
||||
type: "bytes",
|
||||
},
|
||||
],
|
||||
internalType: "struct L1Helper.Account",
|
||||
name: "_account",
|
||||
type: "tuple",
|
||||
},
|
||||
],
|
||||
name: "register",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: "address",
|
||||
name: "_newOwner",
|
||||
type: "address",
|
||||
},
|
||||
],
|
||||
name: "transferOwnership",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "wrapAndRelayTokens",
|
||||
outputs: [],
|
||||
stateMutability: "payable",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: "address",
|
||||
name: "_receiver",
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
internalType: "bytes",
|
||||
name: "_data",
|
||||
type: "bytes",
|
||||
},
|
||||
],
|
||||
name: "wrapAndRelayTokens",
|
||||
outputs: [],
|
||||
stateMutability: "payable",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: "address",
|
||||
name: "_receiver",
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
internalType: "bytes",
|
||||
name: "_data",
|
||||
type: "bytes",
|
||||
},
|
||||
{
|
||||
components: [
|
||||
{
|
||||
internalType: "address",
|
||||
name: "owner",
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
internalType: "bytes",
|
||||
name: "publicKey",
|
||||
type: "bytes",
|
||||
},
|
||||
],
|
||||
internalType: "struct L1Helper.Account",
|
||||
name: "_account",
|
||||
type: "tuple",
|
||||
},
|
||||
],
|
||||
name: "wrapAndRelayTokens",
|
||||
outputs: [],
|
||||
stateMutability: "payable",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: "address",
|
||||
name: "_receiver",
|
||||
type: "address",
|
||||
},
|
||||
],
|
||||
name: "wrapAndRelayTokens",
|
||||
outputs: [],
|
||||
stateMutability: "payable",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
stateMutability: "payable",
|
||||
type: "receive",
|
||||
},
|
||||
]
|
||||
177
assets/services/constants.js
Normal file
177
assets/services/constants.js
Normal file
@@ -0,0 +1,177 @@
|
||||
export const BSC_CHAIN_ID = 56
|
||||
export const XDAI_CHAIN_ID = 100
|
||||
export const MAINNET_CHAIN_ID = 1
|
||||
|
||||
export const ChainId = {
|
||||
BSC: BSC_CHAIN_ID,
|
||||
XDAI: XDAI_CHAIN_ID,
|
||||
MAINNET: MAINNET_CHAIN_ID,
|
||||
}
|
||||
|
||||
export const OFFCHAIN_ORACLE_CONTRACT = '0x07D91f5fb9Bf7798734C3f606dB065549F6893bb'
|
||||
|
||||
export const POOL_CONTRACT = {
|
||||
[ChainId.XDAI]: '0xD692Fd2D0b2Fbd2e52CFa5B5b9424bC981C30696', // ETH
|
||||
// [ChainId.XDAI]: '0x772F007F13604ac286312C85b9Cd9B2D691B353E', // BNB
|
||||
}
|
||||
export const REDGISTRY_CONTRACT = {
|
||||
[ChainId.MAINNET]: '0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2',
|
||||
}
|
||||
|
||||
export const AGGREGATOR_FACTORY = {
|
||||
[ChainId.MAINNET]: '0xE8F47A78A6D52D317D0D2FFFac56739fE14D1b49',
|
||||
}
|
||||
|
||||
export const WRAPPED_TOKEN = {
|
||||
[ChainId.MAINNET]: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // WETH on mainnet
|
||||
[ChainId.XDAI]: '0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1', // WETH on xdai
|
||||
[ChainId.BSC]: '0xCa8d20f3e0144a72C6B5d576e9Bd3Fd8557E2B04', // WBNB on xdai
|
||||
}
|
||||
|
||||
export const RPC_LIST = {
|
||||
[ChainId.BSC]: 'https://tornadocash-rpc.com/bsc',
|
||||
[ChainId.MAINNET]: 'https://tornadocash-rpc.com/mainnet',
|
||||
[ChainId.XDAI]: 'https://tornadocash-rpc.com/gnosis',
|
||||
}
|
||||
|
||||
export const FALLBACK_RPC_LIST = {
|
||||
[ChainId.BSC]: [
|
||||
'https://binance.nodereal.io',
|
||||
// 'https://rpc.ankr.com/bsc/dbe08b852ba176a8aeac783cc1fa8becaf4f107235dfdae79241063fbf52ca4a',
|
||||
],
|
||||
[ChainId.MAINNET]: [
|
||||
'https://rpc.mevblocker.io',
|
||||
// 'https://rpc.ankr.com/eth/dbe08b852ba176a8aeac783cc1fa8becaf4f107235dfdae79241063fbf52ca4a',
|
||||
],
|
||||
[ChainId.XDAI]: [
|
||||
// 'https://rpc.ankr.com/gnosis/dbe08b852ba176a8aeac783cc1fa8becaf4f107235dfdae79241063fbf52ca4a',
|
||||
'https://tornadocash-rpc.com/gnosis',
|
||||
],
|
||||
}
|
||||
|
||||
export const RPC_WS_LIST = {
|
||||
[ChainId.MAINNET]: 'wss://mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607',
|
||||
[ChainId.BSC]: 'wss://bsc-mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607',
|
||||
[ChainId.XDAI]: 'wss://gnosis-mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607',
|
||||
}
|
||||
|
||||
export const MULTICALL = {
|
||||
[ChainId.BSC]: '0xf072f255A3324198C7F653237B44E1C4e66f8C42',
|
||||
[ChainId.XDAI]: '0x8677b93D543d0217B32B8FDc20F2316E138D619B',
|
||||
[ChainId.MAINNET]: '0x1F98415757620B543A52E61c46B32eB19261F984',
|
||||
}
|
||||
|
||||
export const BRIDGE_PROXY = {
|
||||
[ChainId.BSC]: '0x05185872898b6f94AA600177EF41B9334B1FA48B',
|
||||
[ChainId.MAINNET]: '0x4c36d2919e407f0cc2ee3c993ccf8ac26d9ce64e',
|
||||
}
|
||||
|
||||
export const AMB_BRIDGE = {
|
||||
[ChainId.XDAI]: '0x75Df5AF045d91108662D8080fD1FEFAd6aA0bb59', // ETH
|
||||
// [ChainId.XDAI]: '0x162E898bD0aacB578C8D5F8d6ca588c13d2A383F', // BNB
|
||||
[ChainId.MAINNET]: '0x162E898bD0aacB578C8D5F8d6ca588c13d2A383F',
|
||||
}
|
||||
|
||||
export const BRIDGE_HELPER = {
|
||||
[ChainId.MAINNET]: '0xCa0840578f57fE71599D29375e16783424023357',
|
||||
[ChainId.BSC]: '0x8845F740F8B01bC7D9A4C82a6fD4A60320c07AF1',
|
||||
}
|
||||
|
||||
export const BRIDGE_FEE_MANAGER = {
|
||||
[ChainId.XDAI]: '0x5dbC897aEf6B18394D845A922BF107FA98E3AC55',
|
||||
}
|
||||
|
||||
export const FOREIGN_OMNIBRIDGE = {
|
||||
[ChainId.MAINNET]: '0x88ad09518695c6c3712AC10a214bE5109a655671',
|
||||
}
|
||||
|
||||
export const OMNIBRIDGE = {
|
||||
[ChainId.XDAI]: '0xf6A78083ca3e2a662D6dd1703c939c8aCE2e268d',
|
||||
}
|
||||
|
||||
export const SANCTION_LIST = {
|
||||
[ChainId.MAINNET]: '0x40C57923924B5c5c5455c48D93317139ADDaC8fb',
|
||||
}
|
||||
|
||||
export const CHAINS = {
|
||||
[ChainId.XDAI]: {
|
||||
symbol: 'XDAI',
|
||||
name: 'xdai',
|
||||
shortName: 'xdai',
|
||||
icon: 'ethereum',
|
||||
network: 'XDAI',
|
||||
blockDuration: 3000, // ms
|
||||
deployBlock: 19097755, // ETH
|
||||
// deployBlock: 20446605, // BNB
|
||||
blockGasLimit: 144000000, // rpc block gas limit
|
||||
hexChainId: '0x64',
|
||||
isEipSupported: false,
|
||||
ensSubdomainKey: 'gnosis-nova',
|
||||
blockExplorerUrl: 'https://gnosisscan.io'
|
||||
},
|
||||
[ChainId.MAINNET]: {
|
||||
symbol: 'ETH',
|
||||
name: 'ethereum',
|
||||
shortName: 'eth',
|
||||
icon: 'ethereum',
|
||||
network: 'Mainnet',
|
||||
deployBlock: 13494216,
|
||||
blockDuration: 15000,
|
||||
blockGasLimit: 144000000,
|
||||
hexChainId: '0x1',
|
||||
isEipSupported: true,
|
||||
ensSubdomainKey: 'mainnet-tornado',
|
||||
blockExplorerUrl: 'https://etherscan.io'
|
||||
},
|
||||
[ChainId.BSC]: {
|
||||
symbol: 'BNB',
|
||||
name: 'bsc',
|
||||
shortName: 'bsc',
|
||||
icon: 'binance',
|
||||
network: 'BSC',
|
||||
deployBlock: 14931075,
|
||||
blockDuration: 3000,
|
||||
blockGasLimit: 144000000,
|
||||
hexChainId: '0x38',
|
||||
isEipSupported: false,
|
||||
ensSubdomainKey: 'bsc-tornado',
|
||||
blockExplorerUrl: 'https://bscscan.com'
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
export const workerEvents = {
|
||||
INIT_WORKER: 'initWorker',
|
||||
GET_COMMITMENT_EVENTS: 'get_commitment_events',
|
||||
// nullifier
|
||||
GET_UNSPENT_EVENTS: 'get_unspent_events',
|
||||
GET_NULLIFIER_EVENT: 'get_nullifier_event',
|
||||
GET_NULLIFIER_EVENTS_FROM_TX_HASH: 'get_nullifier_events_from_tx_hash',
|
||||
UPDATE_NULLIFIER_EVENTS: 'update_nullifier_events',
|
||||
// events
|
||||
GET_BATCH_EVENTS: 'get_batch_events',
|
||||
GET_BATCH_COMMITMENTS_EVENTS: 'get_batch_commitments_events',
|
||||
GET_EVENTS_FROM_TX_HASH: 'get_events_from_tx_hash',
|
||||
SAVE_EVENTS: 'save_events',
|
||||
GET_CACHED_EVENTS: 'get_cached_events',
|
||||
GET_CACHED_COMMITMENTS_EVENTS: 'get_cached_commitments_events',
|
||||
SAVE_LAST_SYNC_BLOCK: 'save_last_sync_block',
|
||||
}
|
||||
|
||||
export const numbers = {
|
||||
ZERO: 0,
|
||||
TWO: 2,
|
||||
ONE: 1,
|
||||
BYTES_31: 31,
|
||||
BYTES_62: 62,
|
||||
IS_SPENT_INDEX: 1,
|
||||
OX_LENGTH: 2,
|
||||
RECALL_DELAY: 500,
|
||||
NULLIFIER_LENGTH: 66,
|
||||
NONCE_BUF_LENGTH: 24,
|
||||
COMMITMENTS_CHAIN: 100,
|
||||
DEPLOYED_BLOCK: 19097755,
|
||||
DECRYPT_WORKERS_COUNT: 8,
|
||||
MIN_BLOCKS_INTERVAL_LINE: 200000,
|
||||
EPHEM_PUBLIC_KEY_BUF_LENGTH: 56,
|
||||
}
|
||||
37
assets/services/downloadEvents.js
Normal file
37
assets/services/downloadEvents.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { unzipAsync } from "./zip"
|
||||
|
||||
export async function downloadEvents(fileName, deployedBlock) {
|
||||
fileName = fileName.toLowerCase()
|
||||
|
||||
// @ts-ignore
|
||||
const prefix = __webpack_public_path__.slice(0, -7)
|
||||
|
||||
try {
|
||||
const resp = await fetch(`${prefix}/${fileName}.zip`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
}
|
||||
})
|
||||
|
||||
const arrayBuffer = await resp.arrayBuffer()
|
||||
|
||||
const { [fileName]: content } = await unzipAsync(new Uint8Array(arrayBuffer))
|
||||
|
||||
const events = JSON.parse(new TextDecoder().decode(content))
|
||||
|
||||
const lastBlock = events && Array.isArray(events) && events[events.length - 1]
|
||||
? events[events.length - 1].blockNumber
|
||||
: deployedBlock
|
||||
|
||||
return {
|
||||
events,
|
||||
lastBlock
|
||||
}
|
||||
} catch {
|
||||
return {
|
||||
events: [],
|
||||
lastBlock: deployedBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
279
assets/services/graph/index.js
Normal file
279
assets/services/graph/index.js
Normal file
@@ -0,0 +1,279 @@
|
||||
import { isEmpty } from 'lodash'
|
||||
import { ApolloClient, InMemoryCache, gql } from '@apollo/client/core'
|
||||
import { utils } from 'ethers'
|
||||
|
||||
import { GET_ACCOUNTS, GET_COMMITMENT, GET_NULLIFIER } from './queries'
|
||||
import { ChainId, numbers } from '../constants'
|
||||
|
||||
const { getAddress } = utils
|
||||
|
||||
const first = 1000
|
||||
const breakLength = 900
|
||||
|
||||
const CHAIN_GRAPH_URLS = {
|
||||
[ChainId.BSC]: 'https://api.thegraph.com/subgraphs/name/dan1kov/bsc-tornado-pool-subgraph',
|
||||
[ChainId.MAINNET]: 'https://tornadocash-rpc.com/subgraphs/name/tornadocash/mainnet-tornado-pool-subgraph',
|
||||
[ChainId.XDAI]: 'https://tornadocash-rpc.com/subgraphs/name/tornadocash/gnosis-tornado-nova-subgraph',
|
||||
}
|
||||
|
||||
const link = (operation) => {
|
||||
const { chainId } = operation.getContext()
|
||||
return CHAIN_GRAPH_URLS[chainId]
|
||||
}
|
||||
|
||||
const client = new ApolloClient({
|
||||
uri: link,
|
||||
cache: new InMemoryCache(),
|
||||
})
|
||||
|
||||
export async function getAccounts({ fromBlock, chainId }) {
|
||||
const { data } = await client.query({
|
||||
context: {
|
||||
chainId,
|
||||
},
|
||||
query: gql(GET_ACCOUNTS),
|
||||
variables: { first, fromBlock },
|
||||
})
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
results: [],
|
||||
lastSyncBlock: data._meta.block.number
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
results: data.accounts,
|
||||
lastSyncBlock: data._meta.block.number
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAllAccounts({ fromBlock, toBlock, chainId }) {
|
||||
try {
|
||||
let accounts = []
|
||||
let lastSyncBlock
|
||||
|
||||
while (true) {
|
||||
let { results, lastSyncBlock: lastBlock } = await getAccounts({ fromBlock, chainId })
|
||||
|
||||
lastSyncBlock = lastBlock
|
||||
|
||||
if (isEmpty(results)) {
|
||||
break
|
||||
}
|
||||
|
||||
if (results.length < breakLength) {
|
||||
accounts = accounts.concat(results)
|
||||
break
|
||||
}
|
||||
|
||||
const [lastEvent] = results.slice(-numbers.ONE)
|
||||
|
||||
results = results.filter((e) => e.blockNumber !== lastEvent.blockNumber)
|
||||
fromBlock = Number(lastEvent.blockNumber)
|
||||
|
||||
accounts = accounts.concat(results)
|
||||
|
||||
if (toBlock && fromBlock >= Number(toBlock)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!accounts) {
|
||||
return {
|
||||
lastSyncBlock,
|
||||
events: [],
|
||||
}
|
||||
}
|
||||
|
||||
const data = accounts.map((e) => ({
|
||||
key: e.key,
|
||||
owner: getAddress(e.owner),
|
||||
blockNumber: Number(e.blockNumber),
|
||||
}))
|
||||
|
||||
const [lastEvent] = data.slice(-numbers.ONE)
|
||||
|
||||
return {
|
||||
events: data,
|
||||
lastSyncBlock: (lastEvent && lastEvent.blockNumber >= lastSyncBlock)
|
||||
? lastEvent.blockNumber + numbers.ONE
|
||||
: lastSyncBlock,
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Error from getAllAccounts')
|
||||
console.log(err)
|
||||
return {
|
||||
lastSyncBlock: '',
|
||||
events: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCommitments({ fromBlock, chainId }) {
|
||||
const { data } = await client.query({
|
||||
context: {
|
||||
chainId,
|
||||
},
|
||||
query: gql(GET_COMMITMENT),
|
||||
variables: { first, fromBlock },
|
||||
})
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
results: [],
|
||||
lastSyncBlock: data._meta.block.number
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
results: data.commitments,
|
||||
lastSyncBlock: data._meta.block.number
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAllCommitments({ fromBlock, toBlock, chainId }) {
|
||||
try {
|
||||
let commitments = []
|
||||
let lastSyncBlock
|
||||
|
||||
while (true) {
|
||||
let { results, lastSyncBlock: lastBlock } = await getCommitments({ fromBlock, chainId })
|
||||
|
||||
lastSyncBlock = lastBlock
|
||||
|
||||
if (isEmpty(results)) {
|
||||
break
|
||||
}
|
||||
|
||||
if (results.length < breakLength) {
|
||||
commitments = commitments.concat(results)
|
||||
break
|
||||
}
|
||||
|
||||
const [lastEvent] = results.slice(-numbers.ONE)
|
||||
|
||||
results = results.filter((e) => e.blockNumber !== lastEvent.blockNumber)
|
||||
fromBlock = Number(lastEvent.blockNumber)
|
||||
|
||||
commitments = commitments.concat(results)
|
||||
|
||||
if (toBlock && fromBlock >= Number(toBlock)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!commitments) {
|
||||
return {
|
||||
lastSyncBlock,
|
||||
events: [],
|
||||
}
|
||||
}
|
||||
|
||||
const data = commitments
|
||||
.map((e) => ({
|
||||
blockNumber: Number(e.blockNumber),
|
||||
transactionHash: e.transactionHash,
|
||||
index: Number(e.index),
|
||||
commitment: e.commitment,
|
||||
encryptedOutput: e.encryptedOutput
|
||||
}))
|
||||
.sort((a, b) => a.index - b.index)
|
||||
|
||||
const [lastEvent] = data.slice(-numbers.ONE)
|
||||
|
||||
return {
|
||||
events: data,
|
||||
lastSyncBlock: (lastEvent && lastEvent.blockNumber >= lastSyncBlock)
|
||||
? lastEvent.blockNumber + numbers.ONE
|
||||
: lastSyncBlock,
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Error from getAllCommitments')
|
||||
console.log(err)
|
||||
return {
|
||||
lastSyncBlock: '',
|
||||
events: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getNullifiers({ fromBlock, chainId }) {
|
||||
const { data } = await client.query({
|
||||
context: {
|
||||
chainId,
|
||||
},
|
||||
query: gql(GET_NULLIFIER),
|
||||
variables: { first, fromBlock },
|
||||
})
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
results: [],
|
||||
lastSyncBlock: data._meta.block.number
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
results: data.nullifiers,
|
||||
lastSyncBlock: data._meta.block.number
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAllNullifiers({ fromBlock, chainId }) {
|
||||
try {
|
||||
let nullifiers = []
|
||||
let lastSyncBlock
|
||||
|
||||
while (true) {
|
||||
let { results, lastSyncBlock: lastBlock } = await getNullifiers({ fromBlock, chainId })
|
||||
|
||||
lastSyncBlock = lastBlock
|
||||
|
||||
if (isEmpty(results)) {
|
||||
break
|
||||
}
|
||||
|
||||
if (results.length < breakLength) {
|
||||
nullifiers = nullifiers.concat(results)
|
||||
break
|
||||
}
|
||||
|
||||
const [lastEvent] = results.slice(-numbers.ONE)
|
||||
|
||||
results = results.filter((e) => e.blockNumber !== lastEvent.blockNumber)
|
||||
fromBlock = Number(lastEvent.blockNumber)
|
||||
|
||||
nullifiers = nullifiers.concat(results)
|
||||
}
|
||||
|
||||
if (!nullifiers) {
|
||||
return {
|
||||
lastSyncBlock,
|
||||
events: [],
|
||||
}
|
||||
}
|
||||
|
||||
const data = nullifiers.map((e) => ({
|
||||
nullifier: e.nullifier,
|
||||
blockNumber: Number(e.blockNumber),
|
||||
transactionHash: e.transactionHash
|
||||
}))
|
||||
|
||||
const [lastEvent] = data.slice(-numbers.ONE)
|
||||
|
||||
return {
|
||||
events: data,
|
||||
lastSyncBlock: (lastEvent && lastEvent.blockNumber >= lastSyncBlock)
|
||||
? lastEvent.blockNumber + numbers.ONE
|
||||
: lastSyncBlock,
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Error from getAllNullifiers')
|
||||
console.log(err)
|
||||
return {
|
||||
lastSyncBlock: '',
|
||||
events: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
56
assets/services/graph/queries.js
Normal file
56
assets/services/graph/queries.js
Normal file
@@ -0,0 +1,56 @@
|
||||
export const GET_ACCOUNTS = `
|
||||
query getAccounts($first: Int, $fromBlock: Int) {
|
||||
accounts(first: $first, orderBy: blockNumber, orderDirection: asc, where: {
|
||||
blockNumber_gte: $fromBlock
|
||||
}) {
|
||||
id
|
||||
key
|
||||
owner
|
||||
blockNumber
|
||||
}
|
||||
_meta {
|
||||
block {
|
||||
number
|
||||
}
|
||||
hasIndexingErrors
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const GET_COMMITMENT = `
|
||||
query getCommitment($first: Int, $fromBlock: Int) {
|
||||
commitments(first: $first, orderBy: blockNumber, orderDirection: asc, where: {
|
||||
blockNumber_gte: $fromBlock
|
||||
}) {
|
||||
index
|
||||
commitment
|
||||
blockNumber
|
||||
encryptedOutput
|
||||
transactionHash
|
||||
}
|
||||
_meta {
|
||||
block {
|
||||
number
|
||||
}
|
||||
hasIndexingErrors
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const GET_NULLIFIER = `
|
||||
query getNullifier($first: Int, $fromBlock: Int) {
|
||||
nullifiers(first: $first, orderBy: blockNumber, orderDirection: asc, where: {
|
||||
blockNumber_gte: $fromBlock
|
||||
}) {
|
||||
nullifier
|
||||
blockNumber
|
||||
transactionHash
|
||||
}
|
||||
_meta {
|
||||
block {
|
||||
number
|
||||
}
|
||||
hasIndexingErrors
|
||||
}
|
||||
}
|
||||
`
|
||||
222
assets/services/idb.js
Normal file
222
assets/services/idb.js
Normal file
@@ -0,0 +1,222 @@
|
||||
import { deleteDB, openDB } from 'idb'
|
||||
|
||||
export const VERSION_ERROR = 'less than the existing version'
|
||||
export const INDEX_DB_ERROR = 'A mutation operation was attempted on a database that did not allow mutations.'
|
||||
|
||||
export const IDB_VERSION = 9
|
||||
|
||||
// TODO method for migration, remove indexed
|
||||
export class IndexedDB {
|
||||
constructor({ stores, dbName }) {
|
||||
this.dbExists = false
|
||||
this.isBlocked = false
|
||||
|
||||
this.options = {
|
||||
upgrade(db) {
|
||||
Object.values(db.objectStoreNames).forEach((value) => {
|
||||
db.deleteObjectStore(value)
|
||||
})
|
||||
|
||||
stores.forEach(({ name, keyPath, indexes }) => {
|
||||
const store = db.createObjectStore(name, {
|
||||
keyPath,
|
||||
autoIncrement: true,
|
||||
})
|
||||
|
||||
if (Array.isArray(indexes)) {
|
||||
indexes.forEach(({ name, unique = false }) => {
|
||||
store.createIndex(name, String(name), { unique })
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
this.dbName = dbName
|
||||
}
|
||||
|
||||
async initDB() {
|
||||
try {
|
||||
if (this.dbExists) {
|
||||
return
|
||||
}
|
||||
|
||||
this.db = await openDB(this.dbName, IDB_VERSION, this.options) // version (optional): Schema version, or undefined to open the current version.
|
||||
this.onEventHandler()
|
||||
|
||||
this.dbExists = true
|
||||
} catch (err) {
|
||||
// need for private mode firefox browser
|
||||
if (err.message.includes(INDEX_DB_ERROR)) {
|
||||
this.isBlocked = true
|
||||
return
|
||||
}
|
||||
|
||||
if (err.message.includes(VERSION_ERROR)) {
|
||||
await this.removeExist()
|
||||
}
|
||||
|
||||
console.error(`initDB has error: ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async createTransactions({ storeName, data, mode = 'readwrite' }) {
|
||||
try {
|
||||
const tx = this.db.transaction(storeName, mode)
|
||||
const storedItem = tx.objectStore(storeName)
|
||||
|
||||
if (storedItem.add) {
|
||||
await storedItem.add(data)
|
||||
await tx.done
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(`Method createTransactions has error: ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
createMultipleTransactions({
|
||||
storeName,
|
||||
data,
|
||||
index,
|
||||
mode = 'readwrite',
|
||||
}) {
|
||||
try {
|
||||
const tx = this.db.transaction(storeName, mode)
|
||||
|
||||
data.forEach((item) => {
|
||||
if (item && tx.store && tx.store.put) {
|
||||
tx.store.put({ ...item, ...index })
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
throw new Error(`Method createMultipleTransactions has error: ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async getFromIndex(params) {
|
||||
if (this.isBlocked) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const item = await this.getFromIndexHandler(params)
|
||||
return item
|
||||
} catch (err) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
async getItem({ storeName, key }) {
|
||||
try {
|
||||
if (this.isBlocked) {
|
||||
return
|
||||
}
|
||||
|
||||
const store = this.db.transaction(storeName).objectStore(storeName)
|
||||
|
||||
const value = await store.get(key)
|
||||
return value
|
||||
} catch (err) {
|
||||
throw new Error(`Method getItem has error: ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async addItem({ storeName, data, key }) {
|
||||
try {
|
||||
const tx = this.db.transaction(storeName, 'readwrite')
|
||||
const isExist = await tx.objectStore(storeName).get(key)
|
||||
|
||||
if (!isExist) {
|
||||
await tx.objectStore(storeName).add(data)
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(`Method addItem has error: ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async putItem({ storeName, data }) {
|
||||
try {
|
||||
if (this.isBlocked) {
|
||||
return
|
||||
}
|
||||
|
||||
const tx = this.db.transaction(storeName, 'readwrite')
|
||||
await tx.objectStore(storeName).put(data)
|
||||
} catch (err) {
|
||||
throw new Error(`Method putItem has error: ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async getAll({ storeName }) {
|
||||
try {
|
||||
if (this.isBlocked || !this.dbExists) {
|
||||
return []
|
||||
}
|
||||
|
||||
const tx = this.db.transaction(storeName, 'readonly')
|
||||
const store = tx.objectStore(storeName)
|
||||
const data = await store.getAll()
|
||||
return data
|
||||
} catch (err) {
|
||||
throw new Error(`Method getAll has error: ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async clearStore({ storeName, mode = 'readwrite' }) {
|
||||
try {
|
||||
const tx = this.db.transaction(storeName, mode)
|
||||
const storedItem = tx.objectStore(storeName)
|
||||
|
||||
if (storedItem.clear) {
|
||||
await storedItem.clear()
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(`Method clearStore has error: ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async getAllFromIndex(params) {
|
||||
if (this.isBlocked) {
|
||||
return []
|
||||
}
|
||||
|
||||
try {
|
||||
const items = await this.getAllFromIndexHandler(params)
|
||||
return items
|
||||
} catch (err) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
onEventHandler() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
this.db.addEventListener('onupgradeneeded', async () => {
|
||||
await this.removeExist()
|
||||
})
|
||||
}
|
||||
|
||||
async removeExist() {
|
||||
await deleteDB(this.dbName)
|
||||
this.dbExists = false
|
||||
|
||||
await this.initDB()
|
||||
}
|
||||
|
||||
async getFromIndexHandler({ storeName, indexName, key }) {
|
||||
try {
|
||||
const value = await this.db.getFromIndex(storeName, indexName, key)
|
||||
return value
|
||||
} catch (err) {
|
||||
throw new Error(`Method getFromIndexHandler has error: ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async getAllFromIndexHandler({ storeName, indexName, key, count }) {
|
||||
try {
|
||||
const value = await this.db.getAllFromIndex(storeName, indexName, key, count)
|
||||
return value
|
||||
} catch (err) {
|
||||
throw new Error(`Method getAllFromIndex has error: ${err.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
1040
assets/services/pool.js
Normal file
1040
assets/services/pool.js
Normal file
File diff suppressed because it is too large
Load Diff
86
assets/services/provider.js
Normal file
86
assets/services/provider.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import { ethers } from 'ethers'
|
||||
import { fetchJson } from 'ethers/lib/utils'
|
||||
import { numbers } from './constants'
|
||||
|
||||
const defaultRetryAttempt = 0
|
||||
|
||||
export class ExtendedProvider extends ethers.providers.StaticJsonRpcProvider {
|
||||
constructor(url, network, fallbackRpcs) {
|
||||
super(url, network)
|
||||
this.fallbackRpcs = fallbackRpcs
|
||||
}
|
||||
|
||||
async send(method, params, retryAttempt = defaultRetryAttempt) {
|
||||
try {
|
||||
return await super.send(method, params)
|
||||
} catch (err) {
|
||||
if (!retryAttempt) {
|
||||
const TIME_OUT = 3000
|
||||
|
||||
await this.sleep(TIME_OUT)
|
||||
|
||||
if (this.fallbackRpcs) {
|
||||
return await this.fallbackSend(method, params, this.fallbackRpcs)
|
||||
}
|
||||
return this.send(method, params, ++retryAttempt)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
async fallbackSend(method, params, fallbackRpcs, retryAttempt = defaultRetryAttempt) {
|
||||
|
||||
function getResult(payload) {
|
||||
if (payload.error) {
|
||||
const error = new Error(payload.error.message)
|
||||
error.code = payload.error.code
|
||||
error.data = payload.error.data
|
||||
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
||||
throw error
|
||||
}
|
||||
return payload.result
|
||||
}
|
||||
|
||||
try {
|
||||
const request = {
|
||||
method: method,
|
||||
params: params,
|
||||
id: this._nextId + numbers.ONE,
|
||||
jsonrpc: '2.0',
|
||||
}
|
||||
|
||||
const result = fetchJson({ url: fallbackRpcs[retryAttempt] }, JSON.stringify(request), getResult).then(
|
||||
(result) => result,
|
||||
(error) => {
|
||||
throw error
|
||||
},
|
||||
)
|
||||
|
||||
return await result
|
||||
} catch (err) {
|
||||
retryAttempt += numbers.ONE
|
||||
if (!fallbackRpcs[retryAttempt]) {
|
||||
throw err
|
||||
} else {
|
||||
return await this.fallbackSend(method, params, fallbackRpcs, retryAttempt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async sleep(ms) {
|
||||
return await new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
// private checkRpcError(err: { data: string; code: string; message: string }) {
|
||||
// const code = String(err?.code)
|
||||
// const data = err.data?.toLowerCase()
|
||||
// const message = err.message?.toLowerCase()
|
||||
|
||||
// const ERROR_DATA = 'too many concurrent request'
|
||||
// const ERROR_MESSAGE = 'timeout'
|
||||
// const ERROR_CODE = '-32017'
|
||||
|
||||
// return (data?.includes(ERROR_DATA) || message?.includes(ERROR_MESSAGE)) && code === ERROR_CODE
|
||||
// }
|
||||
}
|
||||
13
assets/services/utilities.js
Normal file
13
assets/services/utilities.js
Normal file
@@ -0,0 +1,13 @@
|
||||
export const ZERO_ELEMENT = 0
|
||||
|
||||
export function getBatches(array, batchSize) {
|
||||
const batches = []
|
||||
while (array.length) {
|
||||
batches.push(array.splice(ZERO_ELEMENT, batchSize))
|
||||
}
|
||||
return batches
|
||||
}
|
||||
|
||||
export async function sleep(ms) {
|
||||
return await new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
25
assets/services/zip.js
Normal file
25
assets/services/zip.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { zip, unzip } from 'fflate'
|
||||
|
||||
export function zipAsync(file) {
|
||||
return new Promise((res, rej) => {
|
||||
zip(file, { mtime: new Date('1/1/1980') }, (err, data) => {
|
||||
if (err) {
|
||||
rej(err);
|
||||
return;
|
||||
}
|
||||
res(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function unzipAsync(data) {
|
||||
return new Promise((res, rej) => {
|
||||
unzip(data, {}, (err, data) => {
|
||||
if (err) {
|
||||
rej(err);
|
||||
return;
|
||||
}
|
||||
res(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
285
assets/syncEvents.js
Normal file
285
assets/syncEvents.js
Normal file
@@ -0,0 +1,285 @@
|
||||
import path from 'path'
|
||||
import { stat, readFile, writeFile } from 'fs/promises'
|
||||
import { Contract, providers, utils } from 'ethers'
|
||||
|
||||
import { BatchEventsService } from './services/batch'
|
||||
import { getAllAccounts, getAllCommitments, getAllNullifiers } from './services/graph'
|
||||
import { POOL_CONTRACT, BRIDGE_HELPER, RPC_LIST, ChainId, CHAINS, numbers } from './services/constants'
|
||||
import { zipAsync, unzipAsync } from './services/zip'
|
||||
import { poolAbi } from './services/pool'
|
||||
import { bridgeAbi } from './services/bridgeHelper'
|
||||
|
||||
const { getAddress } = utils
|
||||
const { StaticJsonRpcProvider } = providers
|
||||
|
||||
const EVENT_PATH = './static'
|
||||
|
||||
async function existsAsync(fileOrDir) {
|
||||
try {
|
||||
await stat(fileOrDir);
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const getProvider = (chainId) => {
|
||||
return new StaticJsonRpcProvider({ skipFetchSetup: true, url: RPC_LIST[chainId] }, chainId)
|
||||
}
|
||||
|
||||
const getTornadoPool = (chainId, provider) => {
|
||||
const TornadoPool = new Contract(POOL_CONTRACT[chainId], poolAbi, provider)
|
||||
|
||||
return {
|
||||
TornadoPool,
|
||||
BatchEventsService: new BatchEventsService({
|
||||
provider,
|
||||
contract: TornadoPool
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const getBridgeHelper = (chainId, provider) => {
|
||||
const BridgeHelper = new Contract(BRIDGE_HELPER[chainId], bridgeAbi, provider)
|
||||
|
||||
return {
|
||||
BridgeHelper,
|
||||
BridgeEventsService: new BatchEventsService({
|
||||
provider,
|
||||
contract: BridgeHelper
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const loadEvents = async (fileName, deployedBlock) => {
|
||||
fileName = fileName.toLowerCase()
|
||||
|
||||
const filePath = path.join(EVENT_PATH, fileName + '.zip')
|
||||
|
||||
if (!(await existsAsync(filePath))) {
|
||||
return {
|
||||
events: [],
|
||||
lastBlock: deployedBlock
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await readFile(filePath)
|
||||
const { [fileName]: content } = await unzipAsync(data)
|
||||
|
||||
const events = JSON.parse(new TextDecoder().decode(content))
|
||||
|
||||
const lastBlock = events && Array.isArray(events) && events[events.length - 1]
|
||||
? events[events.length - 1].blockNumber
|
||||
: deployedBlock
|
||||
|
||||
return {
|
||||
events,
|
||||
lastBlock
|
||||
}
|
||||
} catch {
|
||||
return {
|
||||
events: [],
|
||||
lastBlock: deployedBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const saveEvents = async (fileName, events) => {
|
||||
fileName = fileName.toLowerCase()
|
||||
|
||||
const filePath = path.join(EVENT_PATH, fileName + '.zip')
|
||||
|
||||
const payload = await zipAsync({
|
||||
[fileName]: new TextEncoder().encode(JSON.stringify(events, null, 2) + '\n')
|
||||
})
|
||||
|
||||
await writeFile(filePath, payload)
|
||||
}
|
||||
|
||||
const syncAccounts = async (chainId, BatchEventsService) => {
|
||||
const fileName = `accounts_${chainId}.json`
|
||||
|
||||
console.log(`Syncing ${fileName}`)
|
||||
|
||||
const cachedEvents = await loadEvents(fileName, CHAINS[chainId].deployBlock)
|
||||
|
||||
const events = [...cachedEvents.events]
|
||||
let fromBlock = cachedEvents.lastBlock + numbers.ONE
|
||||
|
||||
console.log({
|
||||
cachedEvents: events.length,
|
||||
cachedBlock: fromBlock
|
||||
})
|
||||
|
||||
const { events: graphEvents, lastSyncBlock } = await getAllAccounts({
|
||||
fromBlock,
|
||||
chainId
|
||||
})
|
||||
|
||||
console.log({
|
||||
graphEvents: graphEvents.length,
|
||||
graphBlock: lastSyncBlock
|
||||
})
|
||||
|
||||
if (lastSyncBlock) {
|
||||
events.push(...graphEvents)
|
||||
fromBlock = lastSyncBlock
|
||||
}
|
||||
|
||||
let nodeEvents = await BatchEventsService.getBatchEvents({
|
||||
fromBlock,
|
||||
type: 'PublicKey'
|
||||
})
|
||||
|
||||
console.log({
|
||||
nodeEvents: nodeEvents.length,
|
||||
nodeBlock: nodeEvents && nodeEvents[nodeEvents.length - 1] ? nodeEvents[nodeEvents.length - 1].blockNumber : undefined
|
||||
})
|
||||
|
||||
if (nodeEvents && nodeEvents.length) {
|
||||
nodeEvents = nodeEvents.map(({ blockNumber, args }) => ({
|
||||
key: args.key,
|
||||
owner: getAddress(args.owner),
|
||||
blockNumber,
|
||||
}))
|
||||
|
||||
events.push(...nodeEvents)
|
||||
}
|
||||
|
||||
await saveEvents(fileName, events)
|
||||
}
|
||||
|
||||
const syncCommitments = async (chainId, BatchEventsService) => {
|
||||
const fileName = `commitments_${chainId}.json`
|
||||
|
||||
console.log(`Syncing ${fileName}`)
|
||||
|
||||
const cachedEvents = await loadEvents(fileName, CHAINS[chainId].deployBlock)
|
||||
|
||||
const events = [...cachedEvents.events]
|
||||
let fromBlock = cachedEvents.lastBlock + numbers.ONE
|
||||
|
||||
console.log({
|
||||
cachedEvents: events.length,
|
||||
cachedBlock: fromBlock
|
||||
})
|
||||
|
||||
const { events: graphEvents, lastSyncBlock } = await getAllCommitments({
|
||||
fromBlock,
|
||||
chainId
|
||||
})
|
||||
|
||||
console.log({
|
||||
graphEvents: graphEvents.length,
|
||||
graphBlock: lastSyncBlock
|
||||
})
|
||||
|
||||
if (lastSyncBlock) {
|
||||
events.push(...graphEvents)
|
||||
fromBlock = lastSyncBlock
|
||||
}
|
||||
|
||||
let nodeEvents = await BatchEventsService.getBatchEvents({
|
||||
fromBlock,
|
||||
type: 'NewCommitment'
|
||||
})
|
||||
|
||||
console.log({
|
||||
nodeEvents: nodeEvents.length,
|
||||
nodeBlock: nodeEvents && nodeEvents[nodeEvents.length - 1] ? nodeEvents[nodeEvents.length - 1].blockNumber : undefined
|
||||
})
|
||||
|
||||
if (nodeEvents && nodeEvents.length) {
|
||||
nodeEvents = nodeEvents.map(({ blockNumber, transactionHash, args }) => ({
|
||||
blockNumber,
|
||||
transactionHash,
|
||||
index: Number(args.index),
|
||||
commitment: args.commitment,
|
||||
encryptedOutput: args.encryptedOutput,
|
||||
}))
|
||||
|
||||
events.push(...nodeEvents)
|
||||
}
|
||||
|
||||
await saveEvents(fileName, events)
|
||||
}
|
||||
|
||||
const syncNullifiers = async (chainId, BatchEventsService) => {
|
||||
const fileName = `nullifiers_${chainId}.json`
|
||||
|
||||
console.log(`Syncing ${fileName}`)
|
||||
|
||||
const cachedEvents = await loadEvents(fileName, CHAINS[chainId].deployBlock)
|
||||
|
||||
const events = [...cachedEvents.events]
|
||||
let fromBlock = cachedEvents.lastBlock + numbers.ONE
|
||||
|
||||
console.log({
|
||||
cachedEvents: events.length,
|
||||
cachedBlock: fromBlock
|
||||
})
|
||||
|
||||
const { events: graphEvents, lastSyncBlock } = await getAllNullifiers({
|
||||
fromBlock,
|
||||
chainId
|
||||
})
|
||||
|
||||
console.log({
|
||||
graphEvents: graphEvents.length,
|
||||
graphBlock: lastSyncBlock
|
||||
})
|
||||
|
||||
if (lastSyncBlock) {
|
||||
events.push(...graphEvents)
|
||||
fromBlock = lastSyncBlock
|
||||
}
|
||||
|
||||
let nodeEvents = await BatchEventsService.getBatchEvents({
|
||||
fromBlock,
|
||||
type: 'NewNullifier'
|
||||
})
|
||||
|
||||
console.log({
|
||||
nodeEvents: nodeEvents.length,
|
||||
nodeBlock: nodeEvents && nodeEvents[nodeEvents.length - 1] ? nodeEvents[nodeEvents.length - 1].blockNumber : undefined
|
||||
})
|
||||
|
||||
if (nodeEvents && nodeEvents.length) {
|
||||
nodeEvents = nodeEvents.map(({ blockNumber, transactionHash, args }) => ({
|
||||
blockNumber,
|
||||
transactionHash,
|
||||
nullifier: args.nullifier,
|
||||
}))
|
||||
|
||||
events.push(...nodeEvents)
|
||||
}
|
||||
|
||||
await saveEvents(fileName, events)
|
||||
}
|
||||
|
||||
const main = async () => {
|
||||
const chainId = ChainId.XDAI
|
||||
|
||||
const ethChainId = ChainId.MAINNET
|
||||
|
||||
const provider = getProvider(chainId)
|
||||
|
||||
const ethProvider = getProvider(ethChainId)
|
||||
|
||||
const { BatchEventsService } = getTornadoPool(chainId, provider)
|
||||
|
||||
const { BridgeEventsService } = getBridgeHelper(ethChainId, ethProvider)
|
||||
|
||||
console.log(`Connected with ${chainId}: (block: ${await provider.getBlockNumber()})`)
|
||||
|
||||
console.log(`Connected with ${ethChainId}: (block: ${await ethProvider.getBlockNumber()})`)
|
||||
|
||||
await syncAccounts(ethChainId, BridgeEventsService)
|
||||
|
||||
await syncCommitments(chainId, BatchEventsService)
|
||||
|
||||
await syncNullifiers(chainId, BatchEventsService)
|
||||
}
|
||||
main()
|
||||
@@ -21,9 +21,9 @@ export const WRAPPED_TOKEN: { [chainId in ChainId]: string } = {
|
||||
}
|
||||
|
||||
export const RPC_LIST: { [chainId in ChainId]: string } = {
|
||||
[ChainId.BSC]: 'https://bsc-mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607',
|
||||
[ChainId.MAINNET]: 'https://mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607',
|
||||
[ChainId.XDAI]: 'https://gnosis-mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607',
|
||||
[ChainId.BSC]: 'https://tornadocash-rpc.com/bsc',
|
||||
[ChainId.MAINNET]: 'https://tornadocash-rpc.com/mainnet',
|
||||
[ChainId.XDAI]: 'https://tornadocash-rpc.com/gnosis',
|
||||
}
|
||||
|
||||
export const FALLBACK_RPC_LIST: { [chainId in ChainId]: string[] } = {
|
||||
@@ -37,7 +37,7 @@ export const FALLBACK_RPC_LIST: { [chainId in ChainId]: string[] } = {
|
||||
],
|
||||
[ChainId.XDAI]: [
|
||||
// 'https://rpc.ankr.com/gnosis/dbe08b852ba176a8aeac783cc1fa8becaf4f107235dfdae79241063fbf52ca4a',
|
||||
'https://rpc.gnosis.gateway.fm',
|
||||
'https://tornadocash-rpc.com/gnosis',
|
||||
],
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ export const BRIDGE_FEE_MANAGER: { [chainId in ChainId]: string } = {
|
||||
}
|
||||
|
||||
export const FOREIGN_OMNIBRIDGE = {
|
||||
[ChainId.MAINNET]: '0x88ad09518695c6c3712ac10a214be5109a655671',
|
||||
[ChainId.MAINNET]: '0x88ad09518695c6c3712AC10a214bE5109a655671',
|
||||
}
|
||||
|
||||
export const OMNIBRIDGE = {
|
||||
|
||||
@@ -55,7 +55,6 @@ export default {
|
||||
'setProvider',
|
||||
'changeChain',
|
||||
'checkNetwork',
|
||||
'checkSanction',
|
||||
'setWalletParams',
|
||||
'getWalletBalance',
|
||||
]),
|
||||
@@ -93,9 +92,6 @@ export default {
|
||||
const address = await provider.setupProvider()
|
||||
const network = await provider.checkNetworkVersion()
|
||||
|
||||
if (address) {
|
||||
await this.checkSanction(address)
|
||||
}
|
||||
await this.setProvider({ network, name: key })
|
||||
await this.setAccountData(address)
|
||||
|
||||
@@ -126,8 +122,6 @@ export default {
|
||||
if (address) {
|
||||
const checksumAddress = toChecksumAddress(address)
|
||||
|
||||
await this.checkSanction(checksumAddress)
|
||||
|
||||
if (!this.isConnected) {
|
||||
return
|
||||
}
|
||||
|
||||
15
copyFile.ts
Normal file
15
copyFile.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { argv } from 'process'
|
||||
import { copyFile } from 'fs'
|
||||
|
||||
function copyFiles() {
|
||||
const [, , inFile, outFile] = argv
|
||||
|
||||
copyFile(inFile, outFile, function(err) {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
|
||||
console.log(`Copied ${inFile} to ${outFile}`)
|
||||
})
|
||||
}
|
||||
copyFiles()
|
||||
@@ -5,11 +5,7 @@
|
||||
<p :class="$style.confirm__text">Processing...</p>
|
||||
|
||||
<ul :class="$style.confirm__list">
|
||||
<li
|
||||
v-for="{ name, text, progress } in stepsData"
|
||||
:key="name"
|
||||
:class="[$style.confirm__listItem, $style[processingStatuses[name]]]"
|
||||
>
|
||||
<li v-for="{ name, text, progress } in stepsData" :key="name" :class="[$style.confirm__listItem, $style[processingStatuses[name]]]">
|
||||
<span :class="$style.confirm__listItemStatus">
|
||||
<base-icon v-if="processingStatuses[name] === 'success'" name="tick" size="medium" />
|
||||
<base-icon v-if="processingStatuses[name] === 'fail'" name="cross" size="medium" />
|
||||
@@ -27,13 +23,7 @@
|
||||
<span v-if="transactionOptions.from" :class="$style.confirm__linksItemTitle">
|
||||
{{ transactionOptions.from }} transaction:
|
||||
</span>
|
||||
<a
|
||||
v-if="!!txHash"
|
||||
:class="$style.confirm__linksItemValue"
|
||||
:href="explorerLink"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<a v-if="!!txHash" :class="$style.confirm__linksItemValue" :href="explorerLink" target="_blank" rel="noopener noreferrer">
|
||||
{{ shortenLink }}
|
||||
</a>
|
||||
<span v-else :class="$style.confirm__linksItemValue_none">—</span>
|
||||
@@ -58,9 +48,9 @@
|
||||
<script>
|
||||
import { mapGetters, mapMutations, mapState } from 'vuex'
|
||||
|
||||
import { ApplicationMutation } from '@/types'
|
||||
import { ApplicationMutation, ChainId } from '@/types'
|
||||
import { SuccessModal } from '@/modals'
|
||||
import { createModalArgs, getEtherscanLink } from '@/utilities'
|
||||
import { createModalArgs, getEtherscanLink, getAmbBridgeTxLink } from '@/utilities'
|
||||
import { numbers, confirmationStatus, confirmationStep, transactionMethods, CHAINS } from '@/constants'
|
||||
|
||||
export default {
|
||||
@@ -136,14 +126,14 @@ export default {
|
||||
}
|
||||
},
|
||||
l2Link() {
|
||||
return getEtherscanLink(this.modal.chainId, this.txHash, 'transaction')
|
||||
return getAmbBridgeTxLink(this.modal.chainId, this.txHash)
|
||||
},
|
||||
explorerLink() {
|
||||
return getEtherscanLink(this.modal.chainId, this.txHash, 'transaction')
|
||||
},
|
||||
shortenLink() {
|
||||
return `${this.txHash.substring(numbers.ZERO, Number('8') + numbers.OX_LENGTH)}...${this.txHash.substring(
|
||||
Number('60') - Number('8'),
|
||||
Number('60') - Number('8')
|
||||
)}`
|
||||
},
|
||||
symbol() {
|
||||
|
||||
@@ -96,6 +96,10 @@ const config: NuxtConfig = {
|
||||
'@/plugins/prevent-multitabs.ts',
|
||||
],
|
||||
|
||||
typescript: {
|
||||
typeCheck: false,
|
||||
},
|
||||
|
||||
styleResources: {
|
||||
scss: ['@/assets/styles/_variables.scss', '@/assets/styles/*.scss'],
|
||||
},
|
||||
@@ -119,6 +123,7 @@ const config: NuxtConfig = {
|
||||
config.output.globalObject = 'this'
|
||||
}
|
||||
if (config?.module != null) {
|
||||
/**
|
||||
if (isClient) {
|
||||
config.module.rules.push({
|
||||
test: /\.worker\.js$/,
|
||||
@@ -129,6 +134,7 @@ const config: NuxtConfig = {
|
||||
},
|
||||
})
|
||||
}
|
||||
**/
|
||||
|
||||
config.module.rules.push({
|
||||
test: /\.bin$/,
|
||||
|
||||
21
package.json
21
package.json
@@ -10,21 +10,24 @@
|
||||
"lint": "eslint --ext .js,.ts",
|
||||
"lint:fix": "eslint --ext .js,.ts --quiet --fix",
|
||||
"compile": "typechain --target ethers-v5 --out-dir ./_contracts './abi/*.json'",
|
||||
"generate": "nuxt generate && cp dist/404.html dist/ipfs-404.html",
|
||||
"copyFile": "node --loader ts-node/esm copyFile.ts",
|
||||
"generate": "yarn worker:compile && nuxt generate && yarn copyFile dist/404.html dist/ipfs-404.html",
|
||||
"prepare": "husky install",
|
||||
"ipfs:upload": "node --loader ts-node/esm ipfsUpload.ts",
|
||||
"worker:compile": "nuxt generate && yarn compile:events && yarn compile:nullifier",
|
||||
"compile:events": "babel dist/_nuxt/workers/events.worker.js --out-file static/events.worker.js",
|
||||
"compile:nullifier": "babel dist/_nuxt/workers/nullifier.worker.js --out-file static/nullifier.worker.js"
|
||||
"worker:compile": "webpack",
|
||||
"update:events": "webpack && node ./syncEvents.cjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.4.16",
|
||||
"@babel/cli": "^7.15.7",
|
||||
"@flashbots/ethers-provider-bundle": "^0.3.2",
|
||||
"@mycrypto/gas-estimation": "^1.1.0",
|
||||
"@tornado/circomlib": "0.4.1",
|
||||
"@tornado/fixed-merkle-tree": "0.5.1",
|
||||
"@tornado/gas-price-oracle": "^0.5.3",
|
||||
"@tornado/snarkjs": "0.4.15",
|
||||
"ajv": "^8.6.2",
|
||||
"axios": "^0.27.2",
|
||||
"circomlib": "git+https://github.com/tornadocash/circomlib.git#d20d53411d1bef61f38c99a8b36d5d0cc4836aa1",
|
||||
"core-js": "^3.9.1",
|
||||
"crypto-js": "^4.1.1",
|
||||
"effector": "^21.8.12",
|
||||
@@ -34,8 +37,6 @@
|
||||
"ffjavascript": "^0.2.37",
|
||||
"file-saver": "^2.0.5",
|
||||
"find-replacement-tx": "^1.2.1",
|
||||
"fixed-merkle-tree": "0.5.1",
|
||||
"gas-price-oracle": "0.4.4",
|
||||
"graphql": "^15.6.1",
|
||||
"idb": "6.0.0",
|
||||
"jszip": "^3.7.1",
|
||||
@@ -43,7 +44,6 @@
|
||||
"luxon": "^2.0.1",
|
||||
"nuxt": "^2.15.7",
|
||||
"sanitize.css": "^12.0.1",
|
||||
"snarkjs": "git+https://github.com/tornadocash/snarkjs.git#c103e3bf10e95e2e9bbf0f7952ed13812f8e39d3",
|
||||
"v-tooltip": "^2.1.3",
|
||||
"vue-clickaway": "^2.2.2",
|
||||
"vue-js-modal": "^2.0.0-rc.6",
|
||||
@@ -74,7 +74,7 @@
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-vue": "^7.16.0",
|
||||
"fibers": "^5.0.0",
|
||||
"fflate": "^0.8.2",
|
||||
"form-data": "^4.0.0",
|
||||
"husky": "^6.0.0",
|
||||
"lint-staged": "10.2.11",
|
||||
@@ -82,9 +82,12 @@
|
||||
"sass": "1.32",
|
||||
"sass-loader": "10",
|
||||
"ts-loader": "8.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"tslib": "^2.6.2",
|
||||
"typechain": "^5.1.0",
|
||||
"typescript": "^4.3.4",
|
||||
"vue-eslint-parser": "^7.6.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"worker-loader": "^3.0.8"
|
||||
},
|
||||
"husky": {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import Jszip from 'jszip'
|
||||
import { BigNumber } from 'ethers'
|
||||
// @ts-expect-error
|
||||
import MerkleTree from 'fixed-merkle-tree'
|
||||
import MerkleTree from '@tornado/fixed-merkle-tree'
|
||||
import axios, { AxiosResponse } from 'axios'
|
||||
import { BytesLike } from '@ethersproject/bytes'
|
||||
|
||||
@@ -186,8 +186,12 @@ async function prepareTransaction({
|
||||
async function getIPFSIdFromENS(ensName: string) {
|
||||
const { provider } = getProvider(ChainId.MAINNET)
|
||||
const resolver = await provider.getResolver(ensName)
|
||||
const cHash = await resolver.getContentHash()
|
||||
if (!resolver) {
|
||||
console.error(`Cannot fetch ENS resolver for ${ensName}`)
|
||||
return ''
|
||||
}
|
||||
|
||||
const cHash = await resolver.getContentHash()
|
||||
const [, id] = cHash.split('://')
|
||||
|
||||
return id
|
||||
@@ -208,12 +212,7 @@ async function fetchFile<T>({ url, name, id, retryAttempt = numbers.ZERO }: Fetc
|
||||
id = await getIPFSIdFromENS(APP_ENS_NAME)
|
||||
}
|
||||
|
||||
const knownResources = [
|
||||
url,
|
||||
`https://ipfs.io/ipfs/${id}`,
|
||||
`https://dweb.link/ipfs/${id}`,
|
||||
`https://gateway.pinata.cloud/ipfs/${id}`,
|
||||
]
|
||||
const knownResources = [url, `https://ipfs.io/ipfs/${id}`, `https://dweb.link/ipfs/${id}`, `https://gateway.pinata.cloud/ipfs/${id}`]
|
||||
|
||||
if (retryAttempt < knownResources.length) {
|
||||
const fallbackUrl = knownResources[retryAttempt]
|
||||
@@ -257,7 +256,7 @@ async function estimateTransact(payload: EstimateTransactParams) {
|
||||
} catch (err) {
|
||||
console.error('estimateTransact has error:', err.message)
|
||||
throw new Error(
|
||||
`Looks like you are accessing an outdated version of the user interface. Reload page or try an alternative gateway. If that doesn't work please contact support`,
|
||||
`Looks like you are accessing an outdated version of the user interface. Reload page or try an alternative gateway. If that doesn't work please contact support`
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -274,7 +273,7 @@ async function createTransactionData(params: CreateTransactionParams, keypair: K
|
||||
} else {
|
||||
const commitmentsService = commitmentsFactory.getService(ChainId.XDAI)
|
||||
|
||||
params.events =await commitmentsService.fetchCommitments(keypair)
|
||||
params.events = await commitmentsService.fetchCommitments(keypair)
|
||||
}
|
||||
|
||||
const { extData, args, amount } = await prepareTransaction(params)
|
||||
|
||||
@@ -2,7 +2,7 @@ import crypto from 'crypto'
|
||||
|
||||
import { BigNumber, utils } from 'ethers'
|
||||
// @ts-expect-error
|
||||
import { poseidon } from 'circomlib'
|
||||
import { poseidon } from '@tornado/circomlib'
|
||||
|
||||
import { numbers, FIELD_SIZE } from '@/constants'
|
||||
|
||||
@@ -36,16 +36,7 @@ interface Params {
|
||||
encryptedOutput2: string
|
||||
}
|
||||
|
||||
function getExtDataHash({
|
||||
recipient,
|
||||
extAmount,
|
||||
isL1Withdrawal,
|
||||
relayer,
|
||||
fee,
|
||||
l1Fee,
|
||||
encryptedOutput1,
|
||||
encryptedOutput2,
|
||||
}: Params) {
|
||||
function getExtDataHash({ recipient, extAmount, isL1Withdrawal, relayer, fee, l1Fee, encryptedOutput1, encryptedOutput2 }: Params) {
|
||||
const abi = new utils.AbiCoder()
|
||||
|
||||
const encodedData = abi.encode(
|
||||
@@ -63,7 +54,7 @@ function getExtDataHash({
|
||||
isL1Withdrawal: isL1Withdrawal,
|
||||
l1Fee: toFixedHex(l1Fee),
|
||||
},
|
||||
],
|
||||
]
|
||||
)
|
||||
const hash = utils.keccak256(encodedData)
|
||||
return BigNumber.from(hash).mod(FIELD_SIZE)
|
||||
@@ -74,7 +65,7 @@ function toFixedHex(number?: number | Buffer | BigNumber | string, length = BYTE
|
||||
'0x' +
|
||||
(number instanceof Buffer ? number.toString('hex') : BigNumber.from(number).toHexString().replace('0x', '')).padStart(
|
||||
length * numbers.TWO,
|
||||
'0',
|
||||
'0'
|
||||
)
|
||||
if (result.includes('-')) {
|
||||
result = '-' + result.replace('-', '')
|
||||
|
||||
109
services/events/batch.ts
Normal file
109
services/events/batch.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { Provider, Contract, EventLog } from "ethers";
|
||||
import { sleep, getBatches } from "@/utilities";
|
||||
|
||||
export interface BatchEventServiceConstructor {
|
||||
provider: Provider;
|
||||
contract: Contract;
|
||||
concurrencySize?: number;
|
||||
blocksPerRequest?: number;
|
||||
shouldRetry?: boolean;
|
||||
retryMax?: number;
|
||||
retryOn?: number;
|
||||
}
|
||||
|
||||
export type EventInput = {
|
||||
fromBlock: number;
|
||||
toBlock: number;
|
||||
type: string;
|
||||
};
|
||||
|
||||
export class BatchEventsService {
|
||||
provider: Provider;
|
||||
contract: Contract;
|
||||
concurrencySize: number;
|
||||
blocksPerRequest: number;
|
||||
shouldRetry: boolean;
|
||||
retryMax: number;
|
||||
retryOn: number;
|
||||
constructor({
|
||||
provider,
|
||||
contract,
|
||||
concurrencySize = 10,
|
||||
blocksPerRequest = 2000,
|
||||
shouldRetry = true,
|
||||
retryMax = 5,
|
||||
retryOn = 500,
|
||||
}: BatchEventServiceConstructor) {
|
||||
this.provider = provider;
|
||||
this.contract = contract;
|
||||
this.concurrencySize = concurrencySize;
|
||||
this.blocksPerRequest = blocksPerRequest;
|
||||
this.shouldRetry = shouldRetry;
|
||||
this.retryMax = retryMax;
|
||||
this.retryOn = retryOn;
|
||||
}
|
||||
|
||||
async getPastEvents({ fromBlock, toBlock, type }: EventInput): Promise<EventLog[]> {
|
||||
let err;
|
||||
let retries = 0;
|
||||
|
||||
// eslint-disable-next-line no-unmodified-loop-condition
|
||||
while ((!this.shouldRetry && retries === 0) || (this.shouldRetry && retries < this.retryMax)) {
|
||||
try {
|
||||
return (await this.contract.queryFilter(type, fromBlock, toBlock)) as EventLog[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (e: any) {
|
||||
err = e;
|
||||
retries++;
|
||||
|
||||
// If provider.getBlockNumber returned last block that isn't accepted (happened on Avalanche/Gnosis),
|
||||
// get events to last accepted block
|
||||
if (e.message.includes('after last accepted block')) {
|
||||
const acceptedBlock = parseInt(e.message.split('after last accepted block ')[1]);
|
||||
toBlock = acceptedBlock;
|
||||
}
|
||||
|
||||
// retry on 0.5 seconds
|
||||
await sleep(this.retryOn);
|
||||
}
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
createBatchRequest(batchArray: EventInput[]): Promise<EventLog[]>[] {
|
||||
return batchArray.map(async (event: EventInput, index: number) => {
|
||||
await sleep(20 * index);
|
||||
|
||||
return this.getPastEvents(event);
|
||||
});
|
||||
}
|
||||
|
||||
async getBatchEvents({ fromBlock, toBlock, type = '*' }: EventInput): Promise<EventLog[]> {
|
||||
if (!toBlock) {
|
||||
toBlock = await this.provider.getBlockNumber();
|
||||
}
|
||||
|
||||
const eventsToSync: EventInput[] = [];
|
||||
|
||||
for (let i = fromBlock; i < toBlock; i += this.blocksPerRequest) {
|
||||
const j = i + this.blocksPerRequest - 1 > toBlock ? toBlock : i + this.blocksPerRequest - 1;
|
||||
|
||||
eventsToSync.push({ fromBlock: i, toBlock: j, type } as EventInput);
|
||||
}
|
||||
|
||||
const events = [];
|
||||
const eventChunk = getBatches<EventInput>(eventsToSync, this.concurrencySize);
|
||||
|
||||
let chunkCount = 0;
|
||||
|
||||
for (const chunk of eventChunk) {
|
||||
chunkCount++;
|
||||
|
||||
const fetchedEvents = (await Promise.all(this.createBatchRequest(chunk))).flat();
|
||||
events.push(...fetchedEvents);
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,9 @@ import { isEmpty, sleep, toChecksumAddress } from '@/utilities'
|
||||
import { getBridgeHelper, getBridgeProxy, getAmbBridge } from '@/contracts'
|
||||
|
||||
import { EventsClass, GetAffirmationParams, GetRelayedMessageParams, SaveEventsParams } from './@types'
|
||||
import { downloadEvents } from './load'
|
||||
|
||||
export * from './batch'
|
||||
|
||||
class EventAggregator implements EventsClass {
|
||||
public async getBackupedAddressFromPublicKey(publicKey: string) {
|
||||
@@ -31,19 +34,31 @@ class EventAggregator implements EventsClass {
|
||||
storeName: `${IndexDBStores.ACCOUNT_EVENTS}_${chainId}`,
|
||||
})
|
||||
|
||||
const newEvents = []
|
||||
|
||||
if (cachedEvents?.length) {
|
||||
const [latestEvent] = cachedEvents.slice(-numbers.ONE)
|
||||
blockFrom = Number(latestEvent.blockNumber) + numbers.ONE
|
||||
} else {
|
||||
const downloadedEvents = await downloadEvents(`accounts_${chainId}.json`, blockFrom)
|
||||
|
||||
if (downloadedEvents.events.length) {
|
||||
newEvents.push(...downloadedEvents.events)
|
||||
|
||||
blockFrom = downloadedEvents.lastBlock
|
||||
}
|
||||
}
|
||||
|
||||
const { events: graphEvents, lastSyncBlock } = await getAllAccounts({ fromBlock: blockFrom, chainId })
|
||||
|
||||
const [account] = graphEvents.filter((e: { key: string }) => e.key === publicKey)
|
||||
newEvents.push(...graphEvents)
|
||||
|
||||
const [account] = newEvents.filter((e: { key: string }) => e.key === publicKey)
|
||||
|
||||
if (account) {
|
||||
this.saveEvents({
|
||||
chainId,
|
||||
events: graphEvents,
|
||||
events: newEvents,
|
||||
storeName: IndexDBStores.ACCOUNT_EVENTS,
|
||||
})
|
||||
return account.owner
|
||||
@@ -66,7 +81,7 @@ class EventAggregator implements EventsClass {
|
||||
}
|
||||
})
|
||||
|
||||
const newEvents = graphEvents.concat(accountEvents)
|
||||
newEvents.push(...accountEvents)
|
||||
|
||||
this.saveEvents({
|
||||
chainId,
|
||||
@@ -74,7 +89,7 @@ class EventAggregator implements EventsClass {
|
||||
storeName: IndexDBStores.ACCOUNT_EVENTS,
|
||||
})
|
||||
|
||||
const events = cachedEvents.concat(newEvents).filter((e: { key: string }) => e.key === publicKey)
|
||||
const events = newEvents.filter((e: { key: string }) => e.key === publicKey)
|
||||
|
||||
if (isEmpty(events)) {
|
||||
return undefined
|
||||
@@ -85,6 +100,7 @@ class EventAggregator implements EventsClass {
|
||||
|
||||
return event.owner
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
@@ -111,19 +127,30 @@ class EventAggregator implements EventsClass {
|
||||
storeName: `${IndexDBStores.ACCOUNT_EVENTS}_${chainId}`,
|
||||
})
|
||||
|
||||
const newEvents = []
|
||||
|
||||
if (cachedEvents?.length) {
|
||||
const [latestEvent] = cachedEvents.slice(-numbers.ONE)
|
||||
blockFrom = Number(latestEvent.blockNumber) + numbers.ONE
|
||||
} else {
|
||||
const downloadedEvents = await downloadEvents(`accounts_${chainId}.json`, blockFrom)
|
||||
|
||||
if (downloadedEvents.events.length) {
|
||||
newEvents.push(...downloadedEvents.events)
|
||||
|
||||
blockFrom = downloadedEvents.lastBlock
|
||||
}
|
||||
}
|
||||
|
||||
const { events: graphEvents, lastSyncBlock } = await getAllAccounts({ fromBlock: blockFrom, chainId })
|
||||
newEvents.push(...graphEvents)
|
||||
|
||||
const [account] = graphEvents.filter((e: { owner: string }) => toChecksumAddress(e.owner) === toChecksumAddress(address))
|
||||
const [account] = newEvents.filter((e: { owner: string }) => toChecksumAddress(e.owner) === toChecksumAddress(address))
|
||||
|
||||
if (account) {
|
||||
this.saveEvents({
|
||||
chainId,
|
||||
events: graphEvents,
|
||||
events: newEvents,
|
||||
storeName: IndexDBStores.ACCOUNT_EVENTS,
|
||||
})
|
||||
return account.key
|
||||
@@ -146,7 +173,7 @@ class EventAggregator implements EventsClass {
|
||||
}
|
||||
})
|
||||
|
||||
const newEvents = graphEvents.concat(accountEvents)
|
||||
newEvents.push(...accountEvents)
|
||||
|
||||
this.saveEvents({
|
||||
chainId,
|
||||
@@ -167,6 +194,7 @@ class EventAggregator implements EventsClass {
|
||||
|
||||
return event.key
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
49
services/events/load.ts
Normal file
49
services/events/load.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { unzip } from 'fflate'
|
||||
|
||||
export function unzipAsync(data: Uint8Array) {
|
||||
return new Promise((res, rej) => {
|
||||
unzip(data, {}, (err, data) => {
|
||||
if (err) {
|
||||
rej(err);
|
||||
return;
|
||||
}
|
||||
res(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function downloadEvents(fileName: string, deployedBlock: number) {
|
||||
fileName = fileName.toLowerCase()
|
||||
|
||||
// @ts-ignore
|
||||
const prefix = __webpack_public_path__.slice(0, -7)
|
||||
|
||||
try {
|
||||
const resp = await fetch(`${prefix}/${fileName}.zip`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
}
|
||||
})
|
||||
|
||||
const arrayBuffer = await resp.arrayBuffer()
|
||||
|
||||
const { [fileName]: content } = (await unzipAsync(new Uint8Array(arrayBuffer))) as any
|
||||
|
||||
const events = JSON.parse(new TextDecoder().decode(content))
|
||||
|
||||
const lastBlock = events && Array.isArray(events) && events[events.length - 1]
|
||||
? events[events.length - 1].blockNumber
|
||||
: deployedBlock
|
||||
|
||||
return {
|
||||
events,
|
||||
lastBlock
|
||||
}
|
||||
} catch {
|
||||
return {
|
||||
events: [],
|
||||
lastBlock: deployedBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { GasPriceOracle } from 'gas-price-oracle'
|
||||
import { GasPrice } from 'gas-price-oracle/lib/types'
|
||||
import { GasPriceOracle } from '@tornado/gas-price-oracle'
|
||||
import { GasPrice } from '@/types'
|
||||
|
||||
import { ChainId } from '@/types'
|
||||
import { numbers, RPC_LIST } from '@/constants'
|
||||
@@ -26,7 +26,7 @@ const getGasPrice = async (chainId: ChainId): Promise<GasPrice> => {
|
||||
timeout: TEN_SECOND,
|
||||
defaultRpc: RPC_LIST[chainId],
|
||||
})
|
||||
const result = await instance.gasPrices()
|
||||
const result = (await instance.gasPrices({ isLegacy: true })) as GasPrice
|
||||
|
||||
if (chainId === ChainId.XDAI || chainId === ChainId.MAINNET) {
|
||||
return {
|
||||
|
||||
@@ -12,3 +12,21 @@ export type Account = {
|
||||
}
|
||||
|
||||
export type Accounts = Account[]
|
||||
|
||||
export type Commitment = {
|
||||
index: string
|
||||
commitment: string
|
||||
blockNumber: string
|
||||
encryptedOutput: string
|
||||
transactionHash: string
|
||||
}
|
||||
|
||||
export type Commitments = Commitment[]
|
||||
|
||||
export type Nullifier = {
|
||||
nullifier: string
|
||||
blockNumber: string
|
||||
transactionHash: string
|
||||
}
|
||||
|
||||
export type Nullifiers = Nullifier[]
|
||||
@@ -5,8 +5,8 @@ import { ChainId } from '@/types'
|
||||
import { numbers } from '@/constants'
|
||||
import { isEmpty, toChecksumAddress } from '@/utilities'
|
||||
|
||||
import { Params, Accounts } from './@types'
|
||||
import { _META, GET_ACCOUNTS, GET_REGISTERED } from './queries'
|
||||
import { Params, Accounts, Commitments, Nullifiers } from './@types'
|
||||
import { _META, GET_ACCOUNTS, GET_REGISTERED, GET_COMMITMENT, GET_NULLIFIER } from './queries'
|
||||
|
||||
const first = 1000
|
||||
const breakLength = 900
|
||||
@@ -18,7 +18,8 @@ const link = (operation: Operation) => {
|
||||
|
||||
const CHAIN_GRAPH_URLS: { [chainId in ChainId]: string } = {
|
||||
[ChainId.BSC]: 'https://api.thegraph.com/subgraphs/name/dan1kov/bsc-tornado-pool-subgraph',
|
||||
[ChainId.MAINNET]: 'https://api.thegraph.com/subgraphs/name/tornadocash/mainnet-tornado-pool-subgraph',
|
||||
[ChainId.MAINNET]: 'https://tornadocash-rpc.com/subgraphs/name/tornadocash/mainnet-tornado-pool-subgraph',
|
||||
[ChainId.XDAI]: 'https://tornadocash-rpc.com/subgraphs/name/tornadocash/gnosis-tornado-nova-subgraph',
|
||||
}
|
||||
|
||||
const client = new ApolloClient({
|
||||
@@ -27,7 +28,7 @@ const client = new ApolloClient({
|
||||
})
|
||||
|
||||
const registryClient = new ApolloClient({
|
||||
uri: 'https://api.thegraph.com/subgraphs/name/tornadocash/tornado-relayer-registry',
|
||||
uri: 'https://tornadocash-rpc.com/subgraphs/name/tornadocash/tornado-relayer-registry',
|
||||
cache: new InMemoryCache(),
|
||||
})
|
||||
|
||||
@@ -169,3 +170,166 @@ async function getMeta({ chainId }: Params) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function getCommitments({ fromBlock, chainId }: Params): Promise<{
|
||||
results: Commitments,
|
||||
lastSyncBlock: number
|
||||
}> {
|
||||
const { data } = await client.query({
|
||||
context: {
|
||||
chainId,
|
||||
},
|
||||
query: gql(GET_COMMITMENT),
|
||||
variables: { first, fromBlock },
|
||||
})
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
results: [],
|
||||
lastSyncBlock: data._meta.block.number
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
results: data.commitments,
|
||||
lastSyncBlock: data._meta.block.number
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAllCommitments({ fromBlock, chainId }: Params) {
|
||||
try {
|
||||
let commitments: Commitments = []
|
||||
let lastSyncBlock
|
||||
|
||||
while (true) {
|
||||
let { results, lastSyncBlock: lastBlock } = await getCommitments({ fromBlock, chainId })
|
||||
|
||||
lastSyncBlock = lastBlock
|
||||
|
||||
if (isEmpty(results)) {
|
||||
break
|
||||
}
|
||||
|
||||
if (results.length < breakLength) {
|
||||
commitments = commitments.concat(results)
|
||||
break
|
||||
}
|
||||
|
||||
const [lastEvent] = results.slice(-numbers.ONE)
|
||||
|
||||
results = results.filter((e) => e.blockNumber !== lastEvent.blockNumber)
|
||||
fromBlock = Number(lastEvent.blockNumber)
|
||||
|
||||
commitments = commitments.concat(results)
|
||||
}
|
||||
|
||||
if (!commitments) {
|
||||
return {
|
||||
lastSyncBlock,
|
||||
events: [],
|
||||
}
|
||||
}
|
||||
|
||||
const data = commitments
|
||||
.map((e) => ({
|
||||
index: Number(e.index),
|
||||
commitment: e.commitment,
|
||||
blockNumber: Number(e.blockNumber),
|
||||
encryptedOutput: e.encryptedOutput,
|
||||
transactionHash: e.transactionHash
|
||||
}))
|
||||
.sort((a, b) => a.index - b.index)
|
||||
|
||||
const [lastEvent] = data.slice(-numbers.ONE)
|
||||
|
||||
return {
|
||||
events: data,
|
||||
lastSyncBlock: lastEvent?.blockNumber > lastSyncBlock ? lastEvent.blockNumber + numbers.ONE : lastSyncBlock,
|
||||
}
|
||||
} catch {
|
||||
return {
|
||||
lastSyncBlock: '',
|
||||
events: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getNullifiers({ fromBlock, chainId }: Params): Promise<{
|
||||
results: Nullifiers,
|
||||
lastSyncBlock: number
|
||||
}> {
|
||||
const { data } = await client.query({
|
||||
context: {
|
||||
chainId,
|
||||
},
|
||||
query: gql(GET_NULLIFIER),
|
||||
variables: { first, fromBlock },
|
||||
})
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
results: [],
|
||||
lastSyncBlock: data._meta.block.number
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
results: data.nullifiers,
|
||||
lastSyncBlock: data._meta.block.number
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAllNullifiers({ fromBlock, chainId }: Params) {
|
||||
try {
|
||||
let nullifiers: Nullifiers = []
|
||||
let lastSyncBlock
|
||||
|
||||
while (true) {
|
||||
let { results, lastSyncBlock: lastBlock } = await getNullifiers({ fromBlock, chainId })
|
||||
|
||||
lastSyncBlock = lastBlock
|
||||
|
||||
if (isEmpty(results)) {
|
||||
break
|
||||
}
|
||||
|
||||
if (results.length < breakLength) {
|
||||
nullifiers = nullifiers.concat(results)
|
||||
break
|
||||
}
|
||||
|
||||
const [lastEvent] = results.slice(-numbers.ONE)
|
||||
|
||||
results = results.filter((e) => e.blockNumber !== lastEvent.blockNumber)
|
||||
fromBlock = Number(lastEvent.blockNumber)
|
||||
|
||||
nullifiers = nullifiers.concat(results)
|
||||
}
|
||||
|
||||
if (!nullifiers) {
|
||||
return {
|
||||
lastSyncBlock,
|
||||
events: [],
|
||||
}
|
||||
}
|
||||
|
||||
const data = nullifiers.map((e) => ({
|
||||
nullifier: e.nullifier,
|
||||
blockNumber: Number(e.blockNumber),
|
||||
transactionHash: e.transactionHash
|
||||
}))
|
||||
|
||||
const [lastEvent] = data.slice(-numbers.ONE)
|
||||
|
||||
return {
|
||||
events: data,
|
||||
lastSyncBlock: lastEvent?.blockNumber > lastSyncBlock ? lastEvent.blockNumber + numbers.ONE : lastSyncBlock,
|
||||
}
|
||||
} catch {
|
||||
return {
|
||||
lastSyncBlock: '',
|
||||
events: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,3 +33,41 @@ export const GET_REGISTERED = `
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const GET_COMMITMENT = `
|
||||
query getCommitment($first: Int, $fromBlock: Int) {
|
||||
commitments(first: $first, orderBy: blockNumber, orderDirection: asc, where: {
|
||||
blockNumber_gte: $fromBlock
|
||||
}) {
|
||||
index
|
||||
commitment
|
||||
blockNumber
|
||||
encryptedOutput
|
||||
transactionHash
|
||||
}
|
||||
_meta {
|
||||
block {
|
||||
number
|
||||
}
|
||||
hasIndexingErrors
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const GET_NULLIFIER = `
|
||||
query getNullifier($first: Int, $fromBlock: Int) {
|
||||
nullifiers(first: $first, orderBy: blockNumber, orderDirection: asc, where: {
|
||||
blockNumber_gte: $fromBlock
|
||||
}) {
|
||||
nullifier
|
||||
blockNumber
|
||||
transactionHash
|
||||
}
|
||||
_meta {
|
||||
block {
|
||||
number
|
||||
}
|
||||
hasIndexingErrors
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -7,12 +7,6 @@ import { CommitmentEvents, NullifierEvents } from '@/services/events/@types'
|
||||
|
||||
import { EventsPayload, DecryptedEvents, GetEventsFromTxHashParams } from './@types'
|
||||
|
||||
import '@/assets/events.worker.js'
|
||||
import '@/assets/nullifier.worker.js'
|
||||
|
||||
// import NWorker from '@/assets/nullifier.worker.js'
|
||||
// import EWorker from '@/assets/events.worker.js'
|
||||
|
||||
export interface WorkerProvider {
|
||||
workerSetup: (chainId: ChainId) => void
|
||||
getCommitmentEvents: () => Promise<CommitmentEvents>
|
||||
@@ -41,13 +35,8 @@ class Provider implements WorkerProvider {
|
||||
|
||||
const basePath = `${window.location.origin}${ipfsPathPrefix}`
|
||||
|
||||
this.nullifierWorkers = new Array(CORES).fill('').map(() => new Worker(`${basePath}/_nuxt/workers/nullifier.worker.js`))
|
||||
this.eventsWorkers = new Array(CORES).fill('').map(() => new Worker(`${basePath}/_nuxt/workers/events.worker.js`))
|
||||
|
||||
// // @ts-expect-error
|
||||
// this.nullifierWorkers = new Array(CORES).fill('').map(() => new NWorker())
|
||||
// // @ts-expect-error
|
||||
// this.eventsWorkers = new Array(CORES).fill('').map(() => new EWorker())
|
||||
this.nullifierWorkers = new Array(CORES).fill('').map(() => new Worker(`${basePath}/nullifier.worker.js`))
|
||||
this.eventsWorkers = new Array(CORES).fill('').map(() => new Worker(`${basePath}/events.worker.js`))
|
||||
}
|
||||
|
||||
public workerSetup = (chainId: ChainId) => {
|
||||
|
||||
BIN
static/accounts_1.json.zip
Normal file
BIN
static/accounts_1.json.zip
Normal file
Binary file not shown.
BIN
static/commitments_100.json.zip
Normal file
BIN
static/commitments_100.json.zip
Normal file
Binary file not shown.
1
static/events.worker.js
Normal file
1
static/events.worker.js
Normal file
File diff suppressed because one or more lines are too long
1
static/nullifier.worker.js
Normal file
1
static/nullifier.worker.js
Normal file
File diff suppressed because one or more lines are too long
BIN
static/nullifiers_100.json.zip
Normal file
BIN
static/nullifiers_100.json.zip
Normal file
Binary file not shown.
@@ -19,17 +19,6 @@ export const actions: ActionTree<WalletState, RootState> = {
|
||||
}
|
||||
},
|
||||
|
||||
async checkSanction({ getters }, address) {
|
||||
const contract = getSanctionList(getters.dependencies.l1ChainId)
|
||||
|
||||
const isSanctioned = await contract.callStatic.isSanctioned(address)
|
||||
if (isSanctioned) {
|
||||
window.onbeforeunload = null
|
||||
// ToDo add type
|
||||
// @ts-expect-error
|
||||
window.location = 'https://twitter.com/TornadoCash/status/1514904975037669386'
|
||||
}
|
||||
},
|
||||
checkAppNetwork({ commit }, network) {
|
||||
try {
|
||||
// TODO create a selector for active network
|
||||
|
||||
6
types/global.d.ts
vendored
6
types/global.d.ts
vendored
@@ -21,6 +21,12 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
declare module '@tornado/circomlib' {
|
||||
interface Circom {
|
||||
poseidon: CallableFunction
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'vue/types/vue' {
|
||||
interface Vue {
|
||||
$eventsWorker: WorkerProvider
|
||||
|
||||
@@ -70,6 +70,16 @@ const ETHERSCAN_PREFIXES: { [chainId in ChainId]: string } = {
|
||||
100: '',
|
||||
}
|
||||
|
||||
const AMB_EXPLORER_SUFFIXES: { [chainId in ChainId]: string } = {
|
||||
1: 'xdai',
|
||||
56: 'bsc',
|
||||
100: 'xdai',
|
||||
}
|
||||
|
||||
export function getAmbBridgeTxLink(chainId: ChainId, data: string): string {
|
||||
return `https://alm-${AMB_EXPLORER_SUFFIXES[chainId]}.tornado.ws/${chainId}/${data}`
|
||||
}
|
||||
|
||||
export function getEtherscanLink(chainId: ChainId, data: string, type: 'transaction' | 'token' | 'address' | 'block'): string {
|
||||
let prefix = `https://${ETHERSCAN_PREFIXES[chainId]}etherscan.io`
|
||||
|
||||
@@ -215,7 +225,7 @@ export function encodeTransactData({ args, extData }: { args: ArgsProof; extData
|
||||
'tuple(bytes proof,bytes32 root,bytes32[] inputNullifiers,bytes32[2] outputCommitments,uint256 publicAmount,bytes32 extDataHash)',
|
||||
'tuple(address recipient,int256 extAmount,address relayer,uint256 fee,bytes encryptedOutput1,bytes encryptedOutput2,bool isL1Withdrawal,uint256 l1Fee)',
|
||||
],
|
||||
[args, extData],
|
||||
[args, extData]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -268,7 +278,7 @@ export const getMessageIdFromTransaction = (type: 'withdrawal' | 'deposit', rece
|
||||
// eslint-disable-next-line
|
||||
export const onStaticMulticall = async <C extends BaseContract, A extends any[]>(
|
||||
chainId: ChainId,
|
||||
calls: Array<{ contract: C; methodName: string; args: A }>,
|
||||
calls: Array<{ contract: C; methodName: string; args: A }>
|
||||
) => {
|
||||
const multicall = getMulticall(chainId)
|
||||
|
||||
|
||||
53
webpack.config.js
Normal file
53
webpack.config.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import path from 'path'
|
||||
import webpack from 'webpack'
|
||||
|
||||
export default [
|
||||
{
|
||||
mode: 'production',
|
||||
entry: './assets/events.worker.js',
|
||||
output: {
|
||||
path: path.resolve('static'),
|
||||
filename: 'events.worker.js',
|
||||
}
|
||||
},
|
||||
{
|
||||
mode: 'production',
|
||||
entry: './assets/nullifier.worker.js',
|
||||
output: {
|
||||
path: path.resolve('static'),
|
||||
filename: 'nullifier.worker.js',
|
||||
}
|
||||
},
|
||||
{
|
||||
mode: 'production',
|
||||
entry: './assets/syncEvents.js',
|
||||
output: {
|
||||
path: path.resolve('.'),
|
||||
filename: 'syncEvents.cjs',
|
||||
},
|
||||
target: 'node',
|
||||
plugins: [
|
||||
new webpack.BannerPlugin({
|
||||
banner: '#!/usr/bin/env node\n',
|
||||
raw: true
|
||||
})
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.mjs$/,
|
||||
include: /node_modules/,
|
||||
type: 'javascript/auto'
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'fflate': 'fflate/esm'
|
||||
}
|
||||
},
|
||||
optimization: {
|
||||
minimize: false,
|
||||
}
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user