Initial commit: add .gitignore and README

This commit is contained in:
defiQUG
2026-02-09 21:51:48 -08:00
commit d4ba3d45e5
174 changed files with 32756 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}

View File

@@ -0,0 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
}
module.exports = nextConfig

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
{
"name": "webapp",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev -p 3001",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@chakra-ui/react": "^2.8.0",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"ethers": "6.7.1",
"framer-motion": "^10.16.1",
"next": "13.4.19",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
"@types/node": "20.5.4",
"@types/react": "18.2.21",
"@types/react-dom": "18.2.7",
"eslint": "8.47.0",
"eslint-config-next": "13.4.19",
"typescript": "5.1.6"
}
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="313" height="311" viewBox="0 0 313 311" fill="none" xmlns="http://www.w3.org/2000/svg">
<style>
path {
fill: black;
}
@media (prefers-color-scheme: dark) {
path {
fill: white;
}
}
</style>
<path d="M 297 155.5 C 297 233.1 234.1 296 156.5 296 C 78.9 296 16 233.1 16 155.5 C 16 77.9 78.9 15 156.5 15 C 234.1 15 297 77.9 297 155.5 Z M 235.952 170.863 C 228.56 170.91 222.596 176.865 222.632 184.184 C 222.632 184.196 222.632 184.196 222.632 184.208 C 222.596 191.515 228.56 197.47 235.952 197.505 C 243.345 197.458 249.297 191.504 249.273 184.208 C 249.333 176.9 243.381 170.922 235.988 170.863 C 235.976 170.863 235.964 170.863 235.952 170.863 Z M 232.204 155.603 C 238.57 151.928 240.766 143.779 237.09 137.415 C 233.414 131.051 225.263 128.855 218.897 132.531 C 212.554 136.182 210.358 144.272 213.964 150.636 L 214.046 150.754 C 217.722 157.106 225.838 159.278 232.192 155.603 C 232.192 155.615 232.204 155.615 232.204 155.603 Z M 94.567 132.603 C 88.198 128.915 80.042 131.076 76.352 137.442 C 72.662 143.808 74.824 151.959 81.194 155.646 C 87.563 159.334 95.719 157.173 99.409 150.808 L 99.409 150.761 C 103.076 144.407 100.913 136.279 94.567 132.603 Z M 116.395 128.926 C 123.761 128.914 129.726 122.935 129.714 115.581 C 129.714 113.279 129.114 111.023 127.973 109.026 C 124.361 102.648 116.242 100.392 109.842 104.01 C 109.83 104.01 109.83 104.022 109.818 104.022 L 109.595 104.151 C 103.265 107.91 101.182 116.086 104.947 122.406 C 107.348 126.435 111.689 128.914 116.395 128.926 Z M 156.385 91.42 C 149.032 91.42 143.064 97.387 143.064 104.741 C 143.064 112.094 149.032 118.062 156.385 118.062 C 163.739 118.062 169.706 112.094 169.706 104.741 C 169.706 97.387 163.739 91.42 156.385 91.42 Z M 113.889 170.873 C 106.535 170.873 100.568 176.84 100.568 184.194 C 100.568 191.547 106.535 197.515 113.889 197.515 C 121.242 197.515 127.21 191.547 127.21 184.194 C 127.21 176.84 121.242 170.873 113.889 170.873 Z M 178.656 134.517 C 171.303 134.517 165.335 140.484 165.335 147.838 C 165.335 155.191 171.303 161.159 178.656 161.159 C 186.01 161.159 191.977 155.191 191.977 147.838 C 191.977 140.484 186.01 134.517 178.656 134.517 Z M 189.788 127.625 L 189.96 127.721 C 196.823 131.132 205.197 128.432 208.672 121.696 C 211.876 115.479 209.801 107.887 203.846 104.079 C 197.007 100.621 188.609 103.248 185.085 109.96 C 181.819 116.165 183.845 123.781 189.788 127.625 Z M 77.307 170.863 C 69.957 170.745 63.88 176.59 63.727 183.942 C 63.727 184.024 63.727 184.13 63.727 184.213 C 63.75 191.577 69.722 197.527 77.083 197.504 C 84.444 197.48 90.392 191.506 90.369 184.142 C 90.345 176.908 84.538 171.005 77.307 170.863 Z M 198.428 170.833 C 191.074 170.833 185.107 176.801 185.107 184.154 C 185.107 191.508 191.074 197.475 198.428 197.475 C 205.781 197.475 211.749 191.508 211.749 184.154 C 211.749 176.801 205.781 170.833 198.428 170.833 Z M 142.078 158.915 C 148.201 154.855 149.866 146.594 145.808 140.469 C 142.113 134.895 134.852 132.935 128.858 135.88 L 128.776 135.927 C 122.196 139.19 119.51 147.181 122.771 153.764 C 126.031 160.347 134.019 163.034 140.6 159.772 C 141.116 159.525 141.608 159.232 142.078 158.915 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,27 @@
import React, { ReactNode } from 'react'
import { Container, Flex, useColorModeValue, Spacer, Heading, Center, Text } from '@chakra-ui/react'
interface LayoutProps {
children: React.ReactNode;
}
export default function Layout({ children }: LayoutProps) {
return (
<div>
<Flex w="100%" bg={useColorModeValue('gray.100', 'gray.900')} px="6" py="5" align="center" justify="space-between">
<Heading size="md">Quorum Quickstart DApp</Heading>
<Spacer />
</Flex>
<Container maxW="container.lg" py='8'>
{children}
</Container>
<Center as="footer" bg={useColorModeValue('gray.100', 'gray.700')} p={6}>
<Text fontSize="md"> &copy; {new Date().getFullYear()} ConsenSys Software, Inc. All rights reserved.</Text>
</Center>
</div>
)
}

View File

@@ -0,0 +1,25 @@
import React, {useEffect, useState } from 'react';
import { Heading, Text, VStack, Box, Button, Input, Spacer, Flex } from '@chakra-ui/react'
interface MMAccountProps {
balance: string | undefined,
chainId: number | undefined
erc20ContractAddress: string
deployedAddressHandler: any
}
export default function MMAccount(props:MMAccountProps){
return (
<Box mb={0} p={4} w='100%' borderWidth="1px" borderRadius="lg">
<Heading my={4} fontSize='xl'>Account</Heading>
<Text my={4}>Details of the account connected to Metamask</Text>
<Text><b>Balance of current account (ETH)</b>: {props.balance}</Text>
<Text><b>ChainId</b>: {props.chainId} </Text>
{/* todo: fix formatting here */}
<Text><b>Address that the QuorumToken was deployed to</b>: </Text>
<Input value={props.erc20ContractAddress} name="erc20ContractAddress" onChange={props.deployedAddressHandler} />
</Box>
)
}

View File

@@ -0,0 +1,14 @@
// In order to interact with a contract from js, we need the contract's ABI
// Im using a human readable format here, but you can just as easily use the compiled output from HardHat
export const QuorumTokenABI = [
// Read-Only Functions
"function balanceOf(address owner) view returns (uint256)",
"function totalSupply() view returns (uint256)",
"function decimals() view returns (uint8)",
"function symbol() view returns (string)",
// Authenticated Functions
"function transfer(address to, uint amount) returns (bool)",
// Events
"event Transfer(address indexed from, address indexed to, uint amount)"
];

View File

@@ -0,0 +1,88 @@
import React, {useEffect, useState } from 'react';
import {Text} from '@chakra-ui/react'
import {QuorumTokenABI as abi} from './QuorumTokenABI'
import {ethers, Contract} from 'ethers'
declare let window: any;
interface ReadQuorumTokenProps {
addressContract: string,
currentAccount: string | undefined
}
export default function ReadQuorumToken(props:ReadQuorumTokenProps){
const addressContract = props.addressContract
const currentAccount = props.currentAccount
const [totalSupply,setTotalSupply]=useState<string>()
const [symbol,setSymbol]= useState<string>("")
const [balance, setBalance] =useState<number|undefined>(undefined)
useEffect( () => {
if(!window.ethereum) return;
const provider = new ethers.BrowserProvider(window.ethereum);
const erc20:Contract = new ethers.Contract(addressContract, abi, provider);
provider.getCode(addressContract).then((result:string)=>{
//check whether it is a contract
if(result === '0x') return
erc20.symbol().then((result:string)=>{
setSymbol(result)
}).catch('error', console.error)
erc20.totalSupply().then((result:string)=>{
setTotalSupply(ethers.formatEther(result))
}).catch('error', console.error);
})
},[])
// when currentAccount changes, we call this hook ie useEffect(()=>{ .... },[currentAccount]
//
useEffect(()=>{
if(!window.ethereum) return
if(!currentAccount) return
queryTokenBalance(window);
const provider = new ethers.BrowserProvider(window.ethereum);
const erc20:Contract = new ethers.Contract(addressContract, abi, provider);
// listen for changes on an Ethereum address
console.log(`listening for Transfer...`)
const fromMe = erc20.filters.Transfer(currentAccount, null)
erc20.on(fromMe, (from, to, amount, event) => {
console.log('Transfer|sent', {from, to, amount, event} )
queryTokenBalance(window)
})
const toMe = erc20.filters.Transfer(null, currentAccount)
erc20.on(toMe, (from, to, amount, event) => {
console.log('Transfer|received', {from, to, amount, event} )
queryTokenBalance(window)
})
// remove listener when the component is unmounted
return () => {
erc20.removeAllListeners(toMe)
erc20.removeAllListeners(fromMe)
}
}, [currentAccount])
async function queryTokenBalance(window:any){
const provider = new ethers.BrowserProvider(window.ethereum);
const erc20:Contract = new ethers.Contract(addressContract, abi, provider);
erc20.balanceOf(currentAccount)
.then((result:string)=>{
setBalance(Number(ethers.formatEther(result)))
}).catch((e:Error)=>console.log(e))
}
return (
<div>
<Text><b>ERC20 Contract Address</b>: {addressContract}</Text>
<Text><b>QuorumToken totalSupply</b>: {totalSupply} {symbol}</Text>
<Text><b>QuorumToken in current account</b>: {balance} {symbol}</Text>
</div>
)
}

View File

@@ -0,0 +1,54 @@
import React, { useEffect, useState } from 'react';
import { Text, Button, Input , NumberInput, NumberInputField, FormControl, FormLabel } from '@chakra-ui/react';
import {ethers, Contract} from 'ethers';
import {QuorumTokenABI as abi} from './QuorumTokenABI';
import { TransactionResponse,TransactionReceipt } from "@ethersproject/abstract-provider";
declare let window: any;
interface Props {
addressContract: string,
currentAccount: string | undefined
}
export default function TransferQuorumToken(props:Props){
const addressContract = props.addressContract
const currentAccount = props.currentAccount
const [amount, setAmount]=useState<string>('100')
const [toAddress, setToAddress]=useState<string>("")
const handleChange = (value:string) => setAmount(value)
// https://docs.ethers.org/v6/getting-started/#starting-contracts
async function transfer(event:React.FormEvent) {
event.preventDefault()
// const provider = new ethers.JsonRpcProvider('http://127.0.0.1:8545');
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const erc20:Contract = new ethers.Contract(addressContract, abi, signer);
erc20.transfer(toAddress, ethers.parseEther(amount))
.then((tr: TransactionResponse) => {
console.log(`TransactionResponse TX hash: ${tr.hash}`)
// todo: maybe put this in a modal thing?
tr.wait().then((receipt:TransactionReceipt)=>{console.log("transfer receipt",receipt)})
})
.catch((e:Error)=>console.log(e))
}
return (
<form onSubmit={transfer}>
<FormControl>
<FormLabel htmlFor='amount'>Amount: </FormLabel>
<NumberInput defaultValue={amount} min={10} max={1000} onChange={handleChange}>
<NumberInputField />
</NumberInput>
<FormLabel htmlFor='toaddress'>To address: </FormLabel>
<Input id="toaddress" type="text" required onChange={(e) => setToAddress(e.target.value)} my={3}/>
<Button type="submit" isDisabled={!currentAccount}>Transfer</Button>
</FormControl>
</form>
)
}

View File

@@ -0,0 +1,19 @@
import type { AppProps } from "next/app";
import { ChakraProvider } from "@chakra-ui/react";
import "../../styles/globals.css";
import Layout from '../components/Layout';
function MyApp({ Component, pageProps, router }: AppProps) {
return (
<ChakraProvider>
<title>Quorum Quickstart DApp</title>
<Layout>
<Component {...pageProps} />
</Layout>
</ChakraProvider>
)
}
export default MyApp;

View File

@@ -0,0 +1,19 @@
import { ColorModeScript } from "@chakra-ui/react";
import NextDocument, { Html, Head, Main, NextScript } from "next/document";
export default class Document extends NextDocument {
render() {
return (
<Html lang="en">
<Head>
<link rel="shortcut icon" href="/explorer/images/favicon.svg" />
</Head>
<body>
<ColorModeScript />
<Main />
<NextScript />
</body>
</Html>
);
}
}

View File

@@ -0,0 +1,106 @@
import type { NextPage } from 'next'
import { Heading, Text, VStack, Box, Button, Input, Spacer, Flex } from '@chakra-ui/react'
import { useState, useEffect} from 'react'
import {ethers} from "ethers"
import ReadQuorumToken from "../components/quorumToken/ReadQuorumToken"
import TransferQuorumToken from "../components/quorumToken/TransferQuorumToken"
import MMAccount from "../components/MMAccount"
declare let window:any
export default function Home() {
const [balance, setBalance] = useState<string | undefined>();
const [currentAccount, setCurrentAccount] = useState<string | undefined>();
const [erc20ContractAddress, setErc20ContractAddress] = useState<string>("0x");
const [chainId, setChainId] = useState<number | undefined>();
useEffect( () => {
if(!currentAccount || !ethers.isAddress(currentAccount)) return;
if(!window.ethereum) return;
const provider = new ethers.BrowserProvider(window.ethereum);
provider.getBalance(currentAccount).then((result)=> {
setBalance(ethers.formatEther(result));
})
provider.getNetwork().then((result)=>{
setChainId(ethers.toNumber(result.chainId));
})
},[currentAccount])
const onClickConnect = () => {
if(!window.ethereum) {
console.log("please install MetaMask");
return;
}
const provider = new ethers.BrowserProvider(window.ethereum);
// MetaMask requires requesting permission to connect users accounts
provider.send("eth_requestAccounts", [])
.then((accounts)=>{
if(accounts.length>0) setCurrentAccount(accounts[0])
})
.catch((e)=>console.log(e))
}
const onClickDisconnect = () => {
setBalance(undefined)
setCurrentAccount(undefined)
}
const deployedAddressHandler = (e: any) => {
setErc20ContractAddress(e.target.value);
}
return (
<>
<Heading as="h3" my={4}>QuorumToken</Heading>
<VStack>
<Box w='100%' my={4}>
{currentAccount
? <Button type="button" w='100%' onClick={onClickDisconnect}>
Connected to Metamask with account: {currentAccount}
</Button>
: <Button type="button" w='100%' onClick={onClickConnect}>
Connect to MetaMask
</Button>
}
</Box>
{currentAccount
?<MMAccount
balance={balance}
chainId={chainId}
erc20ContractAddress={erc20ContractAddress}
deployedAddressHandler={deployedAddressHandler} />
:<></>
}
{(erc20ContractAddress!="0x")
?<Box mb={0} p={4} w='100%' borderWidth="1px" borderRadius="lg">
<Heading my={4} fontSize='xl'>Read QuorumToken</Heading>
<Text my={4}>Query the smart contract info at address provided</Text>
<Spacer />
<ReadQuorumToken
addressContract={erc20ContractAddress}
currentAccount={currentAccount}
/>
</Box>
:<></>
}
{(erc20ContractAddress!="0x")
?<Box mb={0} p={4} w='100%' borderWidth="1px" borderRadius="lg">
<Heading my={4} fontSize='xl'>Transfer QuorumToken</Heading>
<Text my={4}>Interact with the token</Text>
<TransferQuorumToken
addressContract={erc20ContractAddress}
currentAccount={currentAccount}
/>
</Box>
:<></>
}
</VStack>
</>
)
}

View File

@@ -0,0 +1,21 @@
:root,
body {
height: 100%;
margin: 0;
padding: 0;
width: 100%;
}
#__next {
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
width: 100%;
}
main {
display: flex;
flex: 1;
flex-direction: column;
}

View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"baseUrl": "./src"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}