Compare commits

...

3 Commits

10 changed files with 199 additions and 20 deletions

View File

@ -1,2 +1,5 @@
PINATA_API_KEY=
PINATA_SECRET_API_KEY=
MAINNET_SUBGRAPH=
MAINNET_RPC=

46
Dockerfile Normal file
View 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=382fe5a1274a9c553cf962721e96a93b274ff823
# 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" ]

11
constants/graph.ts Normal file
View File

@ -0,0 +1,11 @@
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-relayer-registry',
}

View File

@ -3,6 +3,7 @@ export * from './link'
export * from './enums'
export * from './steps'
export * from './errors'
export * from './graph'
export * from './relayer'
export * from './variables'
export * from './contracts'

View File

@ -1,5 +1,5 @@
import { ChainId } from '@/types'
export const RPC_LIST: { [chainId in ChainId]: string } = {
[ChainId.MAINNET]: 'https://tornadocash-rpc.com',
export const RPC_LIST: Record<number, string> = {
[ChainId.MAINNET]: process.env.MAINNET_RPC ?? 'https://rpc.mevblocker.io',
}

View File

@ -4,14 +4,17 @@
"private": true,
"type": "module",
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"nuxt": "cross-env NODE_OPTIONS=\"--max_old_space_size=8192 --openssl-legacy-provider\" nuxt",
"dev": "yarn nuxt",
"build": "yarn nuxt build",
"start": "yarn nuxt start",
"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",
"generate": "yarn nuxt generate && cp dist/404.html dist/ipfs-404.html",
"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"
},
"dependencies": {
@ -56,6 +59,7 @@
"@types/node": "^16.10.9",
"@typescript-eslint/eslint-plugin": "^4.28.0",
"@typescript-eslint/parser": "^4.28.0",
"cross-env": "^7.0.3",
"dotenv": "^10.0.0",
"eslint": "^7.29.0",
"eslint-config-prettier": "^8.3.0",

View File

@ -7,15 +7,16 @@ async function getEnsOwner(ensName: string, chainId: ChainId) {
const { provider } = getProvider(chainId)
const ownerAddress = await provider.resolveName(ensName)
return ownerAddress || undefined
return ownerAddress ?? undefined
} catch (err) {
return undefined
}
}
/**
async function getNameFromHash(ensHash: string) {
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({
query: `{
domain(id: "${ensHash}") {
@ -33,4 +34,6 @@ async function getNameFromHash(ensHash: string) {
throw new Error(err.message)
}
}
export { getEnsOwner, getNameFromHash }
**/
export { getEnsOwner }

View File

@ -1,9 +1,8 @@
import { checkSubdomains, subdomains } from './ensSubdomains'
import { getEnsOwner, getNameFromHash } from './ens'
import { getEnsOwner } from './ens'
export const ensService = {
subdomains,
getEnsOwner,
checkSubdomains,
getNameFromHash,
}

View File

@ -5,9 +5,24 @@ import { AddStakeParams, AddStakePermitParams, ChainId, RootState } from '@/type
import { RelayerMutation, RelayerState } from '@/types/store/relayer'
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 { errorParser, fromWei, toDecimalsPlaces } from '@/utilities'
import { getAddress } from 'ethers/lib/utils'
interface GraphRelayer {
id: string
address: string
ensName: string
ensHash: string
blockRegistration: string
}
interface GraphRelayerFormatted extends Omit<GraphRelayer, 'id' | 'blockRegistration'> {
registerBlock: number
registerTx: string
registerLogIndex: number
}
export const actions: ActionTree<RelayerState, RootState> = {
async checkIsRelayerRegistered({ getters, commit }) {
@ -24,6 +39,7 @@ export const actions: ActionTree<RelayerState, RootState> = {
}
},
// Unused because block range is too broad
async geRelayerWorkers({ getters, commit }) {
try {
const { walletAddress, chainId } = getters.dependencies
@ -51,11 +67,82 @@ export const actions: ActionTree<RelayerState, RootState> = {
}
},
async getRelayerENSData({ getters, commit, dispatch }, ensHash) {
/**
* todo: add worker events to relayer registry subgraph
*/
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) {
id
address
ensName
ensHash
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(({ id, address, ensName, ensHash, blockRegistration }) => {
const [registerTx, registerLogIndex] = id.split('-')
return {
address: getAddress(address),
ensName,
ensHash,
registerBlock: Number(blockRegistration),
registerTx,
registerLogIndex: Number(registerLogIndex),
}
})
} catch (err) {
console.log(err)
throw err
}
},
async getRelayerENSData({ getters, commit }, ensName) {
try {
const { chainId } = getters.dependencies
const ensName = await ensService.getNameFromHash(ensHash)
const subdomains = await ensService.checkSubdomains(ensName, chainId)
const mainnetSubdomain = subdomains.find((el) => el.chainId === ChainId.MAINNET)
@ -74,17 +161,28 @@ export const actions: ActionTree<RelayerState, RootState> = {
}
},
async getRelayers({ getters, commit }) {
async getRelayers({ getters, commit, dispatch }) {
try {
const { walletAddress, chainId } = getters.dependencies
// todo: add worker data
const relayers = (await dispatch('getRelayersFromGraph')) as GraphRelayerFormatted[]
const relayer = relayers.find((r) => r.address === walletAddress)
if (!relayer) {
throw new Error(`No relayer found for ${walletAddress}`)
}
const registryContract = getRelayerRegistry(chainId)
const { balance, ensHash } = await registryContract.callStatic.relayers(walletAddress)
const { balance } = await registryContract.callStatic.relayers(walletAddress)
// todo: add worker data
commit(RelayerMutation.SET_WORKERS, [walletAddress])
commit(RelayerMutation.SET_BALANCE, balance)
return ensHash
await dispatch('getRelayerENSData', relayer.ensName)
} catch (err) {
throw new Error(err.message)
}
@ -98,9 +196,7 @@ export const actions: ActionTree<RelayerState, RootState> = {
throw new Error(errors.relayer.NOT_REGISTERED)
}
await dispatch('geRelayerWorkers')
const ensHash = await dispatch('getRelayers')
await dispatch('getRelayerENSData', ensHash)
await dispatch('getRelayers')
} catch (err) {
const errorText = errorParser(err.message, errors.validation.NO_RESPONSE)

View File

@ -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"
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:
version "7.0.3"
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"
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:
version "3.12.0"
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"