add PAD token
This commit is contained in:
parent
545bdf7bae
commit
fec688478b
|
|
@ -1,5 +1,6 @@
|
||||||
rm "./frontend/src/evm-output/*"
|
rm ./frontend/src/evm-output/*
|
||||||
|
|
||||||
cp "./evm/ignition/deployments/chain-31337/deployed_addresses.json" "./frontend/src/evm-output/deployed_addresses.json"
|
cp "./evm/ignition/deployments/chain-31337/deployed_addresses.json" "./frontend/src/evm-output/deployed_addresses.json"
|
||||||
|
|
||||||
cp "./evm/ignition/deployments/chain-31337/artifacts/MessageBoxModule#MessageBox.json" "./frontend/src/evm-output/MessageBox.artifacts.json"
|
cp "./evm/ignition/deployments/chain-31337/artifacts/MessageBoxModule#MessageBox.json" "./frontend/src/evm-output/MessageBox.artifacts.json"
|
||||||
|
cp "./evm/ignition/deployments/chain-31337/artifacts/MainModule#PADToken.json" "./frontend/src/evm-output/PADToken.artifacts.json"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export const PAD_TOKEN_INITIAL_OWNER =
|
||||||
|
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266";
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Compatible with OpenZeppelin Contracts ^5.0.0
|
||||||
|
pragma solidity ^0.8.20;
|
||||||
|
|
||||||
|
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||||
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||||
|
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
|
||||||
|
|
||||||
|
contract PADToken is ERC20, Ownable, ERC20Permit {
|
||||||
|
constructor(address initialOwner)
|
||||||
|
ERC20("PAD Token", "PAD")
|
||||||
|
Ownable(initialOwner)
|
||||||
|
ERC20Permit("PAD Token")
|
||||||
|
{}
|
||||||
|
|
||||||
|
function mint(address to, uint256 amount) public onlyOwner {
|
||||||
|
_mint(to, amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
|
||||||
|
import { PAD_TOKEN_INITIAL_OWNER } from "../../config";
|
||||||
|
|
||||||
|
const MainModule = buildModule("MainModule", (m) => {
|
||||||
|
const initialOwner = m.getParameter(
|
||||||
|
"padTokenInitialOwner",
|
||||||
|
PAD_TOKEN_INITIAL_OWNER
|
||||||
|
);
|
||||||
|
|
||||||
|
const padToken = m.contract("PADToken", [initialOwner]);
|
||||||
|
|
||||||
|
return { padToken };
|
||||||
|
});
|
||||||
|
|
||||||
|
export default MainModule;
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@openzeppelin/contracts": "^5.0.2",
|
||||||
"hardhat": "^2.22.2"
|
"hardhat": "^2.22.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ settings:
|
||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@openzeppelin/contracts':
|
||||||
|
specifier: ^5.0.2
|
||||||
|
version: 5.0.2
|
||||||
hardhat:
|
hardhat:
|
||||||
specifier: ^2.22.2
|
specifier: ^2.22.2
|
||||||
version: 2.22.2(ts-node@10.9.2)(typescript@5.4.5)
|
version: 2.22.2(ts-node@10.9.2)(typescript@5.4.5)
|
||||||
|
|
@ -844,6 +847,10 @@ packages:
|
||||||
'@nomicfoundation/solidity-analyzer-win32-ia32-msvc': 0.1.1
|
'@nomicfoundation/solidity-analyzer-win32-ia32-msvc': 0.1.1
|
||||||
'@nomicfoundation/solidity-analyzer-win32-x64-msvc': 0.1.1
|
'@nomicfoundation/solidity-analyzer-win32-x64-msvc': 0.1.1
|
||||||
|
|
||||||
|
/@openzeppelin/contracts@5.0.2:
|
||||||
|
resolution: {integrity: sha512-ytPc6eLGcHHnapAZ9S+5qsdomhjo6QBHTDRRBFfTxXIpsicMhVPouPgmUPebZZZGX7vt9USA+Z+0M0dSVtSUEA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@scure/base@1.1.6:
|
/@scure/base@1.1.6:
|
||||||
resolution: {integrity: sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==}
|
resolution: {integrity: sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,19 @@ module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
env: { browser: true, es2020: true },
|
env: { browser: true, es2020: true },
|
||||||
extends: [
|
extends: [
|
||||||
'eslint:recommended',
|
"eslint:recommended",
|
||||||
'plugin:@typescript-eslint/recommended',
|
"plugin:@typescript-eslint/recommended",
|
||||||
'plugin:react-hooks/recommended',
|
"plugin:react-hooks/recommended",
|
||||||
],
|
],
|
||||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
ignorePatterns: ["dist", ".eslintrc.cjs"],
|
||||||
parser: '@typescript-eslint/parser',
|
parser: "@typescript-eslint/parser",
|
||||||
plugins: ['react-refresh'],
|
plugins: ["react-refresh"],
|
||||||
rules: {
|
rules: {
|
||||||
'react-refresh/only-export-components': [
|
"react-refresh/only-export-components": [
|
||||||
'warn',
|
"warn",
|
||||||
{ allowConstantExport: true },
|
{ allowConstantExport: true },
|
||||||
],
|
],
|
||||||
|
// My additions
|
||||||
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,8 @@
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"sonner": "^1.4.41",
|
"sonner": "^1.4.41",
|
||||||
"web3": "^4.7.0"
|
"web3": "^4.7.0",
|
||||||
|
"zustand": "^4.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tanstack/router-vite-plugin": "^1.28.2",
|
"@tanstack/router-vite-plugin": "^1.28.2",
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,9 @@ dependencies:
|
||||||
web3:
|
web3:
|
||||||
specifier: ^4.7.0
|
specifier: ^4.7.0
|
||||||
version: 4.7.0(typescript@5.4.5)
|
version: 4.7.0(typescript@5.4.5)
|
||||||
|
zustand:
|
||||||
|
specifier: ^4.5.2
|
||||||
|
version: 4.5.2(@types/react@18.2.79)(react@18.2.0)
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@tanstack/router-vite-plugin':
|
'@tanstack/router-vite-plugin':
|
||||||
|
|
@ -1026,7 +1029,6 @@ packages:
|
||||||
|
|
||||||
/@types/prop-types@15.7.12:
|
/@types/prop-types@15.7.12:
|
||||||
resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==}
|
resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/react-dom@18.2.25:
|
/@types/react-dom@18.2.25:
|
||||||
resolution: {integrity: sha512-o/V48vf4MQh7juIKZU2QGDfli6p1+OOi5oXx36Hffpc9adsHeXjVp8rHuPkjd8VT8sOJ2Zp05HR7CdpGTIUFUA==}
|
resolution: {integrity: sha512-o/V48vf4MQh7juIKZU2QGDfli6p1+OOi5oXx36Hffpc9adsHeXjVp8rHuPkjd8VT8sOJ2Zp05HR7CdpGTIUFUA==}
|
||||||
|
|
@ -1039,7 +1041,6 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/prop-types': 15.7.12
|
'@types/prop-types': 15.7.12
|
||||||
csstype: 3.1.3
|
csstype: 3.1.3
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/semver@7.5.8:
|
/@types/semver@7.5.8:
|
||||||
resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
|
resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
|
||||||
|
|
@ -1484,7 +1485,6 @@ packages:
|
||||||
|
|
||||||
/csstype@3.1.3:
|
/csstype@3.1.3:
|
||||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/culori@3.3.0:
|
/culori@3.3.0:
|
||||||
resolution: {integrity: sha512-pHJg+jbuFsCjz9iclQBqyL3B2HLCBF71BwVNujUYEvCeQMvV97R59MNK3R2+jgJ3a1fcZgI9B3vYgz8lzr/BFQ==}
|
resolution: {integrity: sha512-pHJg+jbuFsCjz9iclQBqyL3B2HLCBF71BwVNujUYEvCeQMvV97R59MNK3R2+jgJ3a1fcZgI9B3vYgz8lzr/BFQ==}
|
||||||
|
|
@ -3233,3 +3233,23 @@ packages:
|
||||||
|
|
||||||
/zod@3.22.4:
|
/zod@3.22.4:
|
||||||
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
|
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
|
||||||
|
|
||||||
|
/zustand@4.5.2(@types/react@18.2.79)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==}
|
||||||
|
engines: {node: '>=12.7.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '>=16.8'
|
||||||
|
immer: '>=9.0.6'
|
||||||
|
react: '>=16.8'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
immer:
|
||||||
|
optional: true
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.2.79
|
||||||
|
react: 18.2.0
|
||||||
|
use-sync-external-store: 1.2.0(react@18.2.0)
|
||||||
|
dev: false
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { useStore } from "../store";
|
||||||
|
import { shortenAddress, tryWithToast } from "../utils";
|
||||||
|
import { web3 } from "../web3";
|
||||||
|
|
||||||
|
export function ConnectMetamaskButton() {
|
||||||
|
const connectedAccount = useStore((state) => state.connectedAccount);
|
||||||
|
const setConnectedAccount = useStore((state) => state.setConnectedAccount);
|
||||||
|
const unsetConnectedAccount = useStore(
|
||||||
|
(state) => state.unsetConnectedAccount
|
||||||
|
);
|
||||||
|
|
||||||
|
// https://docs.web3js.org/guides/getting_started/metamask/#react-app
|
||||||
|
async function connectMetamask() {
|
||||||
|
await tryWithToast("Connect MetaMask", async () => {
|
||||||
|
// Request user to connect accounts (MetaMask will prompt)
|
||||||
|
await window.ethereum.request({ method: "eth_requestAccounts" });
|
||||||
|
|
||||||
|
// Get the connected accounts
|
||||||
|
const accounts = await web3.eth.getAccounts();
|
||||||
|
|
||||||
|
// Show the first connected account in the page
|
||||||
|
setConnectedAccount(accounts[0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function disconnectWallet() {
|
||||||
|
unsetConnectedAccount();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn"
|
||||||
|
onClick={() =>
|
||||||
|
connectedAccount ? disconnectWallet() : connectMetamask()
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{connectedAccount ? shortenAddress(connectedAccount) : "Connect MetaMask"}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"MessageBoxModule#MessageBox": "0x5FbDB2315678afecb367f032d93F642f64180aa3"
|
"MessageBoxModule#MessageBox": "0x5FbDB2315678afecb367f032d93F642f64180aa3",
|
||||||
|
"MainModule#PADToken": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,18 @@ import { Route as rootRoute } from './routes/__root'
|
||||||
|
|
||||||
// Create Virtual Routes
|
// Create Virtual Routes
|
||||||
|
|
||||||
|
const PadTokenLazyImport = createFileRoute('/pad-token')()
|
||||||
const MessageBoxLazyImport = createFileRoute('/message-box')()
|
const MessageBoxLazyImport = createFileRoute('/message-box')()
|
||||||
const AboutLazyImport = createFileRoute('/about')()
|
const AboutLazyImport = createFileRoute('/about')()
|
||||||
const IndexLazyImport = createFileRoute('/')()
|
const IndexLazyImport = createFileRoute('/')()
|
||||||
|
|
||||||
// Create/Update Routes
|
// Create/Update Routes
|
||||||
|
|
||||||
|
const PadTokenLazyRoute = PadTokenLazyImport.update({
|
||||||
|
path: '/pad-token',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any).lazy(() => import('./routes/pad-token.lazy').then((d) => d.Route))
|
||||||
|
|
||||||
const MessageBoxLazyRoute = MessageBoxLazyImport.update({
|
const MessageBoxLazyRoute = MessageBoxLazyImport.update({
|
||||||
path: '/message-box',
|
path: '/message-box',
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
|
|
@ -53,6 +59,10 @@ declare module '@tanstack/react-router' {
|
||||||
preLoaderRoute: typeof MessageBoxLazyImport
|
preLoaderRoute: typeof MessageBoxLazyImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
|
'/pad-token': {
|
||||||
|
preLoaderRoute: typeof PadTokenLazyImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,6 +72,7 @@ export const routeTree = rootRoute.addChildren([
|
||||||
IndexLazyRoute,
|
IndexLazyRoute,
|
||||||
AboutLazyRoute,
|
AboutLazyRoute,
|
||||||
MessageBoxLazyRoute,
|
MessageBoxLazyRoute,
|
||||||
|
PadTokenLazyRoute,
|
||||||
])
|
])
|
||||||
|
|
||||||
/* prettier-ignore-end */
|
/* prettier-ignore-end */
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { createRootRoute, Link, Outlet } from "@tanstack/react-router";
|
import { createRootRoute, Link, Outlet } from "@tanstack/react-router";
|
||||||
import { Toaster } from "sonner";
|
import { Toaster } from "sonner";
|
||||||
|
import { ConnectMetamaskButton } from "../components/ConnectMetamaskButton";
|
||||||
|
|
||||||
export const Route = createRootRoute({
|
export const Route = createRootRoute({
|
||||||
component: RootLayout,
|
component: RootLayout,
|
||||||
|
|
@ -8,10 +9,11 @@ export const Route = createRootRoute({
|
||||||
function RootLayout() {
|
function RootLayout() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<header className="bg-primary text-primary-content p-4">
|
<header className="bg-primary text-primary-content p-4 flex items-center">
|
||||||
|
<div className="flex-grow">
|
||||||
<span className="text-4xl font-bold">OsiPad™</span>
|
<span className="text-4xl font-bold">OsiPad™</span>
|
||||||
<hr className="my-1" />
|
<hr className="my-1" />
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-3">
|
||||||
<Link to="/" className="[&.active]:font-bold">
|
<Link to="/" className="[&.active]:font-bold">
|
||||||
Home
|
Home
|
||||||
</Link>
|
</Link>
|
||||||
|
|
@ -21,7 +23,12 @@ function RootLayout() {
|
||||||
<Link to="/message-box" className="[&.active]:font-bold">
|
<Link to="/message-box" className="[&.active]:font-bold">
|
||||||
Message Box
|
Message Box
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link to="/pad-token" className="[&.active]:font-bold">
|
||||||
|
PAD Token
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<ConnectMetamaskButton />
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,18 @@
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { tryWithToast } from "../utils";
|
import { useStore } from "../store";
|
||||||
import { messageBox, web3 } from "../web3";
|
import { toastError, tryWithToast } from "../utils";
|
||||||
|
import { messageBox } from "../web3";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/message-box")({
|
export const Route = createLazyFileRoute("/message-box")({
|
||||||
component: MessageBoxPage,
|
component: MessageBoxPage,
|
||||||
});
|
});
|
||||||
|
|
||||||
function MessageBoxPage() {
|
function MessageBoxPage() {
|
||||||
const [connectedAccount, setConnectedAccount] = useState("(nothingness)");
|
const connectedAccount = useStore((state) => state.connectedAccount);
|
||||||
const [message, setMessage] = useState("(nothingness)");
|
const [message, setMessage] = useState("(nothingness)");
|
||||||
const [newMessage, setNewMessage] = useState("");
|
const [newMessage, setNewMessage] = useState("");
|
||||||
|
|
||||||
// https://docs.web3js.org/guides/getting_started/metamask/#react-app
|
|
||||||
async function connectMetamask() {
|
|
||||||
await tryWithToast("Connect MetaMask", async () => {
|
|
||||||
// Request user to connect accounts (MetaMask will prompt)
|
|
||||||
await window.ethereum.request({ method: "eth_requestAccounts" });
|
|
||||||
|
|
||||||
// Get the connected accounts
|
|
||||||
const accounts = await web3.eth.getAccounts();
|
|
||||||
|
|
||||||
// Show the first connected account in the page
|
|
||||||
setConnectedAccount(accounts[0]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getMessage() {
|
async function getMessage() {
|
||||||
await tryWithToast("Get Message", async () => {
|
await tryWithToast("Get Message", async () => {
|
||||||
const _message: string = await messageBox.methods.getMessage().call();
|
const _message: string = await messageBox.methods.getMessage().call();
|
||||||
|
|
@ -34,6 +21,11 @@ function MessageBoxPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _setMessage() {
|
async function _setMessage() {
|
||||||
|
if (!connectedAccount) {
|
||||||
|
toastError("Set Message", "Connect your wallet first.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await tryWithToast("Set Message", async () => {
|
await tryWithToast("Set Message", async () => {
|
||||||
await messageBox.methods
|
await messageBox.methods
|
||||||
.setMessage(newMessage)
|
.setMessage(newMessage)
|
||||||
|
|
@ -43,24 +35,6 @@ function MessageBoxPage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto p-4 grid md:grid-cols-2 xl:grid-cols-3 gap-4">
|
<div className="container mx-auto p-4 grid md:grid-cols-2 xl:grid-cols-3 gap-4">
|
||||||
<div className="card bg-base-200">
|
|
||||||
<div className="card-body items-start">
|
|
||||||
<h2 className="text-2xl font-bold">✨Essentials✨</h2>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-primary"
|
|
||||||
onClick={() => connectMetamask()}
|
|
||||||
>
|
|
||||||
Connect to MetaMask
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<span className="font-bold">Connected account address:</span>{" "}
|
|
||||||
{connectedAccount}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="card bg-base-200">
|
<div className="card bg-base-200">
|
||||||
<div className="card-body items-start">
|
<div className="card-body items-start">
|
||||||
<h2 className="text-2xl font-bold">Get Message</h2>
|
<h2 className="text-2xl font-bold">Get Message</h2>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
|
import { useState } from "react";
|
||||||
|
import type { Numbers } from "web3";
|
||||||
|
import { useStore } from "../store";
|
||||||
|
import { getFormInputValue, toastError, tryWithToast } from "../utils";
|
||||||
|
import { padToken, web3 } from "../web3";
|
||||||
|
|
||||||
|
export const Route = createLazyFileRoute("/pad-token")({
|
||||||
|
component: PadTokenPage,
|
||||||
|
});
|
||||||
|
|
||||||
|
function PadTokenPage() {
|
||||||
|
const connectedAccount = useStore((state) => state.connectedAccount);
|
||||||
|
const [balance, setBalance] = useState("(nothingness)");
|
||||||
|
|
||||||
|
const onBalanceOfFormSubmit: React.FormEventHandler<HTMLFormElement> = async (
|
||||||
|
event
|
||||||
|
) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const address = getFormInputValue(
|
||||||
|
event.target as HTMLFormElement,
|
||||||
|
"address"
|
||||||
|
).trim();
|
||||||
|
if (!address) {
|
||||||
|
toastError("Balance Of", "Enter an address.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await tryWithToast("Balance Of", async () => {
|
||||||
|
const balanceBN: Numbers = await padToken.methods
|
||||||
|
.balanceOf(address)
|
||||||
|
.call();
|
||||||
|
setBalance(web3.utils.fromWei(balanceBN, "ether"));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMintFormSubmit: React.FormEventHandler<HTMLFormElement> = async (
|
||||||
|
event
|
||||||
|
) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (!connectedAccount) {
|
||||||
|
toastError("Mint", "Connect your wallet first.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const to = getFormInputValue(event.target as HTMLFormElement, "to").trim();
|
||||||
|
if (!to) {
|
||||||
|
toastError("Mint", "Enter a target address.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const amount = getFormInputValue(
|
||||||
|
event.target as HTMLFormElement,
|
||||||
|
"amount"
|
||||||
|
).trim();
|
||||||
|
if (!amount) {
|
||||||
|
toastError("Mint", "Enter an amount.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await tryWithToast("Mint", async () => {
|
||||||
|
await padToken.methods
|
||||||
|
.mint(to, web3.utils.toWei(amount, "ether"))
|
||||||
|
.send({ from: connectedAccount });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto p-4 grid md:grid-cols-2 xl:grid-cols-3 gap-4">
|
||||||
|
<div className="card bg-base-200">
|
||||||
|
<div className="card-body items-start">
|
||||||
|
<h2 className="text-2xl font-bold">Balance Of</h2>
|
||||||
|
<form onSubmit={onBalanceOfFormSubmit}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="address"
|
||||||
|
className="input"
|
||||||
|
placeholder="Address"
|
||||||
|
required
|
||||||
|
minLength={42}
|
||||||
|
maxLength={42}
|
||||||
|
/>
|
||||||
|
<button type="submit" className="btn btn-primary">
|
||||||
|
Get
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
<span className="font-bold">Balance:</span> {balance} PAD
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="card bg-base-200">
|
||||||
|
<div className="card-body items-start">
|
||||||
|
<h2 className="text-2xl font-bold">Mint</h2>
|
||||||
|
<form onSubmit={onMintFormSubmit}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="to"
|
||||||
|
className="input"
|
||||||
|
placeholder="To"
|
||||||
|
required
|
||||||
|
minLength={42}
|
||||||
|
maxLength={42}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="amount"
|
||||||
|
className="input"
|
||||||
|
placeholder="Amount"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<button type="submit" className="btn btn-primary">
|
||||||
|
Mint
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { create } from "zustand";
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
connectedAccount: string | null;
|
||||||
|
setConnectedAccount: (newAccount: string) => void;
|
||||||
|
unsetConnectedAccount: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useStore = create<State>()((set) => ({
|
||||||
|
connectedAccount: null,
|
||||||
|
setConnectedAccount: (newAccount) => set({ connectedAccount: newAccount }),
|
||||||
|
unsetConnectedAccount: () => set({ connectedAccount: null }),
|
||||||
|
}));
|
||||||
|
|
@ -1,21 +1,41 @@
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
export function toastSuccess(taskName: string) {
|
||||||
|
toast.success(`${taskName}: Success`, {
|
||||||
|
className: "border-none bg-green-100",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toastError(taskName: string, errorMessage: string) {
|
||||||
|
toast.error(`${taskName}: Error`, {
|
||||||
|
description: errorMessage,
|
||||||
|
className: "border-none bg-red-100",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function tryWithToast(
|
export async function tryWithToast(
|
||||||
taskName: string,
|
taskName: string,
|
||||||
taskFn: () => Promise<void>
|
taskFn: () => Promise<void>
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
await taskFn();
|
await taskFn();
|
||||||
toast.success(`${taskName}: Success`, {
|
toastSuccess(taskName);
|
||||||
className: "border-none bg-green-100",
|
|
||||||
});
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
toast.error(`${taskName}: Error`, {
|
toastError(
|
||||||
description:
|
taskName,
|
||||||
error.message ||
|
error.message ||
|
||||||
"No error message. Check the DevTools console for details.",
|
"No error message. Check the DevTools console for details."
|
||||||
className: "border-none bg-red-100",
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function shortenAddress(address: string) {
|
||||||
|
return address.slice(0, 6) + "..." + address.slice(-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFormInputValue(form: HTMLFormElement, name: string) {
|
||||||
|
// @ts-expect-error
|
||||||
|
const input = form.elements[name] as HTMLInputElement;
|
||||||
|
return input.value;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { Web3 } from "web3";
|
import { Web3 } from "web3";
|
||||||
import messageBoxArtifacts from "./evm-output/MessageBox.artifacts.json";
|
import messageBoxArtifacts from "./evm-output/MessageBox.artifacts.json";
|
||||||
|
import padTokenArtifacts from "./evm-output/PADToken.artifacts.json";
|
||||||
import deployedAddresses from "./evm-output/deployed_addresses.json";
|
import deployedAddresses from "./evm-output/deployed_addresses.json";
|
||||||
|
|
||||||
export let web3: Web3;
|
export let web3: Web3;
|
||||||
|
|
@ -17,3 +18,8 @@ export const messageBox = new web3!.eth.Contract(
|
||||||
messageBoxArtifacts.abi,
|
messageBoxArtifacts.abi,
|
||||||
deployedAddresses["MessageBoxModule#MessageBox"]
|
deployedAddresses["MessageBoxModule#MessageBox"]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const padToken = new web3!.eth.Contract(
|
||||||
|
padTokenArtifacts.abi,
|
||||||
|
deployedAddresses["MainModule#PADToken"]
|
||||||
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue