diff --git a/.github/workflows/azure-static-web-apps-happy-pebble-07bcb0203.yml b/.github/workflows/azure-static-web-apps-happy-pebble-07bcb0203.yml new file mode 100644 index 00000000..76d83db2 --- /dev/null +++ b/.github/workflows/azure-static-web-apps-happy-pebble-07bcb0203.yml @@ -0,0 +1,46 @@ +name: Azure Static Web Apps CI/CD + +on: + push: + branches: + - feature/combined-modes + pull_request: + types: [opened, synchronize, reopened, closed] + branches: + - feature/combined-modes + +jobs: + build_and_deploy_job: + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') + runs-on: ubuntu-latest + name: Build and Deploy Job + steps: + - uses: actions/checkout@v3 + with: + submodules: true + lfs: false + - name: Build And Deploy + id: builddeploy + uses: Azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_HAPPY_PEBBLE_07BCB0203 }} + repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) + action: "upload" + ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### + # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig + app_location: "/" # App source code path + api_location: "" # Api source code path - optional + output_location: "build" # Built app content directory - optional + ###### End of Repository/Build Configurations ###### + + close_pull_request_job: + if: github.event_name == 'pull_request' && github.event.action == 'closed' + runs-on: ubuntu-latest + name: Close Pull Request Job + steps: + - name: Close Pull Request + id: closepullrequest + uses: Azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_HAPPY_PEBBLE_07BCB0203 }} + action: "close" diff --git a/package.json b/package.json index e3f5bc7d..a7b30466 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,9 @@ "name": "staking-launchpad", "version": "1.0.1", "private": true, + "engines": { + "node": "16" + }, "dependencies": { "@chainsafe/bls": "^2.0.0", "@chainsafe/ssz": "^0.6.7", diff --git a/src/Routes.tsx b/src/Routes.tsx index dfcfa17e..2455d4eb 100644 --- a/src/Routes.tsx +++ b/src/Routes.tsx @@ -26,6 +26,7 @@ import { import ScrollToTop from './utils/ScrollToTop'; import { Prysm } from './pages/Clients/Consensus/Prysm'; import { Geth } from './pages/Clients/Execution/Geth'; +import { SelectModePage } from './pages/SelectMode'; type RouteType = { path: string; @@ -39,6 +40,7 @@ export enum routesEnum { connectWalletPage = '/connect-wallet', generateKeysPage = '/generate-keys', acknowledgementPage = '/overview', + selectMode = '/select-mode', selectClient = '/select-client', summaryPage = '/summary', uploadValidatorPage = '/upload-deposit-data', @@ -89,6 +91,11 @@ const routes: RouteType[] = [ exact: true, component: AcknowledgementPage, }, + { + path: routesEnum.selectMode, + exact: true, + component: SelectModePage, + }, { path: routesEnum.summaryPage, exact: true, component: SummaryPage }, { path: routesEnum.uploadValidatorPage, diff --git a/src/pages/Acknowledgements/AcknowledgementProgressTracker/index.tsx b/src/pages/Acknowledgements/AcknowledgementProgressTracker/index.tsx index 1e517a05..fb1047bb 100644 --- a/src/pages/Acknowledgements/AcknowledgementProgressTracker/index.tsx +++ b/src/pages/Acknowledgements/AcknowledgementProgressTracker/index.tsx @@ -49,9 +49,6 @@ export const _AcknowledgementProgressTracker = ({ [AcknowledgementIdsEnum.keyManagement]: formatMessage({ defaultMessage: 'Key management', }), - [AcknowledgementIdsEnum.earlyAdoptionRisks]: formatMessage({ - defaultMessage: 'Early adoption risks', - }), [AcknowledgementIdsEnum.checklist]: formatMessage({ defaultMessage: 'Checklist', }), diff --git a/src/pages/Acknowledgements/index copy.tsx b/src/pages/Acknowledgements/index copy.tsx deleted file mode 100644 index cfe97ccf..00000000 --- a/src/pages/Acknowledgements/index copy.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import React, { useState } from 'react'; -import { FormattedMessage, useIntl } from 'react-intl'; -import { connect } from 'react-redux'; -import styled from 'styled-components'; -import { Dispatch } from 'redux'; -import _every from 'lodash/every'; -import _pickBy from 'lodash/pickBy'; -import _values from 'lodash/values'; -import { - AcknowledgementIdsEnum, - AcknowledgementStateInterface, - StoreState, -} from '../../store/reducers'; -import { - DispatchWorkflowUpdateType, - WorkflowStep, - updateWorkflow, -} from '../../store/actions/workflowActions'; -import { - DispatchAcknowledgementStateUpdateType, - updateAcknowledgementState, -} from '../../store/actions/acknowledgementActions'; -import { pageContent, PageContentInterface } from './pageContent'; -import { AcknowledgementProgressTracker } from './AcknowledgementProgressTracker'; -import { AcknowledgementSection } from './AcknowledgementSection'; -import { WorkflowPageTemplate } from '../../components/WorkflowPage/WorkflowPageTemplate'; -import { Paper } from '../../components/Paper'; - -interface OwnProps {} -interface StateProps { - acknowledgementState: AcknowledgementStateInterface; - workflow: WorkflowStep; -} - -interface DispatchProps { - dispatchAcknowledgementStateUpdate: DispatchAcknowledgementStateUpdateType; - dispatchWorkflowUpdate: DispatchWorkflowUpdateType; -} -type Props = StateProps & DispatchProps & OwnProps; - -const _AcknowledgementPage = ({ - acknowledgementState, - dispatchAcknowledgementStateUpdate, - workflow, - dispatchWorkflowUpdate, -}: Props): JSX.Element => { - const [activeAcknowledgementId, setActiveAcknowledgementId] = useState< - AcknowledgementIdsEnum - >( - workflow === WorkflowStep.OVERVIEW - ? AcknowledgementIdsEnum.introSection - : AcknowledgementIdsEnum.confirmation - ); - - const allAgreedTo = _every( - _values( - _pickBy( - acknowledgementState, - // @ts-ignore - (val: boolean, id: AcknowledgementIdsEnum) => { - // eslint-disable-next-line eqeqeq - return id != AcknowledgementIdsEnum.confirmation; - } - ) - ) - ); - - const Subtitle = styled.p` - font-size: 20px; - margin-bottom: 32px; - `; - - const handleSubmit = () => { - if (workflow === WorkflowStep.OVERVIEW) { - dispatchWorkflowUpdate(WorkflowStep.SELECT_CLIENT); - } - }; - - const handleContinueClick = (id: AcknowledgementIdsEnum) => { - dispatchAcknowledgementStateUpdate(id, true); - if (+id + 1 in AcknowledgementIdsEnum) { - setTimeout(() => setActiveAcknowledgementId(+id + 1), 500); - } - }; - - const handleGoBackClick = (id: AcknowledgementIdsEnum) => { - if (+id - 1 in AcknowledgementIdsEnum) { - setActiveAcknowledgementId(+id - 1); - } - }; - - const { - title, - content, - acknowledgementText, - }: PageContentInterface = pageContent[activeAcknowledgementId]; - const { formatMessage } = useIntl(); - return ( - - - - - - - - - - ); -}; - -const mapStateToProps = (state: StoreState): StateProps => ({ - workflow: state.workflow, - acknowledgementState: state.acknowledgementState, -}); - -const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ - dispatchAcknowledgementStateUpdate: (id, value) => - dispatch(updateAcknowledgementState(id, value)), - dispatchWorkflowUpdate: step => dispatch(updateWorkflow(step)), -}); - -export const AcknowledgementPage = connect< - StateProps, - DispatchProps, - OwnProps, - StoreState ->( - mapStateToProps, - mapDispatchToProps -)(_AcknowledgementPage); diff --git a/src/pages/Acknowledgements/index.tsx b/src/pages/Acknowledgements/index.tsx index acf3e87f..03c28e9e 100644 --- a/src/pages/Acknowledgements/index.tsx +++ b/src/pages/Acknowledgements/index.tsx @@ -29,6 +29,7 @@ import { WorkflowPageTemplate } from '../../components/WorkflowPage/WorkflowPage import { Paper } from '../../components/Paper'; import { Accordion } from './Accordion'; import { AccordionItem } from './AccordionItem'; +import { ClientId, DispatchClientUpdate, updateClient } from '../../store/actions/clientActions'; interface OwnProps {} interface StateProps { @@ -39,6 +40,7 @@ interface StateProps { interface DispatchProps { dispatchAcknowledgementStateUpdate: DispatchAcknowledgementStateUpdateType; dispatchWorkflowUpdate: DispatchWorkflowUpdateType; + dispatchClientUpdate: DispatchClientUpdate; } type Props = StateProps & DispatchProps & OwnProps; @@ -47,6 +49,7 @@ const _AcknowledgementPage = ({ dispatchAcknowledgementStateUpdate, workflow, dispatchWorkflowUpdate, + dispatchClientUpdate }: Props): JSX.Element => { @@ -80,7 +83,13 @@ const _AcknowledgementPage = ({ } }; + const setClientFxn = (clientId: ClientId) => { + dispatchClientUpdate(clientId, 'execution'); + }; + const handleAccept = () => { + const isOneClick = sessionStorage.getItem('oneClick') === 'true'; + setClientFxn(isOneClick ? ClientId.STEREUM : ClientId.GETH); acknowledgementIdsArray.forEach((id) => { console.log(workflow); dispatchAcknowledgementStateUpdate(id, true); @@ -123,8 +132,8 @@ const _AcknowledgementPage = ({ - {acknowledgementIdsArray.filter(value => value !== AcknowledgementIdsEnum.confirmation && value !== AcknowledgementIdsEnum.terminal).map((value) => ( - + {acknowledgementIdsArray.filter(value => value !== AcknowledgementIdsEnum.confirmation && value !== AcknowledgementIdsEnum.terminal).map((value) => ( + - + ))} @@ -175,6 +184,12 @@ const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ dispatchAcknowledgementStateUpdate: (id, value) => dispatch(updateAcknowledgementState(id, value)), dispatchWorkflowUpdate: step => dispatch(updateWorkflow(step)), + dispatchClientUpdate: ( + clientId: ClientId, + ethClientType: 'execution' | 'consensus' + ) => { + dispatch(updateClient(clientId, ethClientType)); + }, }); export const AcknowledgementPage = connect< diff --git a/src/pages/Acknowledgements/pageContent.tsx b/src/pages/Acknowledgements/pageContent.tsx index 9d962b6b..a5189d9c 100644 --- a/src/pages/Acknowledgements/pageContent.tsx +++ b/src/pages/Acknowledgements/pageContent.tsx @@ -193,24 +193,6 @@ export const pageContent = { /> ), }, - [AcknowledgementIdsEnum.earlyAdoptionRisks]: { - title: , - content: ( - - - - ), - acknowledgementText: ( - - ), - }, [AcknowledgementIdsEnum.terminal]: { title: , content: ( diff --git a/src/pages/GenerateKeys/Instructions.tsx b/src/pages/GenerateKeys/Instructions.tsx new file mode 100644 index 00000000..919ff0c7 --- /dev/null +++ b/src/pages/GenerateKeys/Instructions.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { FormattedMessage, useIntl } from 'react-intl'; +import { keysTool } from './index'; +import { Paper } from '../../components/Paper'; +import { TextSelectionBox } from '../../components/TextSelectionBox'; +import { Option1 } from './Option1'; +import { Heading } from '../../components/Heading'; + +interface Props { + validatorCount: number | string; + withdrawalAddress: string; + os: 'mac' | 'linux' | 'windows'; + chosenTool: keysTool; + setChosenTool: (tool: keysTool) => void; +} + +export const Instructions = ({ + validatorCount, + withdrawalAddress, + os, + chosenTool, + setChosenTool, +}: Props) => { + const { formatMessage } = useIntl(); + return ( + + + + +
+ setChosenTool(keysTool.CLI)} + style={{ marginEnd: '20px' }} + > + {formatMessage({ defaultMessage: 'Download CLI app' })} + +
+
+ {chosenTool === keysTool.CLI && ( + + )} +
+
+ ); +}; diff --git a/src/pages/GenerateKeys/index.tsx b/src/pages/GenerateKeys/index.tsx index b3583be6..6f44a175 100644 --- a/src/pages/GenerateKeys/index.tsx +++ b/src/pages/GenerateKeys/index.tsx @@ -1,5 +1,6 @@ // Import libraries import React, { useEffect, useState, useMemo } from 'react'; +import BigNumber from 'bignumber.js'; import { Dispatch } from 'redux'; import { connect } from 'react-redux'; import styled from 'styled-components'; @@ -7,7 +8,11 @@ import { Box, CheckBox } from 'grommet'; import { FormattedMessage, useIntl } from 'react-intl'; import { toChecksumAddress } from 'ethereumjs-util'; // Components +import { Instructions } from './Instructions'; +import { NumberInput } from './NumberInput'; +import { OperatingSystemButtons } from './OperatingSystemButtons'; import { WorkflowPageTemplate } from '../../components/WorkflowPage/WorkflowPageTemplate'; +import { Alert } from '../../components/Alert'; import { Button } from '../../components/Button'; import { Heading } from '../../components/Heading'; import { Link } from '../../components/Link'; @@ -26,9 +31,16 @@ import { updateWorkflow, WorkflowStep, } from '../../store/actions/workflowActions'; +import { + IS_MAINNET, + PRICE_PER_VALIDATOR, + TICKER_NAME, +} from '../../utils/envVars'; import { StoreState } from '../../store/reducers'; // Utilities import { routeToCorrectWorkflowStep } from '../../utils/RouteToCorrectWorkflowStep'; +import instructions1 from '../../static/instructions_1.svg'; +import instructions2 from '../../static/instructions_2.svg'; // Images // Routes import { routesEnum } from '../../Routes'; @@ -40,6 +52,72 @@ export enum operatingSystem { 'WINDOWS', } +const osMapping: { [os: number]: 'mac' | 'linux' | 'windows' } = { + [operatingSystem.MAC]: 'mac', + [operatingSystem.LINUX]: 'linux', + [operatingSystem.WINDOWS]: 'windows', +}; + +export enum keysTool { + 'CLI', + 'GUI', + 'CLISOURCE', +} + +const AddressInputContainer = styled.div` + display: flex; + gap: 1rem; +`; + +const AddressInput = styled.input` + height: 50px; + flex: 1; + font-size: 18px; + line-height: 24px; + color: #444444; + padding-left: 10px; + box-sizing: border-box; + background-color: ${(p: any) => p.theme.gray.lightest}; + border-radius: ${(p: any) => p.theme.borderRadius}; + -webkit-appearance: textfield; + -moz-appearance: textfield; + appearance: textfield; + ::-webkit-inner-spin-button, + ::-webkit-outer-spin-button { + -webkit-appearance: none; + } + border: 1px solid #ddd; + display: inline-flex; + :focus { + outline: none; + } +`; + +const AddressIndicator = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + font-size: 2rem; +`; + +const Highlight = styled.span` + background: ${p => p.theme.green.medium}; +`; + +const InstructionImgContainer = styled.div` + height: 250px; + margin: 64px; + border-radius: 4px; + display: flex; + justify-content: center; +`; + +const NumValidatorContainer = styled.div` + display: flex; + margin-top: 20px; + gap: 50px; +`; + const ButtonContainer = styled.div` display: flex; justify-content: center; @@ -82,16 +160,30 @@ const _GenerateKeysPage = ({ workflow, }: Props): JSX.Element => { const { formatMessage } = useIntl(); + const [validatorCount, setValidatorCount] = useState(0); const [ mnemonicAcknowledgementChecked, setMnemonicAcknowledgementChecked, ] = useState(workflow > WorkflowStep.GENERATE_KEY_PAIRS); + const [chosenOs, setChosenOs] = useState( + operatingSystem.LINUX + ); const [withdrawalAddress, setWithdrawalAddress] = useState(''); + const defaultKeysTool = IS_MAINNET ? keysTool.CLI : keysTool.GUI; + const [chosenTool, setChosenTool] = useState(defaultKeysTool); + const onCheckboxClick = (e: any) => { setMnemonicAcknowledgementChecked(e.target.checked); }; + const handleAddressChange = (e: any) => { + // Only allow hexadecimal characters and 'x' (for 0x prefix) + const re = /[^0-9a-fx]/gi; + const value = e.target.value.replace(re, ''); + setWithdrawalAddress(value); + }; + const isValidWithdrawalAddress = useMemo( () => /^0x[0-9a-f]{40}$/i.test(withdrawalAddress), [withdrawalAddress] @@ -102,6 +194,12 @@ const _GenerateKeysPage = ({ setWithdrawalAddress(toChecksumAddress(withdrawalAddress)); }, [isValidWithdrawalAddress, withdrawalAddress]); + const addressIndicatorEmoji = useMemo(() => { + if (!withdrawalAddress) return '⬅'; + if (isValidWithdrawalAddress) return '✅'; + return '❌'; + }, [withdrawalAddress, isValidWithdrawalAddress]); + const handleSubmit = () => { if (workflow === WorkflowStep.GENERATE_KEY_PAIRS) { dispatchWorkflowUpdate(WorkflowStep.UPLOAD_VALIDATOR_FILE); @@ -112,156 +210,354 @@ const _GenerateKeysPage = ({ return routeToCorrectWorkflowStep(workflow); } + const isOneClick = sessionStorage.getItem('oneClick') === 'true'; + return ( - - - - - - - - - - - - - - - - - - - - - - - - - WagyuStep1 - - - - - - - - - WagyuStep2 - - - - - - - - - WagyuStep3 - - - + <> + {isOneClick ? ( + + + + - + - - - WagyuStep4 - - - - - - + + + + + + + + + + + + + + + + + WagyuStep1 + + + + + + + + + WagyuStep2 + + + + + + + + + WagyuStep3 + + + + + + + + + WagyuStep4 + + + + + + + + + WagyuStep5 + + + + + + + + + WagyuStep6 + + + + + + + + + WagyuStep7 + + + + + +

+ Progress to the next page to upload your deposit_data-xxxxxxxxxx.json to make the deposits for your validator keys. Please note that once the deposits are made, your validator may take up to 24 hours to become active. +

+

+ You will need the MetaMask (https://metamask.io/) extension installed with a wallet that contains the correct STRAX balance before you can progress to make deposits for your validator(s). +

+
+
+
+
+ + + + + + )} + /> + + + + +