Compare commits

..

17 Commits
main ... main

Author SHA1 Message Date
eba6cd5d68 Merge pull request 'Skin onboarding app' (#33) from style/vaidator into main
Reviewed-on: cerc-io/testnet-onboarding-app#33
2024-08-11 21:05:04 +00:00
zramsay
5baccf3a84 bump version 2024-08-11 17:02:20 -04:00
b39afe386f style: validator success page 2024-08-11 12:34:35 -04:00
565d1887e0 style: add codeblock and style validator page 2024-08-11 12:32:55 -04:00
zramsay
e6fa6aabd7 copy: fix role names 2024-08-11 15:53:43 +05:30
3fe0576f34 style: add back t&c text and style pre block 2024-08-11 15:30:40 +05:30
Monkey
31e3b75dfa remove width setting for terms and conditions 2024-08-11 15:29:59 +05:30
bee7379e86 style: sign with cosmos 2024-08-11 15:29:58 +05:30
bff5ab9f31 style: sign with nitro 2024-08-11 15:28:14 +05:30
051de43480 chore: add dm mono 2024-08-11 15:28:14 +05:30
ad614aff2f style: remove disclaimer and center button 2024-08-11 15:28:14 +05:30
c038085b87 style: first pass with dark mui theme 2024-08-11 15:28:09 +05:30
09104b50bf Add download button for terms and conditions PDF (#31)
Part of [laconicd testnet validator enrollment](https://www.notion.so/laconicd-testnet-validator-enrollment-6fc1d3cafcc64fef8c5ed3affa27c675)

- Replace Cosmos address with Laconic address

Co-authored-by: Shreerang Kale <shreerangkale@gmail.com>
Reviewed-on: cerc-io/testnet-onboarding-app#31
2024-08-11 09:47:18 +00:00
663eb42a74 Wait for session to load before redirecting (#29)
Part of [laconicd testnet validator enrollment](https://www.notion.so/laconicd-testnet-validator-enrollment-6fc1d3cafcc64fef8c5ed3affa27c675)

Co-authored-by: Shreerang Kale <shreerangkale@gmail.com>
Reviewed-on: cerc-io/testnet-onboarding-app#29
2024-08-09 13:00:30 +00:00
8ba837b2f4 Add functionality to create a validator (#28)
Part of [laconicd testnet validator enrollment](https://www.notion.so/laconicd-testnet-validator-enrollment-6fc1d3cafcc64fef8c5ed3affa27c675)

Co-authored-by: Adw8 <adwaitgharpure@gmail.com>
Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Co-authored-by: Shreerang Kale <shreerangkale@gmail.com>
Reviewed-on: cerc-io/testnet-onboarding-app#28
2024-08-09 10:18:13 +00:00
fc1c8df06b Persist subscriber ID in local storage (#26)
Part of [laconicd testnet validator enrollment](https://www.notion.so/laconicd-testnet-validator-enrollment-6fc1d3cafcc64fef8c5ed3affa27c675)
- Disable nitro key sign button if subscribe ID does not exist

Co-authored-by: Shreerang Kale <shreerangkale@gmail.com>
Reviewed-on: cerc-io/testnet-onboarding-app#26
2024-08-09 06:24:29 +00:00
aa9aed89f2 Modify app UI content (#25)
Part of [laconicd testnet validator enrollment](https://www.notion.so/laconicd-testnet-validator-enrollment-6fc1d3cafcc64fef8c5ed3affa27c675) and  issues:
- cerc-io/testnet-onboarding-app#19
- cerc-io/testnet-onboarding-app#20
- cerc-io/testnet-onboarding-app#23

Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Reviewed-on: cerc-io/testnet-onboarding-app#25
2024-08-09 06:02:08 +00:00
42 changed files with 2854 additions and 2547 deletions

View File

@ -3,7 +3,8 @@ REACT_APP_ETHEREUM_MAINNET_CHAIN_ID=1
REACT_APP_LACONICD_CHAIN_ID=laconic_9000-1
REACT_APP_REGISTRY_GQL_ENDPOINT=http://localhost:9473/api
REACT_APP_LACONICD_RPC_ENDPOINT=http://localhost:26657
REACT_APP_LACONICD_DENOM=alnt
REACT_APP_FAUCET_ENDPOINT=http://localhost:4000
REACT_APP_WALLET_META_URL=http://localhost:3000
REACT_APP_SUMSUB_API_ENDPOINT=
REACT_APP_STAKING_AMOUNT=1000000000000000
REACT_APP_LACONICD_DENOM=alnt

View File

@ -12,6 +12,7 @@
}
},
"rules": {
"indent": ["error", 2, { "SwitchCase": 1 }]
"indent": ["error", 2, { "SwitchCase": 1 }],
"semi": ["error", "always"]
}
}

View File

@ -1,42 +1,40 @@
# testnet-onboarding-app
React app for onboarding participants to laconicd chain with Nitro/Cosmos key attestation
## Setup for testnet-onboarding-app
1. Clone the repository
```zsh
```
git clone git@git.vdb.to:cerc-io/testnet-onboarding-app.git
```
2. Enter the project directory
```zsh
```
cd testnet-onboarding-app
```
3. Install dependencies
```zsh
```
yarn
```
4. Setup .env
- Copy and update `.env`
```zsh
```
cp .env.example .env
```
- In the `.env` file, add the WalletConnect project ID used in your [laconic-wallet](https://git.vdb.to/cerc-io/laconic-wallet) setup.
```zsh
```
WALLET_CONNECT_PROJECT_ID=39bc93c...
```
5. Start the application
```zsh
```
yarn start
```

View File

@ -1,56 +1,56 @@
const fs = require("fs");
const path = require("path");
const webpack = require("webpack");
const resolve = require("resolve");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin");
const InlineChunkHtmlPlugin = require("react-dev-utils/InlineChunkHtmlPlugin");
const TerserPlugin = require("terser-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
const InterpolateHtmlPlugin = require("react-dev-utils/InterpolateHtmlPlugin");
const WorkboxWebpackPlugin = require("workbox-webpack-plugin");
const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin");
const getCSSModuleLocalIdent = require("react-dev-utils/getCSSModuleLocalIdent");
const ESLintPlugin = require("eslint-webpack-plugin");
const paths = require("./paths");
const modules = require("./modules");
const getClientEnvironment = require("./env");
const ModuleNotFoundPlugin = require("react-dev-utils/ModuleNotFoundPlugin");
const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
const resolve = require('resolve');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
const ESLintPlugin = require('eslint-webpack-plugin');
const paths = require('./paths');
const modules = require('./modules');
const getClientEnvironment = require('./env');
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
const ForkTsCheckerWebpackPlugin =
process.env.TSC_COMPILE_ON_ERROR === "true"
? require("react-dev-utils/ForkTsCheckerWarningWebpackPlugin")
: require("react-dev-utils/ForkTsCheckerWebpackPlugin");
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
process.env.TSC_COMPILE_ON_ERROR === 'true'
? require('react-dev-utils/ForkTsCheckerWarningWebpackPlugin')
: require('react-dev-utils/ForkTsCheckerWebpackPlugin');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const createEnvironmentHash = require("./webpack/persistentCache/createEnvironmentHash");
const createEnvironmentHash = require('./webpack/persistentCache/createEnvironmentHash');
// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== "false";
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
const reactRefreshRuntimeEntry = require.resolve("react-refresh/runtime");
const reactRefreshRuntimeEntry = require.resolve('react-refresh/runtime');
const reactRefreshWebpackPluginRuntimeEntry = require.resolve(
"@pmmmwh/react-refresh-webpack-plugin"
'@pmmmwh/react-refresh-webpack-plugin'
);
const babelRuntimeEntry = require.resolve("babel-preset-react-app");
const babelRuntimeEntry = require.resolve('babel-preset-react-app');
const babelRuntimeEntryHelpers = require.resolve(
"@babel/runtime/helpers/esm/assertThisInitialized",
'@babel/runtime/helpers/esm/assertThisInitialized',
{ paths: [babelRuntimeEntry] }
);
const babelRuntimeRegenerator = require.resolve("@babel/runtime/regenerator", {
const babelRuntimeRegenerator = require.resolve('@babel/runtime/regenerator', {
paths: [babelRuntimeEntry],
});
// Some apps do not need the benefits of saving a web request, so not inlining the chunk
// makes for a smoother build process.
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== "false";
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
const emitErrorsAsWarnings = process.env.ESLINT_NO_DEV_ERRORS === "true";
const disableESLintPlugin = process.env.DISABLE_ESLINT_PLUGIN === "true";
const emitErrorsAsWarnings = process.env.ESLINT_NO_DEV_ERRORS === 'true';
const disableESLintPlugin = process.env.DISABLE_ESLINT_PLUGIN === 'true';
const imageInlineSizeLimit = parseInt(
process.env.IMAGE_INLINE_SIZE_LIMIT || "10000"
process.env.IMAGE_INLINE_SIZE_LIMIT || '10000'
);
// Check if TypeScript is setup
@ -58,7 +58,7 @@ const useTypeScript = fs.existsSync(paths.appTsConfig);
// Check if Tailwind config exists
const useTailwind = fs.existsSync(
path.join(paths.appPath, "tailwind.config.ts")
path.join(paths.appPath, 'tailwind.config.js')
);
// Get the path to the uncompiled service worker (if it exists).
@ -71,12 +71,12 @@ const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
const hasJsxRuntime = (() => {
if (process.env.DISABLE_NEW_JSX_TRANSFORM === "true") {
if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') {
return false;
}
try {
require.resolve("react/jsx-runtime");
require.resolve('react/jsx-runtime');
return true;
} catch (e) {
return false;
@ -86,13 +86,13 @@ const hasJsxRuntime = (() => {
// This is the production and development configuration.
// It is focused on developer experience, fast rebuilds, and a minimal bundle.
module.exports = function (webpackEnv) {
const isEnvDevelopment = webpackEnv === "development";
const isEnvProduction = webpackEnv === "production";
const isEnvDevelopment = webpackEnv === 'development';
const isEnvProduction = webpackEnv === 'production';
// Variable used for enabling profiling in Production
// passed into alias object. Uses a flag if passed into the build command
const isEnvProductionProfile =
isEnvProduction && process.argv.includes("--profile");
isEnvProduction && process.argv.includes('--profile');
// We will provide `paths.publicUrlOrPath` to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
@ -105,38 +105,38 @@ module.exports = function (webpackEnv) {
// common function to get style loaders
const getStyleLoaders = (cssOptions, preProcessor) => {
const loaders = [
isEnvDevelopment && require.resolve("style-loader"),
isEnvDevelopment && require.resolve('style-loader'),
isEnvProduction && {
loader: MiniCssExtractPlugin.loader,
// css is located in `static/css`, use '../../' to locate index.html folder
// in production `paths.publicUrlOrPath` can be a relative path
options: paths.publicUrlOrPath.startsWith(".")
? { publicPath: "../../" }
options: paths.publicUrlOrPath.startsWith('.')
? { publicPath: '../../' }
: {},
},
{
loader: require.resolve("css-loader"),
loader: require.resolve('css-loader'),
options: cssOptions,
},
{
// Options for PostCSS as we reference these options twice
// Adds vendor prefixing based on your specified browser support in
// package.json
loader: require.resolve("postcss-loader"),
loader: require.resolve('postcss-loader'),
options: {
postcssOptions: {
// Necessary for external CSS imports to work
// https://github.com/facebook/create-react-app/issues/2677
ident: "postcss",
ident: 'postcss',
config: false,
plugins: !useTailwind
? [
"postcss-flexbugs-fixes",
'postcss-flexbugs-fixes',
[
"postcss-preset-env",
'postcss-preset-env',
{
autoprefixer: {
flexbox: "no-2009",
flexbox: 'no-2009',
},
stage: 3,
},
@ -144,16 +144,16 @@ module.exports = function (webpackEnv) {
// Adds PostCSS Normalize as the reset css with default options,
// so that it honors browserslist config in package.json
// which in turn let's users customize the target behavior as per their needs.
"postcss-normalize",
'postcss-normalize',
]
: [
"tailwindcss",
"postcss-flexbugs-fixes",
'tailwindcss',
'postcss-flexbugs-fixes',
[
"postcss-preset-env",
'postcss-preset-env',
{
autoprefixer: {
flexbox: "no-2009",
flexbox: 'no-2009',
},
stage: 3,
},
@ -167,7 +167,7 @@ module.exports = function (webpackEnv) {
if (preProcessor) {
loaders.push(
{
loader: require.resolve("resolve-url-loader"),
loader: require.resolve('resolve-url-loader'),
options: {
sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
root: paths.appSrc,
@ -185,17 +185,17 @@ module.exports = function (webpackEnv) {
};
return {
target: ["browserslist"],
target: ['browserslist'],
// Webpack noise constrained to errors and warnings
stats: "errors-warnings",
mode: isEnvProduction ? "production" : isEnvDevelopment && "development",
stats: 'errors-warnings',
mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
// Stop compilation early in production
bail: isEnvProduction,
devtool: isEnvProduction
? shouldUseSourceMap
? "source-map"
? 'source-map'
: false
: isEnvDevelopment && "cheap-module-source-map",
: isEnvDevelopment && 'cheap-module-source-map',
// These are the "entry points" to our application.
// This means they will be the "root" imports that are included in JS bundle.
entry: paths.appIndexJs,
@ -207,42 +207,41 @@ module.exports = function (webpackEnv) {
// There will be one main bundle, and one file per asynchronous chunk.
// In development, it does not produce real files.
filename: isEnvProduction
? "static/js/[name].[contenthash:8].js"
: isEnvDevelopment && "static/js/bundle.js",
? 'static/js/[name].[contenthash:8].js'
: isEnvDevelopment && 'static/js/bundle.js',
// There are also additional JS chunk files if you use code splitting.
chunkFilename: isEnvProduction
? "static/js/[name].[contenthash:8].chunk.js"
: isEnvDevelopment && "static/js/[name].chunk.js",
assetModuleFilename: "static/media/[name].[hash][ext]",
? 'static/js/[name].[contenthash:8].chunk.js'
: isEnvDevelopment && 'static/js/[name].chunk.js',
assetModuleFilename: 'static/media/[name].[hash][ext]',
// webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
// We inferred the "public path" (such as / or /my-project) from homepage.
publicPath: paths.publicUrlOrPath,
// Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: isEnvProduction
? (info) =>
? info =>
path
.relative(paths.appSrc, info.absoluteResourcePath)
.replace(/\\/g, "/")
.replace(/\\/g, '/')
: isEnvDevelopment &&
((info) =>
path.resolve(info.absoluteResourcePath).replace(/\\/g, "/")),
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
},
cache: {
type: "filesystem",
type: 'filesystem',
version: createEnvironmentHash(env.raw),
cacheDirectory: paths.appWebpackCache,
store: "pack",
store: 'pack',
buildDependencies: {
defaultWebpack: ["webpack/lib/"],
defaultWebpack: ['webpack/lib/'],
config: [__filename],
tsconfig: [paths.appTsConfig, paths.appJsConfig].filter((f) =>
tsconfig: [paths.appTsConfig, paths.appJsConfig].filter(f =>
fs.existsSync(f)
),
},
},
infrastructureLogging: {
level: "none",
level: 'none',
},
optimization: {
minimize: isEnvProduction,
@ -313,16 +312,16 @@ module.exports = function (webpackEnv) {
// `web` extension prefixes have been added for better support
// for React Native Web.
extensions: paths.moduleFileExtensions
.map((ext) => `.${ext}`)
.filter((ext) => useTypeScript || !ext.includes("ts")),
.map(ext => `.${ext}`)
.filter(ext => useTypeScript || !ext.includes('ts')),
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
"react-native": "react-native-web",
'react-native': 'react-native-web',
// Allows for better profiling with ReactDevTools
...(isEnvProductionProfile && {
"react-dom$": "react-dom/profiling",
"scheduler/tracing": "scheduler/tracing-profiling",
'react-dom$': 'react-dom/profiling',
'scheduler/tracing': 'scheduler/tracing-profiling',
}),
...(modules.webpackAliases || {}),
},
@ -347,10 +346,10 @@ module.exports = function (webpackEnv) {
rules: [
// Handle node_modules packages that contain sourcemaps
shouldUseSourceMap && {
enforce: "pre",
enforce: 'pre',
exclude: /@babel(?:\/|\\{1,2})runtime/,
test: /\.(js|mjs|jsx|ts|tsx|css)$/,
loader: require.resolve("source-map-loader"),
loader: require.resolve('source-map-loader'),
},
{
// "oneOf" will traverse all following loaders until one will
@ -361,8 +360,8 @@ module.exports = function (webpackEnv) {
// https://github.com/jshttp/mime-db
{
test: [/\.avif$/],
type: "asset",
mimetype: "image/avif",
type: 'asset',
mimetype: 'image/avif',
parser: {
dataUrlCondition: {
maxSize: imageInlineSizeLimit,
@ -374,7 +373,7 @@ module.exports = function (webpackEnv) {
// A missing `test` is equivalent to a match.
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
type: "asset",
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: imageInlineSizeLimit,
@ -385,7 +384,7 @@ module.exports = function (webpackEnv) {
test: /\.svg$/,
use: [
{
loader: require.resolve("@svgr/webpack"),
loader: require.resolve('@svgr/webpack'),
options: {
prettier: false,
svgo: false,
@ -397,9 +396,9 @@ module.exports = function (webpackEnv) {
},
},
{
loader: require.resolve("file-loader"),
loader: require.resolve('file-loader'),
options: {
name: "static/media/[name].[hash].[ext]",
name: 'static/media/[name].[hash].[ext]',
},
},
],
@ -412,16 +411,16 @@ module.exports = function (webpackEnv) {
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
loader: require.resolve("babel-loader"),
loader: require.resolve('babel-loader'),
options: {
customize: require.resolve(
"babel-preset-react-app/webpack-overrides"
'babel-preset-react-app/webpack-overrides'
),
presets: [
[
require.resolve("babel-preset-react-app"),
require.resolve('babel-preset-react-app'),
{
runtime: hasJsxRuntime ? "automatic" : "classic",
runtime: hasJsxRuntime ? 'automatic' : 'classic',
},
],
],
@ -429,7 +428,7 @@ module.exports = function (webpackEnv) {
plugins: [
isEnvDevelopment &&
shouldUseReactRefresh &&
require.resolve("react-refresh/babel"),
require.resolve('react-refresh/babel'),
].filter(Boolean),
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
@ -445,14 +444,14 @@ module.exports = function (webpackEnv) {
{
test: /\.(js|mjs)$/,
exclude: /@babel(?:\/|\\{1,2})runtime/,
loader: require.resolve("babel-loader"),
loader: require.resolve('babel-loader'),
options: {
babelrc: false,
configFile: false,
compact: false,
presets: [
[
require.resolve("babel-preset-react-app/dependencies"),
require.resolve('babel-preset-react-app/dependencies'),
{ helpers: true },
],
],
@ -483,7 +482,7 @@ module.exports = function (webpackEnv) {
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: "icss",
mode: 'icss',
},
}),
// Don't consider CSS imports dead code even if the
@ -502,7 +501,7 @@ module.exports = function (webpackEnv) {
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: "local",
mode: 'local',
getLocalIdent: getCSSModuleLocalIdent,
},
}),
@ -520,10 +519,10 @@ module.exports = function (webpackEnv) {
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: "icss",
mode: 'icss',
},
},
"sass-loader"
'sass-loader'
),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
@ -542,11 +541,11 @@ module.exports = function (webpackEnv) {
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: "local",
mode: 'local',
getLocalIdent: getCSSModuleLocalIdent,
},
},
"sass-loader"
'sass-loader'
),
},
// "file" loader makes sure those assets get served by WebpackDevServer.
@ -560,7 +559,7 @@ module.exports = function (webpackEnv) {
// Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders.
exclude: [/^$/, /\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
type: "asset/resource",
type: 'asset/resource',
},
// ** STOP ** Are you adding a new loader?
// Make sure to add the new loader(s) before the "file" loader.
@ -634,8 +633,8 @@ module.exports = function (webpackEnv) {
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: "static/css/[name].[contenthash:8].css",
chunkFilename: "static/css/[name].[contenthash:8].chunk.css",
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
}),
// Generate an asset manifest file with the following content:
// - "files" key: Mapping of all asset filenames to their corresponding
@ -644,7 +643,7 @@ module.exports = function (webpackEnv) {
// - "entrypoints" key: Array of files which are included in `index.html`,
// can be used to reconstruct the HTML if necessary
new WebpackManifestPlugin({
fileName: "asset-manifest.json",
fileName: 'asset-manifest.json',
publicPath: paths.publicUrlOrPath,
generate: (seed, files, entrypoints) => {
const manifestFiles = files.reduce((manifest, file) => {
@ -652,7 +651,7 @@ module.exports = function (webpackEnv) {
return manifest;
}, seed);
const entrypointFiles = entrypoints.main.filter(
(fileName) => !fileName.endsWith(".map")
fileName => !fileName.endsWith('.map')
);
return {
@ -688,7 +687,7 @@ module.exports = function (webpackEnv) {
new ForkTsCheckerWebpackPlugin({
async: isEnvDevelopment,
typescript: {
typescriptPath: resolve.sync("typescript", {
typescriptPath: resolve.sync('typescript', {
basedir: paths.appNodeModules,
}),
configOverwrite: {
@ -708,7 +707,7 @@ module.exports = function (webpackEnv) {
diagnosticOptions: {
syntactic: true,
},
mode: "write-references",
mode: 'write-references',
// profile: true,
},
issue: {
@ -717,41 +716,41 @@ module.exports = function (webpackEnv) {
// '../cra-template-typescript/template/src/App.tsx'
// otherwise.
include: [
{ file: "../**/src/**/*.{ts,tsx}" },
{ file: "**/src/**/*.{ts,tsx}" },
{ file: '../**/src/**/*.{ts,tsx}' },
{ file: '**/src/**/*.{ts,tsx}' },
],
exclude: [
{ file: "**/src/**/__tests__/**" },
{ file: "**/src/**/?(*.){spec|test}.*" },
{ file: "**/src/setupProxy.*" },
{ file: "**/src/setupTests.*" },
{ file: '**/src/**/__tests__/**' },
{ file: '**/src/**/?(*.){spec|test}.*' },
{ file: '**/src/setupProxy.*' },
{ file: '**/src/setupTests.*' },
],
},
logger: {
infrastructure: "silent",
infrastructure: 'silent',
},
}),
!disableESLintPlugin &&
new ESLintPlugin({
// Plugin options
extensions: ["js", "mjs", "jsx", "ts", "tsx"],
formatter: require.resolve("react-dev-utils/eslintFormatter"),
eslintPath: require.resolve("eslint"),
extensions: ['js', 'mjs', 'jsx', 'ts', 'tsx'],
formatter: require.resolve('react-dev-utils/eslintFormatter'),
eslintPath: require.resolve('eslint'),
failOnError: !(isEnvDevelopment && emitErrorsAsWarnings),
context: paths.appSrc,
cache: true,
cacheLocation: path.resolve(
paths.appNodeModules,
".cache/.eslintcache"
'.cache/.eslintcache'
),
// ESLint class options
cwd: paths.appPath,
resolvePluginsRelativeTo: __dirname,
baseConfig: {
extends: [require.resolve("eslint-config-react-app/base")],
extends: [require.resolve('eslint-config-react-app/base')],
rules: {
...(!hasJsxRuntime && {
"react/react-in-jsx-scope": "error",
'react/react-in-jsx-scope': 'error',
}),
},
},

View File

@ -1,9 +1,11 @@
{
"name": "testnet-onboarding-app",
"version": "0.1.0",
"version": "0.1.2",
"private": true,
"dependencies": {
"@cerc-io/registry-sdk": "^0.2.5",
"@cosmjs/encoding": "^0.32.4",
"@cosmjs/proto-signing": "^0.32.4",
"@cosmjs/stargate": "^0.32.4",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0",
@ -33,7 +35,7 @@
"resolve": "^1.20.0",
"semver": "^7.3.5",
"stream-browserify": "^3.0.0",
"tailwindcss": "^3.4.9",
"tailwindcss": "^3.0.2",
"typescript": "^4.9.5"
},
"scripts": {
@ -66,10 +68,8 @@
"@types/node": "^16.18.90",
"@types/react": "^18.2.67",
"@types/react-dom": "^18.2.22",
"@types/tailwindcss": "^3.1.0",
"@typescript-eslint/eslint-plugin": "^6.13.2",
"@typescript-eslint/parser": "^6.13.2",
"autoprefixer": "^10.4.20",
"babel-jest": "^27.4.2",
"babel-loader": "^8.2.3",
"babel-plugin-named-asset-import": "^0.3.8",
@ -94,7 +94,7 @@
"jest-resolve": "^27.4.2",
"jest-watch-typeahead": "^1.0.0",
"mini-css-extract-plugin": "^2.4.5",
"postcss": "^8.4.41",
"postcss": "^8.4.4",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-loader": "^6.2.1",
"postcss-normalize": "^10.0.1",

View File

@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -1,21 +1,24 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Testnet Onboarding App"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Testnet Onboarding App" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap"
rel="stylesheet" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
@ -24,52 +27,53 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Testnet Onboarding App</title>
<style>
body {
margin: 0;
padding: 0;
height: 100%;
<title>Testnet Onboarding App</title>
<style>
body {
margin: 0;
padding: 0;
height: 100%;
}
.loader-wrapper {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
display: grid;
place-items: center;
}
.loader {
border: 16px solid #e3e3e3;
border-top: 16px solid #1976d2;
border-radius: 50%;
width: 140px;
height: 140px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
.loader-wrapper {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
display: grid;
place-items: center;
100% {
transform: rotate(360deg);
}
.loader {
border: 16px solid #e3e3e3;
border-top: 16px solid #1976d2;
border-radius: 50%;
width: 140px;
height: 140px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<div class="loader-wrapper">
<div class="loader"></div>
</div>
}
</style>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<div class="loader-wrapper">
<div class="loader"></div>
</div>
<!--
</div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
@ -79,5 +83,6 @@
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</body>
</html>

View File

@ -1,3 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.04924 12.6164C6.84238 9.85387 8.57128 6.03867 8.57051 1.82634C8.57154 1.25458 8.53962 0.689188 8.47618 0.130795L0 0.131558L0.000257231 16.2401C-0.000514887 18.1926 0.75217 20.1458 2.25767 21.6349C3.7633 23.124 5.73941 23.8693 7.71384 23.8683L7.71333 23.8688L24 23.8692L23.9994 15.4843C23.4361 15.4228 22.8645 15.3911 22.2851 15.391C18.0274 15.3915 14.17 17.1014 11.3769 19.864C9.34443 21.8239 6.08571 21.8243 4.07833 19.8388C2.07224 17.8545 2.07133 14.6303 4.04924 12.6164ZM22.2419 1.87395C19.9011 -0.441217 16.0981 -0.442108 13.7566 1.87395C11.415 4.18987 11.4159 7.95136 13.7566 10.2664C16.0985 12.5828 19.9003 12.5825 22.2419 10.2664C24.5835 7.95047 24.5838 4.19026 22.2419 1.87395Z" fill="#FBFBFB"/>
</svg>

Before

Width:  |  Height:  |  Size: 859 B

View File

@ -1,10 +0,0 @@
<svg width="117" height="21" viewBox="0 0 117 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.22282 11.0194C6.57609 8.69185 8.03272 5.47748 8.03206 1.92852C8.03293 1.44681 8.00604 0.970455 7.95259 0.499999L0.811272 0.500642L0.811488 14.0723C0.810838 15.7174 1.44499 17.3629 2.7134 18.6175C3.98191 19.8721 5.64682 20.5001 7.31031 20.4992L7.30988 20.4997L21.0317 20.5L21.0311 13.4355C20.5566 13.3838 20.075 13.357 19.5869 13.3569C15.9997 13.3574 12.7498 14.798 10.3965 17.1255C8.6841 18.7768 5.93858 18.7771 4.24733 17.1043C2.55716 15.4325 2.5564 12.7161 4.22282 11.0194ZM19.5504 1.96863C17.5783 0.0180696 14.3742 0.017319 12.4014 1.96863C10.4286 3.91984 10.4293 7.08895 12.4014 9.03941C14.3745 10.991 17.5776 10.9907 19.5504 9.03941C21.5232 7.0882 21.5236 3.92016 19.5504 1.96863Z" fill="#FBFBFB"/>
<path d="M32.6326 19.0836H40.4995V16.83H35.2622V1.91329H32.6326V19.0836Z" fill="#FBFBFB"/>
<path d="M51.1715 1.91329H46.8108L42.2528 19.0836H44.97L46.1095 14.791H51.7413L52.8808 19.0836H55.7295L51.1715 1.91329ZM46.6574 12.6448L48.9363 3.75909H49.024L51.2153 12.6448H46.6574Z" fill="#FBFBFB"/>
<path d="M64.4348 8.56823H67.5027C67.5027 3.67471 65.903 1.57134 62.1777 1.57134C58.2552 1.57134 56.5679 4.23278 56.5679 10.4785C56.5679 16.7456 58.2552 19.4285 62.1777 19.4285C65.903 19.4285 67.5027 17.3895 67.5246 12.6462H64.4567C64.4348 16.3593 63.9746 17.2822 62.1777 17.2822C60.1179 17.2822 59.6138 15.93 59.6357 10.4785C59.6357 5.04833 60.1398 3.69616 62.1777 3.71761C63.9746 3.71761 64.4348 4.68351 64.4348 8.56823Z" fill="#FBFBFB"/>
<path d="M75.4027 1.57146C79.391 1.59302 81.1002 4.27581 81.1002 10.5C81.1002 16.7243 79.391 19.4072 75.4027 19.4286C71.3926 19.4501 69.6833 16.7672 69.6833 10.5C69.6833 4.23291 71.3926 1.55002 75.4027 1.57146ZM72.7512 10.5C72.7512 15.9302 73.2772 17.2823 75.4027 17.2823C77.5064 17.2823 78.0324 15.9302 78.0324 10.5C78.0324 5.04846 77.5064 3.69629 75.4027 3.71784C73.2772 3.73929 72.7512 5.09146 72.7512 10.5Z" fill="#FBFBFB"/>
<path d="M86.8315 19.0621L84.2019 19.0836V1.91329H88.8257L93.1427 15.9071H93.1865V1.91329H95.8156V19.0836H91.4773L86.8755 4.01667H86.8315V19.0621Z" fill="#FBFBFB"/>
<path d="M102.387 1.91329H99.7577V19.0836H102.387V1.91329Z" fill="#FBFBFB"/>
<path d="M113.176 8.56823H116.244C116.244 3.67471 114.644 1.57134 110.919 1.57134C106.997 1.57134 105.309 4.23278 105.309 10.4785C105.309 16.7456 106.997 19.4285 110.919 19.4285C114.644 19.4285 116.244 17.3895 116.266 12.6462H113.198C113.176 16.3593 112.716 17.2822 110.919 17.2822C108.859 17.2822 108.355 15.93 108.377 10.4785C108.377 5.04833 108.881 3.69616 110.919 3.71761C112.716 3.71761 113.176 4.68351 113.176 8.56823Z" fill="#FBFBFB"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -8,38 +8,125 @@ import PageNotFound from "./pages/PageNotFound";
import OnboardingSuccess from "./pages/OnboardingSuccess";
import SignPageLayout from "./layout/SignPageLayout";
import UserVerification from "./pages/UserVerification";
import TermsAndConditions from "./pages/TermsAndConditions";
import LandingPage from "./pages/LandingPage";
import Header from "./components/Header";
import { WalletConnectProvider } from "./context/WalletConnectContext";
import VerifyEmail from "./pages/VerifyEmail";
import Email from "./pages/Email";
import Thanks from "./pages/Thanks";
import Validator from "./pages/Validator";
import ValidatorSuccess from "./pages/ValidatorSuccess";
import { createTheme, Box, ThemeProvider, CssBaseline } from "@mui/material";
import "./App.css";
const darkTheme = createTheme({
components: {
MuiAccordion: {
defaultProps: {
sx: {
border: "1px solid #48474F",
borderBottomRightRadius: 3,
borderBottomLeftRadius: 3,
},
},
},
MuiButton: {
defaultProps: {
color: "primary",
sx: {
fontFamily: `DM Mono, monospace`,
fontWeight: 400,
},
},
},
MuiLink: {
defaultProps: {
color: "text.primary",
fontSize: "14px",
},
},
MuiTypography: {
defaultProps: {
color: "text.primary",
fontWeight: 400,
},
},
MuiPaper: {
defaultProps: {
sx: {
backgroundImage: "none",
},
},
},
},
palette: {
mode: "dark",
primary: {
main: "#0000F4",
},
secondary: {
main: "#A2A2FF",
},
error: {
main: "#B20710",
},
background: {
default: "#0F0F0F",
paper: "#18181A",
},
text: {
primary: "#FBFBFB",
},
info: {
main: "#FBFBFB",
},
},
});
function App() {
return (
<Router>
<Header />
<WalletConnectProvider>
<Routes>
<Route path="/" element={<TermsAndConditions />} />
<Route path="/verify-email" element={<VerifyEmail />} />
<Route path="/email" element={<Email />} />
<Route path="/connect-wallet" element={<ConnectWallet />} />
<Route path="/thanks" element={<Thanks />} />
<Route element={<SignPageLayout />}>
<Route path="/sign-with-nitro-key" element={<SignWithNitroKey />} />
<Route path="/user-verification" element={<UserVerification />} />
<Route path="/sign-with-cosmos" element={<SignWithCosmos />} />
<Route
path="/onboarding-success"
element={<OnboardingSuccess />}
></Route>
</Route>
<Route path="*" element={<PageNotFound />} />
</Routes>
</WalletConnectProvider>
<ThemeProvider theme={darkTheme}>
<Box
sx={{
height: "100vh",
width: "100vw",
backgroundColor: "background.default",
}}
>
<Header />
<CssBaseline />
<WalletConnectProvider>
<Routes>
<Route path="/" element={<LandingPage />} />
<Route path="/verify-email" element={<VerifyEmail />} />
<Route path="/email" element={<Email />} />
<Route path="/connect-wallet" element={<ConnectWallet />} />
<Route path="/thanks" element={<Thanks />} />
<Route element={<SignPageLayout />}>
<Route
path="/sign-with-nitro-key"
element={<SignWithNitroKey />}
/>
<Route
path="/user-verification"
element={<UserVerification />}
/>
<Route path="/sign-with-cosmos" element={<SignWithCosmos />} />
<Route
path="/onboarding-success"
element={<OnboardingSuccess />}
/>
<Route path="/validator" element={<Validator />} />
<Route
path="/validator-success"
element={<ValidatorSuccess />}
/>
</Route>
<Route path="*" element={<PageNotFound />} />
</Routes>
</WalletConnectProvider>
</Box>
</ThemeProvider>
</Router>
);
}

View File

@ -0,0 +1,25 @@
import { Box } from "@mui/material";
import React, { PropsWithChildren } from "react";
export const CodeBlock: React.FC<PropsWithChildren> = ({ children }) => (
<Box
sx={{
backgroundColor: "#48474F",
padding: 3,
wordWrap: "break-word",
mt: 1,
borderRadius: 1,
}}
>
<pre
style={{
whiteSpace: "pre-wrap",
margin: 0,
backgroundColor: "#48474F",
color: "#FBFBFB",
}}
>
{children}
</pre>
</Box>
);

View File

@ -0,0 +1,21 @@
import { Box, BoxProps } from "@mui/material";
import React, { PropsWithChildren } from "react";
export const Container: React.FC<
PropsWithChildren<{ boxProps?: BoxProps }>
> = ({ children, boxProps = {} }) => (
<Box
{...boxProps}
sx={{
width: "100%",
maxWidth: "752px",
mx: "auto",
backgroundColor: "background.paper",
padding: 3,
borderRadius: 2,
...boxProps.sx,
}}
>
{children}
</Box>
);

View File

@ -1,30 +0,0 @@
import React, { ComponentPropsWithoutRef } from "react";
export interface CustomIconProps extends ComponentPropsWithoutRef<"svg"> {
size?: number | string; // width and height will both be set as the same value
name?: string;
}
export const CustomIcon: React.FC<CustomIconProps> = ({
children,
width = 24,
height = 24,
size,
viewBox = "0 0 24 24",
name,
...rest
}: CustomIconProps) => {
return (
<svg
aria-labelledby={name}
height={size || height}
role="presentation"
viewBox={viewBox}
width={size || width}
xmlns="http://www.w3.org/2000/svg"
{...rest}
>
{children}
</svg>
);
};

View File

@ -1,78 +0,0 @@
# CustomIcon
`CustomIcon` is a flexible and reusable React component for rendering SVG icons. It allows for easy customization of size, color, and other SVG properties.
- Viewbox "0 0 24 24": From where you're exporting from, please make sure the icon is using viewBox="0 0 24 24" before downloading/exporting. Not doing so will result in incorrect icon scaling
## Create a Custom Icon
1. Duplicate a current icon e.g. `LaconicIcon` and rename it accordingly.
2. Rename the function inside the new file you duplicated too
3. Replace the markup with your SVG markup (make sure it complies with the above section's rule)
4. Depending on the svg you pasted...
A. If the `<svg>` has only 1 child, remove the `<svg>` parent entirely so you only have the path left
B. If your component has more than 1 paths, rename `<svg>` tag with the `<g>` tag. Then, remove all attributes of this `<g>` tag so that it's just `<g>`
5. Usually, icons are single colored. If that's the case, replace all fill/stroke color with `currentColor`. E.g. `<path d="..." fill="currentColor">`. Leave the other attributes without removing them.
6. If your icon has more than one color, then it's up to you to decide whether we want to use tailwind to help set the fill and stroke colors
7. Lastly, export your icon in `index.ts` by following what was done for `LaconicIcon`
8. Make sure to provide a `name` for the `<CustomIcon>` component for accessibility
9. Done!
Example:
```tsx
import { CustomIcon, CustomIconProps } from "../CustomIcon";
export const LaconicIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
{...props}
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M4.04924 12.6164C6.84238 9.85387 8.57128 6.03867 8.57051 1.82634C8.57154 1.25458 8.53962 0.689188 8.47618 0.130795L0 0.131558L0.000257231 16.2401C-0.000514887 18.1926 0.75217 20.1458 2.25767 21.6349C3.7633 23.124 5.73941 23.8693 7.71384 23.8683L7.71333 23.8688L24 23.8692L23.9994 15.4843C23.4361 15.4228 22.8645 15.3911 22.2851 15.391C18.0274 15.3915 14.17 17.1014 11.3769 19.864C9.34443 21.8239 6.08571 21.8243 4.07833 19.8388C2.07224 17.8545 2.07133 14.6303 4.04924 12.6164ZM22.2419 1.87395C19.9011 -0.441217 16.0981 -0.442108 13.7566 1.87395C11.415 4.18987 11.4159 7.95136 13.7566 10.2664C16.0985 12.5828 19.9003 12.5825 22.2419 10.2664C24.5835 7.95047 24.5838 4.19026 22.2419 1.87395Z"
fill="currentColor"
/>
</CustomIcon>
);
};
```
## Usage
```tsx
import { LaconicIcon } from './components/CustomIcon';
...
<LaconicIcon size={32} />
```
## Props
The `CustomIcon` component accepts the following props:
- `children`: SVG content (paths, groups, etc.)
- `size`: Sets both width and height (number or string)
- `width`: SVG width (default: 24)
- `height`: SVG height (default: 24)
- `viewBox`: SVG viewBox (default: "0 0 24 24")
- `name`: Icon name for accessibility (sets aria-labelledby)
- ...other SVG props
## Accessibility
Always provide a `name` prop to the CustomIcon for improved accessibility. This sets the `aria-labelledby` attribute on the SVG.
```tsx
<CustomIcon name="laconic-logo-icon">
{/* SVG content */}
</CustomIcon>
```
For icons that are purely decorative, the component sets `role="presentation"` by default.

View File

@ -1,23 +0,0 @@
import React from "react";
import { CustomIcon, CustomIconProps } from "../CustomIcon";
export const LaconicIcon: React.FC<CustomIconProps> = (props) => {
return (
<CustomIcon
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
name="laconic-logo-icon"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4.04924 12.6164C6.84238 9.85387 8.57128 6.03867 8.57051 1.82634C8.57154 1.25458 8.53962 0.689188 8.47618 0.130795L0 0.131558L0.000257231 16.2401C-0.000514887 18.1926 0.75217 20.1458 2.25767 21.6349C3.7633 23.124 5.73941 23.8693 7.71384 23.8683L7.71333 23.8688L24 23.8692L23.9994 15.4843C23.4361 15.4228 22.8645 15.3911 22.2851 15.391C18.0274 15.3915 14.17 17.1014 11.3769 19.864C9.34443 21.8239 6.08571 21.8243 4.07833 19.8388C2.07224 17.8545 2.07133 14.6303 4.04924 12.6164ZM22.2419 1.87395C19.9011 -0.441217 16.0981 -0.442108 13.7566 1.87395C11.415 4.18987 11.4159 7.95136 13.7566 10.2664C16.0985 12.5828 19.9003 12.5825 22.2419 10.2664C24.5835 7.95047 24.5838 4.19026 22.2419 1.87395Z"
fill="currentColor"
/>
</CustomIcon>
);
};

View File

@ -1,50 +0,0 @@
import React from "react";
import { CustomIcon, CustomIconProps } from "../CustomIcon";
export const LaconicWithTextIcon: React.FC<CustomIconProps> = (props) => {
return (
<CustomIcon
width="117"
height="21"
viewBox="0 0 117 21"
fill="none"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4.22282 11.0194C6.57609 8.69185 8.03272 5.47748 8.03206 1.92852C8.03293 1.44681 8.00604 0.970455 7.95259 0.499999L0.811272 0.500642L0.811488 14.0723C0.810838 15.7174 1.44499 17.3629 2.7134 18.6175C3.98191 19.8721 5.64682 20.5001 7.31031 20.4992L7.30988 20.4997L21.0317 20.5L21.0311 13.4355C20.5566 13.3838 20.075 13.357 19.5869 13.3569C15.9997 13.3574 12.7498 14.798 10.3965 17.1255C8.6841 18.7768 5.93858 18.7771 4.24733 17.1043C2.55716 15.4325 2.5564 12.7161 4.22282 11.0194ZM19.5504 1.96863C17.5783 0.0180696 14.3742 0.017319 12.4014 1.96863C10.4286 3.91984 10.4293 7.08895 12.4014 9.03941C14.3745 10.991 17.5776 10.9907 19.5504 9.03941C21.5232 7.0882 21.5236 3.92016 19.5504 1.96863Z"
fill="currentColor"
/>
<path
d="M32.6326 19.0836H40.4995V16.83H35.2622V1.91329H32.6326V19.0836Z"
fill="currentColor"
/>
<path
d="M51.1715 1.91329H46.8108L42.2528 19.0836H44.97L46.1095 14.791H51.7413L52.8808 19.0836H55.7295L51.1715 1.91329ZM46.6574 12.6448L48.9363 3.75909H49.024L51.2153 12.6448H46.6574Z"
fill="currentColor"
/>
<path
d="M64.4348 8.56823H67.5027C67.5027 3.67471 65.903 1.57134 62.1777 1.57134C58.2552 1.57134 56.5679 4.23278 56.5679 10.4785C56.5679 16.7456 58.2552 19.4285 62.1777 19.4285C65.903 19.4285 67.5027 17.3895 67.5246 12.6462H64.4567C64.4348 16.3593 63.9746 17.2822 62.1777 17.2822C60.1179 17.2822 59.6138 15.93 59.6357 10.4785C59.6357 5.04833 60.1398 3.69616 62.1777 3.71761C63.9746 3.71761 64.4348 4.68351 64.4348 8.56823Z"
fill="currentColor"
/>
<path
d="M75.4027 1.57146C79.391 1.59302 81.1002 4.27581 81.1002 10.5C81.1002 16.7243 79.391 19.4072 75.4027 19.4286C71.3926 19.4501 69.6833 16.7672 69.6833 10.5C69.6833 4.23291 71.3926 1.55002 75.4027 1.57146ZM72.7512 10.5C72.7512 15.9302 73.2772 17.2823 75.4027 17.2823C77.5064 17.2823 78.0324 15.9302 78.0324 10.5C78.0324 5.04846 77.5064 3.69629 75.4027 3.71784C73.2772 3.73929 72.7512 5.09146 72.7512 10.5Z"
fill="currentColor"
/>
<path
d="M86.8315 19.0621L84.2019 19.0836V1.91329H88.8257L93.1427 15.9071H93.1865V1.91329H95.8156V19.0836H91.4773L86.8755 4.01667H86.8315V19.0621Z"
fill="currentColor"
/>
<path
d="M102.387 1.91329H99.7577V19.0836H102.387V1.91329Z"
fill="currentColor"
/>
<path
d="M113.176 8.56823H116.244C116.244 3.67471 114.644 1.57134 110.919 1.57134C106.997 1.57134 105.309 4.23278 105.309 10.4785C105.309 16.7456 106.997 19.4285 110.919 19.4285C114.644 19.4285 116.244 17.3895 116.266 12.6462H113.198C113.176 16.3593 112.716 17.2822 110.919 17.2822C108.859 17.2822 108.355 15.93 108.377 10.4785C108.377 5.04833 108.881 3.69616 110.919 3.71761C112.716 3.71761 113.176 4.68351 113.176 8.56823Z"
fill="currentColor"
/>
</CustomIcon>
);
};

View File

@ -1,2 +0,0 @@
export { LaconicIcon } from "./icons/LaconicIcon";
export { LaconicWithTextIcon } from "./icons/LaconicWithTextIcon";

View File

@ -1,31 +1,90 @@
import React from 'react';
import { Link, useLocation } from 'react-router-dom';
import React from "react";
import { Link, useLocation } from "react-router-dom";
import { AppBar, Toolbar, Avatar, Box, IconButton } from '@mui/material';
import { AppBar, SvgIcon, Stack, Divider, Typography } from "@mui/material";
const Header: React.FC = () => {
const location = useLocation()
const location = useLocation();
return (
<AppBar position="static" color="inherit">
<Toolbar>
<Link to={location.pathname === "/" ? "/" : "/connect-wallet"} style={{ color: "inherit", textDecoration: "none" }}>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Avatar
alt="Laconic logo"
src="https://avatars.githubusercontent.com/u/92608123"
/>
<IconButton
edge="start"
color="inherit"
aria-label="menu"
sx={{ ml: 2, mr: 2 }}
<AppBar
position="static"
color="inherit"
sx={{ boxShadow: "none", mb: 4, height: 48 }}
>
<Stack
direction="row"
sx={{
backgroundColor: "background.paper",
pl: 2,
alignItems: "center",
py: 1,
}}
spacing={1}
>
<Link
to={location.pathname === "/" ? "/" : "/connect-wallet"}
style={{
color: "inherit",
textDecoration: "none",
display: "flex",
alignItems: "center",
marginRight: 4,
}}
>
<SvgIcon sx={{ height: 20, width: 100 }}>
<svg
width="115"
height="20"
viewBox="0 0 115 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
Testnet Onboarding
</IconButton>
</Box>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3.37388 10.5194C5.70149 8.19185 7.14225 4.97748 7.1416 1.42853C7.14246 0.94681 7.11586 0.470456 7.063 0L-0.000488281 0.000643078L-0.000273922 13.5723C-0.000917354 15.2174 0.62632 16.863 1.88091 18.1175C3.1356 19.3721 4.78235 20.0001 6.42772 19.9993L6.42729 19.9997L19.9995 20L19.999 12.9355C19.5296 12.8838 19.0532 12.857 18.5704 12.8569C15.0224 12.8574 11.8079 14.298 9.48026 16.6255C7.78654 18.2768 5.07093 18.2771 3.39812 16.6043C1.72638 14.9325 1.72562 12.2161 3.37388 10.5194ZM18.5344 1.46863C16.5837 -0.481929 13.4146 -0.48268 11.4633 1.46863C9.512 3.41984 9.51276 6.58895 11.4633 8.53941C13.415 10.491 16.5831 10.4907 18.5344 8.53941C20.4857 6.5882 20.4861 3.42016 18.5344 1.46863Z"
fill="#FBFBFB"
/>
<path
d="M31.4741 18.5838H39.2552V16.3302H34.075V1.41351H31.4741V18.5838Z"
fill="#FBFBFB"
/>
<path
d="M49.8108 1.41351H45.4976L40.9893 18.5838H43.6769L44.8039 14.2913H50.3744L51.5014 18.5838H54.3191L49.8108 1.41351ZM45.3458 12.145L47.6 3.2593H47.6866L49.8541 12.145H45.3458Z"
fill="#FBFBFB"
/>
<path
d="M62.9292 8.06885H65.9636C65.9636 3.17534 64.3813 1.07196 60.6967 1.07196C56.8169 1.07196 55.1479 3.73341 55.1479 9.97909C55.1479 16.2462 56.8169 18.9291 60.6967 18.9291C64.3813 18.9291 65.9636 16.8901 65.9853 12.1468H62.9508C62.9292 15.8599 62.474 16.7828 60.6967 16.7828C58.6593 16.7828 58.1607 15.4307 58.1824 9.97909C58.1824 4.54896 58.6809 3.19678 60.6967 3.21823C62.474 3.21823 62.9292 4.18413 62.9292 8.06885Z"
fill="#FBFBFB"
/>
<path
d="M73.7781 1.07209C77.7229 1.09364 79.4135 3.77643 79.4135 10.0007C79.4135 16.2249 77.7229 18.9078 73.7781 18.9292C69.8117 18.9507 68.1211 16.2678 68.1211 10.0007C68.1211 3.73354 69.8117 1.05064 73.7781 1.07209ZM71.1555 10.0007C71.1555 15.4308 71.6757 16.783 73.7781 16.783C75.8589 16.783 76.3791 15.4308 76.3791 10.0007C76.3791 4.54909 75.8589 3.19691 73.7781 3.21847C71.6757 3.23992 71.1555 4.59209 71.1555 10.0007Z"
fill="#FBFBFB"
/>
<path
d="M85.0819 18.5624L82.481 18.5838V1.41351H87.0544L91.3243 15.4073H91.3676V1.41351H93.968V18.5838H89.677L85.1254 3.51689H85.0819V18.5624Z"
fill="#FBFBFB"
/>
<path
d="M100.468 1.41351H97.8677V18.5838H100.468V1.41351Z"
fill="#FBFBFB"
/>
<path
d="M111.139 8.06885H114.174C114.174 3.17534 112.591 1.07196 108.906 1.07196C105.028 1.07196 103.358 3.73341 103.358 9.97909C103.358 16.2462 105.028 18.9291 108.906 18.9291C112.591 18.9291 114.174 16.8901 114.195 12.1468H111.161C111.139 15.8599 110.684 16.7828 108.906 16.7828C106.869 16.7828 106.371 15.4307 106.393 9.97909C106.393 4.54896 106.891 3.19678 108.906 3.21823C110.684 3.21823 111.139 4.18413 111.139 8.06885Z"
fill="#FBFBFB"
/>
</svg>
</SvgIcon>
</Link>
</Toolbar>
<Divider
flexItem
orientation="vertical"
color="#FBFBFB"
sx={{ height: "1.2rem", alignSelf: "center", width: "1px" }}
/>
<Typography fontSize="1.25rem">Testnet Onboarding</Typography>
</Stack>
</AppBar>
);
};

View File

@ -14,16 +14,16 @@ const SelectRoleCard = ({ handleAccept, handleRoleChange }: { handleAccept: () =
const [checked, setChecked] = useState(false);
const [isHidden, setIsHidden] = useState(false);
const [isDialogOpen, setisDialogOpen] = useState(false)
const [isDialogOpen, setisDialogOpen] = useState(false);
const handleCheckboxChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setChecked(event.target.checked);
};
const handleContinue = () => {
handleAccept()
setIsHidden(true)
}
handleAccept();
setIsHidden(true);
};
const handleRadioChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSelectedRole(event.target.value as Role);
@ -44,12 +44,12 @@ const SelectRoleCard = ({ handleAccept, handleRoleChange }: { handleAccept: () =
<FormControlLabel
value={Role.Validator}
control={<Radio />}
label="Validator"
label="Validator / Service Provider"
/>
<FormControlLabel
value={Role.Participant}
control={<Radio />}
label="Participant"
label="App Publisher"
/>
</RadioGroup>
</FormControl>

View File

@ -1,34 +1,47 @@
import React, { useState } from 'react';
import { Document, Page, pdfjs } from 'react-pdf';
import React, { useState } from "react";
import { Document, Page, pdfjs } from "react-pdf";
import { Typography } from '@mui/material';
import { Typography } from "@mui/material";
// https://github.com/wojtekmaj/react-pdf?tab=readme-ov-file#copy-worker-to-public-directory
pdfjs.GlobalWorkerOptions.workerSrc = process.env.PUBLIC_URL + '/pdf.worker.min.mjs'
pdfjs.GlobalWorkerOptions.workerSrc =
process.env.PUBLIC_URL + "/pdf.worker.min.mjs";
const TermsAndConditionsBox = ({height}: {height: string}) => {
interface TermsAndConditionsBoxProps {
height: string;
onLoad?: () => void;
}
const TermsAndConditionsBox = ({
height,
onLoad,
}: TermsAndConditionsBoxProps) => {
const [numPages, setNumPages] = useState<number>();
function onDocumentLoadSuccess({ numPages }: { numPages: number }): void {
setNumPages(numPages);
if (onLoad) {
onLoad();
}
}
return (
<>
<Typography variant='h4' textAlign="center" gutterBottom>
<Typography variant="h4" textAlign="center" gutterBottom>
Terms and Conditions
</Typography>
<div
style={{
height: height,
overflowY: 'auto',
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
overflowY: "auto",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<Document
file={process.env.PUBLIC_URL + '/TermsAndConditions.pdf'}
file={process.env.PUBLIC_URL + "/TermsAndConditions.pdf"}
onLoadSuccess={onDocumentLoadSuccess}
>
{Array.apply(null, Array(numPages))
@ -40,7 +53,6 @@ const TermsAndConditionsBox = ({height}: {height: string}) => {
pageNumber={page}
renderTextLayer={false}
renderAnnotationLayer={false}
width={1070}
/>
);
})}

View File

@ -16,7 +16,12 @@ const TermsAndConditionsDialog: React.FC<TermsDialogProps> = ({ open, onClose })
<TermsAndConditionsBox height='65vh' />
</DialogContent>
<DialogActions>
<Button onClick={onClose} color="primary">
<Button>
<a href="/TermsAndConditions.pdf" download style={{textDecoration: "none", color: "inherit"}}>
Download PDF
</a>
</Button>
<Button onClick={onClose}>
Close
</Button>
</DialogActions>
@ -24,4 +29,4 @@ const TermsAndConditionsDialog: React.FC<TermsDialogProps> = ({ open, onClose })
);
};
export default TermsAndConditionsDialog;
export default TermsAndConditionsDialog;

View File

@ -1,35 +1,7 @@
export const TNC_GENERIC_CONTENT = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`;
export const WALLET_DISCLAIMER_MSG = 'You are connecting to an experimental wallet! It is not secure. Do not use it elsewhere and/or for managing real assets.';
export const TNC_VALIDATOR_CONTENT = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`;
export const TNC_PARTICIPANT_CONTENT = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <br/>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`;
export const WALLET_DISCLAIMER_MSG = 'You are connecting to an experimental wallet! It is not secure. Do not use it elsewhere and/or for managing real assets.'
export const REDIRECT_EMAIL_MSG = 'Please check your inbox and click the link to verify your email address.'
export const REDIRECT_EMAIL_MSG = 'Close this tab and the confirmation link in your email will bring you back to the onboarding app.';
export const ENABLE_KYC = false;
export const HASHED_SUBSCRIBER_ID_KEY = 'subscriberIdHash';

View File

@ -22,6 +22,7 @@ assert(PROJECT_ID, "Wallet connect project id not provided");
interface ContextValue {
connect: () => Promise<void>;
session: SessionTypes.Struct | null;
isSessionLoading: boolean;
signClient: SignClient | undefined;
checkPersistedState: (client: SignClient) => Promise<void>;
disconnect: () => Promise<void>;
@ -30,6 +31,7 @@ interface ContextValue {
const walletConnectContext = createContext<ContextValue>({
connect: () => Promise.resolve(),
session: null,
isSessionLoading: true,
signClient: undefined,
checkPersistedState: () => Promise.resolve(),
disconnect: () => Promise.resolve(),
@ -47,6 +49,7 @@ export const WalletConnectProvider = ({
}) => {
const [signClient, setSignClient] = useState<SignClient>();
const [session, setSession] = useState<SessionTypes.Struct | null>(null);
const [isSessionLoading, setIsSessionLoading] = useState(true);
const isSignClientInitializing = useRef<boolean>(false);
const navigate = useNavigate();
@ -59,6 +62,7 @@ export const WalletConnectProvider = ({
}
setSession(null);
setIsSessionLoading(false);
}, [signClient, session]);
const checkPersistedState = useCallback(async (client: SignClient) => {
@ -67,6 +71,8 @@ export const WalletConnectProvider = ({
const session = client.session.get(client.session.keys[lastKeyIndex]);
setSession(session);
}
setIsSessionLoading(false);
}, []);
const subscribeToEvents = useCallback(
@ -76,10 +82,12 @@ export const WalletConnectProvider = ({
const currentSession = client.session.get(topic);
const updatedSession = { ...currentSession, namespaces };
setSession(updatedSession);
setIsSessionLoading(false);
});
client.on("session_delete", () => {
setSession(null);
setIsSessionLoading(false);
navigate("/");
});
},
@ -133,12 +141,14 @@ export const WalletConnectProvider = ({
try {
const session = await approval();
setSession(session);
setIsSessionLoading(false);
} catch (error) {
enqueueSnackbar("User rejected pairing request", { variant: "error" });
console.log(error);
}
web3Modal.closeModal();
}
};
useEffect(() => {
@ -152,6 +162,7 @@ export const WalletConnectProvider = ({
value={{
connect,
session,
isSessionLoading,
signClient,
checkPersistedState,
disconnect,
@ -163,12 +174,13 @@ export const WalletConnectProvider = ({
};
export const useWalletConnectContext = () => {
const { connect, session, signClient, checkPersistedState, disconnect } =
const { connect, session, signClient, isSessionLoading, checkPersistedState, disconnect } =
useContext(walletConnectContext);
return {
connect,
session,
isSessionLoading,
signClient,
checkPersistedState,
disconnect,

View File

@ -1,17 +1,13 @@
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

34
src/layout/Layout.tsx Normal file
View File

@ -0,0 +1,34 @@
import { Button, Typography } from "@mui/material";
import React, { PropsWithChildren } from "react";
import { Container } from "../components/Container";
import { ArrowBack } from "@mui/icons-material";
import { useNavigate } from "react-router-dom";
export const Layout: React.FC<
PropsWithChildren<{
title: string;
backLinkTitle?: string;
noBackButton?: boolean;
}>
> = ({ children, title, backLinkTitle = "Home", noBackButton = false }) => {
const navigate = useNavigate();
return (
<Container boxProps={{ sx: { backgroundColor: "inherit", padding: 0 } }}>
{noBackButton ? null : (
<Button
startIcon={<ArrowBack />}
color="info"
sx={{ mb: 4 }}
onClick={() => navigate("/")}
>
{backLinkTitle}
</Button>
)}
<Typography variant="h4" sx={{ mb: 4 }}>
{title}
</Typography>
<Container>{children}</Container>
</Container>
);
};

View File

@ -1,44 +1,38 @@
import React from "react";
import { Outlet, useNavigate } from "react-router-dom";
import { Outlet, useLocation, useNavigate } from "react-router-dom";
import {
Toolbar,
Avatar,
Button,
Typography,
Container
} from "@mui/material";
import { Avatar, Button, Stack, Typography } from "@mui/material";
import { useWalletConnectContext } from "../context/WalletConnectContext";
import { Container } from "../components/Container";
const SignPageLayout = () => {
const { disconnect, session } = useWalletConnectContext();
const navigate = useNavigate();
const location = useLocation();
const disconnectHandler = async () => {
const { pathname } = location;
const redirectTo = pathname ? pathname.substring(1) : "";
await disconnect();
navigate("/");
navigate(`/connect-wallet?redirectTo=${redirectTo}`);
};
return (
<>
<Toolbar variant="dense">
<Button
variant="outlined"
style={{
marginLeft: "auto",
}}
color="error"
onClick={disconnectHandler}
>
Disconnect
</Button>
</Toolbar>
<Container maxWidth="md">
<Stack justifyContent="center" alignItems="center">
<Container
boxProps={{
sx: {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
mb: 4,
},
}}
>
{session && (
<div style={{ display: "flex", flexDirection: "column" }}>
<Stack spacing={0.5}>
<div
style={{
display: "flex",
@ -47,7 +41,7 @@ const SignPageLayout = () => {
}}
>
<Typography variant="body2">
Connected to: <b> {session.peer.metadata.name}</b>{" "}
<b>Connected to:</b> {session.peer.metadata.name}
</Typography>
<Avatar
variant="square"
@ -62,13 +56,21 @@ const SignPageLayout = () => {
/>
</div>
<Typography variant="body2">
Session ID: <b>{session.topic} </b>
<b>Session ID:</b> {session.topic}
</Typography>
</div>
</Stack>
)}
<Outlet />
<Button
variant="outlined"
color="error"
onClick={disconnectHandler}
sx={{ color: "text.primary" }}
>
Disconnect
</Button>
</Container>
</>
<Outlet />
</Stack>
);
};

View File

@ -1,62 +1,57 @@
import React, { useEffect } from "react";
import {useLocation, useNavigate } from "react-router-dom";
import { useLocation, useNavigate, useSearchParams } from "react-router-dom";
import { Button, Box, Container, Typography, colors } from "@mui/material";
import { Button, Box, Container } from "@mui/material";
import { useWalletConnectContext } from "../context/WalletConnectContext";
import { WALLET_DISCLAIMER_MSG } from "../constants";
const ConnectWallet = () => {
const { connect, session } = useWalletConnectContext();
const { connect, session, signClient, checkPersistedState } =
useWalletConnectContext();
const navigate = useNavigate();
const location = useLocation();
const [searchParams] = useSearchParams();
const redirectTo = searchParams.get("redirectTo");
useEffect(() => {
if (signClient && !session) {
checkPersistedState(signClient);
}
}, [checkPersistedState, signClient, session]);
if (session) {
useEffect(() => {
if (!session) {
return;
}
if (redirectTo) {
navigate(`/${redirectTo}`, {
state: location.state,
});
} else {
navigate("/sign-with-nitro-key", {
state: location.state
state: location.state,
});
}
}, [session, navigate, location]);
}, [session, navigate, redirectTo, location.state]);
const handler = async () => {
await connect();
};
return (
<Container maxWidth="lg">
<Box
display="flex"
flexDirection="column"
alignItems="center"
justifyContent="center"
marginTop={10}
sx={{
border: 1,
borderColor: 'grey.500',
}}
padding={5}
>
<Typography variant="h5" component="h1" gutterBottom color={colors.red[400]}>
Disclaimer
</Typography>
<Typography variant="body1">
{WALLET_DISCLAIMER_MSG}
</Typography>
</Box>
<Container maxWidth="lg" sx={{ height: "75%" }}>
<Box
display="flex"
flexDirection="column"
alignItems="center"
padding={5}
justifyContent="center"
height="100%"
>
<Button
variant="contained"
onClick={handler}
>
<Button variant="contained" onClick={handler}>
Connect Wallet
</Button>
</Box>

View File

@ -1,8 +1,8 @@
import React from 'react'
import React from 'react';
import { Box, Typography } from '@mui/material'
import { Box, Typography } from '@mui/material';
import { REDIRECT_EMAIL_MSG } from '../constants'
import { REDIRECT_EMAIL_MSG } from '../constants';
const Email = () => {
return (
@ -27,7 +27,7 @@ const Email = () => {
{REDIRECT_EMAIL_MSG}
</Typography>
</Box>
)
}
);
};
export default Email
export default Email;

86
src/pages/LandingPage.tsx Normal file
View File

@ -0,0 +1,86 @@
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { Button, Box, Typography } from "@mui/material";
import TermsAndConditionsBox from "../components/TermsAndConditionsBox";
import { Container } from "../components/Container";
const LandingPage = () => {
const navigate = useNavigate();
const [isDisabled, setIsDisabled] = useState(true);
const handleAccept = () => {
navigate("/verify-email");
};
return (
<Container>
<Box
display="flex"
flexDirection="column"
alignItems="center"
justifyContent="center"
margin={10}
sx={{
border: 1,
borderColor: "grey.500",
borderRadius: 1,
}}
padding={5}
>
<Typography variant="h6">
Welcome to the LORO Testnet Onboarding App. The detailed instructions
for completing this first step are found in the{" "}
<a
href="https://github.com/hyphacoop/loro-testnet/"
target="_blank"
rel="noopener noreferrer"
>
LORO testnet repo
</a>
. Once your onboarding transaction has been submitted, await the
completion of stage0. The genesis.json file and peer nodes will then
be published in the aforementioned repository for validators to begin
stage1. Once enough validators are online and the Laconic chain is
running, those same validators can complete their service provider
setup. Once service providers are live, app publishers can start
deploying webapps to individual service providers.
</Typography>
</Box>
<TermsAndConditionsBox
height="43vh"
onLoad={() => {
setIsDisabled(false);
}}
/>
<Box m={2} display="flex" justifyContent="center" gap={2}>
<Button
variant="outlined"
color="primary"
sx={{ color: "text.primary" }}
disabled={isDisabled}
>
<a
href="/TermsAndConditions.pdf"
download
style={{textDecoration: "none", color: "inherit"}}
>
Download PDF
</a>
</Button>
<Button
variant="contained"
color="primary"
onClick={handleAccept}
disabled={isDisabled}
>
Accept
</Button>
</Box>
</Container>
);
};
export default LandingPage;

View File

@ -7,47 +7,50 @@ import { Registry } from "@cerc-io/registry-sdk";
import SumsubWebSdk from "@sumsub/websdk-react";
import { MessageHandler } from "@sumsub/websdk";
import { config, fetchAccessToken, getAccessTokenExpirationHandler, options } from "../utils/sumsub";
import { ENABLE_KYC } from "../constants";
import {
config,
fetchAccessToken,
getAccessTokenExpirationHandler,
options,
} from "../utils/sumsub";
import { ENABLE_KYC, HASHED_SUBSCRIBER_ID_KEY } from "../constants";
import { Participant } from "../types";
import { CodeBlock } from "../components/CodeBlock";
interface Participant {
cosmosAddress: string;
nitroAddress: string;
role: string;
kycId: string;
}
const registry = new Registry(
process.env.REACT_APP_REGISTRY_GQL_ENDPOINT!
);
const registry = new Registry(process.env.REACT_APP_REGISTRY_GQL_ENDPOINT!);
const OnboardingSuccess = () => {
const location = useLocation();
const { cosmosAddress } = location.state as {
cosmosAddress?: string
}
cosmosAddress?: string;
};
const [participant, setParticipant] = useState<Participant>();
const [token, setToken] = useState<string>('');
const [token, setToken] = useState<string>("");
const [loading, setLoading] = useState<boolean>(true);
const messageHandler: MessageHandler = (event, payload) => {
console.log('sumsubEvent:', event, payload);
console.log("sumsubEvent:", event, payload);
};
useEffect(() => {
const fetchParticipants = async () => {
try {
if (!cosmosAddress) {
enqueueSnackbar("Cosmos address is not provided", { variant: "error" });
enqueueSnackbar("Laconic address is not provided", {
variant: "error",
});
return;
}
const participant: Participant = await registry.getParticipantByAddress(cosmosAddress);
const participant: Participant =
await registry.getParticipantByAddress(cosmosAddress);
if (!participant) {
enqueueSnackbar("Participant not found", { variant: "error" });
return;
}
localStorage.removeItem(HASHED_SUBSCRIBER_ID_KEY);
setParticipant(participant);
} catch (error) {
console.error("Error fetching participants", error);
@ -65,7 +68,7 @@ const OnboardingSuccess = () => {
};
if (cosmosAddress && ENABLE_KYC) {
getToken(cosmosAddress).catch(error => {
getToken(cosmosAddress).catch((error) => {
console.error(error);
alert("Failed to fetch token");
});
@ -73,54 +76,90 @@ const OnboardingSuccess = () => {
}, [cosmosAddress]);
return (
<Box
sx={{
display: "flex",
flexDirection: "column",
marginTop: 6,
gap: 1,
}}
>
<Typography variant="h5">Transaction Successful</Typography>
<Typography variant="body1">
Participant onboarded: <br />
</Typography>
<>
<Box
sx={{
backgroundColor: "lightgray",
padding: 3,
wordWrap: "break-word",
marginBottom: 6,
display: "flex",
flexDirection: "column",
marginTop: 6,
gap: 1,
}}
>
<pre style={{ whiteSpace: "pre-wrap", margin: 0 }}>
<Typography variant="h5">Transaction Successful</Typography>
<Typography variant="body1">
Participant onboarded: <br />
</Typography>
<CodeBlock>
{participant && (
<div>
Cosmos Address: {participant.cosmosAddress} <br />
Laconic Address: {participant.cosmosAddress} <br />
Nitro Address: {participant.nitroAddress} <br />
Role: {participant.role} <br />
KYC ID: {participant.kycId} <br />
<br />
</div>
)}
</pre>
</CodeBlock>
{ENABLE_KYC ? (
<Box>
<Typography variant="h5">KYC Status</Typography>
{!loading && token && cosmosAddress && (
<SumsubWebSdk
accessToken={token}
expirationHandler={getAccessTokenExpirationHandler(
cosmosAddress,
)}
config={config}
options={options}
onMessage={messageHandler}
/>
)}
</Box>
) : (
""
)}
</Box>
{ENABLE_KYC ? (
<Box>
<Typography variant="h5">KYC Status</Typography>
{!loading && token && cosmosAddress && (
<SumsubWebSdk
accessToken={token}
expirationHandler={getAccessTokenExpirationHandler(cosmosAddress)}
config={config}
options={options}
onMessage={messageHandler}
/>
)}
</Box>
) : ''
}
</Box>
<Typography variant="h5">Next Steps</Typography>
<Box
display="flex"
flexDirection="column"
alignItems="center"
justifyContent="center"
marginTop={3}
sx={{
border: 1,
borderColor: "grey.500",
}}
padding={5}
>
<Typography variant="body1" gutterBottom sx={{ p: 2 }}>
For app publishers, await the start of the stage 1 chain, which will
be announced in various social media channels. In the meantime,
familiarize yourself with the{" "}
<a
href="https://github.com/hyphacoop/loro-testnet/blob/main/docs/publishing-webapps.md"
target="_blank"
rel="noopener noreferrer"
>
webapp publishing workflow
</a>{" "}
as this is the main task you will be participating in.
<br />
<br />
For validators, ensure your service provider is running and ready to
deploy webapps. Await publication of the laconicd version, genesis
file, and peer info to the LORO testnet repo, then follow{" "}
<a
href="https://git.vdb.to/cerc-io/testnet-laconicd-stack/src/branch/main/testnet-onboarding-validator.md#join-as-a-validator-on-stage1"
target="_blank"
rel="noopener noreferrer"
>
these instructions
</a>{" "}
for joining stage 1 as a validator/service provider.
</Typography>
</Box>
</>
);
};

View File

@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { enqueueSnackbar } from "notistack";
import { Box, Card, CardContent, Grid, Typography } from "@mui/material";
import { Box, Divider, Typography } from "@mui/material";
import LoadingButton from "@mui/lab/LoadingButton/LoadingButton";
import {
MsgOnboardParticipantEncodeObject,
@ -11,31 +11,38 @@ import {
import { StargateClient } from "@cosmjs/stargate";
import { useWalletConnectContext } from "../context/WalletConnectContext";
import SelectRoleCard, {Role} from "../components/SelectRoleCard";
import SelectRoleCard, { Role } from "../components/SelectRoleCard";
import { HASHED_SUBSCRIBER_ID_KEY } from "../constants";
import { Layout } from "../layout/Layout";
import { CodeBlock } from "../components/CodeBlock";
const SignWithCosmos = () => {
const { session, signClient } = useWalletConnectContext();
const location = useLocation();
const [isLoading, setIsLoading] = useState(false);
const [balance, setBalance] = useState('');
const [balance, setBalance] = useState("");
const [isRequesting, setIsRequesting] = useState(false);
const [isTncAccepted, setIsTncAccepted] = useState(false);
const [role, setRole] = useState(Role.Participant);
const navigate = useNavigate();
const {message: innerMessage, cosmosAddress, receivedEthSig: ethSignature, subscriberIdHash} = location.state as {
const {
message: innerMessage,
cosmosAddress,
receivedEthSig: ethSignature,
} = location.state as {
message?: {
msg: string;
address: string;
};
cosmosAddress?: string;
receivedEthSig?: string;
subscriberIdHash?: string;
};
const ethAddress = innerMessage!.address;
const subscriberIdHash = localStorage.getItem(HASHED_SUBSCRIBER_ID_KEY);
const createCosmosClient = useCallback(async (endpoint: string) => {
return await StargateClient.connect(endpoint);
@ -50,7 +57,7 @@ const SignWithCosmos = () => {
ethPayload: innerMessage,
ethSignature: ethSignature!,
kycId: subscriberIdHash!,
role
role,
},
};
}, [cosmosAddress, innerMessage, ethSignature, subscriberIdHash, role]);
@ -58,22 +65,27 @@ const SignWithCosmos = () => {
const handleTokenRequest = async () => {
try {
setIsRequesting(true);
const response = await fetch(`${process.env.REACT_APP_FAUCET_ENDPOINT!}/faucet`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
const response = await fetch(
`${process.env.REACT_APP_FAUCET_ENDPOINT!}/faucet`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
address: cosmosAddress,
}),
},
body: JSON.stringify({
address: cosmosAddress,
}),
});
);
if (response.ok) {
enqueueSnackbar('Tokens sent successfully', { variant: "success" });
enqueueSnackbar("Tokens sent successfully", { variant: "success" });
} else {
const errorResponse = await response.json();
if (response.status === 429) {
enqueueSnackbar(`${response.statusText} : ${errorResponse.error}`, { variant: "error" });
enqueueSnackbar(`${response.statusText} : ${errorResponse.error}`, {
variant: "error",
});
} else {
throw new Error(errorResponse.error);
}
@ -89,7 +101,7 @@ const SignWithCosmos = () => {
};
const sendTransaction = async (
transactionMessage: MsgOnboardParticipantEncodeObject
transactionMessage: MsgOnboardParticipantEncodeObject,
) => {
if (!ethAddress) {
enqueueSnackbar("Set nitro address");
@ -99,6 +111,10 @@ const SignWithCosmos = () => {
try {
setIsLoading(true);
enqueueSnackbar("View and sign the message from your Laconic Wallet", {
variant: "info",
});
const params = { transactionMessage, signer: cosmosAddress };
const responseFromWallet = await signClient!.request<{
code: number;
@ -115,8 +131,8 @@ const SignWithCosmos = () => {
} else {
navigate("/onboarding-success", {
state: {
cosmosAddress
}
cosmosAddress,
},
});
}
} catch (error) {
@ -129,11 +145,16 @@ const SignWithCosmos = () => {
const getBalances = useCallback(async () => {
try {
const cosmosClient = await createCosmosClient(process.env.REACT_APP_LACONICD_RPC_ENDPOINT!);
const balance = await cosmosClient.getBalance(cosmosAddress!, process.env.REACT_APP_LACONICD_DENOM!);
const cosmosClient = await createCosmosClient(
process.env.REACT_APP_LACONICD_RPC_ENDPOINT!,
);
const balance = await cosmosClient.getBalance(
cosmosAddress!,
process.env.REACT_APP_LACONICD_DENOM!,
);
setBalance(balance.amount);
} catch (error) {
console.error('Error fetching balance:', error);
console.error("Error fetching balance:", error);
throw error;
}
}, [cosmosAddress, createCosmosClient]);
@ -143,69 +164,58 @@ const SignWithCosmos = () => {
}, [getBalances]);
return (
<Box
sx={{
display: "flex",
flexDirection: "column",
marginTop: 6,
gap: 1,
}}
>
<Typography variant="h5" display={`${isTncAccepted ? "none" : "block"}`}>Please accept terms and conditions to continue</Typography>
<SelectRoleCard handleAccept={() => setIsTncAccepted(true)} handleRoleChange={setRole}/>
<Typography variant="h5">Send transaction to chain</Typography>
<Typography>Cosmos Account:</Typography>
<Card className='mt-1 mb-1'>
<CardContent>
<Grid container spacing={2}>
<Grid item xs={9}>
<Typography variant="body1">Address: {cosmosAddress}</Typography>
<Typography variant="body1">Balance: {balance} {process.env.REACT_APP_LACONICD_DENOM}</Typography>
</Grid>
<Grid item xs={3} container justifyContent="flex-end">
<LoadingButton
variant="contained"
onClick={handleTokenRequest}
disabled={isTncAccepted ? isRequesting : !isTncAccepted}
loading={isRequesting}
>
Request tokens from Faucet
</LoadingButton>
</Grid>
</Grid>
</CardContent>
</Card>
<Typography variant="body1">
Onboarding message: <br />
</Typography>
<Box
sx={{
backgroundColor: "lightgray",
padding: 3,
wordWrap: "break-word",
}}
>
<pre style={{ whiteSpace: "pre-wrap", margin: 0 }}>
{JSON.stringify(onboardParticipantMsg, null, 2)}{" "}
</pre>
</Box>
<Box
sx={{
paddingBottom: 2,
}}>
<>
{!isTncAccepted && (
<Layout
title="Please accept the terms and conditions to continue"
noBackButton
>
<SelectRoleCard
handleAccept={() => setIsTncAccepted(true)}
handleRoleChange={setRole}
/>
</Layout>
)}
<Layout title="Send transaction to chain" noBackButton>
<Typography>Laconic Account:</Typography>
<Box sx={{ backgroundColor: "#29292E", p: 2, borderRadius: 1, mb: 2 }}>
<Typography variant="body1">Address: {cosmosAddress}</Typography>
<Typography variant="body1">
Balance: {balance} {process.env.REACT_APP_LACONICD_DENOM}
</Typography>
</Box>
<LoadingButton
variant="contained"
onClick={async () => {
await sendTransaction(onboardParticipantMsg);
}}
loading={isLoading}
disabled={isTncAccepted ? (balance === '0') : !isTncAccepted}
onClick={handleTokenRequest}
disabled={isTncAccepted ? isRequesting : !isTncAccepted}
loading={isRequesting}
>
Send transaction
Request tokens from Faucet
</LoadingButton>
</Box>
</Box>
<Divider flexItem sx={{ my: 2 }} />
<Typography variant="body1">Onboarding message:</Typography>
<CodeBlock>{JSON.stringify(onboardParticipantMsg, null, 2)} </CodeBlock>
<Box
sx={{
paddingBottom: 2,
mt: 2,
}}
>
<LoadingButton
variant="contained"
onClick={async () => {
await sendTransaction(onboardParticipantMsg);
}}
loading={isLoading}
disabled={isTncAccepted ? balance === "0" : !isTncAccepted}
>
Send transaction
</LoadingButton>
</Box>
</Layout>
</>
);
};

View File

@ -3,31 +3,33 @@ import { useLocation, useNavigate } from "react-router-dom";
import { enqueueSnackbar } from "notistack";
import canonicalStringify from "canonical-json";
import {
Select,
MenuItem,
Box,
Typography,
} from "@mui/material";
import LoadingButton from '@mui/lab/LoadingButton';
import { Select, MenuItem, Box, Typography, Stack } from "@mui/material";
import LoadingButton from "@mui/lab/LoadingButton";
import { utf8ToHex } from "@walletconnect/encoding";
import { useWalletConnectContext } from "../context/WalletConnectContext";
import { ENABLE_KYC } from "../constants";
import { ENABLE_KYC, HASHED_SUBSCRIBER_ID_KEY } from "../constants";
import { Layout } from "../layout/Layout";
import { CodeBlock } from "../components/CodeBlock";
const SignWithNitroKey = () => {
const { session, signClient, checkPersistedState } =
useWalletConnectContext();
useEffect(() => {
if (signClient && !session) {
checkPersistedState(signClient);
}
}, [session, signClient, checkPersistedState]);
const { session, signClient, isSessionLoading } = useWalletConnectContext();
const navigate = useNavigate();
const location = useLocation();
useEffect(() => {
if (isSessionLoading) {
return;
}
if (!session) {
navigate("/connect-wallet?redirectTo=sign-with-nitro-key", {
state: location.state,
});
}
}, [session, isSessionLoading, navigate, location.state]);
const [ethAddress, setEthAddress] = useState("");
const [ethSignature, setEthSignature] = useState("");
@ -35,6 +37,20 @@ const SignWithNitroKey = () => {
const [isLoading, setIsLoading] = useState(false);
const subscriberIdHash = useMemo(() => {
return localStorage.getItem(HASHED_SUBSCRIBER_ID_KEY);
}, []);
useEffect(() => {
if (!subscriberIdHash) {
setIsLoading(false);
enqueueSnackbar(
"Subscriber ID not found. Please verify your email and try again",
{ variant: "error" },
);
}
}, [subscriberIdHash]);
const message = useMemo(() => {
return {
msg: "Register my account as a participant on the Laconic network",
@ -45,7 +61,12 @@ const SignWithNitroKey = () => {
const signEth = async () => {
if (session && signClient) {
try {
setIsLoading(true)
setIsLoading(true);
enqueueSnackbar("View and sign the message from your Laconic Wallet", {
variant: "info",
});
const jsonMessage = canonicalStringify(message);
const hexMsg = utf8ToHex(jsonMessage, true);
const receivedEthSig: string = await signClient!.request({
@ -56,7 +77,7 @@ const SignWithNitroKey = () => {
params: [hexMsg, ethAddress],
},
});
setIsLoading(false)
setIsLoading(false);
setEthSignature(ethSignature);
if (ENABLE_KYC) {
@ -68,43 +89,26 @@ const SignWithNitroKey = () => {
},
});
} else {
const state = location.state as {
subscriberIdHash?: string
}
if (!state.subscriberIdHash) {
throw new Error("Subscriber ID not found. Please verify your email and try again")
}
navigate("/sign-with-cosmos", {
state: {
message,
cosmosAddress,
receivedEthSig,
subscriberIdHash: state.subscriberIdHash,
},
});
}
} catch (error) {
console.log("err in signing ", error);
setIsLoading(false)
setIsLoading(false);
enqueueSnackbar("Error signing message", { variant: "error" });
}
}
};
return (
<div>
<Layout title="New Session" noBackButton>
{session ? (
<Box
sx={{
display: "flex",
flexDirection: "column",
marginTop: 6,
gap: 1,
}}
>
<Typography variant="h5">Sign with Nitro key</Typography>
<Stack spacing={2}>
<Typography variant="body1">Select Laconic account:</Typography>
<Select
labelId="demo-simple-select-label"
@ -138,31 +142,25 @@ const SignWithNitroKey = () => {
))}
</Select>
{(Boolean(ethAddress) && Boolean(cosmosAddress)) && (<Box
sx={{
backgroundColor: "lightgray",
padding: 3,
wordWrap: "break-word",
}}
>
<pre style={{ whiteSpace: "pre-wrap", margin: 0 }}>{canonicalStringify(message, null, 2)} </pre>
</Box>)}
{Boolean(ethAddress) && Boolean(cosmosAddress) && (
<CodeBlock>{canonicalStringify(message, null, 2)} </CodeBlock>
)}
<Box>
<LoadingButton
variant="contained"
onClick={signEth}
disabled={!Boolean(ethAddress)}
disabled={!Boolean(ethAddress) || !subscriberIdHash}
sx={{ marginTop: 2 }}
loading={isLoading}
>
Sign using Nitro key
</LoadingButton>
</Box>
</Box>
</Stack>
) : (
<>Loading...</>
)}
</div>
</Layout>
);
};

View File

@ -1,29 +0,0 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { Container, Button, Box, Paper } from '@mui/material';
import TermsAndConditionsBox from '../components/TermsAndConditionsBox';
const TermsAndConditions = () => {
const navigate = useNavigate();
const handleAccept = () => {
navigate('/verify-email');
};
return (
<Container maxWidth="lg">
<Paper elevation={3} style={{ padding: '2rem', marginTop: '2rem', height: '83vh', display: 'flex', flexDirection: 'column' }}>
<TermsAndConditionsBox height='80vh' />
<Box mt={2} display="flex" justifyContent="center">
<Button variant="contained" color="primary" onClick={handleAccept}>
Accept
</Button>
</Box>
</Paper>
</Container>
);
};
export default TermsAndConditions;

View File

@ -1,10 +1,12 @@
import React, { useEffect, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { jwtDecode } from "jwt-decode";
import { jwtDecode } from 'jwt-decode';
import { ethers } from 'ethers';
import { Box, colors, Typography } from '@mui/material';
import { HASHED_SUBSCRIBER_ID_KEY } from '../constants';
interface JwtPayload {
subscriber_id: string;
exp: number;
@ -23,29 +25,27 @@ const Thanks: React.FC = () => {
const token = queryParams.get('jwt_token');
try {
if(!token){
throw new Error("Invalid JWT Token")
if (!token) {
throw new Error("Invalid JWT Token");
}
const decoded = jwtDecode(token) as JwtPayload;
const currentTime = Math.floor(Date.now() / 1000);
if (!decoded.subscriber_id) {
throw new Error("Subscriber ID not found")
throw new Error("Subscriber ID not found");
}
if (decoded.exp < currentTime) {
throw new Error("Token has expired");
}
const subscriberIdBytes = ethers.utils.toUtf8Bytes(decoded.subscriber_id)
const subscriberIdBytes = ethers.utils.toUtf8Bytes(decoded.subscriber_id);
const subscriberIdHash = ethers.utils.sha256(subscriberIdBytes);
navigate('/connect-wallet', {
state:{
subscriberIdHash
}
});
localStorage.setItem(HASHED_SUBSCRIBER_ID_KEY, subscriberIdHash);
navigate('/sign-with-nitro-key');
} catch (error) {
setErr(String(error));
}

View File

@ -45,13 +45,14 @@ const UserVerification = () => {
useEffect(() => {
if (applicationSubmitted && kycId !== '') {
const kycIdHash = ethers.utils.sha256(ethers.utils.toUtf8Bytes(kycId));
navigate("/sign-with-cosmos", {
state: {
message,
cosmosAddress,
receivedEthSig,
subscriberIdHash: kycIdHash,
}})
kycIdHash
}});
}
}, [applicationSubmitted, kycId, navigate, cosmosAddress, message, receivedEthSig]);

279
src/pages/Validator.tsx Normal file
View File

@ -0,0 +1,279 @@
import React, { useEffect, useMemo, useState } from "react";
import { enqueueSnackbar } from "notistack";
import { useNavigate } from "react-router-dom";
import { MsgCreateValidator } from "cosmjs-types/cosmos/staking/v1beta1/tx";
import {
Box,
Link,
MenuItem,
Select,
TextField,
Typography,
} from "@mui/material";
import { fromBech32, toBech32 } from "@cosmjs/encoding";
import { LoadingButton } from "@mui/lab";
import { EncodeObject, encodePubkey } from "@cosmjs/proto-signing";
import { Registry } from "@cerc-io/registry-sdk";
import { useWalletConnectContext } from "../context/WalletConnectContext";
import { Participant } from "../types";
import { Layout } from "../layout/Layout";
import { CodeBlock } from "../components/CodeBlock";
const Validator = () => {
const { session, signClient, isSessionLoading } = useWalletConnectContext();
const navigate = useNavigate();
const [cosmosAddress, setCosmosAddress] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [moniker, setMoniker] = useState("");
const [pubKey, setPubKey] = useState("");
const [participant, setParticipant] = useState<Participant | null>(null);
const [isError, setIsError] = useState(false);
useEffect(() => {
if (isSessionLoading) {
return;
}
if (!session) {
navigate("/connect-wallet?redirectTo=validator");
}
}, [session, navigate, isSessionLoading]);
useEffect(() => {
if (!cosmosAddress) {
setParticipant(null);
return;
}
const fetchParticipant = async () => {
const registry = new Registry(
process.env.REACT_APP_REGISTRY_GQL_ENDPOINT!,
);
try {
const fetchedParticipant =
await registry.getParticipantByAddress(cosmosAddress);
if (fetchedParticipant) {
setParticipant(fetchedParticipant);
} else {
throw new Error("Participant not found");
}
} catch (error) {
console.error("Error fetching participant", error);
enqueueSnackbar("Error in fetching participant", { variant: "error" });
setParticipant(null);
}
};
fetchParticipant();
}, [cosmosAddress]);
const isMonikerValid = useMemo(() => moniker.trim().length > 0, [moniker]);
const isPubKeyValid = useMemo(() => pubKey.length === 44, [pubKey]);
const msgCreateValidator: MsgCreateValidator = useMemo(() => {
const encodedPubKey = encodePubkey({
type: "tendermint/PubKeyEd25519",
value: pubKey.length === 44 ? pubKey : "",
});
return {
description: {
moniker,
identity: "",
website: "",
securityContact: "",
details: "",
},
commission: {
maxChangeRate: "10000000000000000", // 0.01
maxRate: "200000000000000000", // 0.2
rate: "100000000000000000", // 0.1
},
minSelfDelegation: "1",
delegatorAddress: "",
validatorAddress:
cosmosAddress &&
toBech32("laconicvaloper", fromBech32(cosmosAddress).data),
pubkey: encodedPubKey,
value: {
amount: process.env.REACT_APP_STAKING_AMOUNT!,
denom: process.env.REACT_APP_LACONICD_DENOM!,
},
};
}, [cosmosAddress, pubKey, moniker]);
const msgCreateValidatorEncodeObject: EncodeObject = {
typeUrl: "/cosmos.staking.v1beta1.MsgCreateValidator",
value: MsgCreateValidator.toJSON(msgCreateValidator),
};
const sendTransaction = async () => {
if (
!isMonikerValid ||
!isPubKeyValid ||
!msgCreateValidator.validatorAddress
) {
setIsError(true);
return;
}
setIsLoading(true);
enqueueSnackbar("View and sign the message from your Laconic Wallet", {
variant: "info",
});
try {
const params = {
transactionMessage: msgCreateValidatorEncodeObject,
signer: cosmosAddress,
};
const response = await signClient!.request<{ code: number }>({
topic: session!.topic,
chainId: `cosmos:${process.env.REACT_APP_LACONICD_CHAIN_ID}`,
request: {
method: "cosmos_sendTransaction",
params,
},
});
if (response.code !== 0) {
throw new Error("Transaction not sent");
} else {
navigate("/validator-success", {
state: { validatorAddress: msgCreateValidator.validatorAddress },
});
}
} catch (error) {
console.error("Error sending transaction", error);
enqueueSnackbar("Error in sending transaction", { variant: "error" });
} finally {
setIsLoading(false);
}
};
const replacer = (_key: string, value: any): any => {
if (value instanceof Uint8Array) {
return Buffer.from(value).toString("hex");
}
return value;
};
return (
<Layout title="Create a validator">
<Typography variant="body1">Select Laconic account:</Typography>
<Select
sx={{ marginBottom: 2 }}
id="cosmos-address-select"
value={cosmosAddress}
onChange={(e) => setCosmosAddress(e.target.value)}
style={{ maxWidth: "600px", display: "block" }}
>
{session?.namespaces.cosmos.accounts.map((address, index) => (
<MenuItem value={address.split(":")[2]} key={index}>
{address.split(":")[2]}
</MenuItem>
))}
</Select>
{Boolean(cosmosAddress) && (
<>
{participant ? (
<Typography>Onboarded participant</Typography>
) : (
<Typography>No participant found</Typography>
)}
{participant && (
<CodeBlock>
Laconic Address: {participant.cosmosAddress} <br />
Nitro Address: {participant.nitroAddress} <br />
Role: {participant.role} <br />
KYC ID: {participant.kycId} <br />
</CodeBlock>
)}
{participant?.role === "validator" && (
<>
<Box style={{ maxWidth: "600px" }}>
<TextField
id="moniker"
label="Enter your node moniker (example: AliceNode)"
variant="outlined"
fullWidth
margin="normal"
value={moniker}
onChange={(e) => {
setIsError(false);
setMoniker(e.target.value);
}}
error={!isMonikerValid && isError}
helperText={
!isMonikerValid && isError ? "Moniker is required" : ""
}
/>
</Box>
<Typography sx={{ marginTop: 3 }}>
Fetch your validator public key using the following command
(refer&nbsp;
<Link
href="https://git.vdb.to/cerc-io/testnet-laconicd-stack/src/branch/main/testnet-onboarding-validator.md#join-as-testnet-validator"
target="_blank"
rel="noopener noreferrer"
>
this guide
</Link>
)
</Typography>
<CodeBlock>
{`laconic-so deployment --dir testnet-laconicd-deployment exec laconicd "laconicd cometbft show-validator" | jq -r .key`}
</CodeBlock>
<Box sx={{ maxWidth: "600px" }}>
<TextField
id="pub-key"
label="Enter your validator public key"
variant="outlined"
fullWidth
margin="normal"
value={pubKey}
onChange={(e) => {
setIsError(false);
setPubKey(e.target.value);
}}
error={!isPubKeyValid && isError}
helperText={
!isPubKeyValid && isError
? "Public key must be 44 characters"
: ""
}
/>
</Box>
<Typography>Send transaction to chain</Typography>
<CodeBlock>
{JSON.stringify(msgCreateValidator, replacer, 2)}
</CodeBlock>
<Box marginTop={1} marginBottom={1}>
<LoadingButton
variant="contained"
onClick={sendTransaction}
loading={isLoading}
disabled={isError}
>
Send transaction
</LoadingButton>
</Box>
</>
)}
</>
)}
</Layout>
);
};
export default Validator;

View File

@ -0,0 +1,35 @@
import React from "react";
import { useLocation } from "react-router-dom";
import { Link, Typography } from "@mui/material";
import { Layout } from "../layout/Layout";
import { CodeBlock } from "../components/CodeBlock";
const ValidatorSuccess = () => {
const location = useLocation();
const { validatorAddress } = location.state as {
validatorAddress?: string;
};
return (
<Layout title="Validator created successfully">
<Typography sx={{ marginTop: 3 }}>
You can view your validator details using the following command
(Refer&nbsp;
<Link
href="https://git.vdb.to/cerc-io/testnet-laconicd-stack/src/branch/main/testnet-onboarding-validator.md#join-as-testnet-validator"
target="_blank"
rel="noopener noreferrer"
>
this guide
</Link>
)
</Typography>
<CodeBlock>
{`laconic-so deployment --dir testnet-laconicd-deployment exec laconicd "laconicd query staking validators --output json" | jq '.validators[] | select(.operator_address == "${validatorAddress}")'`}
</CodeBlock>
</Layout>
);
};
export default ValidatorSuccess;

View File

@ -1,4 +1,4 @@
import React from 'react'
import React from 'react';
const VerifyEmail = () => {
return (
@ -18,7 +18,7 @@ const VerifyEmail = () => {
}}
></iframe>
</div>
)
}
);
};
export default VerifyEmail
export default VerifyEmail;

6
src/types.ts Normal file
View File

@ -0,0 +1,6 @@
export interface Participant {
cosmosAddress: string;
nitroAddress: string;
role: string;
kycId: string;
}

View File

@ -29,5 +29,5 @@ export const getAccessTokenExpirationHandler = (userId: string) => {
return async () => {
const newToken = await fetchAccessToken(userId);
return newToken;
}
};
};

View File

@ -1,15 +0,0 @@
import type { Config } from "tailwindcss";
const config: Config = {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
colors: {
snowball: "#0f86f5",
},
},
},
plugins: [],
};
export default config;

3341
yarn.lock

File diff suppressed because it is too large Load Diff