Search
Duplicate
Try Notion
πŸŒ“
Coding a NextJs - React APP
We are going to create a website to use our Faucet-API and to send coins between wallets, that will look something like this:
Requirements
nvm use v18.12.0 # only if you are using nvm to manage your node installation cd /tmp npx create-next-app@latest workshop --typescript cd workshop npm install -D tailwindcss postcss autoprefixer npx tailwindcss init -p npm add evmosjs npm add @hanchon/signature-to-pubkey
Bash
Add tailwind support
./tailwind.config.js, change the content variable
content: [ "./pages/**/*.{ts,tsx}", "./src/**/*.{ts,tsx}", "./public/**/*.html", ],
JSON
./styles/globals.css, replace the content
@tailwind base; @tailwind components; @tailwind utilities;
CSS
Faucet Component
Let’s create a component to connect to our API
Create the file: src/components/faucet.tsx
import { useEffect, useState } from "react"; import { addressConverter } from "evmosjs"; declare global { interface Window { ethereum?: any; keplr?: any; getOfflineSigner?: any; } } export async function getKeplrAddress() { try { await window.keplr.enable("evmos_9001-2"); const offlineSigner = window.getOfflineSigner("evmos_9001-2"); const wallets = await offlineSigner.getAccounts(); return wallets[0].address; } catch (e) { return ""; } } export async function getMetamaskAddress() { try { const accounts = await window.ethereum.request({ method: "eth_requestAccounts", }); return accounts[0]; } catch (e) { return ""; } } export async function alertTxResult(response: any) { if (response.tx_response.code !== 0) { return alert(`Transaction Failed:${response.tx_response.raw_log}`); } else { return alert(`Transaction sent! ${response.tx_response.txhash}`); } } export const buttonStyle = "border-green-300 border-2 rounded-lg p-2"; export default function Faucet() { const [wallet, setWallet] = useState(""); const [faucetWalletEvmos, setFaucetWalletEvmos] = useState(""); const [faucetWalletEth, setFaucetWalletEth] = useState(""); const [balance, setBalance] = useState(""); useEffect(() => { (async () => { const res = await fetch("http://localhost:8080/address"); const values = await res.json(); setFaucetWalletEth(values.eth); setFaucetWalletEvmos(values.evmos); })(); (async () => { const res = await fetch("http://localhost:8080/balance"); const values = await res.json(); setBalance(values.balance); })(); }); return ( <div className="flex flex-col"> <div className="flex flex-col w-full text-center p-2"> <h1>Faucet Wallet:</h1> <h2>{faucetWalletEvmos}</h2> <h2>{faucetWalletEth}</h2> <h1>Balance:</h1> <h2>{balance}</h2> </div> <div className="flex flex-row space-x-7 py-5 mx-auto"> <button className={buttonStyle} onClick={async () => { const tempWallet = await getMetamaskAddress(); setWallet(addressConverter.ethToEvmos(tempWallet)); }} > Get Address Metamask </button> <button className={buttonStyle} onClick={async () => { const tempWallet = await getKeplrAddress(); setWallet(tempWallet); }} > Get Address Keplr </button> </div> <div className="flex flex-col w-full text-center"> <div className="text-lg">Selected Wallet</div> <div>{wallet}</div> </div> <div className="flex justify-center py-2"> <button className={buttonStyle} onClick={async () => { const res = await fetch(`http://localhost:8080/faucet/${wallet}`); const response = await res.json(); alertTxResult(response); }} > Request coins </button> </div> </div> ); }
TypeScript
Edit your file pages/index.tsx to look like this:
import Head from "next/head"; import Faucet from "../src/components/faucet"; import styles from "../styles/Home.module.css"; export default function Home() { return ( <div className="bg-gray-700 text-white"> <Head> <title>Wallet workshop</title> <meta name="description" content="Evmosjs example" /> <link rel="icon" href="/favicon.ico" /> </Head> <main className={styles.main}> <h1 className={styles.title}>Wallet workshop</h1> <Faucet /> </main> </div> ); }
TypeScript
Message Send between wallets
Let’s create a new component to send coins using Metamask/Keplr and EvmosJS
Constants:
Create the file src/components/constants.ts:
export const MAINNET_CHAIN = { chainId: 9001, cosmosChainId: "evmos_9001-2", }; export const MAINNET_FEE = { amount: "3000000000000000", denom: "aevmos", gas: "150000", }; export const ENDPOINT_URL = "https://rest.bd.evmos.org:1317";
TypeScript
Helpers:
Create the file src/components/chain.ts:
import { provider, transactions } from "evmosjs"; import { ENDPOINT_URL } from "./constants"; export async function getSender(address: string, pubkey: string) { const walletInfoEndpoint = provider.generateEndpointAccount(address); const res = await ( await fetch(`${ENDPOINT_URL}${walletInfoEndpoint}`) ).json(); const sender: transactions.Sender = { accountAddress: address, sequence: res.account.base_account.sequence, accountNumber: res.account.base_account.account_number, pubkey: pubkey, }; return sender; }
TypeScript
Metamask:
Create the file src/components/metamask.ts:
import { signatureToPubkey } from "@hanchon/signature-to-pubkey"; import { addressConverter, provider, transactions } from "evmosjs"; import { getSender } from "./chain"; import { MAINNET_CHAIN, ENDPOINT_URL } from "./constants"; import { getMetamaskAddress } from "./faucet"; export async function getMetamaskSender() { const mmAddress = await getMetamaskAddress(); const address = addressConverter.ethToEvmos(mmAddress); const signature = await window.ethereum.request({ method: "personal_sign", params: [mmAddress, "generate_pubkey"], }); const message = Buffer.from([ 50, 215, 18, 245, 169, 63, 252, 16, 225, 169, 71, 95, 254, 165, 146, 216, 40, 162, 115, 78, 147, 125, 80, 182, 25, 69, 136, 250, 65, 200, 94, 178, ]); const pubkey = signatureToPubkey(signature, message); return getSender(address, pubkey); } export async function signAndBroadcastWithMetamask( sender: transactions.Sender, tx: transactions.TxGenerated ) { const signature = await window.ethereum.request({ method: "eth_signTypedData_v4", params: [ addressConverter.evmosToEth(sender.accountAddress), JSON.stringify(tx.eipToSign), ], }); const extension = transactions.signatureToWeb3Extension( MAINNET_CHAIN, sender, signature ); const txToBroadcast = transactions.createTxRawEIP712( tx.legacyAmino.body, tx.legacyAmino.authInfo, extension ); const postOptions = { method: "POST", headers: { "Content-Type": "application/json" }, body: provider.generatePostBodyBroadcast(txToBroadcast), }; let broadcastPost = await fetch( `${ENDPOINT_URL}${provider.generateEndpointBroadcast()}`, postOptions ); return await broadcastPost.json(); }
TypeScript
Keplr
Create the src/components/keplr.ts file:
import { proto, provider, transactions } from "evmosjs"; import { getSender } from "./chain"; import { ENDPOINT_URL, MAINNET_CHAIN } from "./constants"; import { getKeplrAddress } from "./faucet"; export async function getKeplrSender() { const address = await getKeplrAddress(); const offlineSigner = window.getOfflineSigner(MAINNET_CHAIN.cosmosChainId); const wallets = await offlineSigner.getAccounts(); const pubkey = Buffer.from(wallets[0].pubkey).toString("base64"); return getSender(address, pubkey); } export async function signAndBroadcastKeplr( sender: transactions.Sender, tx: transactions.TxGenerated ) { await window.keplr.enable(MAINNET_CHAIN.cosmosChainId); const sign: { signed: { bodyBytes: Uint8Array; authInfoBytes: Uint8Array; }; signature: { signature: string; }; } = await window.keplr.signDirect( MAINNET_CHAIN.cosmosChainId, sender.accountAddress, { bodyBytes: tx.signDirect.body.serializeBinary(), authInfoBytes: tx.signDirect.authInfo.serializeBinary(), chainId: MAINNET_CHAIN.cosmosChainId, accountNumber: sender.accountNumber, }, { isEthereum: true } ); const txToBroadcast = proto.createTxRaw( sign.signed.bodyBytes, sign.signed.authInfoBytes, [new Uint8Array(Buffer.from(sign.signature.signature, "base64"))] ); const postOptions = { method: "POST", headers: { "Content-Type": "application/json" }, body: provider.generatePostBodyBroadcast(txToBroadcast), }; let broadcastPost = await fetch( `${ENDPOINT_URL}${provider.generateEndpointBroadcast()}`, postOptions ); return await broadcastPost.json(); }
TypeScript
Transaction generation component:
Create the src/components/msgsend.ts file:
import { addressConverter, transactions } from "evmosjs"; import { useState } from "react"; import { alertTxResult, buttonStyle } from "./faucet"; import { getMetamaskSender, signAndBroadcastWithMetamask } from "./metamask"; import { MAINNET_CHAIN, MAINNET_FEE } from "./constants"; import { getKeplrSender, signAndBroadcastKeplr } from "./keplr"; export default function MsgSend() { const [dest, setDest] = useState(""); const [amount, setAmount] = useState("1000000000000000"); const [sender, setSender] = useState<transactions.Sender>({ accountAddress: "", accountNumber: 0, sequence: 0, pubkey: "", }); const [walletProvider, setWalletProvider] = useState(""); async function createTransaction() { if (dest === "" || amount === "" || sender.accountAddress === "") { alert("Please select your wallet and set the inputs"); return; } let destInEvmosFormat = dest; if (dest.startsWith("0x")) { destInEvmosFormat = addressConverter.ethToEvmos(dest); } const params: transactions.MessageSendParams = { destinationAddress: destInEvmosFormat, amount: amount, denom: "aevmos", }; return transactions.createMessageSend( MAINNET_CHAIN, sender, MAINNET_FEE, "workshop transaction", params ); } return ( <div className="w-full p-10 text-center"> <h1 className="text-lg pt-10">Message Send</h1> <div className="flex flex-col text-center"> <span>Sender:</span> <span>{sender.accountAddress}</span> <span>{sender.sequence}</span> </div> <div className="flex flex-row space-x-2 justify-center mt-2"> <button className={buttonStyle} onClick={async () => { setSender(await getMetamaskSender()); setWalletProvider("metamask"); }} > Use Metamask </button> <button className={buttonStyle} onClick={async () => { setSender(await getKeplrSender()); setWalletProvider("keplr"); }} > Use Keplr </button> </div> <form className="flex flex-col w-full"> <input className="text-black w-full my-2 rounded-md p-2" type="text" placeholder="0x.../evmos1..." value={dest} onChange={(e) => { setDest(e.target.value); }} /> <div className="flex flex-row space-x-2 my-auto"> <input className="text-black w-full rounded-md p-2" type="text" placeholder="10000" value={amount} onChange={(e) => { setAmount(e.target.value); }} /> <span className="py-2">aevmos</span> </div> <button className={`${buttonStyle} mt-2`} onClick={async (e) => { e.preventDefault(); let tx = await createTransaction(); if (tx) { if (walletProvider === "metamask") { alertTxResult(await signAndBroadcastWithMetamask(sender, tx)); } else { alertTxResult(await signAndBroadcastKeplr(sender, tx)); } } }} > Send Transaction </button> </form> </div> ); }
TypeScript