diff --git a/web-app/src/contants.tsx b/web-app/src/contants.tsx index d22a2d7..ef62f25 100644 --- a/web-app/src/contants.tsx +++ b/web-app/src/contants.tsx @@ -5,58 +5,3 @@ export const OL_LANDING_LINK = 'https://0l.network/'; export const OL_DISCORD_URL = 'https://discord.gg/0lnetwork'; export const OL_COINGECKO_URL = 'https://www.coingecko.com/en/coins/0l-network'; - -export const SOCIAL_LINKS: Record< - string, - { - label: string; - href: string; - logoSvg: JSX.Element; - } -> = { - discord: { - label: 'Discord', - href: 'https://discord.com/invite/0lnetwork', - logoSvg: ( - - - - ), - }, - x: { - label: 'X', - href: 'https://twitter.com/0LNetwork', - logoSvg: ( - - - - ), - }, - github: { - label: 'Github', - href: 'https://github.com/0LNetworkCommunity', - logoSvg: ( - - - - ), - }, -}; diff --git a/web-app/src/modules/core/router.tsx b/web-app/src/modules/core/router.tsx index ddd168e..7ed22c7 100644 --- a/web-app/src/modules/core/router.tsx +++ b/web-app/src/modules/core/router.tsx @@ -11,7 +11,6 @@ import AccountModules from './routes/Account/Modules'; import Block from './routes/Block'; import Root from './Root'; import Validators from './routes/Validators'; -import Test from './routes/Test'; import Module from './routes/Account/Modules/Module'; import Stats from './routes/Stats'; import Postero from './routes/Postero'; @@ -86,10 +85,6 @@ const router = createBrowserRouter([ path: '/postero', element: , }, - { - path: '/test', - element: , - }, ], }, ]); diff --git a/web-app/src/modules/core/routes/Home/Home.tsx b/web-app/src/modules/core/routes/Home/Home.tsx index 36d4740..4d4d77f 100644 --- a/web-app/src/modules/core/routes/Home/Home.tsx +++ b/web-app/src/modules/core/routes/Home/Home.tsx @@ -1,12 +1,12 @@ import { gql, useQuery } from '@apollo/client'; import { FC } from 'react'; import { Link } from 'react-router-dom'; +import { ArrowUpRightIcon } from '@heroicons/react/20/solid'; + import Page from '../../../ui/Page/Page'; import TransactionsTable from '../../../ui/UserTransactionsTable'; import Stats from './Stats'; -import { ArrowUpRightIcon } from '@heroicons/react/20/solid'; import { OL_DISCORD_URL } from '../../../../contants'; -// import NodeMap from '../../../ui/NodeMap'; const GET_USER_TRANSACTIONS = gql` query GetUserTransactions { @@ -36,9 +36,9 @@ const Transactions: FC = () => { const Home: FC = () => { return ( - <> +
-
+

0L Network Explorer

@@ -58,19 +58,17 @@ const Home: FC = () => {
- - -
-
-

Latest Transactions

- - View all - -
- -
-
- + +
+
+

Latest Transactions

+ + View all + +
+ +
+
); }; diff --git a/web-app/src/modules/core/routes/Home/Stats.tsx b/web-app/src/modules/core/routes/Home/Stats.tsx index 62bdf0e..c6e2a88 100644 --- a/web-app/src/modules/core/routes/Home/Stats.tsx +++ b/web-app/src/modules/core/routes/Home/Stats.tsx @@ -4,9 +4,38 @@ import { Link, NavLink } from 'react-router-dom'; import useAptos from '../../../aptos'; import { useLedgerInfo, useTotalSupply, useValidatorSet } from '../../../ol'; import Countdown from '../../../ui/Countdown'; -import PriceStats from './Stats/PriceStats'; import NodeMap from '../../../ui/NodeMap'; +const Validators = () => { + const validatorSet = useValidatorSet(); + + return ( +
+
+ + Validators + +
+
+ Total Validators + + {validatorSet ? validatorSet.active_validators.length : null} + +
+ + {/*
+ Eligible + 70 +
*/} +
+
+
+ +
+
+ ); +}; + const Stats: FC = () => { const aptos = useAptos(); @@ -14,11 +43,8 @@ const Stats: FC = () => { const [nextEpochDate, setNextEpochDate] = useState(); const totalSupply = useTotalSupply(); - const validatorSet = useValidatorSet(); const ledgerInfo = useLedgerInfo(); - const dev = location.search.includes('dev=true'); - useEffect(() => { let timeout: NodeJS.Timeout | undefined = undefined; const load = async () => { @@ -63,35 +89,14 @@ const Stats: FC = () => { }, []); return ( -
- {dev && ( -
- - -
-
- Validator Map -
- {/* @TODO: DUMMY DATA */} -
- Total Validators - 15 -
- {/* @TODO: DUMMY DATA */} -
- Eligible - 70 -
-
-
-
- -
-
+
+
+ {/* */} + +
+
- )} -
Total Supply {
- - Validators - - {validatorSet ? validatorSet.active_validators.length : null} - - -
Next Epoch @@ -159,7 +153,7 @@ const Stats: FC = () => {
- {dev && ( + {false && ( <> {/* @TODO: DUMMY DATA */}
diff --git a/web-app/src/modules/core/routes/Home/Stats/PriceStats.tsx b/web-app/src/modules/core/routes/Home/Stats/PriceStats.tsx index feef5d2..0b008a1 100644 --- a/web-app/src/modules/core/routes/Home/Stats/PriceStats.tsx +++ b/web-app/src/modules/core/routes/Home/Stats/PriceStats.tsx @@ -7,6 +7,9 @@ import { OL_COINGECKO_URL } from '../../../../../contants'; import CoingeckoLogo from '../../../../assets/images/coingecko.png'; const PriceStats: FC = () => { + + const dev = location.search.includes('dev=true'); + const priceIsIncreasing = true; const upIcon = ( @@ -63,41 +66,47 @@ const PriceStats: FC = () => {
{/* @TODO: DUMMY DATA */} -
- $0.00000 - {priceIsIncreasing ? upIcon : downIcon} - - 2.57% - -
- {/* @TODO: DUMMY DATA */} -
-
- Marketcap - $000,000,000 -
-
- Trading Volume (24hrs) - $000,000 + {dev && ( +
+ $0.00000 + {priceIsIncreasing ? upIcon : downIcon} + + 2.57% +
-
+ )} {/* @TODO: DUMMY DATA */} -
-
- Marketcap - $000,000,000 -
-
- Trading Volume (24hrs) - $000,000 -
-
+ {dev && ( + <> +
+
+ Marketcap + $000,000,000 +
+
+ Trading Volume (24hrs) + $000,000 +
+
+ {/* @TODO: DUMMY DATA */} +
+
+ Marketcap + $000,000,000 +
+
+ Trading Volume (24hrs) + $000,000 +
+
+ + )}
); }; diff --git a/web-app/src/modules/core/routes/Test/MessageForm.tsx b/web-app/src/modules/core/routes/Test/MessageForm.tsx deleted file mode 100644 index 6c9ae5f..0000000 --- a/web-app/src/modules/core/routes/Test/MessageForm.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { FC, useState } from "react"; -import { - AptosAccount, BCS, TxnBuilderTypes, - HexString, -} from "aptos"; - -import useAptos from "../../../aptos"; -import axios from "axios"; -import { sha3_256 as sha3Hash } from "@noble/hashes/sha3"; - -const { - AccountAddress, - EntryFunction, - TransactionPayloadEntryFunction, - RawTransaction, - ChainId, - TransactionAuthenticatorEd25519, - Ed25519PublicKey, - Ed25519Signature, - SignedTransaction -} = TxnBuilderTypes; - -const MessageForm: FC = () => { - const aptos = useAptos(); - const [message, setMessage] = useState(''); - - const onSubmit = async (event: React.FormEvent) => { - event.preventDefault(); - - const entryFunctionPayload = new TransactionPayloadEntryFunction( - EntryFunction.natural( - // Fully qualified module name, `AccountAddress::ModuleName` - "0x00000000000000000000000000000000d0383924341821f9e43a6cff46f0a74e::message", - // Module function - "set_message", - // The coin type to transfer - [], - // Arguments for function `transfer`: receiver account address and amount to transfer - [ - BCS.bcsSerializeStr(message) - ], - ), - ); - - const chainId = await aptos.getChainId(); - - const walletAddress = 'D0383924341821F9E43A6CFF46F0A74E'; - const account = await aptos.getAccount(walletAddress); - - const rawTxn = new RawTransaction( - // Transaction sender account address - AccountAddress.fromHex(walletAddress), - - BigInt(account.sequence_number), - entryFunctionPayload, - // Max gas unit to spend - BigInt(2000000), - // Gas price per unit - BigInt(200), - // Expiration timestamp. Transaction is discarded if it is not executed within 10 seconds from now. - BigInt(Math.floor(Date.now() / 1000) + 10), - // BigInt(1699656761), - new ChainId(chainId), - ); - - const privateKey = new HexString("0x60b9b1bcc7f9af9011d7fb26466cb00fd35a21db22f373f085b3bc0604aca78c"); - - const signer = new AptosAccount(privateKey.toUint8Array()); - - const hash = sha3Hash.create(); - hash.update("DIEM::RawTransaction"); - const prefix = hash.digest(); - const body = BCS.bcsToBytes(rawTxn); - const mergedArray = new Uint8Array(prefix.length + body.length); - mergedArray.set(prefix); - mergedArray.set(body, prefix.length); - - const signingMessage = mergedArray; - - const signature = signer.signBuffer(signingMessage); - const sig = new Ed25519Signature(signature.toUint8Array()); - - const authenticator = new TransactionAuthenticatorEd25519( - new Ed25519PublicKey(signer.pubKey().toUint8Array()), - sig - ); - const signedTx = new SignedTransaction(rawTxn, authenticator); - - const bcsTxn = BCS.bcsToBytes(signedTx); - - const res = await axios({ - method: 'POST', - url: 'https://rpc.0l.fyi./v1/transactions', - headers: { - "content-type": "application/x.diem.signed_transaction+bcs", - }, - data: bcsTxn, - }); - console.log(res); - }; - - return ( -
-

Set Message

-
- setMessage(event.target.value)} - /> - -
-
- ); -}; - -export default MessageForm; \ No newline at end of file diff --git a/web-app/src/modules/core/routes/Test/Test.tsx b/web-app/src/modules/core/routes/Test/Test.tsx deleted file mode 100644 index 977bf9a..0000000 --- a/web-app/src/modules/core/routes/Test/Test.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { FC, useEffect, useState } from "react"; -import useAptos from "../../../aptos"; -import MessageForm from "./MessageForm"; - -const Test: FC = () => { - const [message, setMessage] = useState(''); - const aptos = useAptos(); - - useEffect(() => { - const load = async () => { - const res = await aptos.getAccountResource( - '0xD0383924341821F9E43A6CFF46F0A74E', '0xD0383924341821F9E43A6CFF46F0A74E::message::MessageHolder'); - console.log(res); - setMessage((res.data as any).message); - }; - load(); - }, []); - - return ( -
-

Test

-
{`message = ${message}`}
- - - -
- ); -}; - -export default Test; diff --git a/web-app/src/modules/core/routes/Test/index.ts b/web-app/src/modules/core/routes/Test/index.ts deleted file mode 100644 index ef09c7b..0000000 --- a/web-app/src/modules/core/routes/Test/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./Test"; diff --git a/web-app/src/modules/core/routes/Validators/Validators.tsx b/web-app/src/modules/core/routes/Validators/Validators.tsx index 9c47b5b..b413630 100644 --- a/web-app/src/modules/core/routes/Validators/Validators.tsx +++ b/web-app/src/modules/core/routes/Validators/Validators.tsx @@ -1,8 +1,10 @@ import { FC } from 'react'; import { gql, useQuery } from '@apollo/client'; + import Page from '../../../ui/Page'; import ValidatorsTable from './components/ValidatorsTable'; import ValidatorsStats from './components/ValidatorsStats'; +import { IValidator } from '../../../interface/Validator.interface'; const GET_VALIDATORS = gql` query GetValidators { @@ -37,36 +39,13 @@ const GET_VALIDATORS = gql` const Validators: FC = () => { const { data, error } = useQuery<{ - validators: { - address: string; - inSet: boolean; - index: number; - votingPower: number; - account: { - balance: number; - slowWallet: { - unlocked: number; - } | null; - }; - vouches: { - epoch: number; - }[]; - grade: { - compliant: boolean; - failedBlocks: number; - proposedBlocks: number; - }; - currentBid: { - currentBid: number; - expirationEpoch: number; - }; - }[]; + validators: IValidator[]; }>(GET_VALIDATORS); if (error) { console.log('error', error); return ( - +

{`Error: ${error.message}`}

); @@ -83,8 +62,6 @@ const Validators: FC = () => {
); - - return null; }; export default Validators; diff --git a/web-app/src/modules/core/routes/Validators/components/ValidatorsStats.tsx b/web-app/src/modules/core/routes/Validators/components/ValidatorsStats.tsx index 8065e51..bc4b747 100644 --- a/web-app/src/modules/core/routes/Validators/components/ValidatorsStats.tsx +++ b/web-app/src/modules/core/routes/Validators/components/ValidatorsStats.tsx @@ -1,7 +1,8 @@ import { FC } from 'react'; -import { IValidator } from '../../../../interface/Validator.interface'; + import Money from '../../../../ui/Money'; import StatsCard from '../../../../ui/StatsCard'; +import { IValidator } from '../../../../interface/Validator.interface'; interface ValidatorsStatsProps { validators?: IValidator[]; diff --git a/web-app/src/modules/core/routes/Validators/components/ValidatorsTable.tsx b/web-app/src/modules/core/routes/Validators/components/ValidatorsTable.tsx index 1a5e2ca..b737408 100644 --- a/web-app/src/modules/core/routes/Validators/components/ValidatorsTable.tsx +++ b/web-app/src/modules/core/routes/Validators/components/ValidatorsTable.tsx @@ -196,11 +196,7 @@ const ValidatorsTable: FC = ({ validators }) => { {cumulativeValidators ? cumulativeValidators.map((validator) => ( - + )) : Array.from({ length: 10 }).map((_, index) => ( diff --git a/web-app/src/modules/interface/Validator.interface.ts b/web-app/src/modules/interface/Validator.interface.ts index f9ba36d..bb6f1de 100644 --- a/web-app/src/modules/interface/Validator.interface.ts +++ b/web-app/src/modules/interface/Validator.interface.ts @@ -4,9 +4,9 @@ export interface IValidator { index: number; votingPower: number; account: { - balance: number; + balance: string; slowWallet: { - unlocked: number; + unlocked: string; } | null; }; vouches: { diff --git a/web-app/src/modules/ui/AccountAddress/AccountAddress.tsx b/web-app/src/modules/ui/AccountAddress/AccountAddress.tsx index 8a0348f..d28dac3 100644 --- a/web-app/src/modules/ui/AccountAddress/AccountAddress.tsx +++ b/web-app/src/modules/ui/AccountAddress/AccountAddress.tsx @@ -27,7 +27,9 @@ const AccountAddress: FC = ({ address }) => { return (
- +
+ +
= ({ address }) => { - const canvasRef = useRef(null); - - // Function to convert hexadecimal to binary - const hexToBinary = (hex: string): string => { - return hex.split('').reduce((binary, hexChar) => { - return binary + parseInt(hexChar, 16).toString(2).padStart(4, '0'); - }, ''); - }; - - // Function to generate an avatar 16x16 from a hexadecimal address - const generateAvatar = (hexAddress: string) => { - const binaryData = hexToBinary(hexAddress); - const canvas = canvasRef.current; - if (canvas) { - const ctx = canvas.getContext('2d'); - if (ctx) { - // Base colors determined by the first 4 characters of the address - const baseColors = [ - parseInt(hexAddress[0], 16), - parseInt(hexAddress[1], 16), - parseInt(hexAddress[2], 16), - parseInt(hexAddress[3], 16), - ]; - - // Color palette (example colors, can be customized) - const colors = [ - '#FF0000', - '#00FF00', - '#0000FF', - '#FFFF00', // Red, Green, Blue, Yellow - '#FF00FF', - '#00FFFF', - '#FFFFFF', - '#000000', // Magenta, Cyan, White, Black - ]; - - // Map base colors to the color palette - const selectedColors = baseColors.map((baseColor) => colors[baseColor % colors.length]); - - // Use the rest of the address to determine the color shades - for (let y = 0; y < 8; y++) { - for (let x = 0; x < 8; x++) { - const index = (y * 8 + x) * 2; - const bit = binaryData.slice(index, index + 2); - const colorIndex = parseInt(bit, 2) % selectedColors.length; - const color = selectedColors[colorIndex]; - ctx.fillStyle = color; - ctx.fillRect(x * 2, y * 2, 2, 2); // Each color block is 2x2 pixels - } - } - } - } - }; - - useEffect(() => { - if (address) { - generateAvatar(address); - } + const icon = useMemo(() => { + return createIcon({ + seed: address, + size: 8, + scale: 2, + }).toDataURL(); }, [address]); - - return ( -
- -
- ); + return {address}; }; export default AddressAvatar; diff --git a/web-app/src/modules/ui/AddressAvatar/blockies.ts b/web-app/src/modules/ui/AddressAvatar/blockies.ts new file mode 100644 index 0000000..29937ea --- /dev/null +++ b/web-app/src/modules/ui/AddressAvatar/blockies.ts @@ -0,0 +1,121 @@ +// Copied from https://github.com/download13/blockies/blob/master/src/blockies.mjs + +// The random number is a js implementation of the Xorshift PRNG +const randseed = new Array(4); // Xorshift: [x, y, z, w] 32 bit values + +interface Options { + seed: string; + size: number; + scale: number; + color: string; + bgcolor: string; + spotcolor: string; +} + +function seedrand(seed: string) { + randseed.fill(0); + + for (let i = 0; i < seed.length; i++) { + randseed[i % 4] = (randseed[i % 4] << 5) - randseed[i % 4] + seed.charCodeAt(i); + } +} + +function rand() { + // based on Java's String.hashCode(), expanded to 4 32bit values + const t = randseed[0] ^ (randseed[0] << 11); + + randseed[0] = randseed[1]; + randseed[1] = randseed[2]; + randseed[2] = randseed[3]; + randseed[3] = randseed[3] ^ (randseed[3] >> 19) ^ t ^ (t >> 8); + + return (randseed[3] >>> 0) / ((1 << 31) >>> 0); +} + +function createColor(): string { + //saturation is the whole color spectrum + const h = Math.floor(rand() * 360); + //saturation goes from 40 to 100, it avoids greyish colors + const s = rand() * 60 + 40 + '%'; + //lightness can be anything from 0 to 100, but probabilities are a bell curve around 50% + const l = (rand() + rand() + rand() + rand()) * 25 + '%'; + + return `hsl(${h},${s},${l})`; +} + +function createImageData(size: number) { + const width = size; // Only support square icons for now + const height = size; + + const dataWidth = Math.ceil(width / 2); + const mirrorWidth = width - dataWidth; + + const data = []; + for (let y = 0; y < height; y++) { + let row = []; + for (let x = 0; x < dataWidth; x++) { + // this makes foreground and background color to have a 43% (1/2.3) probability + // spot color has 13% chance + row[x] = Math.floor(rand() * 2.3); + } + const r = row.slice(0, mirrorWidth); + r.reverse(); + row = row.concat(r); + + for (let i = 0; i < row.length; i++) { + data.push(row[i]); + } + } + + return data; +} + +function buildOpts(opts: Partial): Options { + const newOpts = { + size: opts.size || 8, + scale: opts.scale || 4, + color: opts.color || createColor(), + bgcolor: opts.bgcolor || createColor(), + spotcolor: opts.spotcolor || createColor(), + seed: opts.seed || Math.floor(Math.random() * Math.pow(10, 16)).toString(16), + }; + + seedrand(newOpts.seed); + + return newOpts; +} + +export function renderIcon(options: Partial, canvas: HTMLCanvasElement) { + let opts = buildOpts(options); + + const imageData = createImageData(opts.size); + const width = Math.sqrt(imageData.length); + + canvas.width = canvas.height = opts.size * opts.scale; + + const cc = canvas.getContext('2d')!; + cc.fillStyle = opts.bgcolor; + cc.fillRect(0, 0, canvas.width, canvas.height); + cc.fillStyle = opts.color; + + for (let i = 0; i < imageData.length; i++) { + // if data is 0, leave the background + if (imageData[i]) { + const row = Math.floor(i / width); + const col = i % width; + + // if data is 2, choose spot color, if 1 choose foreground + cc.fillStyle = imageData[i] == 1 ? opts.color : opts.spotcolor; + + cc.fillRect(col * opts.scale, row * opts.scale, opts.scale, opts.scale); + } + } + + return canvas; +} + +export function createIcon(opts: Partial) { + const canvas = document.createElement('canvas'); + renderIcon(opts, canvas); + return canvas; +} diff --git a/web-app/src/modules/ui/Layout/Footer/Footer.tsx b/web-app/src/modules/ui/Layout/Footer/Footer.tsx index 98c2126..4941986 100644 --- a/web-app/src/modules/ui/Layout/Footer/Footer.tsx +++ b/web-app/src/modules/ui/Layout/Footer/Footer.tsx @@ -1,7 +1,5 @@ import React from 'react'; -import { Link } from 'react-router-dom'; -import Logo from '../../Logo/Logo'; -import { SOCIAL_LINKS } from '../../../../contants'; +import Logo from '../../Logo'; const CI_COMMIT_SHA: string = import.meta.env.VITE_CI_COMMIT_SHA; @@ -17,33 +15,90 @@ const Footer: React.FC = () => { Join the most open, transparent and community driven network today.

- Join our discord - +
- © {new Date().getFullYear()} 0L Network. All rights reserved. + © 2024 0L Network. All rights reserved. - {CI_COMMIT_SHA}
- {Object.keys(SOCIAL_LINKS).map((socialKey) => { - const socialLink = SOCIAL_LINKS[socialKey]; - return ( - - {socialLink.logoSvg} - - ); - })} + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/web-app/src/modules/ui/Logo/index.ts b/web-app/src/modules/ui/Logo/index.ts new file mode 100644 index 0000000..f252704 --- /dev/null +++ b/web-app/src/modules/ui/Logo/index.ts @@ -0,0 +1 @@ +export { default } from "./Logo"; diff --git a/web-app/src/modules/ui/NodeMap/NodeMap.tsx b/web-app/src/modules/ui/NodeMap/NodeMap.tsx index 564e715..dc83efc 100644 --- a/web-app/src/modules/ui/NodeMap/NodeMap.tsx +++ b/web-app/src/modules/ui/NodeMap/NodeMap.tsx @@ -76,6 +76,7 @@ function NodeMap(): ReactNode { left: point[0] - 5, width: 6, height: 6, + borderRadius: 2, }} /> ))} diff --git a/web-app/src/modules/ui/Page/Page.tsx b/web-app/src/modules/ui/Page/Page.tsx index 1306391..6a5848e 100644 --- a/web-app/src/modules/ui/Page/Page.tsx +++ b/web-app/src/modules/ui/Page/Page.tsx @@ -1,13 +1,12 @@ -import clsx from 'clsx'; import { FC, PropsWithChildren, ReactNode } from 'react'; +import clsx from 'clsx'; type Props = PropsWithChildren<{ title?: string | ReactNode; __deprecated_grayBg?: boolean; - mainClassName?: string; }>; -const Page: FC = ({ title, children, __deprecated_grayBg: grayBg, mainClassName = '' }) => { +const Page: FC = ({ title, children, __deprecated_grayBg: grayBg }) => { return ( <> {title && ( @@ -24,13 +23,7 @@ const Page: FC = ({ title, children, __deprecated_grayBg: grayBg, mainCla )} -
+
{children}