Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e6e2e0a291 | |||
| e5f1b6f91c | |||
| 4b9baa63d0 | |||
| 382fe5a127 |
@ -1,2 +1,5 @@
|
|||||||
PINATA_API_KEY=
|
PINATA_API_KEY=
|
||||||
PINATA_SECRET_API_KEY=
|
PINATA_SECRET_API_KEY=
|
||||||
|
|
||||||
|
MAINNET_SUBGRAPH=
|
||||||
|
MAINNET_RPC=
|
||||||
46
Dockerfile
Normal file
46
Dockerfile
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# Dockefile from https://notes.ethereum.org/@GW1ZUbNKR5iRjjKYx6_dJQ/Bk8zsJ9xj
|
||||||
|
# FROM node:22.17.0-bullseye-slim
|
||||||
|
FROM node@sha256:98663a445a21da13827b841d8df7b4d8743d5133e0d7a4e28ec0852140aa1abe
|
||||||
|
|
||||||
|
# install wget, git and necessary certificates so we can install IPFS below
|
||||||
|
RUN apt update && apt install --yes --no-install-recommends wget git apt-transport-https ca-certificates && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# install IPFS
|
||||||
|
WORKDIR /home/root
|
||||||
|
RUN wget -qO - https://dist.ipfs.tech/kubo/v0.35.0/kubo_v0.35.0_linux-amd64.tar.gz | tar -xvzf - \
|
||||||
|
&& cd kubo \
|
||||||
|
&& ./install.sh \
|
||||||
|
&& cd .. \
|
||||||
|
&& rm -rf kubo
|
||||||
|
RUN ipfs init
|
||||||
|
|
||||||
|
ENV GIT_REPOSITORY=https://codeberg.org/tornadocash/relayers-network-ui.git
|
||||||
|
# From development branch, double check with tornado.ws
|
||||||
|
ENV GIT_COMMIT_HASH=e5f1b6f91c372fc58a7a6eb463e21a62a059db3b
|
||||||
|
|
||||||
|
# clone the repository
|
||||||
|
RUN mkdir /app/
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Simple hack to fetch only commit and nothing more (no need to download 1GB sized repo, only 100MB would be enough)
|
||||||
|
RUN git init && \
|
||||||
|
git remote add origin $GIT_REPOSITORY && \
|
||||||
|
git fetch --depth 1 origin $GIT_COMMIT_HASH && \
|
||||||
|
git checkout $GIT_COMMIT_HASH
|
||||||
|
|
||||||
|
# install, build and prep for deployment
|
||||||
|
RUN yarn install --frozen-lockfile --ignore-scripts
|
||||||
|
RUN yarn build
|
||||||
|
RUN yarn generate
|
||||||
|
|
||||||
|
# add the build output to IPFS and write the hash to a file
|
||||||
|
RUN ipfs add --cid-version 1 --quieter --only-hash --recursive ./dist > ipfs_hash.txt
|
||||||
|
# print the hash for good measure in case someone is looking at the build logs
|
||||||
|
RUN cat ipfs_hash.txt
|
||||||
|
|
||||||
|
# this entrypoint file will execute `ipfs add` of the build output to the docker host's IPFS API endpoint, so we can easily extract the IPFS build out of the docker image
|
||||||
|
RUN printf '#!/bin/sh\nipfs --api /ip4/`getent ahostsv4 host.docker.internal | grep STREAM | head -n 1 | cut -d \ -f 1`/tcp/5001 add --cid-version 1 -r ./dist' >> entrypoint.sh
|
||||||
|
RUN chmod u+x entrypoint.sh
|
||||||
|
|
||||||
|
ENTRYPOINT [ "./entrypoint.sh" ]
|
||||||
10
constants/graph.ts
Normal file
10
constants/graph.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { ChainId } from '@/types'
|
||||||
|
|
||||||
|
export const GRAPHQL_LIMIT = 1000
|
||||||
|
|
||||||
|
/**
|
||||||
|
* todo: add support for subgraph on thegraph & API keys
|
||||||
|
*/
|
||||||
|
export const RELAYER_SUBGRAPH_LIST: Record<number, string> = {
|
||||||
|
[ChainId.MAINNET]: process.env.MAINNET_SUBGRAPH ?? 'https://tornadocash-rpc.com/subgraphs/name/tornadocash/tornado-governance',
|
||||||
|
}
|
||||||
@ -3,6 +3,7 @@ export * from './link'
|
|||||||
export * from './enums'
|
export * from './enums'
|
||||||
export * from './steps'
|
export * from './steps'
|
||||||
export * from './errors'
|
export * from './errors'
|
||||||
|
export * from './graph'
|
||||||
export * from './relayer'
|
export * from './relayer'
|
||||||
export * from './variables'
|
export * from './variables'
|
||||||
export * from './contracts'
|
export * from './contracts'
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { ChainId } from '@/types'
|
import { ChainId } from '@/types'
|
||||||
|
|
||||||
export const RPC_LIST: { [chainId in ChainId]: string } = {
|
export const RPC_LIST: Record<number, string> = {
|
||||||
[ChainId.MAINNET]: 'https://tornadocash-rpc.com',
|
[ChainId.MAINNET]: process.env.MAINNET_RPC ?? 'https://rpc.mevblocker.io',
|
||||||
}
|
}
|
||||||
|
|||||||
12
package.json
12
package.json
@ -4,14 +4,17 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nuxt",
|
"nuxt": "cross-env NODE_OPTIONS=\"--max_old_space_size=8192 --openssl-legacy-provider\" nuxt",
|
||||||
"build": "nuxt build",
|
"dev": "yarn nuxt",
|
||||||
"start": "nuxt start",
|
"build": "yarn nuxt build",
|
||||||
|
"start": "yarn nuxt start",
|
||||||
"lint": "eslint --ext .js,.ts",
|
"lint": "eslint --ext .js,.ts",
|
||||||
"lint:fix": "eslint --ext .js,.ts --quiet --fix",
|
"lint:fix": "eslint --ext .js,.ts --quiet --fix",
|
||||||
"compile": "typechain --target ethers-v5 --out-dir ./_contracts './abi/*.json'",
|
"compile": "typechain --target ethers-v5 --out-dir ./_contracts './abi/*.json'",
|
||||||
"generate": "nuxt generate && cp dist/404.html dist/ipfs-404.html",
|
"generate": "yarn nuxt generate && cp dist/404.html dist/ipfs-404.html",
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
|
"docker:build": "docker build -t relayers-network-ui .",
|
||||||
|
"docker:hash": "docker container run --rm -it --entrypoint cat relayers-network-ui /app/ipfs_hash.txt",
|
||||||
"ipfs:upload": "node --loader ts-node/esm ipfsUpload.ts"
|
"ipfs:upload": "node --loader ts-node/esm ipfsUpload.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -56,6 +59,7 @@
|
|||||||
"@types/node": "^16.10.9",
|
"@types/node": "^16.10.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.28.0",
|
"@typescript-eslint/eslint-plugin": "^4.28.0",
|
||||||
"@typescript-eslint/parser": "^4.28.0",
|
"@typescript-eslint/parser": "^4.28.0",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
"eslint": "^7.29.0",
|
"eslint": "^7.29.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
|
|||||||
@ -7,15 +7,16 @@ async function getEnsOwner(ensName: string, chainId: ChainId) {
|
|||||||
const { provider } = getProvider(chainId)
|
const { provider } = getProvider(chainId)
|
||||||
const ownerAddress = await provider.resolveName(ensName)
|
const ownerAddress = await provider.resolveName(ensName)
|
||||||
|
|
||||||
return ownerAddress || undefined
|
return ownerAddress ?? undefined
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
async function getNameFromHash(ensHash: string) {
|
async function getNameFromHash(ensHash: string) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('https://tornadocash-rpc.com/subgraphs/name/graphprotocol/ens', {
|
const response = await fetch('https://api.thegraph.com/subgraphs/name/ensdomains/ens', {
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
query: `{
|
query: `{
|
||||||
domain(id: "${ensHash}") {
|
domain(id: "${ensHash}") {
|
||||||
@ -33,4 +34,6 @@ async function getNameFromHash(ensHash: string) {
|
|||||||
throw new Error(err.message)
|
throw new Error(err.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export { getEnsOwner, getNameFromHash }
|
**/
|
||||||
|
|
||||||
|
export { getEnsOwner }
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import { checkSubdomains, subdomains } from './ensSubdomains'
|
import { checkSubdomains, subdomains } from './ensSubdomains'
|
||||||
import { getEnsOwner, getNameFromHash } from './ens'
|
import { getEnsOwner } from './ens'
|
||||||
|
|
||||||
export const ensService = {
|
export const ensService = {
|
||||||
subdomains,
|
subdomains,
|
||||||
getEnsOwner,
|
getEnsOwner,
|
||||||
checkSubdomains,
|
checkSubdomains,
|
||||||
getNameFromHash,
|
|
||||||
}
|
}
|
||||||
|
|||||||
115
store/relayer.ts
115
store/relayer.ts
@ -5,9 +5,26 @@ import { AddStakeParams, AddStakePermitParams, ChainId, RootState } from '@/type
|
|||||||
import { RelayerMutation, RelayerState } from '@/types/store/relayer'
|
import { RelayerMutation, RelayerState } from '@/types/store/relayer'
|
||||||
|
|
||||||
import { getRelayerRegistry } from '@/contracts'
|
import { getRelayerRegistry } from '@/contracts'
|
||||||
import { DEPLOYED_BLOCK, errors, numbers } from '@/constants'
|
import { DEPLOYED_BLOCK, GRAPHQL_LIMIT, RELAYER_SUBGRAPH_LIST, errors, numbers } from '@/constants'
|
||||||
import { ensService, tornadoRelayerService } from '@/services'
|
import { ensService, tornadoRelayerService } from '@/services'
|
||||||
import { errorParser, fromWei, toDecimalsPlaces } from '@/utilities'
|
import { errorParser, fromWei, toDecimalsPlaces } from '@/utilities'
|
||||||
|
import { getAddress, parseEther } from 'ethers/lib/utils'
|
||||||
|
import { BigNumber } from 'ethers'
|
||||||
|
|
||||||
|
interface GraphRelayer {
|
||||||
|
address: string
|
||||||
|
ensName: string
|
||||||
|
ensHash: string
|
||||||
|
|
||||||
|
workers: string[]
|
||||||
|
stakeBalance: string
|
||||||
|
blockRegistration: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GraphRelayerFormatted extends Omit<GraphRelayer, 'stakeBalance' | 'blockRegistration'> {
|
||||||
|
stakeBalance: BigNumber
|
||||||
|
registerBlock: number
|
||||||
|
}
|
||||||
|
|
||||||
export const actions: ActionTree<RelayerState, RootState> = {
|
export const actions: ActionTree<RelayerState, RootState> = {
|
||||||
async checkIsRelayerRegistered({ getters, commit }) {
|
async checkIsRelayerRegistered({ getters, commit }) {
|
||||||
@ -24,6 +41,7 @@ export const actions: ActionTree<RelayerState, RootState> = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Unused because block range is too broad
|
||||||
async geRelayerWorkers({ getters, commit }) {
|
async geRelayerWorkers({ getters, commit }) {
|
||||||
try {
|
try {
|
||||||
const { walletAddress, chainId } = getters.dependencies
|
const { walletAddress, chainId } = getters.dependencies
|
||||||
@ -51,11 +69,81 @@ export const actions: ActionTree<RelayerState, RootState> = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async getRelayerENSData({ getters, commit, dispatch }, ensHash) {
|
async getRelayersFromGraph({ getters }) {
|
||||||
|
try {
|
||||||
|
const { chainId } = getters.dependencies
|
||||||
|
|
||||||
|
const graphUrl = RELAYER_SUBGRAPH_LIST[chainId as ChainId]
|
||||||
|
|
||||||
|
const res = await fetch(graphUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
query: `
|
||||||
|
query getRelayers($first: Int) {
|
||||||
|
relayers(first: $first, orderBy: blockRegistration, orderDirection: asc) {
|
||||||
|
address
|
||||||
|
ensName
|
||||||
|
ensHash
|
||||||
|
|
||||||
|
workers
|
||||||
|
stakeBalance
|
||||||
|
blockRegistration
|
||||||
|
}
|
||||||
|
_meta {
|
||||||
|
block {
|
||||||
|
number
|
||||||
|
}
|
||||||
|
hasIndexingErrors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
first: GRAPHQL_LIMIT,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`Invalid response from ${graphUrl} ${res.statusText}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, errors } = await res.json()
|
||||||
|
|
||||||
|
if (errors) {
|
||||||
|
throw new Error(`Error from graph: ${JSON.stringify(errors)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data?._meta?.hasIndexingErrors) {
|
||||||
|
throw new Error('Subgraph has indexing errors')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (data.relayers as GraphRelayer[]).map(({ address, ensName, ensHash, workers, stakeBalance, blockRegistration }) => {
|
||||||
|
if (!workers.includes(address)) {
|
||||||
|
workers.push(getAddress(address))
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
address: getAddress(address),
|
||||||
|
ensName,
|
||||||
|
ensHash,
|
||||||
|
workers,
|
||||||
|
stakeBalance: parseEther(stakeBalance),
|
||||||
|
registerBlock: Number(blockRegistration),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async getRelayerENSData({ getters, commit }, ensName) {
|
||||||
try {
|
try {
|
||||||
const { chainId } = getters.dependencies
|
const { chainId } = getters.dependencies
|
||||||
|
|
||||||
const ensName = await ensService.getNameFromHash(ensHash)
|
|
||||||
const subdomains = await ensService.checkSubdomains(ensName, chainId)
|
const subdomains = await ensService.checkSubdomains(ensName, chainId)
|
||||||
|
|
||||||
const mainnetSubdomain = subdomains.find((el) => el.chainId === ChainId.MAINNET)
|
const mainnetSubdomain = subdomains.find((el) => el.chainId === ChainId.MAINNET)
|
||||||
@ -74,17 +162,22 @@ export const actions: ActionTree<RelayerState, RootState> = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async getRelayers({ getters, commit }) {
|
async getRelayers({ getters, commit, dispatch }) {
|
||||||
try {
|
try {
|
||||||
const { walletAddress, chainId } = getters.dependencies
|
const { walletAddress } = getters.dependencies
|
||||||
|
|
||||||
const registryContract = getRelayerRegistry(chainId)
|
const relayers = (await dispatch('getRelayersFromGraph')) as GraphRelayerFormatted[]
|
||||||
|
|
||||||
const { balance, ensHash } = await registryContract.callStatic.relayers(walletAddress)
|
const relayer = relayers.find((r) => r.address === walletAddress)
|
||||||
|
|
||||||
commit(RelayerMutation.SET_BALANCE, balance)
|
if (!relayer) {
|
||||||
|
throw new Error(`No relayer found for ${walletAddress}`)
|
||||||
|
}
|
||||||
|
|
||||||
return ensHash
|
commit(RelayerMutation.SET_WORKERS, relayer.workers)
|
||||||
|
commit(RelayerMutation.SET_BALANCE, relayer.stakeBalance)
|
||||||
|
|
||||||
|
await dispatch('getRelayerENSData', relayer.ensName)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new Error(err.message)
|
throw new Error(err.message)
|
||||||
}
|
}
|
||||||
@ -98,9 +191,7 @@ export const actions: ActionTree<RelayerState, RootState> = {
|
|||||||
throw new Error(errors.relayer.NOT_REGISTERED)
|
throw new Error(errors.relayer.NOT_REGISTERED)
|
||||||
}
|
}
|
||||||
|
|
||||||
await dispatch('geRelayerWorkers')
|
await dispatch('getRelayers')
|
||||||
const ensHash = await dispatch('getRelayers')
|
|
||||||
await dispatch('getRelayerENSData', ensHash)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorText = errorParser(err.message, errors.validation.NO_RESPONSE)
|
const errorText = errorParser(err.message, errors.validation.NO_RESPONSE)
|
||||||
|
|
||||||
|
|||||||
16
yarn.lock
16
yarn.lock
@ -3923,6 +3923,13 @@ create-require@^1.1.0, create-require@^1.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
|
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
|
||||||
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
|
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
|
||||||
|
|
||||||
|
cross-env@^7.0.3:
|
||||||
|
version "7.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf"
|
||||||
|
integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==
|
||||||
|
dependencies:
|
||||||
|
cross-spawn "^7.0.1"
|
||||||
|
|
||||||
cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
||||||
version "7.0.3"
|
version "7.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
||||||
@ -3932,6 +3939,15 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
|||||||
shebang-command "^2.0.0"
|
shebang-command "^2.0.0"
|
||||||
which "^2.0.1"
|
which "^2.0.1"
|
||||||
|
|
||||||
|
cross-spawn@^7.0.1:
|
||||||
|
version "7.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
|
||||||
|
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
|
||||||
|
dependencies:
|
||||||
|
path-key "^3.1.0"
|
||||||
|
shebang-command "^2.0.0"
|
||||||
|
which "^2.0.1"
|
||||||
|
|
||||||
crypto-browserify@^3.11.0:
|
crypto-browserify@^3.11.0:
|
||||||
version "3.12.0"
|
version "3.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
|
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user