Skip to content

Commit

Permalink
Feat: Enable Deposits (#268)
Browse files Browse the repository at this point in the history
Co-authored-by: antondlr <[email protected]>
  • Loading branch information
rickimoore and antondlr authored Dec 10, 2024
1 parent 2e2e4c3 commit 9a6011b
Show file tree
Hide file tree
Showing 246 changed files with 13,024 additions and 3,853 deletions.
22 changes: 22 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,25 @@ DEBUG=false
# don't change these when building the docker image, only change when running outside of docker
PORT=3000
BACKEND_URL=http://127.0.0.1:3001


#For Local Development only

#Kubernets
#To reach the dashboard `http://localhost:9711/` once it is running

#Find the VC Url via `vc-1-geth-lighthouse` and the http-validator public-port
#VALIDATOR_URL=http://127.0.0.1:YOUR-KUBERNET-VC-PORT

#Find the Beancon Url via `cl-1-lighthouse-geth` and the http public-port
#BEACON_URL=http://127.0.0.1:YOUR-KUBERNET-CL-PORT

#Get Api Token: (change name to current vc-[vc_number])
#docker exec -ti $(docker ps -q -f name=vc-1) cat /validator-keys/keys/api-token.txt | pbcopy
#API_TOKEN=YOUR-COPIED-TOKEN

#This should remain the same but double check on kubernets dashboard
#NEXT_PUBLIC_TESTNET_CHAIN_ID=3151908

#Find the RPC port in `el-1-geth-lighthouse`
#NEXT_PUBLIC_TESTNET_RPC=http://127.0.0.1:YOUR-KUBERNET-RPC
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"plugins": ["@typescript-eslint", "import", "react-hooks", "unused-imports"],
"plugins": ["@typescript-eslint", "import", "unused-imports"],
"extends": ["next/core-web-vitals"],
"rules": {
"@typescript-eslint/ban-ts-comment": "off",
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ RUN rm /etc/nginx/sites-enabled/default; \

COPY --from=intermediate /app /app/

ENTRYPOINT /app/docker-assets/docker-entrypoint.sh
ENTRYPOINT ["/app/docker-assets/docker-entrypoint.sh"]
39 changes: 30 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,25 @@ developers. Specifically the [Lighthouse UI](https://lighthouse-book.sigmaprime.

### Docker (Recommended)

Docker is the recommended way to run Siren. This will expose Siren as a webapp.
Docker is the recommended way to run Siren. This will expose Siren as a webapp.

Configuration is done through environment variables, the best way to get started is by copying `.env.example` to `.env` and editing the relevant sections (typically, this would at least include `BEACON_URL`, `VALIDATOR_URL` and `API_TOKEN`)

Then to run the image:

`docker compose up`
or
`docker run --rm -ti --name siren -p 3443:443 --env-file $PWD/.env sigp/siren`
`docker run --rm -ti --name siren -p 3443:443 --env-file $PWD/.env sigp/siren`

This will open port 3443 and allow your browser to connect.
This will open port 3443 and allow your browser to connect.


To start Siren, visit `https://localhost:3443` in your web browser (ignore the certificate warning).
To start Siren, visit `https://localhost:3443` in your web browser (ignore the certificate warning).

Advanced users can mount their own certificate (the config expects 3 files: `/certs/cert.pem` `/certs/key.pem` `/certs/key.pass`)

## Building From Source

### Docker
### Docker

The docker image can be built with the following command:
`docker build -f Dockerfile -t siren .`
Expand All @@ -50,10 +49,32 @@ The docker image can be built with the following command:
To build from source, ensure that your system has `Node v18.18` and `yarn` installed. Start by configuring your environment variables. The recommended approach is to duplicate the `.env.example` file, rename it to `.env`, and modify the necessary settings. Essential variables typically include `BEACON_URL`, `VALIDATOR_URL`, and `API_TOKEN`.

#### Build and run the backend
Navigate to the backend directory `cd backend`. Install all required Node packages by running `yarn`. Once the installation is complete, compile the backend with `yarn build`. Deploy the backend in a production environment, `yarn start:production`. This ensures optimal performance.

Navigate to the backend directory `cd backend`. Install all required Node packages by running `yarn`. Once the installation is complete, compile the backend with `yarn build`. Deploy the backend in a production environment, `yarn start:production`. This ensures optimal performance.

#### Build and run the frontend
After initializing the backend, return to the root directory. Install all frontend dependencies by executing `yarn`. Build the frontend using `yarn build`. Start the frontend production server with `yarn start`.

This will allow you to access siren at `http://localhost:3000` by default.
After initializing the backend, return to the root directory. Install all frontend dependencies by executing `yarn`. Build the frontend using `yarn build`. Start the frontend production server with `yarn start`.

This will allow you to access siren at `http://localhost:3000` by default.


## Running Local Testnet

If you want to run the local testnet, before running the `backend` you must start the `Kurtosis` network. Navigate to the testnet directory with `cd local-testnet` and run the script with `./start_local_testnet.sh`. When the script completes and the testnet is online you can reach the `Kurtosis Enclave Manager` via `http://localhost:9711/`.

#### Finding the VALIDATOR_URL
To find the variables needed for the `VALIDATOR_URL` open the `Kurtosis Enclave Manager` and click on the running `local-testnet` to gain access to all the services. There may be multiple `VC` running, you can select `vc-1-geth-lighthouse` and copy the `public port` from the `http-validator` row in the ports table. It should resemble `http://127.0.0.1:[YOUR-PORT-NUMBER]`

#### Finding the BEACON_URL
In a similar process to the validator url open the `Kurtosis Enclave Manager`and click on the running `local-testnet`. There may also be multiple `CL` running, you can select `cl-1-lighthouse-geth` and copy the `public ports` for the `http` row in the ports table. It will also resemble `http://127.0.0.1:[YOUR-PORT-NUMBER]`.

#### Finding the API_TOKEN
From your command line you can run the following command: `docker exec -ti $(docker ps -q -f name=vc-1) cat /validator-keys/keys/api-token.txt`. This will print your token, do not copy the `%` at the end of the string. It is not a part of the token.

#### Connection with your wallet
If you want to connect to siren with your browser wallet you must add some additional fields to your `.env` file. Firstly, you must set the `NEXT_PUBLIC_TESTNET_CHAIN_ID` to the one that corresponds to your local testnet. This should be the same as in the example, if not you can find it via the `Kurtosis Enclave Manager` in the local testnet file artifacts section. Scroll down and find the `genesis-el-cl-env-file` and open the `values.env`. Here you will find the `CHAIN_ID`, `MNEMONIC` and other node data.

Lastly you need to add the `NEXT_PUBLIC_TESTNET_RPC` variable to allow siren to connect your wallet. This can be found in the services table in the `EL`. There may be multiple but you can select `el-1-geth-lghthouse`. In the ports table you need to copy the `public ports` for the `rpc` row. It should resemble `http://127.0.0.1:YOUR-KUBERNET-RPC`

When everything is added correctly you need to import the `kurtosis` wallet into your browser wallet provider and add the same `NEXT_PUBLIC_TESTNET_RPC` as the `RPC_URL` so you can see the balance and make movements correctly.
124 changes: 57 additions & 67 deletions app/Main.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
'use client';

import axios from 'axios';
import Cookies from 'js-cookie';
import { useRouter, useSearchParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import AppDescription from '../src/components/AppDescription/AppDescription';
import AuthPrompt from '../src/components/AuthPrompt/AuthPrompt';
import ConfigModal from '../src/components/ConfigModal/ConfigModal';
import LoadingSpinner from '../src/components/LoadingSpinner/LoadingSpinner';
import Typography from '../src/components/Typography/Typography';
import VersionModal from '../src/components/VersionModal/VersionModal';
import { REQUIRED_VALIDATOR_VERSION } from '../src/constants/constants';
import { UiMode } from '../src/constants/enums';
import useLocalStorage from '../src/hooks/useLocalStorage';
import { ToastType } from '../src/types';
import displayToast from '../utilities/displayToast';
import formatSemanticVersion from '../utilities/formatSemanticVersion';
import isExpiredToken from '../utilities/isExpiredToken';
import isRequiredVersion from '../utilities/isRequiredVersion';
'use client'

import axios from 'axios'
import { useRouter, useSearchParams } from 'next/navigation'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import AppDescription from '../src/components/AppDescription/AppDescription'
import AuthPrompt from '../src/components/AuthPrompt/AuthPrompt'
import ConfigModal from '../src/components/ConfigModal/ConfigModal'
import LoadingSpinner from '../src/components/LoadingSpinner/LoadingSpinner'
import Typography from '../src/components/Typography/Typography'
import VersionModal from '../src/components/VersionModal/VersionModal'
import { REQUIRED_VALIDATOR_VERSION } from '../src/constants/constants'
import { UiMode } from '../src/constants/enums'
import useLocalStorage from '../src/hooks/useLocalStorage'
import { ToastType } from '../src/types'
import displayToast from '../utilities/displayToast'
import formatSemanticVersion from '../utilities/formatSemanticVersion'
import isRequiredVersion from '../utilities/isRequiredVersion'

const Main = () => {
const { t } = useTranslation()
Expand All @@ -29,57 +27,44 @@ const Main = () => {
const [step] = useState<number>(1)
const [isReady, setReady] = useState(false)
const [isVersionError, setVersionError] = useState(false)
const [sessionToken, setToken] = useState(Cookies.get('session-token'))
const [isAuthenticated, setIsAuthenticated] = useState(false)
const [, setUsername] = useLocalStorage<string>('username', 'Keeper')
const [healthCheck] = useLocalStorage<boolean>('health-check', false)

const [beaconNodeVersion, setBeaconVersion] = useState('')
const [lighthouseVersion, setLighthouseVersion] = useState('')

useEffect(() => {
if(sessionToken) {
if(isExpiredToken(sessionToken)) {
setToken(undefined)
return
}

(async () => {
try {
const config = {
headers: {
Authorization: `Bearer ${sessionToken}`
}
}

const [beaconResults, lightResults] = await Promise.all([
axios.get('/api/beacon-version', config),
axios.get('/api/lighthouse-version', config)
])

setBeaconVersion(beaconResults.data.version)
setLighthouseVersion(lightResults.data.version)

setReady(true)

} catch (e) {
setReady(true)
console.error(e)
}
})()
const fetchNodeVersion = async () => {
try {
const [beaconResults, lightResults] = await Promise.all([
axios.get('/api/beacon-version'),
axios.get('/api/lighthouse-version'),
])

setBeaconVersion(beaconResults.data.version)
setLighthouseVersion(lightResults.data.version)
setIsAuthenticated(true)
} catch (e) {
console.error(e)
} finally {
setReady(true)
}
}, [sessionToken])
}

useEffect(() => {
if(beaconNodeVersion && lighthouseVersion) {
void fetchNodeVersion()
}, [])

useEffect(() => {
if (beaconNodeVersion && lighthouseVersion) {
if (!isRequiredVersion(lighthouseVersion, REQUIRED_VALIDATOR_VERSION)) {
setVersionError(true)
return
}

let nextRoute = '/setup/health-check'

if(healthCheck) {
if (healthCheck) {
nextRoute = '/dashboard'
}

Expand All @@ -96,30 +81,35 @@ const Main = () => {
try {
setLoading(true)
setUsername(username)
const {status, data} = await axios.post('/api/authenticate', {password})
const token = data.token;
setLoading(false)
const { status } = await axios.post('/api/authenticate', { password })

if(status === 200) {
setToken(token)
Cookies.set('session-token', token)
if (status === 200) {
await fetchNodeVersion()
}

} catch (e: any) {
setLoading(false)
displayToast(t(e.response.data.error as string), ToastType.ERROR)
} finally {
setLoading(false)
}
}

return (
<div className='relative w-screen h-screen bg-gradient-to-r from-primary to-tertiary'>
<ConfigModal
isReady={isReady && configError && !!sessionToken}
beaconNodeVersion={beaconNodeVersion} lighthouseVersion={lighthouseVersion} />
isReady={isReady && configError && isAuthenticated}
beaconNodeVersion={beaconNodeVersion}
lighthouseVersion={lighthouseVersion}
/>
{vcVersion && (
<VersionModal currentVersion={vcVersion} isVisible={isReady && isVersionError}/>
<VersionModal currentVersion={vcVersion} isVisible={isReady && isVersionError} />
)}
<AuthPrompt isNamePrompt mode={UiMode.LIGHT} isLoading={isLoading} isVisible={!sessionToken} onSubmit={storeSessionCookie}/>
<AuthPrompt
isNamePrompt
mode={UiMode.LIGHT}
isLoading={isLoading}
isVisible={isReady && !isAuthenticated}
onSubmit={storeSessionCookie}
/>
<div className='absolute top-0 left-0 w-full h-full bg-cover bg-lighthouse' />
<div className='absolute top-1/2 -translate-y-1/2 left-1/2 -translate-x-1/2'>
<LoadingSpinner />
Expand Down
15 changes: 9 additions & 6 deletions app/Providers.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
'use client'

import { QueryClientProvider, QueryClient } from '@tanstack/react-query'
import React, { FC, ReactElement } from 'react'
import { QueryClient, QueryClientProvider } from 'react-query'
import { ToastContainer } from 'react-toastify'
import { RecoilRoot } from 'recoil'
import 'react-tooltip/dist/react-tooltip.css'
import 'react-toastify/dist/ReactToastify.min.css'
import 'rodal/lib/rodal.css'

import { WagmiProvider } from 'wagmi'
import createWagmiConfig from '../utilities/createWagmiConfig'
const queryClient = new QueryClient()

export interface ProviderProps {
Expand All @@ -17,10 +18,12 @@ export interface ProviderProps {
const Providers: FC<ProviderProps> = ({ children }) => {
return (
<RecoilRoot>
<QueryClientProvider client={queryClient}>
{children}
<ToastContainer />
</QueryClientProvider>
<WagmiProvider reconnectOnMount config={createWagmiConfig()}>
<QueryClientProvider client={queryClient}>
{children}
<ToastContainer />
</QueryClientProvider>
</WagmiProvider>
</RecoilRoot>
)
}
Expand Down
34 changes: 34 additions & 0 deletions app/api/activities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import fetchFromApi from '../../utilities/fetchFromApi'

const backendUrl = process.env.BACKEND_URL

export interface fetchActivitiesProps {
token: string
offset?: string
limit?: string
order?: string
since?: string
}

export const fetchActivities = async (props: fetchActivitiesProps) => {
const { token, offset, limit, order, since } = props
const params = new URLSearchParams()

if (offset) params.append('offset', offset)
if (limit) params.append('limit', limit)
if (order) params.append('order', order)
if (since) params.append('since', since)

return await fetchFromApi(`${backendUrl}/activity?${params.toString()}`, token)
}

export const logActivity = async (data: any, token: string) =>
await fetchFromApi(`${backendUrl}/activity`, token, {
method: 'POST',
body: JSON.stringify(data),
})

export const readActivity = async (id: string, token: string) =>
await fetchFromApi(`${backendUrl}/activity/${id}/read`, token, {
method: 'PUT',
})
19 changes: 19 additions & 0 deletions app/api/activity/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { NextResponse } from 'next/server'
import getReqAuthToken from '../../../utilities/getReqAuthToken'
import { fetchActivities } from '../activities'

export async function GET(req: Request) {
try {
const { searchParams } = new URL(req.url)
const offset = searchParams.get('offset') || undefined
const limit = searchParams.get('limit') || undefined
const order = searchParams.get('order') || undefined
const since = searchParams.get('since') || undefined

const token = getReqAuthToken(req)
const data = await fetchActivities({ token, offset, limit, order, since })
return NextResponse.json(data)
} catch (error) {
return NextResponse.json({ error: 'Failed to fetch activities' }, { status: 500 })
}
}
Loading

0 comments on commit 9a6011b

Please sign in to comment.