From 4272fefb0b75e639bb85cf6c50d8de33516232ea Mon Sep 17 00:00:00 2001 From: Sam Keen Date: Thu, 29 Sep 2022 20:10:53 +0100 Subject: [PATCH] feat(#807): adds app for validator add and remove signer (#1402) * Feat/807: ABI and classes for the contract methods * Feat/807: Added a new multisig-signer app * Feat/807: Added a new multisig-signer app * Feat/800: Untested signer forms * Feat/800: Moved reused bg video into ui-toolkit to use in multisig-signer project, and cleaned up some spacing that was overlooked in the stats theme changes * Feat/800: Componentised a bit, made the app look ok * Feat/800: Linting, prettifying, removing some unneeded tests, ensuring e2e tests run * Feat/800: Bit of translation * chore: fix type errors * chore: some parts error handling * feat: handle error and not found cases * feat: add changes to remove signer form as well * chore: rename component * chore: fix type issues * feat: add web3 connector logic * feat: allow disconnecting and show connected eth wallet info * Feat/800: Removed unused 'useApolloClient' * Feat/800: Ensure bundle.nonce and bundle.signatures have '0x' prepended * Feat/800: Removed unused e2e directory * Feat/800: Removed unnecessary app test * Feat/800: Removed unnecessary router * Feat/800: Capturing GQL errors in Sentry * Feat/800: Removing references to the unused e2e test directory * Feat/807: Consistent react hook imports * Feat/807: Removed unnecessary spreads Co-authored-by: Dexter --- README.md | 4 + apps/multisig-signer/.babelrc | 11 + apps/multisig-signer/.browserslistrc | 16 + apps/multisig-signer/.env | 4 + apps/multisig-signer/.env.devnet | 5 + apps/multisig-signer/.env.mainnet | 5 + apps/multisig-signer/.env.stagnet3 | 5 + apps/multisig-signer/.env.testnet | 5 + apps/multisig-signer/.eslintrc.json | 18 ++ apps/multisig-signer/README.md | 46 +++ apps/multisig-signer/index.d.ts | 5 + apps/multisig-signer/jest.config.ts | 12 + apps/multisig-signer/netlify.toml | 4 + apps/multisig-signer/postcss.config.js | 10 + apps/multisig-signer/project.json | 80 +++++ apps/multisig-signer/src/app/app.tsx | 108 +++++++ .../__generated__/AddSignerBundle.ts | 45 +++ .../__generated__/RemoveSignerBundle.ts | 48 +++ .../add-signer-form/add-signer-form.tsx | 121 ++++++++ .../app/components/add-signer-form/index.tsx | 1 + .../contract-details/contract-details.tsx | 35 +++ .../app/components/contract-details/index.tsx | 1 + .../eth-wallet-container.tsx | 25 ++ .../components/eth-wallet-container/index.tsx | 1 + .../src/app/components/header/header.tsx | 24 ++ .../src/app/components/header/index.tsx | 1 + .../src/app/components/index.tsx | 4 + .../components/remove-signer-form/index.tsx | 1 + .../remove-signer-form/remove-signer-form.tsx | 121 ++++++++ .../app/components/web3-connector/index.ts | 1 + .../web3-connector/web3-connector.tsx | 94 ++++++ .../app/config/contracts/contracts-context.ts | 18 ++ .../config/contracts/contracts-provider.tsx | 60 ++++ apps/multisig-signer/src/app/config/env.ts | 12 + apps/multisig-signer/src/app/config/flags.ts | 3 + apps/multisig-signer/src/app/config/index.tsx | 3 + .../src/app/lib/apollo-client.tsx | 54 ++++ .../src/app/lib/web3-connectors.ts | 34 +++ .../src/app/react-app-env.d.ts | 1 + apps/multisig-signer/src/app/setup-tests.ts | 5 + apps/multisig-signer/src/assets/.gitkeep | 0 apps/multisig-signer/src/assets/env-config.js | 1 + .../src/environments/environment.prod.ts | 3 + .../src/environments/environment.ts | 6 + apps/multisig-signer/src/favicon.ico | Bin 0 -> 7406 bytes apps/multisig-signer/src/index.html | 26 ++ apps/multisig-signer/src/main.tsx | 14 + apps/multisig-signer/src/polyfills.ts | 7 + apps/multisig-signer/src/styles.css | 16 + apps/multisig-signer/tailwind.config.js | 17 ++ apps/multisig-signer/tsconfig.app.json | 23 ++ apps/multisig-signer/tsconfig.json | 26 ++ apps/multisig-signer/tsconfig.spec.json | 24 ++ apps/multisig-signer/webpack.config.js | 17 ++ apps/stats/src/app.tsx | 4 +- apps/stats/src/components/header/header.tsx | 15 +- apps/stats/src/components/videos/index.ts | 1 - .../src/abis/multisig_abi.json | 276 ++++++++++++++++++ libs/smart-contracts/src/contracts/index.ts | 1 + .../src/contracts/multisig-control.ts | 55 ++++ .../background-video.spec.tsx | 9 + .../background-video.stories.tsx | 9 + .../background-video/background-video.tsx | 2 +- .../src/components/background-video/index.ts | 1 + libs/ui-toolkit/src/components/index.ts | 1 + workspace.json | 1 + 66 files changed, 1596 insertions(+), 10 deletions(-) create mode 100644 apps/multisig-signer/.babelrc create mode 100644 apps/multisig-signer/.browserslistrc create mode 100644 apps/multisig-signer/.env create mode 100644 apps/multisig-signer/.env.devnet create mode 100644 apps/multisig-signer/.env.mainnet create mode 100644 apps/multisig-signer/.env.stagnet3 create mode 100644 apps/multisig-signer/.env.testnet create mode 100644 apps/multisig-signer/.eslintrc.json create mode 100644 apps/multisig-signer/README.md create mode 100644 apps/multisig-signer/index.d.ts create mode 100644 apps/multisig-signer/jest.config.ts create mode 100644 apps/multisig-signer/netlify.toml create mode 100644 apps/multisig-signer/postcss.config.js create mode 100644 apps/multisig-signer/project.json create mode 100644 apps/multisig-signer/src/app/app.tsx create mode 100644 apps/multisig-signer/src/app/components/__generated__/AddSignerBundle.ts create mode 100644 apps/multisig-signer/src/app/components/__generated__/RemoveSignerBundle.ts create mode 100644 apps/multisig-signer/src/app/components/add-signer-form/add-signer-form.tsx create mode 100644 apps/multisig-signer/src/app/components/add-signer-form/index.tsx create mode 100644 apps/multisig-signer/src/app/components/contract-details/contract-details.tsx create mode 100644 apps/multisig-signer/src/app/components/contract-details/index.tsx create mode 100644 apps/multisig-signer/src/app/components/eth-wallet-container/eth-wallet-container.tsx create mode 100644 apps/multisig-signer/src/app/components/eth-wallet-container/index.tsx create mode 100644 apps/multisig-signer/src/app/components/header/header.tsx create mode 100644 apps/multisig-signer/src/app/components/header/index.tsx create mode 100644 apps/multisig-signer/src/app/components/index.tsx create mode 100644 apps/multisig-signer/src/app/components/remove-signer-form/index.tsx create mode 100644 apps/multisig-signer/src/app/components/remove-signer-form/remove-signer-form.tsx create mode 100644 apps/multisig-signer/src/app/components/web3-connector/index.ts create mode 100644 apps/multisig-signer/src/app/components/web3-connector/web3-connector.tsx create mode 100644 apps/multisig-signer/src/app/config/contracts/contracts-context.ts create mode 100644 apps/multisig-signer/src/app/config/contracts/contracts-provider.tsx create mode 100644 apps/multisig-signer/src/app/config/env.ts create mode 100644 apps/multisig-signer/src/app/config/flags.ts create mode 100644 apps/multisig-signer/src/app/config/index.tsx create mode 100644 apps/multisig-signer/src/app/lib/apollo-client.tsx create mode 100644 apps/multisig-signer/src/app/lib/web3-connectors.ts create mode 100644 apps/multisig-signer/src/app/react-app-env.d.ts create mode 100644 apps/multisig-signer/src/app/setup-tests.ts create mode 100644 apps/multisig-signer/src/assets/.gitkeep create mode 100644 apps/multisig-signer/src/assets/env-config.js create mode 100644 apps/multisig-signer/src/environments/environment.prod.ts create mode 100644 apps/multisig-signer/src/environments/environment.ts create mode 100644 apps/multisig-signer/src/favicon.ico create mode 100644 apps/multisig-signer/src/index.html create mode 100644 apps/multisig-signer/src/main.tsx create mode 100644 apps/multisig-signer/src/polyfills.ts create mode 100644 apps/multisig-signer/src/styles.css create mode 100644 apps/multisig-signer/tailwind.config.js create mode 100644 apps/multisig-signer/tsconfig.app.json create mode 100644 apps/multisig-signer/tsconfig.json create mode 100644 apps/multisig-signer/tsconfig.spec.json create mode 100644 apps/multisig-signer/webpack.config.js delete mode 100644 apps/stats/src/components/videos/index.ts create mode 100644 libs/smart-contracts/src/abis/multisig_abi.json create mode 100644 libs/smart-contracts/src/contracts/multisig-control.ts create mode 100644 libs/ui-toolkit/src/components/background-video/background-video.spec.tsx create mode 100644 libs/ui-toolkit/src/components/background-video/background-video.stories.tsx rename apps/stats/src/components/videos/vega-background-video.tsx => libs/ui-toolkit/src/components/background-video/background-video.tsx (89%) create mode 100644 libs/ui-toolkit/src/components/background-video/index.ts diff --git a/README.md b/README.md index f0b231109..43166f92f 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,10 @@ An application for the status of the Vega network. Showing block height and othe Hosting for static content being shared across apps, for example fonts. +### [Multisig-signer](./apps/multisig-signer) + +The utility dApp for validators wishing to add or remove themselves as a signer of the multisig contract. + # 🧱 Libraries in this repo ### [UI toolkit](./libs/ui-toolkit) diff --git a/apps/multisig-signer/.babelrc b/apps/multisig-signer/.babelrc new file mode 100644 index 000000000..61641ec8a --- /dev/null +++ b/apps/multisig-signer/.babelrc @@ -0,0 +1,11 @@ +{ + "presets": [ + [ + "@nrwl/react/babel", + { + "runtime": "automatic" + } + ] + ], + "plugins": [] +} diff --git a/apps/multisig-signer/.browserslistrc b/apps/multisig-signer/.browserslistrc new file mode 100644 index 000000000..f1d12df4f --- /dev/null +++ b/apps/multisig-signer/.browserslistrc @@ -0,0 +1,16 @@ +# This file is used by: +# 1. autoprefixer to adjust CSS to support the below specified browsers +# 2. babel preset-env to adjust included polyfills +# +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries +# +# If you need to support different browsers in production, you may tweak the list below. + +last 1 Chrome version +last 1 Firefox version +last 2 Edge major versions +last 2 Safari major version +last 2 iOS major versions +Firefox ESR +not IE 9-11 # For IE 9-11 support, remove 'not'. \ No newline at end of file diff --git a/apps/multisig-signer/.env b/apps/multisig-signer/.env new file mode 100644 index 000000000..12f056b71 --- /dev/null +++ b/apps/multisig-signer/.env @@ -0,0 +1,4 @@ +NX_VEGA_URL=https://api.n01.stagnet3.vega.xyz/graphql +NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json +NX_VEGA_NETWORKS='{"TESTNET":"https://multisig-signer.fairground.wtf","MAINNET":"https://multisig-signer.vega.xyz"}' +NX_VEGA_ENV=STAGNET3 diff --git a/apps/multisig-signer/.env.devnet b/apps/multisig-signer/.env.devnet new file mode 100644 index 000000000..4155154bf --- /dev/null +++ b/apps/multisig-signer/.env.devnet @@ -0,0 +1,5 @@ +# App configuration variables +NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/devnet-network.json +NX_VEGA_URL=https://api.n04.d.vega.xyz/graphql +NX_VEGA_NETWORKS='{"TESTNET":"https://multisig-signer.fairground.wtf","MAINNET":"https://multisig-signer.vega.xyz"}' +NX_VEGA_ENV=DEVNET diff --git a/apps/multisig-signer/.env.mainnet b/apps/multisig-signer/.env.mainnet new file mode 100644 index 000000000..59c595f17 --- /dev/null +++ b/apps/multisig-signer/.env.mainnet @@ -0,0 +1,5 @@ +# App configuration variables +NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mainnet-network.json +NX_VEGA_URL=https://api.vega.xyz/query +NX_VEGA_NETWORKS='{"TESTNET":"https://multisig-signer.fairground.wtf","MAINNET":"https://multisig-signer.vega.xyz"}' +NX_VEGA_ENV=MAINNET diff --git a/apps/multisig-signer/.env.stagnet3 b/apps/multisig-signer/.env.stagnet3 new file mode 100644 index 000000000..8e5875d91 --- /dev/null +++ b/apps/multisig-signer/.env.stagnet3 @@ -0,0 +1,5 @@ +# App configuration variables +NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json +NX_VEGA_URL=https://api.n01.stagnet3.vega.xyz/graphql +NX_VEGA_NETWORKS='{"TESTNET":"https://multisig-signer.fairground.wtf","MAINNET":"https://multisig-signer.vega.xyz"}' +NX_VEGA_ENV=STAGNET3 diff --git a/apps/multisig-signer/.env.testnet b/apps/multisig-signer/.env.testnet new file mode 100644 index 000000000..ae63ae5af --- /dev/null +++ b/apps/multisig-signer/.env.testnet @@ -0,0 +1,5 @@ +# App configuration variables +NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json +NX_VEGA_URL=https://api.n09.testnet.vega.xyz/graphql +NX_VEGA_NETWORKS='{"TESTNET":"https://multisig-signer.fairground.wtf","MAINNET":"https://multisig-signer.vega.xyz"}' +NX_VEGA_ENV=TESTNET diff --git a/apps/multisig-signer/.eslintrc.json b/apps/multisig-signer/.eslintrc.json new file mode 100644 index 000000000..de2a9b877 --- /dev/null +++ b/apps/multisig-signer/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*", "__generated__", "__generated___"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/multisig-signer/README.md b/apps/multisig-signer/README.md new file mode 100644 index 000000000..20ff1270a --- /dev/null +++ b/apps/multisig-signer/README.md @@ -0,0 +1,46 @@ +## Multisig-signer + +## Development + +First copy the configuration of the application you are starting: + +```bash +cp .env.[environment] .env.local +``` + +Starting the app: + +```bash +yarn nx serve multisig-signer +``` + +### Configuration + +Example configurations are provided here: + +- [Mainnet](./.env.mainnet) +- [Devnet](./.env.devnet) +- [Capsule](./.env.capsule) +- [Testnet](./.env.testnet) +- [Stagnet3](./.env.stagnet3) + +For convenience, you can boot the app injecting one of the configurations above by running: + +```bash +yarn nx run multisig-signer:serve --env={env} # e.g. stagnet3 +``` + +There are a few different configuration options offered for this app: + +| **Flag** | **Purpose** | +| -------------------------------- | ---------------------------------------------------------------------------------------------------- | --- | | +| `NX_VEGA_URL` | The GraphQl query endpoint of a [Vega data node](https://github.com/vegaprotocol/networks#data-node) | +| `NX_VEGA_ENV` | The name of the currently connected vega environment | + +## Testing + +To run the minimal set of unit tests, run the following: + +```bash +yarn nx test multisig-signer +``` diff --git a/apps/multisig-signer/index.d.ts b/apps/multisig-signer/index.d.ts new file mode 100644 index 000000000..cf0dffa18 --- /dev/null +++ b/apps/multisig-signer/index.d.ts @@ -0,0 +1,5 @@ +/// + +interface Window { + _env_?: Record; +} diff --git a/apps/multisig-signer/jest.config.ts b/apps/multisig-signer/jest.config.ts new file mode 100644 index 000000000..ee06094a7 --- /dev/null +++ b/apps/multisig-signer/jest.config.ts @@ -0,0 +1,12 @@ +/* eslint-disable */ +export default { + displayName: 'multisig-signer', + preset: '../../jest.preset.js', + transform: { + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', + '^.+\\.[tj]sx?$': 'babel-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/apps/multisig-signer', + setupFilesAfterEnv: ['./src/app/setup-tests.ts'], +}; diff --git a/apps/multisig-signer/netlify.toml b/apps/multisig-signer/netlify.toml new file mode 100644 index 000000000..b87b8d3dd --- /dev/null +++ b/apps/multisig-signer/netlify.toml @@ -0,0 +1,4 @@ +[[redirects]] + from = "/*" + to = "/index.html" + status = 200 diff --git a/apps/multisig-signer/postcss.config.js b/apps/multisig-signer/postcss.config.js new file mode 100644 index 000000000..cbdd9c22c --- /dev/null +++ b/apps/multisig-signer/postcss.config.js @@ -0,0 +1,10 @@ +const { join } = require('path'); + +module.exports = { + plugins: { + tailwindcss: { + config: join(__dirname, 'tailwind.config.js'), + }, + autoprefixer: {}, + }, +}; diff --git a/apps/multisig-signer/project.json b/apps/multisig-signer/project.json new file mode 100644 index 000000000..d149d922e --- /dev/null +++ b/apps/multisig-signer/project.json @@ -0,0 +1,80 @@ +{ + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/multisig-signer/src", + "projectType": "application", + "targets": { + "build": { + "executor": "./tools/executors/webpack:build", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "options": { + "compiler": "babel", + "outputPath": "dist/apps/multisig-signer", + "index": "apps/multisig-signer/src/index.html", + "baseHref": "/", + "main": "apps/multisig-signer/src/main.tsx", + "polyfills": "apps/multisig-signer/src/polyfills.ts", + "tsConfig": "apps/multisig-signer/tsconfig.app.json", + "assets": ["apps/multisig-signer/src/assets"], + "styles": ["apps/multisig-signer/src/styles.css"], + "scripts": [], + "webpackConfig": "apps/multisig-signer/webpack.config.js" + }, + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "apps/multisig-signer/src/environments/environment.ts", + "with": "apps/multisig-signer/src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": true, + "namedChunks": false, + "extractLicenses": true, + "vendorChunk": false + } + } + }, + "serve": { + "executor": "./tools/executors/webpack:serve", + "options": { + "port": 3000, + "buildTarget": "multisig-signer:build:development", + "hmr": true + }, + "configurations": { + "production": { + "buildTarget": "multisig-signer:build:production", + "hmr": false + } + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/multisig-signer/**/*.{ts,tsx,js,jsx}"] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["coverage/apps/multisig-signer"], + "options": { + "jestConfig": "apps/multisig-signer/jest.config.ts", + "passWithNoTests": true + } + }, + "build-netlify": { + "executor": "@nrwl/workspace:run-commands", + "options": { + "commands": [ + "cp apps/multisig-signer/netlify.toml netlify.toml", + "nx build multisig-signer" + ] + } + } + }, + "tags": [] +} diff --git a/apps/multisig-signer/src/app/app.tsx b/apps/multisig-signer/src/app/app.tsx new file mode 100644 index 000000000..450a59be2 --- /dev/null +++ b/apps/multisig-signer/src/app/app.tsx @@ -0,0 +1,108 @@ +import * as Sentry from '@sentry/react'; +import classnames from 'classnames'; +import { useEffect, useMemo, useState } from 'react'; +import { BrowserTracing } from '@sentry/tracing'; +import { + EnvironmentProvider, + NetworkLoader, + useEnvironment, +} from '@vegaprotocol/environment'; +import { AsyncRenderer, Button, Lozenge } from '@vegaprotocol/ui-toolkit'; +import type { EthereumConfig } from '@vegaprotocol/web3'; +import { useEthereumConfig, Web3Provider } from '@vegaprotocol/web3'; +import { ThemeContext, useThemeSwitcher, t } from '@vegaprotocol/react-helpers'; +import { createClient } from './lib/apollo-client'; +import { ENV } from './config/env'; +import { ContractsProvider } from './config/contracts/contracts-provider'; +import { + AddSignerForm, + RemoveSignerForm, + Header, + ContractDetails, +} from './components'; +import { createConnectors } from './lib/web3-connectors'; +import { Web3Connector } from './components/web3-connector'; +import { EthWalletContainer } from './components/eth-wallet-container'; +import { useWeb3React } from '@web3-react/core'; + +const pageWrapperClasses = classnames( + 'min-h-screen w-screen', + 'grid grid-rows-[auto,1fr]', + 'bg-white dark:bg-black', + 'text-neutral-900 dark:text-neutral-100' +); + +const ConnectedApp = ({ config }: { config: EthereumConfig | null }) => { + const { account, connector } = useWeb3React(); + return ( +
+

{t('Multisig signer')}

+
+

+ Connected to Eth wallet: {account} +

+ +
+ +

{t('Add or remove signer')}

+ + +
+ ); +}; + +function App() { + const { VEGA_ENV, ETHEREUM_PROVIDER_URL } = useEnvironment(); + const { config, loading, error } = useEthereumConfig(); + const [dialogOpen, setDialogOpen] = useState(false); + const [theme, toggleTheme] = useThemeSwitcher(); + + useEffect(() => { + Sentry.init({ + dsn: ENV.dsn, + integrations: [new BrowserTracing()], + tracesSampleRate: 1, + environment: VEGA_ENV, + }); + }, [VEGA_ENV]); + const Connectors = useMemo(() => { + if (config?.chain_id) { + return createConnectors(ETHEREUM_PROVIDER_URL, Number(config.chain_id)); + } + return []; + }, [config?.chain_id, ETHEREUM_PROVIDER_URL]); + + return ( + + + +
+ +
+ + + + +
+
+
+
+ ); +} + +const Wrapper = () => { + return ( + + + + + + + + ); +}; + +export default Wrapper; diff --git a/apps/multisig-signer/src/app/components/__generated__/AddSignerBundle.ts b/apps/multisig-signer/src/app/components/__generated__/AddSignerBundle.ts new file mode 100644 index 000000000..cd1a623a3 --- /dev/null +++ b/apps/multisig-signer/src/app/components/__generated__/AddSignerBundle.ts @@ -0,0 +1,45 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL query operation: AddSignerBundle +// ==================================================== + +export interface AddSignerBundle_erc20MultiSigSignerAddedBundles_edges_node { + __typename: "ERC20MultiSigSignerAddedBundle"; + /** + * The ethereum address of the signer to be added + */ + newSigner: string; + /** + * The nonce used in the signing operation + */ + nonce: string; + /** + * The bundle of signatures from current validators to sign in the new signer + */ + signatures: string; +} + +export interface AddSignerBundle_erc20MultiSigSignerAddedBundles_edges { + __typename: "ERC20MultiSigSignerAddedBundleEdge"; + node: AddSignerBundle_erc20MultiSigSignerAddedBundles_edges_node; +} + +export interface AddSignerBundle_erc20MultiSigSignerAddedBundles { + __typename: "ERC20MultiSigSignerAddedConnection"; + edges: (AddSignerBundle_erc20MultiSigSignerAddedBundles_edges | null)[] | null; +} + +export interface AddSignerBundle { + /** + * Get the signature bundle to add a particular validator to the signer list of the multisig contract + */ + erc20MultiSigSignerAddedBundles: AddSignerBundle_erc20MultiSigSignerAddedBundles; +} + +export interface AddSignerBundleVariables { + nodeId: string; +} diff --git a/apps/multisig-signer/src/app/components/__generated__/RemoveSignerBundle.ts b/apps/multisig-signer/src/app/components/__generated__/RemoveSignerBundle.ts new file mode 100644 index 000000000..833b8db58 --- /dev/null +++ b/apps/multisig-signer/src/app/components/__generated__/RemoveSignerBundle.ts @@ -0,0 +1,48 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL query operation: RemoveSignerBundle +// ==================================================== + +export interface RemoveSignerBundle_erc20MultiSigSignerRemovedBundles_edges_node { + __typename: "ERC20MultiSigSignerRemovedBundle"; + /** + * The ethereum address of the signer to be removed + */ + oldSigner: string; + /** + * The nonce used in the signing operation + */ + nonce: string; + /** + * The bundle of signatures from current validators to sign in the new signer + */ + signatures: string; +} + +export interface RemoveSignerBundle_erc20MultiSigSignerRemovedBundles_edges { + __typename: "ERC20MultiSigSignerRemovedBundleEdge"; + node: RemoveSignerBundle_erc20MultiSigSignerRemovedBundles_edges_node; +} + +export interface RemoveSignerBundle_erc20MultiSigSignerRemovedBundles { + __typename: "ERC20MultiSigSignerRemovedConnection"; + /** + * The list of signer bundles for that validator + */ + edges: (RemoveSignerBundle_erc20MultiSigSignerRemovedBundles_edges | null)[] | null; +} + +export interface RemoveSignerBundle { + /** + * Get the signatures bundle to remove a particular validator from signer list of the multisig contract + */ + erc20MultiSigSignerRemovedBundles: RemoveSignerBundle_erc20MultiSigSignerRemovedBundles; +} + +export interface RemoveSignerBundleVariables { + nodeId: string; +} diff --git a/apps/multisig-signer/src/app/components/add-signer-form/add-signer-form.tsx b/apps/multisig-signer/src/app/components/add-signer-form/add-signer-form.tsx new file mode 100644 index 000000000..6c8c93595 --- /dev/null +++ b/apps/multisig-signer/src/app/components/add-signer-form/add-signer-form.tsx @@ -0,0 +1,121 @@ +import { useState } from 'react'; +import { gql, useLazyQuery } from '@apollo/client'; +import { captureException } from '@sentry/react'; +import { t } from '@vegaprotocol/react-helpers'; +import { useEthereumTransaction } from '@vegaprotocol/web3'; +import { + FormGroup, + Input, + Button, + InputError, + Loader, +} from '@vegaprotocol/ui-toolkit'; +import { prepend0x } from '@vegaprotocol/smart-contracts'; +import { useContracts } from '../../config/contracts/contracts-context'; +import type { FormEvent } from 'react'; +import type { + AddSignerBundle, + AddSignerBundleVariables, +} from '../__generated__/AddSignerBundle'; +import type { MultisigControl } from '@vegaprotocol/smart-contracts'; + +export const ADD_SIGNER_QUERY = gql` + query AddSignerBundle($nodeId: ID!) { + erc20MultiSigSignerAddedBundles(nodeId: $nodeId) { + edges { + node { + newSigner + nonce + signatures + } + } + } + } +`; + +export const AddSignerForm = () => { + const { multisig } = useContracts(); + const [address, setAddress] = useState(''); + const [bundleNotFound, setBundleNotFound] = useState(false); + const [runQuery, { data, error, loading }] = useLazyQuery< + AddSignerBundle, + AddSignerBundleVariables + >(ADD_SIGNER_QUERY); + const { perform, Dialog } = useEthereumTransaction< + MultisigControl, + 'add_signer' + >(multisig, 'add_signer'); + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + setBundleNotFound(false); + try { + if (address === '') { + return; + } + await runQuery({ + variables: { nodeId: address }, + }); + const bundle = data?.erc20MultiSigSignerAddedBundles?.edges?.[0]?.node; + + if (!bundle) { + if (!error) { + setBundleNotFound(true); + } + return; + } + + await perform( + bundle.newSigner, + bundle.nonce.startsWith('0x') ? bundle.nonce : prepend0x(bundle.nonce), + bundle.signatures.startsWith('0x') + ? bundle.signatures + : prepend0x(bundle.signatures) + ); + } catch (err: unknown) { + captureException(err); + } + }; + + return ( +
handleSubmit(e)}> + +
+ setAddress(e.target.value)} + data-testid="add-signer-input-input" + /> + +
+
+ {error && ( + + {error?.message.includes('InvalidArgument') + ? t('Invalid node id') + : error?.message} + + )} + {bundleNotFound && !error && ( + + {t( + 'Bundle was not found, are you sure this validator needs to be added?' + )} + + )} +
+
+ + + ); +}; diff --git a/apps/multisig-signer/src/app/components/add-signer-form/index.tsx b/apps/multisig-signer/src/app/components/add-signer-form/index.tsx new file mode 100644 index 000000000..9d1d34d99 --- /dev/null +++ b/apps/multisig-signer/src/app/components/add-signer-form/index.tsx @@ -0,0 +1 @@ +export * from './add-signer-form'; diff --git a/apps/multisig-signer/src/app/components/contract-details/contract-details.tsx b/apps/multisig-signer/src/app/components/contract-details/contract-details.tsx new file mode 100644 index 000000000..f3b98882f --- /dev/null +++ b/apps/multisig-signer/src/app/components/contract-details/contract-details.tsx @@ -0,0 +1,35 @@ +import { useEffect, useState } from 'react'; +import * as Sentry from '@sentry/react'; +import { Lozenge } from '@vegaprotocol/ui-toolkit'; +import { useContracts } from '../../config/contracts/contracts-context'; +import type { EthereumConfig } from '@vegaprotocol/web3'; + +interface ContractDetailsProps { + config: EthereumConfig | null; +} + +export const ContractDetails = ({ config }: ContractDetailsProps) => { + const { multisig } = useContracts(); + const [validSignerCount, setValidSignerCount] = useState(undefined); + + useEffect(() => { + (async () => { + try { + const res = await multisig.get_valid_signer_count(); + setValidSignerCount(res); + } catch (err) { + Sentry.captureException(err); + } + })(); + }, [multisig]); + + return ( +
+

+ Multisig contract address:{' '} + {config?.multisig_control_contract?.address} +

+

Valid signer count: {validSignerCount}

+
+ ); +}; diff --git a/apps/multisig-signer/src/app/components/contract-details/index.tsx b/apps/multisig-signer/src/app/components/contract-details/index.tsx new file mode 100644 index 000000000..0e1ce3c79 --- /dev/null +++ b/apps/multisig-signer/src/app/components/contract-details/index.tsx @@ -0,0 +1 @@ +export * from './contract-details'; diff --git a/apps/multisig-signer/src/app/components/eth-wallet-container/eth-wallet-container.tsx b/apps/multisig-signer/src/app/components/eth-wallet-container/eth-wallet-container.tsx new file mode 100644 index 000000000..78639bf8f --- /dev/null +++ b/apps/multisig-signer/src/app/components/eth-wallet-container/eth-wallet-container.tsx @@ -0,0 +1,25 @@ +import { Button } from '@vegaprotocol/ui-toolkit'; +import { useWeb3React } from '@web3-react/core'; +import type { ReactElement } from 'react'; + +export const EthWalletContainer = ({ + dialogOpen, + setDialogOpen, + children, +}: { + dialogOpen: boolean; + setDialogOpen: (open: boolean) => void; + children: ReactElement; +}) => { + const { account } = useWeb3React(); + if (!account) { + return ( +
+ +
+ ); + } + return children; +}; diff --git a/apps/multisig-signer/src/app/components/eth-wallet-container/index.tsx b/apps/multisig-signer/src/app/components/eth-wallet-container/index.tsx new file mode 100644 index 000000000..669004a23 --- /dev/null +++ b/apps/multisig-signer/src/app/components/eth-wallet-container/index.tsx @@ -0,0 +1 @@ +export * from './eth-wallet-container'; diff --git a/apps/multisig-signer/src/app/components/header/header.tsx b/apps/multisig-signer/src/app/components/header/header.tsx new file mode 100644 index 000000000..357213b64 --- /dev/null +++ b/apps/multisig-signer/src/app/components/header/header.tsx @@ -0,0 +1,24 @@ +import { + BackgroundVideo, + ThemeSwitcher, + VegaLogo, +} from '@vegaprotocol/ui-toolkit'; + +interface HeaderProps { + theme: 'light' | 'dark'; + toggleTheme: () => void; +} + +export const Header = ({ theme, toggleTheme }: HeaderProps) => { + return ( +
+ +
+
+ + +
+
+
+ ); +}; diff --git a/apps/multisig-signer/src/app/components/header/index.tsx b/apps/multisig-signer/src/app/components/header/index.tsx new file mode 100644 index 000000000..677ca79d4 --- /dev/null +++ b/apps/multisig-signer/src/app/components/header/index.tsx @@ -0,0 +1 @@ +export * from './header'; diff --git a/apps/multisig-signer/src/app/components/index.tsx b/apps/multisig-signer/src/app/components/index.tsx new file mode 100644 index 000000000..eb7e1c6ea --- /dev/null +++ b/apps/multisig-signer/src/app/components/index.tsx @@ -0,0 +1,4 @@ +export * from './add-signer-form'; +export * from './remove-signer-form'; +export * from './header'; +export * from './contract-details'; diff --git a/apps/multisig-signer/src/app/components/remove-signer-form/index.tsx b/apps/multisig-signer/src/app/components/remove-signer-form/index.tsx new file mode 100644 index 000000000..d0b447632 --- /dev/null +++ b/apps/multisig-signer/src/app/components/remove-signer-form/index.tsx @@ -0,0 +1 @@ +export * from './remove-signer-form'; diff --git a/apps/multisig-signer/src/app/components/remove-signer-form/remove-signer-form.tsx b/apps/multisig-signer/src/app/components/remove-signer-form/remove-signer-form.tsx new file mode 100644 index 000000000..7ef3a785c --- /dev/null +++ b/apps/multisig-signer/src/app/components/remove-signer-form/remove-signer-form.tsx @@ -0,0 +1,121 @@ +import { useState } from 'react'; +import { gql, useLazyQuery } from '@apollo/client'; +import { captureException } from '@sentry/react'; +import { t } from '@vegaprotocol/react-helpers'; +import { useEthereumTransaction } from '@vegaprotocol/web3'; +import { + FormGroup, + Input, + Button, + InputError, + Loader, +} from '@vegaprotocol/ui-toolkit'; +import { prepend0x } from '@vegaprotocol/smart-contracts'; +import { useContracts } from '../../config/contracts/contracts-context'; +import type { FormEvent } from 'react'; +import type { + RemoveSignerBundle, + RemoveSignerBundleVariables, +} from '../__generated__/RemoveSignerBundle'; +import type { MultisigControl } from '@vegaprotocol/smart-contracts'; + +const REMOVE_SIGNER_QUERY = gql` + query RemoveSignerBundle($nodeId: ID!) { + erc20MultiSigSignerRemovedBundles(nodeId: $nodeId) { + edges { + node { + oldSigner + nonce + signatures + } + } + } + } +`; + +export const RemoveSignerForm = () => { + const { multisig } = useContracts(); + const [address, setAddress] = useState(''); + const [bundleNotFound, setBundleNotFound] = useState(false); + const [runQuery, { data, error, loading }] = useLazyQuery< + RemoveSignerBundle, + RemoveSignerBundleVariables + >(REMOVE_SIGNER_QUERY); + const { perform, Dialog } = useEthereumTransaction< + MultisigControl, + 'remove_signer' + >(multisig, 'remove_signer'); + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + setBundleNotFound(false); + try { + if (address === '') { + return; + } + await runQuery({ + variables: { nodeId: address }, + }); + const bundle = data?.erc20MultiSigSignerRemovedBundles?.edges?.[0]?.node; + + if (!bundle) { + if (!error) { + setBundleNotFound(true); + } + return; + } + + await perform( + bundle.oldSigner, + bundle.nonce.startsWith('0x') ? bundle.nonce : prepend0x(bundle.nonce), + bundle.signatures.startsWith('0x') + ? bundle.signatures + : prepend0x(bundle.signatures) + ); + } catch (err: unknown) { + captureException(err); + } + }; + + return ( +
handleSubmit(e)}> + +
+ setAddress(e.target.value)} + data-testid="remove-signer-input-input" + /> + +
+
+ {error && ( + + {error?.message.includes('InvalidArgument') + ? t('Invalid node id') + : error?.message} + + )} + {bundleNotFound && !error && ( + + {t( + 'Bundle was not found, are you sure this validator needs to be removed?' + )} + + )} +
+
+ + + ); +}; diff --git a/apps/multisig-signer/src/app/components/web3-connector/index.ts b/apps/multisig-signer/src/app/components/web3-connector/index.ts new file mode 100644 index 000000000..1664c8d42 --- /dev/null +++ b/apps/multisig-signer/src/app/components/web3-connector/index.ts @@ -0,0 +1 @@ +export * from './web3-connector'; diff --git a/apps/multisig-signer/src/app/components/web3-connector/web3-connector.tsx b/apps/multisig-signer/src/app/components/web3-connector/web3-connector.tsx new file mode 100644 index 000000000..7e71b0d54 --- /dev/null +++ b/apps/multisig-signer/src/app/components/web3-connector/web3-connector.tsx @@ -0,0 +1,94 @@ +import { useEnvironment } from '@vegaprotocol/environment'; +import { useEthereumConfig } from '@vegaprotocol/web3'; +import { Button, Splash, AsyncRenderer } from '@vegaprotocol/ui-toolkit'; +import { Web3ConnectDialog } from '@vegaprotocol/web3'; +import { useWeb3React } from '@web3-react/core'; +import type { ReactElement } from 'react'; +import { useEffect, useMemo } from 'react'; +import { createConnectors } from '../../lib/web3-connectors'; + +interface Web3ConnectorProps { + children: ReactElement; + dialogOpen: boolean; + setDialogOpen: (open: boolean) => void; +} + +export function Web3Connector({ + children, + dialogOpen, + setDialogOpen, +}: Web3ConnectorProps) { + const { ETHEREUM_PROVIDER_URL } = useEnvironment(); + const { config, loading, error } = useEthereumConfig(); + const Connectors = useMemo(() => { + if (config?.chain_id) { + return createConnectors(ETHEREUM_PROVIDER_URL, Number(config.chain_id)); + } + return undefined; + }, [config?.chain_id, ETHEREUM_PROVIDER_URL]); + const appChainId = Number(config?.chain_id); + return ( + + + {children} + + {Connectors && ( + + )} + + ); +} + +interface Web3ContentProps { + children: ReactElement; + appChainId: number; + setDialogOpen: (isOpen: boolean) => void; +} + +export const Web3Content = ({ + children, + appChainId, + setDialogOpen, +}: Web3ContentProps) => { + const { error, connector, chainId } = useWeb3React(); + + useEffect(() => { + if (connector?.connectEagerly) { + connector.connectEagerly(); + } + // wallet connect doesnt handle connectEagerly being called when connector is also in the + // deps array. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [connector]); + + if (error) { + return ( + +
+

Something went wrong: {error.message}

+ +
+
+ ); + } + + if (chainId !== undefined && chainId !== appChainId) { + return ( + +
+

+ This app only works on chain ID: {appChainId} +

+ +
+
+ ); + } + + return children; +}; diff --git a/apps/multisig-signer/src/app/config/contracts/contracts-context.ts b/apps/multisig-signer/src/app/config/contracts/contracts-context.ts new file mode 100644 index 000000000..514f02c06 --- /dev/null +++ b/apps/multisig-signer/src/app/config/contracts/contracts-context.ts @@ -0,0 +1,18 @@ +import { createContext, useContext } from 'react'; +import type { MultisigControl } from '@vegaprotocol/smart-contracts'; + +export interface ContractsContextShape { + multisig: MultisigControl; +} + +export const ContractsContext = createContext< + ContractsContextShape | undefined +>(undefined); + +export function useContracts() { + const context = useContext(ContractsContext); + if (context === undefined) { + throw new Error('useContracts must be used within ContractsProvider'); + } + return context; +} diff --git a/apps/multisig-signer/src/app/config/contracts/contracts-provider.tsx b/apps/multisig-signer/src/app/config/contracts/contracts-provider.tsx new file mode 100644 index 000000000..c0aba78fb --- /dev/null +++ b/apps/multisig-signer/src/app/config/contracts/contracts-provider.tsx @@ -0,0 +1,60 @@ +import { useEffect, useState } from 'react'; +import { MultisigControl } from '@vegaprotocol/smart-contracts'; +import { Splash } from '@vegaprotocol/ui-toolkit'; +import { ethers } from 'ethers'; +import type { ContractsContextShape } from './contracts-context'; +import { ContractsContext } from './contracts-context'; +import { useEthereumConfig } from '@vegaprotocol/web3'; +import { useEnvironment } from '@vegaprotocol/environment'; + +/** + * Provides Vega Ethereum contract instances to its children. + */ +export const ContractsProvider = ({ children }: { children: JSX.Element }) => { + const { config } = useEthereumConfig(); + const { VEGA_ENV, ETHEREUM_PROVIDER_URL } = useEnvironment(); + const [contracts, setContracts] = useState( + null + ); + + // Create instances of contract classes. If we have an account use a signer for the + // contracts so that we can sign transactions, otherwise use the provider for just + // reading data + useEffect(() => { + let cancelled = false; + const run = async () => { + if (config) { + const provider = new ethers.providers.JsonRpcProvider( + ETHEREUM_PROVIDER_URL, + Number(config.chain_id) + ); + + if (provider && config) { + if (!cancelled) { + setContracts({ + multisig: new MultisigControl( + config.multisig_control_contract.address, + provider + ), + }); + } + } + } + }; + run(); + return () => { + // TODO: hacky quick fix for release to prevent race condition, find a better fix for this. + cancelled = true; + }; + }, [config, VEGA_ENV, ETHEREUM_PROVIDER_URL]); + + if (!contracts) { + return Error: cannot get data on multisig contract; + } + + return ( + + {children} + + ); +}; diff --git a/apps/multisig-signer/src/app/config/env.ts b/apps/multisig-signer/src/app/config/env.ts new file mode 100644 index 000000000..40a93dd07 --- /dev/null +++ b/apps/multisig-signer/src/app/config/env.ts @@ -0,0 +1,12 @@ +const windowOrDefault = (key: string) => { + if (window._env_ && window._env_[key]) { + return window._env_[key] as string; + } + return (process.env[key] as string) || ''; +}; + +export const ENV = { + dsn: windowOrDefault('NX_SENTRY_DSN'), + flags: {}, + dataSources: {}, +}; diff --git a/apps/multisig-signer/src/app/config/flags.ts b/apps/multisig-signer/src/app/config/flags.ts new file mode 100644 index 000000000..7aeab7211 --- /dev/null +++ b/apps/multisig-signer/src/app/config/flags.ts @@ -0,0 +1,3 @@ +import { ENV } from './env'; + +export default ENV.flags; diff --git a/apps/multisig-signer/src/app/config/index.tsx b/apps/multisig-signer/src/app/config/index.tsx new file mode 100644 index 000000000..dbab9b8fe --- /dev/null +++ b/apps/multisig-signer/src/app/config/index.tsx @@ -0,0 +1,3 @@ +import { ENV } from './env'; + +export const DATA_SOURCES = ENV.dataSources; diff --git a/apps/multisig-signer/src/app/lib/apollo-client.tsx b/apps/multisig-signer/src/app/lib/apollo-client.tsx new file mode 100644 index 000000000..742ee1f96 --- /dev/null +++ b/apps/multisig-signer/src/app/lib/apollo-client.tsx @@ -0,0 +1,54 @@ +import * as Sentry from '@sentry/react'; +import { ApolloClient, from, HttpLink, InMemoryCache } from '@apollo/client'; +import { onError } from '@apollo/client/link/error'; +import { RetryLink } from '@apollo/client/link/retry'; + +export function createClient(base?: string) { + if (!base) { + throw new Error('Base must be passed into createClient!'); + } + const urlHTTP = new URL(base); + const urlWS = new URL(base); + // Replace http with ws, preserving if its a secure connection eg. https => wss + urlWS.protocol = urlWS.protocol.replace('http', 'ws'); + + const cache = new InMemoryCache({ + typePolicies: { + Query: {}, + Account: { + keyFields: false, + fields: { + balanceFormatted: {}, + }, + }, + Node: { + keyFields: false, + }, + }, + }); + + const retryLink = new RetryLink({ + delay: { + initial: 300, + max: 10000, + jitter: true, + }, + }); + + const httpLink = new HttpLink({ + uri: urlHTTP.href, + credentials: 'same-origin', + }); + + const errorLink = onError(({ graphQLErrors, networkError }) => { + console.log(graphQLErrors); + console.log(networkError); + Sentry.captureException(graphQLErrors); + }); + + return new ApolloClient({ + connectToDevTools: process.env['NODE_ENV'] === 'development', + link: from([errorLink, retryLink, httpLink]), + cache, + }); +} diff --git a/apps/multisig-signer/src/app/lib/web3-connectors.ts b/apps/multisig-signer/src/app/lib/web3-connectors.ts new file mode 100644 index 000000000..d8490f351 --- /dev/null +++ b/apps/multisig-signer/src/app/lib/web3-connectors.ts @@ -0,0 +1,34 @@ +import { ethers } from 'ethers'; +import type { Web3ReactHooks } from '@web3-react/core'; +import { initializeConnector } from '@web3-react/core'; +import { MetaMask } from '@web3-react/metamask'; +import { WalletConnect } from '@web3-react/walletconnect'; +import type { Connector } from '@web3-react/types'; + +const [metamask, metamaskHooks] = initializeConnector( + (actions) => new MetaMask(actions) +); + +export const createDefaultProvider = (providerUrl: string, chainId: number) => { + return new ethers.providers.JsonRpcProvider(providerUrl, chainId); +}; + +export const createConnectors = (providerUrl: string, chainId: number) => { + if (isNaN(chainId)) { + throw new Error('Invalid Ethereum chain ID for environment'); + } + const [walletconnect, walletconnectHooks] = + initializeConnector( + (actions) => + new WalletConnect(actions, { + rpc: { + [chainId]: providerUrl, + }, + }), + [chainId] + ); + return [ + [metamask, metamaskHooks], + [walletconnect, walletconnectHooks], + ] as [Connector, Web3ReactHooks][]; +}; diff --git a/apps/multisig-signer/src/app/react-app-env.d.ts b/apps/multisig-signer/src/app/react-app-env.d.ts new file mode 100644 index 000000000..6431bc5fc --- /dev/null +++ b/apps/multisig-signer/src/app/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/apps/multisig-signer/src/app/setup-tests.ts b/apps/multisig-signer/src/app/setup-tests.ts new file mode 100644 index 000000000..8f2609b7b --- /dev/null +++ b/apps/multisig-signer/src/app/setup-tests.ts @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom'; diff --git a/apps/multisig-signer/src/assets/.gitkeep b/apps/multisig-signer/src/assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/multisig-signer/src/assets/env-config.js b/apps/multisig-signer/src/assets/env-config.js new file mode 100644 index 000000000..b8be63bb5 --- /dev/null +++ b/apps/multisig-signer/src/assets/env-config.js @@ -0,0 +1 @@ +window._env_ = {}; diff --git a/apps/multisig-signer/src/environments/environment.prod.ts b/apps/multisig-signer/src/environments/environment.prod.ts new file mode 100644 index 000000000..c9669790b --- /dev/null +++ b/apps/multisig-signer/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true, +}; diff --git a/apps/multisig-signer/src/environments/environment.ts b/apps/multisig-signer/src/environments/environment.ts new file mode 100644 index 000000000..7ed83767f --- /dev/null +++ b/apps/multisig-signer/src/environments/environment.ts @@ -0,0 +1,6 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// When building for production, this file is replaced with `environment.prod.ts`. + +export const environment = { + production: false, +}; diff --git a/apps/multisig-signer/src/favicon.ico b/apps/multisig-signer/src/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..03a276d66791f30b7932d26b21cef1c0be9e8d72 GIT binary patch literal 7406 zcmeHLSxi$|82+F;Qrgm5prso{+d?agY^6mCT(pSNb_4_m8JR)FeM$7eB_=MAXksF` zMIIr6XpH(|;`-o=G10^~-{wW5#(j^wbIuXUEg;IwJUE&AC%5PP=l{;RxBtC4=l?z+ z4kF~{g0ylBCILkNtkxl44*W^R)atK(hXm+M0h|OwreF!V=saeMuMPW+8|gPSHKC@a z2BoE?c=zrdy1Tpa;K2i2x^xMRjg2TNDZ%~w_hB#?uyNx?)YsRey1E+s_wUF0_3Lr` z_;FNKRiU@H7cXDF#P;pmq1WrNcI{foWHN-qVGIrqqN1V#&!0cX-o1OF)9J8!^=e2Y z64cezp}f2tpFe*l|1%dNkqG_${U|Fd!=Xcm(An9ExVShpH#g(VnKO9*{yls?A09q@ z2)Emf-Me?gWHKQsDG8S^U&g6Zr||aeTO2rW06TZ?L}Fqh+S}W4@!~}^G&JDk$&+~V z<_(-qClV48uwcOgoH%g;9LM3+t5--#Nx`~x>kx@VP+3`tW5HT#X8khL>kZ83 z$R6MFJ$zjed6b&P1U8>^BIM+n*d~j&(e<@-{i$^Q*%Pd8PV@VfYJ)~o)}BRR6_px| zCZ2Q_E98Fvoa!+%MLEBD78QD%tk!8Xw%AIH-pD$Ak;n6E@)-P#dh!77)G^~$0~yy< zjT!$YJ~TOe6J4JpHNq$xZ+(Yl;X;ecxx}d|AB6_S!xu;F5pg!MvS`Le*?4#fc@z(? z^x#h!4__?cqbU;d1N_&&2loAEM_*}7M=@`1Iv0xFA52_&dYdu^7IprZk&%&+OLv?R zt0hAY{fIC0gbe)D8K6AXO0EF8qC8bhoMGb~7L=DNl}cQ{em#bpx?C>CO)p%yz<6m< zQ4wChevP|#@5145AT>1=w{G2HT(zyOjd9h2f&#`-DId+x&u5%;>(;G|j~+R41W%tn zWn45lITl@8XX-SQEp1PDCMD>Hf_R<8#fpqUAAl) z*DtYaTv)7+qamAax7;em~=u9*>9d%gviNGk!UoR-qL#AY|b0W?)`0 znBbUTavsN3*(Qj`O%S)qrsz`LZsSZ}ongEsJl;UMCO*D0q%T&D7mt*2Tm=yTGdT5- z?8igdBGa48F_U}yWyn>S%J8nJ8FE>NAF zYSvW$rP}f7)2ErneCN&`oI7_8IXO8@FQ#X>R4T>NrAt|@Vzb%Uxld~qS_9GZ-EOzz z?Af!}w{IV-Kd9bJb#JPjo6Tlc_fTz}>dZTK>;SDbXq`j#XPRS`)S%~=?0rSJ6WH|R>_bv zjX*6CPq9Q;eqfoQE(eA%! Fe*s9s&QkyY literal 0 HcmV?d00001 diff --git a/apps/multisig-signer/src/index.html b/apps/multisig-signer/src/index.html new file mode 100644 index 000000000..fabce8ad5 --- /dev/null +++ b/apps/multisig-signer/src/index.html @@ -0,0 +1,26 @@ + + + + + Multisig Signer + + + + + + + + +
+ + diff --git a/apps/multisig-signer/src/main.tsx b/apps/multisig-signer/src/main.tsx new file mode 100644 index 000000000..0e75cc6da --- /dev/null +++ b/apps/multisig-signer/src/main.tsx @@ -0,0 +1,14 @@ +import { createRoot } from 'react-dom/client'; +import './styles.css'; + +import App from './app/app'; +import { StrictMode } from 'react'; + +const rootElement = document.getElementById('root'); +const root = rootElement && createRoot(rootElement); + +root?.render( + + + +); diff --git a/apps/multisig-signer/src/polyfills.ts b/apps/multisig-signer/src/polyfills.ts new file mode 100644 index 000000000..2adf3d05b --- /dev/null +++ b/apps/multisig-signer/src/polyfills.ts @@ -0,0 +1,7 @@ +/** + * Polyfill stable language features. These imports will be optimized by `@babel/preset-env`. + * + * See: https://github.com/zloirock/core-js#babel + */ +import 'core-js/stable'; +import 'regenerator-runtime/runtime'; diff --git a/apps/multisig-signer/src/styles.css b/apps/multisig-signer/src/styles.css new file mode 100644 index 000000000..3ba4ff4aa --- /dev/null +++ b/apps/multisig-signer/src/styles.css @@ -0,0 +1,16 @@ +/* You can add global styles to this file, and also import other style files */ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + h1 { + @apply text-2xl uppercase mb-4; + } + h2 { + @apply text-xl mb-4; + } + p { + @apply mb-2; + } +} diff --git a/apps/multisig-signer/tailwind.config.js b/apps/multisig-signer/tailwind.config.js new file mode 100644 index 000000000..c0da3e3cb --- /dev/null +++ b/apps/multisig-signer/tailwind.config.js @@ -0,0 +1,17 @@ +const { join } = require('path'); +const { createGlobPatternsForDependencies } = require('@nrwl/next/tailwind'); +const theme = require('../../libs/tailwindcss-config/src/theme'); +const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom-classes'); + +module.exports = { + content: [ + join(__dirname, 'src/**/*.{js,ts,jsx,tsx}'), + 'libs/ui-toolkit/src/utils/shared.ts', + ...createGlobPatternsForDependencies(__dirname), + ], + darkMode: 'class', + theme: { + extend: theme, + }, + plugins: [vegaCustomClasses], +}; diff --git a/apps/multisig-signer/tsconfig.app.json b/apps/multisig-signer/tsconfig.app.json new file mode 100644 index 000000000..b2447df1e --- /dev/null +++ b/apps/multisig-signer/tsconfig.app.json @@ -0,0 +1,23 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["node"] + }, + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": [ + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx", + "jest.config.ts" + ], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] +} diff --git a/apps/multisig-signer/tsconfig.json b/apps/multisig-signer/tsconfig.json new file mode 100644 index 000000000..e4e8e4ee8 --- /dev/null +++ b/apps/multisig-signer/tsconfig.json @@ -0,0 +1,26 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "lib": ["es5", "es6", "dom", "dom.iterable"] + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/apps/multisig-signer/tsconfig.spec.json b/apps/multisig-signer/tsconfig.spec.json new file mode 100644 index 000000000..703c42d11 --- /dev/null +++ b/apps/multisig-signer/tsconfig.spec.json @@ -0,0 +1,24 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node", "@testing-library/jest-dom"] + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.test.tsx", + "**/*.spec.tsx", + "**/*.test.js", + "**/*.spec.js", + "**/*.test.jsx", + "**/*.spec.jsx", + "**/*.d.ts", + "jest.config.ts" + ], + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ] +} diff --git a/apps/multisig-signer/webpack.config.js b/apps/multisig-signer/webpack.config.js new file mode 100644 index 000000000..d8ea1fd45 --- /dev/null +++ b/apps/multisig-signer/webpack.config.js @@ -0,0 +1,17 @@ +const SentryPlugin = require('@sentry/webpack-plugin'); + +module.exports = (config, context) => { + const additionalPlugins = process.env.SENTRY_AUTH_TOKEN + ? [ + new SentryPlugin({ + include: './dist/apps/multisig-signer', + project: 'multisig-signer', + }), + ] + : []; + + return { + ...config, + plugins: [...additionalPlugins, ...config.plugins], + }; +}; diff --git a/apps/stats/src/app.tsx b/apps/stats/src/app.tsx index 2712aae4c..553fc6e1b 100644 --- a/apps/stats/src/app.tsx +++ b/apps/stats/src/app.tsx @@ -11,10 +11,10 @@ function App() { return ( -
+
- +
diff --git a/apps/stats/src/components/header/header.tsx b/apps/stats/src/components/header/header.tsx index f35f1f0dd..8e3b850dc 100644 --- a/apps/stats/src/components/header/header.tsx +++ b/apps/stats/src/components/header/header.tsx @@ -1,5 +1,8 @@ -import { VegaLogo, ThemeSwitcher } from '@vegaprotocol/ui-toolkit'; -import { VegaBackgroundVideo } from '../videos'; +import { + BackgroundVideo, + VegaLogo, + ThemeSwitcher, +} from '@vegaprotocol/ui-toolkit'; interface ThemeToggleProps { theme: 'light' | 'dark'; @@ -8,11 +11,11 @@ interface ThemeToggleProps { export const Header = ({ theme, toggleTheme }: ThemeToggleProps) => { return ( -
- +
+ -
-
+
+
diff --git a/apps/stats/src/components/videos/index.ts b/apps/stats/src/components/videos/index.ts deleted file mode 100644 index d43c5dd49..000000000 --- a/apps/stats/src/components/videos/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { VegaBackgroundVideo } from './vega-background-video'; diff --git a/libs/smart-contracts/src/abis/multisig_abi.json b/libs/smart-contracts/src/abis/multisig_abi.json new file mode 100644 index 000000000..bea58e7ce --- /dev/null +++ b/libs/smart-contracts/src/abis/multisig_abi.json @@ -0,0 +1,276 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "NonceBurnt", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "new_signer", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "SignerAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "old_signer", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "SignerRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint16", + "name": "new_threshold", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "ThresholdSet", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "new_signer", + "type": "address" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + } + ], + "name": "add_signer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + } + ], + "name": "burn_nonce", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "get_current_threshold", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "get_valid_signer_count", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "is_nonce_used", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "signer_address", + "type": "address" + } + ], + "name": "is_valid_signer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "old_signer", + "type": "address" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + } + ], + "name": "remove_signer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "new_threshold", + "type": "uint16" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + } + ], + "name": "set_threshold", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "signers", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "message", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "verify_signatures", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/libs/smart-contracts/src/contracts/index.ts b/libs/smart-contracts/src/contracts/index.ts index 15ec967b1..2febe2756 100644 --- a/libs/smart-contracts/src/contracts/index.ts +++ b/libs/smart-contracts/src/contracts/index.ts @@ -6,3 +6,4 @@ export * from './staking-bridge'; export * from './token-vesting'; export * from './token'; export * from './token-faucetable'; +export * from './multisig-control'; diff --git a/libs/smart-contracts/src/contracts/multisig-control.ts b/libs/smart-contracts/src/contracts/multisig-control.ts new file mode 100644 index 000000000..6826378eb --- /dev/null +++ b/libs/smart-contracts/src/contracts/multisig-control.ts @@ -0,0 +1,55 @@ +import { ethers } from 'ethers'; +import abi from '../abis/multisig_abi.json'; + +export class MultisigControl { + public contract: ethers.Contract; + public address: string; + + constructor( + address: string, + signerOrProvider: ethers.Signer | ethers.providers.Provider + ) { + this.contract = new ethers.Contract(address, abi, signerOrProvider); + this.address = address; + } + + add_signer(newSigner: string, nonce: string, signatures: string) { + return this.contract.add_signer(newSigner, nonce, signatures); + } + + burn_nonce(nonce: string, signatures: string) { + return this.contract.burn_nonce(nonce, signatures); + } + + get_current_threshold() { + return this.contract.get_current_threshold(); + } + + get_valid_signer_count() { + return this.contract.get_valid_signer_count(); + } + + is_nonce_used(nonce: string) { + return this.contract.is_nonce_used(nonce); + } + + is_valid_signer(signerAddress: string) { + return this.contract.is_valid_signer(signerAddress); + } + + remove_signer(oldSigner: string, nonce: string, signatures: string) { + return this.contract.remove_signer(oldSigner, nonce, signatures); + } + + set_threshold(newThreshold: string, nonce: string, signatures: string) { + return this.contract.set_threshold(newThreshold, nonce, signatures); + } + + signers(address: string) { + return this.contract.signers(address); + } + + verify_signatures(nonce: string, message: string, signatures: string) { + return this.contract.verify_signatures(nonce, message, signatures); + } +} diff --git a/libs/ui-toolkit/src/components/background-video/background-video.spec.tsx b/libs/ui-toolkit/src/components/background-video/background-video.spec.tsx new file mode 100644 index 000000000..24f2fd082 --- /dev/null +++ b/libs/ui-toolkit/src/components/background-video/background-video.spec.tsx @@ -0,0 +1,9 @@ +import { render } from '@testing-library/react'; +import { BackgroundVideo } from './background-video'; + +describe('Background video', () => { + it('should render successfully', () => { + const { baseElement } = render(); + expect(baseElement).toBeTruthy(); + }); +}); diff --git a/libs/ui-toolkit/src/components/background-video/background-video.stories.tsx b/libs/ui-toolkit/src/components/background-video/background-video.stories.tsx new file mode 100644 index 000000000..afe7deab7 --- /dev/null +++ b/libs/ui-toolkit/src/components/background-video/background-video.stories.tsx @@ -0,0 +1,9 @@ +import type { Story, Meta } from '@storybook/react'; +import { BackgroundVideo } from './background-video'; + +export default { + component: BackgroundVideo, + title: 'BackgroundVideo', +} as Meta; + +export const Default: Story = () => ; diff --git a/apps/stats/src/components/videos/vega-background-video.tsx b/libs/ui-toolkit/src/components/background-video/background-video.tsx similarity index 89% rename from apps/stats/src/components/videos/vega-background-video.tsx rename to libs/ui-toolkit/src/components/background-video/background-video.tsx index 9b36df0be..0af183398 100644 --- a/apps/stats/src/components/videos/vega-background-video.tsx +++ b/libs/ui-toolkit/src/components/background-video/background-video.tsx @@ -1,4 +1,4 @@ -export const VegaBackgroundVideo = () => { +export const BackgroundVideo = () => { return (