We're going to build a web application that allows you to send Ethereum and Binance Coin (BNB) to any address using Metamask.
You can see what the finished product looks like here: https://bit.dev/atila/web-components-library/ui/crypto-payment-form
This video was inspired by Arthur Chmaro's video, How to Send ETH using React.js.
You can either add this to an existing React project or create a new one.
For this tutorial we'll use an existing project, the open source atila client web app:
git clone https://github.com/atilatech/client-web-app
You can also create a new React project using npx create-react-app sample-project
First we're going to create a basic form that takes two inputs: a destination address and an amount.
Create the file: touch src/components/CryptoPaymentsForm.tsx
Paste the following into the fle to create a simple form:
You can also try typing the code yourself, some people find it helps them with muscle memory.
Protip: You can use an IDE extension like ES7 React/Redux/GraphQL/React-Native snippets to simplify the creation of boiler plate comments.
For me, all I had to type was rfce
then it creates the boilerplate for the component.
import React from 'react'
const CryptoPaymentsForm = () => {
return (
<div>
<input type="number" placeholder="Amount" />
<input placeholder="Destination address" />
<button className="col-12 btn btn-primary">
Send Payment
</button>
</div>
)
}
export default CryptoPaymentsForm
Import that component in our practice page.
{/* src/scenes/Practice.tsx */}
import React from 'react'
{/* Add the line below */}
import CryptoPaymentsForm from '../../components/CryptoPaymentsForm'
const Practice = () => {
return (
<div className="my-3 p-5">
<h1>
Practice
</h1>
<CryptoPaymentsForm /> {/* Add this line */}
</div>
)
}
export default Practice
Add some Bootstrap classes to make our form look nicer.
import React from 'react'
const CryptoPaymentsForm = () => {
return (
<div className="p-5 card shadow text-center">
<input type="number" placeholder="Amount" className="col-12 form-control mb-3" />
<input placeholder="Destination address" className="col-12 form-control mb-3" />
<button className="col-12 btn btn-primary">
Send Payment
</button>
</div>
)
}
export default CryptoPaymentsForm
Now we need to be able to set the amount and address and be able to reference them later.
import React, { useState } from 'react'
const CryptoPaymentsForm = () => {
const [amount, setAmount] = useState(0); // new line
const [destinationAddress, setDestinationAddress] = useState(""); // new line
const startPayment = async (event: any) => { // new line
console.log({amount, destinationAddress});
}
return (
<div className="p-5 card shadow text-center">
{/* added onChange and onClick attributes */}
<input type="number" placeholder="Amount" value={amount} className="col-12 form-control mb-3" onChange={event => {setAmount(Number.parseFloat(event.target.value))}} />
<input placeholder="Destination address" value={destinationAddress} className="col-12 form-control mb-3" onChange={event => {setDestinationAddress(event.target.value)}} />
<button className="col-12 btn btn-primary" onClick={startPayment}>
Send Payment
</button>
</div>
)
}
export default CryptoPaymentsForm
Okay, now it's time for the really fun part! We're going to use ethers.js to connect metamask to our website.
Ethers is a Javascript package that allows you to connect to the Ethereum blockchain (and other blockchains) using Metamask.
I also really want to give a shoutout to the author, Richard Moore, it's a really great library.
Install ethers.js: yarn add ethers
or npm install --save ethers
Send the transaction to the blockchain:
const startPayment = async (event: any) => { // new line
console.log({amount, destinationAddress});
event.preventDefault();
try {
if (!window.ethereum) {
throw new Error("No crypto wallet found. Please install it.");
}
await window.ethereum.send("eth_requestAccounts");
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
ethers.utils.getAddress(destinationAddress);
const transactionResponse = await signer.sendTransaction({
to: destinationAddress,
value: ethers.utils.parseEther(amount.toString())
});
console.log({transactionResponse});
} catch (error: any) {
console.log({error});
}
}
Let's walk through what each snippet means.
First we need to see if Metamask (or a similar wallet browser extension) is installed. When you install Metamask, it automatically injects data into the ethereum global object. If metamask isn't installed then window.ethereum
will be empty and an error will be returned to the user. More information on the Ethereum Metamask provider API .
One piece of advice I give people who want to really learn a topic is to "read the original documents". If you want to learn more about how this API conencts to Ethereum, you might also be interested in EIP-1193 which goes into more detail on the specificaitons for the Javascript provider AI.
if (!window.ethereum) {
throw new Error("No crypto wallet found. Please install it.");
}
This line will bring up a Metamask popup that asks the user if they want to allow our website to connect to their wallet. They need to grant us permission before we can send transactions with their wallets.
await window.ethereum.send("eth_requestAccounts");
The next few lines define the parts of the transaction that need to exist for us to send a transaction. For more information, see the Etherscan API reference, it's very well documented.
The final part actually send the transaction. Ether values can go up to 18 decimal places ( 1 wei is 10^-18 Ether)! This is so many digits that you can run into underflow issues and I'm speaking from personal experience. So often you'll have to convert your numbers to a fixed number of decimal places or parse them into a hex representation.
ethers.utils.parseEther(amount.toString())
To get some "fake money" that we can test with, we'll use the Ropsten test network and the Egor Fine Faucet to get some "free eth" sent to us.
We're using the Ropsten test network because it's the test network that most resembles the main Ethereum blockchain.
- Visit https://faucet.egorfine.com/. (Sometimes it can be hard to find a faucet that works reliably. So you can use a search engine for "ropsten faucet" and try different ones until you find one that works.
- Enter the address of the wallet you are going to be sending the ETH from, not the address that will be receiving the ETH..
- Make sure you switch to the Ropsten test network in Metamask
- Enter a desired amount and a wallet address that you want to send the ETH to. If you don't have one, make a new one in Metamask.
- Send the Payment
Next let's display some information to the user if the transaction went through or if an error occured.
const [amount, setAmount] = useState(0);
const [destinationAddress, setDestinationAddress] = useState("");
const [error, setError] = useState(""); //newline
const [transaction, setTransaction] = useState<ethers.providers.TransactionResponse | null >(null); // new line
Inside startPayment()
we'll set the error or transaction response
// startPayment()
// ...
event.preventDefault();
setError("");// clear previous error when a new transaction starts
// ...
console.log({transactionResponse});
setTransaction(transactionResponse); // new line
// ...
catch (error: any) {
console.log({error});
setError(error.message); // new line
}
Render the transaction response:
<button className="col-12 btn btn-primary" onClick={startPayment}>
Send Payment
</button>
{transaction &&
<div className="alert alert-success mt-3" role="alert">
{JSON.stringify(transaction)}
</div>
}
{error &&
<div className="alert alert-danger" role="alert">
{JSON.stringify(error)}
</div>
}
Now send another transaction and you should see the following. Practice confirming and rejecting the transaction to see what the response looks like:
Let's add the ability to actually see the transaction on the blockchain using a block explorer like Metamask.
export interface TransactionResponsePayment extends ethers.providers.TransactionResponse {
network?: ethers.providers.Network,
}
const CryptoPaymentsForm = () => {
// ...
const [error, setError] = useState("");
const [transaction, setTransaction] = useState<TransactionResponsePayment | null >(null);
let transactionUrl = "";
if (transaction?.hash) {
transactionUrl = `https://${transaction.network?.name === "homestead" ? "": transaction.network?.name+"."}etherscan.io/tx/${transaction.hash}`
}
// ...
const signer = provider.getSigner();
const network = await provider.getNetwork();
const transactionResponse = await signer.sendTransaction({
to: destinationAddress,
value: ethers.utils.parseEther(amount.toString())
}) as TransactionResponsePayment;
transactionResponse.network = network;
The block explorer we'll be using is Etherscan which is for uses ropsten.etherscan.com for it's testnet block explorer. So we want to change the block explorer we're using based on the network where our transaction was sent.
if (transaction?.hash) {
transactionUrl = `https://${transaction.network?.name === "homestead" ? "": transaction.network?.name+"."}etherscan.io/tx/${transaction.hash}`
}
That's it! We've now added Ethereum payment to our website in just 5 lines of code.
Excluding the optional stuff, this is all we needed:
await window.ethereum.send("eth_requestAccounts");
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
ethers.utils.getAddress(destinationAddress);
const transactionResponse = await signer.sendTransaction({
to: destinationAddress,
value: ethers.utils.parseEther(amount.toString())
});
This is truly remarkable! In just 5 lines of code, without having to open a bank acount and using completely open source software we're now able to accept money from anyone with an internet connection.
As someone who's integrated credit card payments into my website before the difference is so vast that things like this is what makes me so optimistic about the future and why I believe so much in the power of cryptocurrencies and blockchain in improving the fate of humanity.