From 382fe5a1274a9c553cf962721e96a93b274ff823 Mon Sep 17 00:00:00 2001 From: tornadocontrib Date: Tue, 1 Jul 2025 22:47:38 +0000 Subject: [PATCH 1/4] Use relayer subgraph --- .env.example | 3 ++ Dockerfile | 46 +++++++++++++++++ constants/graph.ts | 11 ++++ constants/index.ts | 1 + constants/rpc.ts | 4 +- package.json | 12 +++-- services/ENS/ens.ts | 9 ++-- services/ENS/index.ts | 3 +- store/relayer.ts | 114 ++++++++++++++++++++++++++++++++++++++---- yarn.lock | 16 ++++++ 10 files changed, 199 insertions(+), 20 deletions(-) create mode 100644 Dockerfile create mode 100644 constants/graph.ts diff --git a/.env.example b/.env.example index adc9b5d..3ced884 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,5 @@ PINATA_API_KEY= PINATA_SECRET_API_KEY= + +MAINNET_SUBGRAPH= +MAINNET_RPC= \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bd66930 --- /dev/null +++ b/Dockerfile @@ -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=57d3ba5ac5ea8bab6e24b30630c933568dddbec9 + +# 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" ] \ No newline at end of file diff --git a/constants/graph.ts b/constants/graph.ts new file mode 100644 index 0000000..c8ef168 --- /dev/null +++ b/constants/graph.ts @@ -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 = { + [ChainId.MAINNET]: + process.env.MAINNET_SUBGRAPH ?? 'https://tornadocash-rpc.com/subgraphs/name/tornadocash/tornado-relayer-registry', +} diff --git a/constants/index.ts b/constants/index.ts index 503fb0e..44a5896 100644 --- a/constants/index.ts +++ b/constants/index.ts @@ -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' diff --git a/constants/rpc.ts b/constants/rpc.ts index 90cfec3..e4fdeba 100644 --- a/constants/rpc.ts +++ b/constants/rpc.ts @@ -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 = { + [ChainId.MAINNET]: process.env.MAINNET_RPC ?? 'https://rpc.mevblocker.io', } diff --git a/package.json b/package.json index e8ce1ed..b8d0d94 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/services/ENS/ens.ts b/services/ENS/ens.ts index ad90181..1c194c0 100644 --- a/services/ENS/ens.ts +++ b/services/ENS/ens.ts @@ -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 } diff --git a/services/ENS/index.ts b/services/ENS/index.ts index c2b5e2d..7e05b30 100644 --- a/services/ENS/index.ts +++ b/services/ENS/index.ts @@ -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, } diff --git a/store/relayer.ts b/store/relayer.ts index a47278c..7b0f68f 100644 --- a/store/relayer.ts +++ b/store/relayer.ts @@ -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 { + registerBlock: number + registerTx: string + registerLogIndex: number +} export const actions: ActionTree = { async checkIsRelayerRegistered({ getters, commit }) { @@ -24,6 +39,7 @@ export const actions: ActionTree = { } }, + // 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 = { } }, - 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 = { } }, - 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 = { 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) diff --git a/yarn.lock b/yarn.lock index 02fd44a..3660e1d 100644 --- a/yarn.lock +++ b/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" 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" -- 2.45.2 From 4b9baa63d0f11830548d7237aa06b86e27a33475 Mon Sep 17 00:00:00 2001 From: tornadocontrib Date: Wed, 2 Jul 2025 00:57:58 +0000 Subject: [PATCH 2/4] Update git commit --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index bd66930..0397052 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ 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=57d3ba5ac5ea8bab6e24b30630c933568dddbec9 +ENV GIT_COMMIT_HASH=382fe5a1274a9c553cf962721e96a93b274ff823 # clone the repository RUN mkdir /app/ @@ -43,4 +43,4 @@ RUN cat ipfs_hash.txt 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" ] \ No newline at end of file +ENTRYPOINT [ "./entrypoint.sh" ] -- 2.45.2 From e5f1b6f91c372fc58a7a6eb463e21a62a059db3b Mon Sep 17 00:00:00 2001 From: tornadocontrib Date: Mon, 7 Jul 2025 07:20:31 +0000 Subject: [PATCH 3/4] Support workers from subgraph --- constants/graph.ts | 3 +-- store/relayer.ts | 45 ++++++++++++++++++++------------------------- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/constants/graph.ts b/constants/graph.ts index c8ef168..53d09ee 100644 --- a/constants/graph.ts +++ b/constants/graph.ts @@ -6,6 +6,5 @@ export const GRAPHQL_LIMIT = 1000 * todo: add support for subgraph on thegraph & API keys */ export const RELAYER_SUBGRAPH_LIST: Record = { - [ChainId.MAINNET]: - process.env.MAINNET_SUBGRAPH ?? 'https://tornadocash-rpc.com/subgraphs/name/tornadocash/tornado-relayer-registry', + [ChainId.MAINNET]: process.env.MAINNET_SUBGRAPH ?? 'https://tornadocash-rpc.com/subgraphs/name/tornadocash/tornado-governance', } diff --git a/store/relayer.ts b/store/relayer.ts index 7b0f68f..939e604 100644 --- a/store/relayer.ts +++ b/store/relayer.ts @@ -8,20 +8,22 @@ import { getRelayerRegistry } from '@/contracts' 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' +import { getAddress, parseEther } from 'ethers/lib/utils' +import { BigNumber } from 'ethers' interface GraphRelayer { - id: string address: string ensName: string ensHash: string + + workers: string[] + stakeBalance: string blockRegistration: string } -interface GraphRelayerFormatted extends Omit { +interface GraphRelayerFormatted extends Omit { + stakeBalance: BigNumber registerBlock: number - registerTx: string - registerLogIndex: number } export const actions: ActionTree = { @@ -67,9 +69,6 @@ export const actions: ActionTree = { } }, - /** - * todo: add worker events to relayer registry subgraph - */ async getRelayersFromGraph({ getters }) { try { const { chainId } = getters.dependencies @@ -85,10 +84,12 @@ export const actions: ActionTree = { query: ` query getRelayers($first: Int) { relayers(first: $first, orderBy: blockRegistration, orderDirection: asc) { - id address ensName ensHash + + workers + stakeBalance blockRegistration } _meta { @@ -115,22 +116,22 @@ export const actions: ActionTree = { throw new Error(`Error from graph: ${JSON.stringify(errors)}`) } - /** if (data?._meta?.hasIndexingErrors) { - throw new Error('Subgraph has indexing errors'); + throw new Error('Subgraph has indexing errors') } - **/ - return (data.relayers as GraphRelayer[]).map(({ id, address, ensName, ensHash, blockRegistration }) => { - const [registerTx, registerLogIndex] = id.split('-') + 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), - registerTx, - registerLogIndex: Number(registerLogIndex), } }) } catch (err) { @@ -163,9 +164,8 @@ export const actions: ActionTree = { async getRelayers({ getters, commit, dispatch }) { try { - const { walletAddress, chainId } = getters.dependencies + const { walletAddress } = getters.dependencies - // todo: add worker data const relayers = (await dispatch('getRelayersFromGraph')) as GraphRelayerFormatted[] const relayer = relayers.find((r) => r.address === walletAddress) @@ -174,13 +174,8 @@ export const actions: ActionTree = { throw new Error(`No relayer found for ${walletAddress}`) } - const registryContract = getRelayerRegistry(chainId) - - const { balance } = await registryContract.callStatic.relayers(walletAddress) - - // todo: add worker data - commit(RelayerMutation.SET_WORKERS, [walletAddress]) - commit(RelayerMutation.SET_BALANCE, balance) + commit(RelayerMutation.SET_WORKERS, relayer.workers) + commit(RelayerMutation.SET_BALANCE, relayer.stakeBalance) await dispatch('getRelayerENSData', relayer.ensName) } catch (err) { -- 2.45.2 From e6e2e0a2919327bcb4d3e3c702d071dc38132b59 Mon Sep 17 00:00:00 2001 From: tornadocontrib Date: Mon, 7 Jul 2025 07:21:21 +0000 Subject: [PATCH 4/4] Update git commit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0397052..eb0ca37 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ 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 +ENV GIT_COMMIT_HASH=e5f1b6f91c372fc58a7a6eb463e21a62a059db3b # clone the repository RUN mkdir /app/ -- 2.45.2