* 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 <dexter.edwards93@gmail.com>
This commit is contained in:
parent
8fee3ca080
commit
4272fefb0b
@ -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)
|
||||
|
11
apps/multisig-signer/.babelrc
Normal file
11
apps/multisig-signer/.babelrc
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@nrwl/react/babel",
|
||||
{
|
||||
"runtime": "automatic"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
16
apps/multisig-signer/.browserslistrc
Normal file
16
apps/multisig-signer/.browserslistrc
Normal file
@ -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'.
|
4
apps/multisig-signer/.env
Normal file
4
apps/multisig-signer/.env
Normal file
@ -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
|
5
apps/multisig-signer/.env.devnet
Normal file
5
apps/multisig-signer/.env.devnet
Normal file
@ -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
|
5
apps/multisig-signer/.env.mainnet
Normal file
5
apps/multisig-signer/.env.mainnet
Normal file
@ -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
|
5
apps/multisig-signer/.env.stagnet3
Normal file
5
apps/multisig-signer/.env.stagnet3
Normal file
@ -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
|
5
apps/multisig-signer/.env.testnet
Normal file
5
apps/multisig-signer/.env.testnet
Normal file
@ -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
|
18
apps/multisig-signer/.eslintrc.json
Normal file
18
apps/multisig-signer/.eslintrc.json
Normal file
@ -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": {}
|
||||
}
|
||||
]
|
||||
}
|
46
apps/multisig-signer/README.md
Normal file
46
apps/multisig-signer/README.md
Normal file
@ -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
|
||||
```
|
5
apps/multisig-signer/index.d.ts
vendored
Normal file
5
apps/multisig-signer/index.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/// <reference types="react-scripts" />
|
||||
|
||||
interface Window {
|
||||
_env_?: Record<string, string>;
|
||||
}
|
12
apps/multisig-signer/jest.config.ts
Normal file
12
apps/multisig-signer/jest.config.ts
Normal file
@ -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'],
|
||||
};
|
4
apps/multisig-signer/netlify.toml
Normal file
4
apps/multisig-signer/netlify.toml
Normal file
@ -0,0 +1,4 @@
|
||||
[[redirects]]
|
||||
from = "/*"
|
||||
to = "/index.html"
|
||||
status = 200
|
10
apps/multisig-signer/postcss.config.js
Normal file
10
apps/multisig-signer/postcss.config.js
Normal file
@ -0,0 +1,10 @@
|
||||
const { join } = require('path');
|
||||
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {
|
||||
config: join(__dirname, 'tailwind.config.js'),
|
||||
},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
80
apps/multisig-signer/project.json
Normal file
80
apps/multisig-signer/project.json
Normal file
@ -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": []
|
||||
}
|
108
apps/multisig-signer/src/app/app.tsx
Normal file
108
apps/multisig-signer/src/app/app.tsx
Normal file
@ -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 (
|
||||
<main className="w-full max-w-3xl px-5 justify-self-center">
|
||||
<h1>{t('Multisig signer')}</h1>
|
||||
<div className="mb-8">
|
||||
<p>
|
||||
Connected to Eth wallet: <Lozenge>{account}</Lozenge>
|
||||
</p>
|
||||
<Button onClick={() => connector.deactivate()}>Disconnect</Button>
|
||||
</div>
|
||||
<ContractDetails config={config} />
|
||||
<h2>{t('Add or remove signer')}</h2>
|
||||
<AddSignerForm />
|
||||
<RemoveSignerForm />
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<ThemeContext.Provider value={theme}>
|
||||
<Web3Provider connectors={Connectors}>
|
||||
<Web3Connector dialogOpen={dialogOpen} setDialogOpen={setDialogOpen}>
|
||||
<div className={pageWrapperClasses}>
|
||||
<AsyncRenderer loading={loading} data={config} error={error}>
|
||||
<Header theme={theme} toggleTheme={toggleTheme} />
|
||||
<EthWalletContainer
|
||||
dialogOpen={dialogOpen}
|
||||
setDialogOpen={setDialogOpen}
|
||||
>
|
||||
<ConnectedApp config={config} />
|
||||
</EthWalletContainer>
|
||||
</AsyncRenderer>
|
||||
</div>
|
||||
</Web3Connector>
|
||||
</Web3Provider>
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return (
|
||||
<EnvironmentProvider>
|
||||
<NetworkLoader createClient={createClient}>
|
||||
<ContractsProvider>
|
||||
<App />
|
||||
</ContractsProvider>
|
||||
</NetworkLoader>
|
||||
</EnvironmentProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default Wrapper;
|
45
apps/multisig-signer/src/app/components/__generated__/AddSignerBundle.ts
generated
Normal file
45
apps/multisig-signer/src/app/components/__generated__/AddSignerBundle.ts
generated
Normal file
@ -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;
|
||||
}
|
48
apps/multisig-signer/src/app/components/__generated__/RemoveSignerBundle.ts
generated
Normal file
48
apps/multisig-signer/src/app/components/__generated__/RemoveSignerBundle.ts
generated
Normal file
@ -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;
|
||||
}
|
@ -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<HTMLFormElement>) => {
|
||||
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 (
|
||||
<form onSubmit={(e) => handleSubmit(e)}>
|
||||
<FormGroup
|
||||
label={t('Add signer')}
|
||||
labelFor="add-signer-input"
|
||||
labelDescription={t('Public key of the signer to add')}
|
||||
className="max-w-xl"
|
||||
>
|
||||
<div className="grid grid-cols-[1fr,auto] gap-2">
|
||||
<Input
|
||||
id="add-signer-input"
|
||||
onChange={(e) => setAddress(e.target.value)}
|
||||
data-testid="add-signer-input-input"
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
data-testid="add-signer-submit"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? <Loader size="small" /> : t('Add')}
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
{error && (
|
||||
<InputError intent="danger">
|
||||
{error?.message.includes('InvalidArgument')
|
||||
? t('Invalid node id')
|
||||
: error?.message}
|
||||
</InputError>
|
||||
)}
|
||||
{bundleNotFound && !error && (
|
||||
<InputError intent="danger">
|
||||
{t(
|
||||
'Bundle was not found, are you sure this validator needs to be added?'
|
||||
)}
|
||||
</InputError>
|
||||
)}
|
||||
</div>
|
||||
</FormGroup>
|
||||
<Dialog />
|
||||
</form>
|
||||
);
|
||||
};
|
@ -0,0 +1 @@
|
||||
export * from './add-signer-form';
|
@ -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 (
|
||||
<div className="mb-8">
|
||||
<p>
|
||||
Multisig contract address:{' '}
|
||||
<Lozenge>{config?.multisig_control_contract?.address}</Lozenge>
|
||||
</p>
|
||||
<p>Valid signer count: {validSignerCount}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1 @@
|
||||
export * from './contract-details';
|
@ -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 (
|
||||
<div className="w-full text-center">
|
||||
<Button onClick={() => setDialogOpen(true)}>
|
||||
Connect Ethereum Wallet
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return children;
|
||||
};
|
@ -0,0 +1 @@
|
||||
export * from './eth-wallet-container';
|
24
apps/multisig-signer/src/app/components/header/header.tsx
Normal file
24
apps/multisig-signer/src/app/components/header/header.tsx
Normal file
@ -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 (
|
||||
<header className="relative overflow-hidden py-2 mb-8">
|
||||
<BackgroundVideo />
|
||||
<div className="relative flex justify-center px-2 dark:bg-black bg-white">
|
||||
<div className="w-full max-w-3xl p-5 flex items-center justify-between">
|
||||
<VegaLogo />
|
||||
<ThemeSwitcher theme={theme} onToggle={toggleTheme} />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
1
apps/multisig-signer/src/app/components/header/index.tsx
Normal file
1
apps/multisig-signer/src/app/components/header/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export * from './header';
|
4
apps/multisig-signer/src/app/components/index.tsx
Normal file
4
apps/multisig-signer/src/app/components/index.tsx
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './add-signer-form';
|
||||
export * from './remove-signer-form';
|
||||
export * from './header';
|
||||
export * from './contract-details';
|
@ -0,0 +1 @@
|
||||
export * from './remove-signer-form';
|
@ -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<HTMLFormElement>) => {
|
||||
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 (
|
||||
<form onSubmit={(e) => handleSubmit(e)}>
|
||||
<FormGroup
|
||||
label={t('Remove signer')}
|
||||
labelFor="remove-signer-input"
|
||||
labelDescription={t('Public key of the signer to remove')}
|
||||
className="max-w-xl"
|
||||
>
|
||||
<div className="grid grid-cols-[1fr,auto] gap-2">
|
||||
<Input
|
||||
id="remove-signer-input"
|
||||
onChange={(e) => setAddress(e.target.value)}
|
||||
data-testid="remove-signer-input-input"
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
data-testid="remove-signer-submit"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? <Loader size="small" /> : t('Remove')}
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
{error && (
|
||||
<InputError intent="danger">
|
||||
{error?.message.includes('InvalidArgument')
|
||||
? t('Invalid node id')
|
||||
: error?.message}
|
||||
</InputError>
|
||||
)}
|
||||
{bundleNotFound && !error && (
|
||||
<InputError intent="danger">
|
||||
{t(
|
||||
'Bundle was not found, are you sure this validator needs to be removed?'
|
||||
)}
|
||||
</InputError>
|
||||
)}
|
||||
</div>
|
||||
</FormGroup>
|
||||
<Dialog />
|
||||
</form>
|
||||
);
|
||||
};
|
@ -0,0 +1 @@
|
||||
export * from './web3-connector';
|
@ -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 (
|
||||
<AsyncRenderer loading={loading} error={error} data={config}>
|
||||
<Web3Content appChainId={appChainId} setDialogOpen={setDialogOpen}>
|
||||
{children}
|
||||
</Web3Content>
|
||||
{Connectors && (
|
||||
<Web3ConnectDialog
|
||||
connectors={Connectors}
|
||||
dialogOpen={dialogOpen}
|
||||
setDialogOpen={setDialogOpen}
|
||||
desiredChainId={appChainId}
|
||||
/>
|
||||
)}
|
||||
</AsyncRenderer>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<Splash>
|
||||
<div className="flex flex-col items-center gap-12">
|
||||
<p className="text-white">Something went wrong: {error.message}</p>
|
||||
<Button onClick={() => connector.deactivate()}>Disconnect</Button>
|
||||
</div>
|
||||
</Splash>
|
||||
);
|
||||
}
|
||||
|
||||
if (chainId !== undefined && chainId !== appChainId) {
|
||||
return (
|
||||
<Splash>
|
||||
<div className="flex flex-col items-center gap-12">
|
||||
<p className="text-white">
|
||||
This app only works on chain ID: {appChainId}
|
||||
</p>
|
||||
<Button onClick={() => connector.deactivate()}>Disconnect</Button>
|
||||
</div>
|
||||
</Splash>
|
||||
);
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
@ -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;
|
||||
}
|
@ -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<ContractsContextShape | null>(
|
||||
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 <Splash>Error: cannot get data on multisig contract</Splash>;
|
||||
}
|
||||
|
||||
return (
|
||||
<ContractsContext.Provider value={contracts}>
|
||||
{children}
|
||||
</ContractsContext.Provider>
|
||||
);
|
||||
};
|
12
apps/multisig-signer/src/app/config/env.ts
Normal file
12
apps/multisig-signer/src/app/config/env.ts
Normal file
@ -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: {},
|
||||
};
|
3
apps/multisig-signer/src/app/config/flags.ts
Normal file
3
apps/multisig-signer/src/app/config/flags.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { ENV } from './env';
|
||||
|
||||
export default ENV.flags;
|
3
apps/multisig-signer/src/app/config/index.tsx
Normal file
3
apps/multisig-signer/src/app/config/index.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
import { ENV } from './env';
|
||||
|
||||
export const DATA_SOURCES = ENV.dataSources;
|
54
apps/multisig-signer/src/app/lib/apollo-client.tsx
Normal file
54
apps/multisig-signer/src/app/lib/apollo-client.tsx
Normal file
@ -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,
|
||||
});
|
||||
}
|
34
apps/multisig-signer/src/app/lib/web3-connectors.ts
Normal file
34
apps/multisig-signer/src/app/lib/web3-connectors.ts
Normal file
@ -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<MetaMask>(
|
||||
(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<WalletConnect>(
|
||||
(actions) =>
|
||||
new WalletConnect(actions, {
|
||||
rpc: {
|
||||
[chainId]: providerUrl,
|
||||
},
|
||||
}),
|
||||
[chainId]
|
||||
);
|
||||
return [
|
||||
[metamask, metamaskHooks],
|
||||
[walletconnect, walletconnectHooks],
|
||||
] as [Connector, Web3ReactHooks][];
|
||||
};
|
1
apps/multisig-signer/src/app/react-app-env.d.ts
vendored
Normal file
1
apps/multisig-signer/src/app/react-app-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
5
apps/multisig-signer/src/app/setup-tests.ts
Normal file
5
apps/multisig-signer/src/app/setup-tests.ts
Normal file
@ -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';
|
0
apps/multisig-signer/src/assets/.gitkeep
Normal file
0
apps/multisig-signer/src/assets/.gitkeep
Normal file
1
apps/multisig-signer/src/assets/env-config.js
Normal file
1
apps/multisig-signer/src/assets/env-config.js
Normal file
@ -0,0 +1 @@
|
||||
window._env_ = {};
|
@ -0,0 +1,3 @@
|
||||
export const environment = {
|
||||
production: true,
|
||||
};
|
6
apps/multisig-signer/src/environments/environment.ts
Normal file
6
apps/multisig-signer/src/environments/environment.ts
Normal file
@ -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,
|
||||
};
|
BIN
apps/multisig-signer/src/favicon.ico
Normal file
BIN
apps/multisig-signer/src/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.2 KiB |
26
apps/multisig-signer/src/index.html
Normal file
26
apps/multisig-signer/src/index.html
Normal file
@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Multisig Signer</title>
|
||||
<base href="/" />
|
||||
<link
|
||||
rel="preload"
|
||||
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
href="https://static.vega.xyz/favicon.ico"
|
||||
/>
|
||||
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
|
||||
<script src="./assets/env-config.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
14
apps/multisig-signer/src/main.tsx
Normal file
14
apps/multisig-signer/src/main.tsx
Normal file
@ -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(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>
|
||||
);
|
7
apps/multisig-signer/src/polyfills.ts
Normal file
7
apps/multisig-signer/src/polyfills.ts
Normal file
@ -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';
|
16
apps/multisig-signer/src/styles.css
Normal file
16
apps/multisig-signer/src/styles.css
Normal file
@ -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;
|
||||
}
|
||||
}
|
17
apps/multisig-signer/tailwind.config.js
Normal file
17
apps/multisig-signer/tailwind.config.js
Normal file
@ -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],
|
||||
};
|
23
apps/multisig-signer/tsconfig.app.json
Normal file
23
apps/multisig-signer/tsconfig.app.json
Normal file
@ -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"]
|
||||
}
|
26
apps/multisig-signer/tsconfig.json
Normal file
26
apps/multisig-signer/tsconfig.json
Normal file
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
24
apps/multisig-signer/tsconfig.spec.json
Normal file
24
apps/multisig-signer/tsconfig.spec.json
Normal file
@ -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"
|
||||
]
|
||||
}
|
17
apps/multisig-signer/webpack.config.js
Normal file
17
apps/multisig-signer/webpack.config.js
Normal file
@ -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],
|
||||
};
|
||||
};
|
@ -11,10 +11,10 @@ function App() {
|
||||
return (
|
||||
<ThemeContext.Provider value={theme}>
|
||||
<NetworkLoader createClient={createClient}>
|
||||
<div className="w-screen min-h-screen grid pb-24 bg-white text-neutral-900 dark:bg-black dark:text-neutral-100">
|
||||
<div className="w-screen min-h-screen grid pb-6 bg-white text-neutral-900 dark:bg-black dark:text-neutral-100">
|
||||
<div className="layout-grid w-screen justify-self-center">
|
||||
<Header theme={theme} toggleTheme={toggleTheme} />
|
||||
<StatsManager className="max-w-3xl px-24" />
|
||||
<StatsManager className="max-w-3xl px-6" />
|
||||
</div>
|
||||
</div>
|
||||
</NetworkLoader>
|
||||
|
@ -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 (
|
||||
<header className="relative overflow-hidden py-8 mb-40 md:mb-64">
|
||||
<VegaBackgroundVideo />
|
||||
<header className="relative overflow-hidden py-2 mb-10 md:mb-16">
|
||||
<BackgroundVideo />
|
||||
|
||||
<div className="relative flex justify-center px-8 dark:bg-black bg-white">
|
||||
<div className="w-full max-w-3xl p-20 flex items-center justify-between">
|
||||
<div className="relative flex justify-center px-2 dark:bg-black bg-white">
|
||||
<div className="w-full max-w-3xl p-5 flex items-center justify-between">
|
||||
<VegaLogo />
|
||||
<ThemeSwitcher theme={theme} onToggle={toggleTheme} />
|
||||
</div>
|
||||
|
@ -1 +0,0 @@
|
||||
export { VegaBackgroundVideo } from './vega-background-video';
|
276
libs/smart-contracts/src/abis/multisig_abi.json
Normal file
276
libs/smart-contracts/src/abis/multisig_abi.json
Normal file
@ -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"
|
||||
}
|
||||
]
|
@ -6,3 +6,4 @@ export * from './staking-bridge';
|
||||
export * from './token-vesting';
|
||||
export * from './token';
|
||||
export * from './token-faucetable';
|
||||
export * from './multisig-control';
|
||||
|
55
libs/smart-contracts/src/contracts/multisig-control.ts
Normal file
55
libs/smart-contracts/src/contracts/multisig-control.ts
Normal file
@ -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);
|
||||
}
|
||||
}
|
@ -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(<BackgroundVideo />);
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
});
|
@ -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 = () => <BackgroundVideo />;
|
@ -1,4 +1,4 @@
|
||||
export const VegaBackgroundVideo = () => {
|
||||
export const BackgroundVideo = () => {
|
||||
return (
|
||||
<video
|
||||
autoPlay
|
1
libs/ui-toolkit/src/components/background-video/index.ts
Normal file
1
libs/ui-toolkit/src/components/background-video/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './background-video';
|
@ -2,6 +2,7 @@ export * from './accordion';
|
||||
export * from './ag-grid';
|
||||
export * from './arrows';
|
||||
export * from './async-renderer';
|
||||
export * from './background-video';
|
||||
export * from './button';
|
||||
export * from './callout';
|
||||
export * from './checkbox';
|
||||
|
@ -20,6 +20,7 @@
|
||||
"market-depth": "libs/market-depth",
|
||||
"market-info": "libs/market-info",
|
||||
"market-list": "libs/market-list",
|
||||
"multisig-signer": "apps/multisig-signer",
|
||||
"network-info": "libs/network-info",
|
||||
"network-stats": "libs/network-stats",
|
||||
"orders": "libs/orders",
|
||||
|
Loading…
Reference in New Issue
Block a user