Skip to main content

Quickstart: Build a decentralized app with Arbitrum (Solidity, Hardhat)

PUBLIC PREVIEW DOCUMENT

This document is currently in public preview and may change significantly as feedback is captured from readers like you. Click the Request an update button at the top of this document or join the Arbitrum Discord to share your feedback.

This quickstart is for web developers who want to start building decentralized applications (dApps) using Arbitrum. It makes no assumptions about your prior experience with Ethereum, Arbitrum, or Solidity. Familiarity with Javascript and yarn is expected. If you're new to Ethereum, consider studying the Ethereum documentation before proceeding.

What we're building

We're going to build a digital cupcake vending machine using Solidity smart contracts1. Our vending machine will follow two rules:

  1. The vending machine will distribute a cupcake to anyone who hasn't recently received one.
  2. The vending machine's rules can't be changed by anyone.

Here's our vending machine implemented with Javascript:

Free Cupcakes

web2Refresh balance 🧁

Cupcake balance:0

Note that although this vending machine appears to follow the rules, it doesn't follow them as much as we'd like. The vending machine's business logic and data are hosted by a centralized service provider. We're trusting that this service provider isn't malicious, but:

  1. Our centralized service provider can deny access to particular users.
  2. A malicious actor can change the rules of the vending machine at any time, for example, to give their friends extra cupcakes.

Centralized third-party intermediaries represent a single point of failure that malicious actors may become incentivized to exploit. To mitigate this type of risk, we can decentralize our vending machine's business logic and data, rendering this type of exploitation infeasible.

This is Arbitrum's core value proposition to you, dear developer. Arbitrum makes it easy for you to deploy your vending machines to Ethereum's permissionless, trustless, decentralized network of nodes2 while keeping costs low for you and your users.

Let's implement the "web3" version of the above vending machine using Arbitrum.

Prerequisites

  • VS Code: The IDE we'll use to build our vending machine. See code.visualstudio.com to install.
  • Metamask: The wallet we'll use to interact with our vending machine. See metamask.io to install.
  • Yarn: The package manager we'll use to install our dependencies. See yarnpkg.com to install.

We'll install the rest of our dependencies as we go.

Ethereum and Arbitrum in a nutshell

  • Ethereum
    • Ethereum is a decentralized network of nodes that use Ethereum's client software (like Offchain's Prysm) to maintain a public blockchain data structure.
    • The data within Ethereum's blockchain data structure changes one transaction at a time.
    • Smart contracts are small programs that execute transactions according to predefined rules. Ethereum's nodes host and execute smart contracts.
    • You can use smart contracts to build decentralized apps (dApps) that use Ethereum's network to process transactions and store data.
    • DApps let users carry their data and identity between applications without having to trust centralized service providers.
    • People who run Ethereum validator nodes3 can earn $ETH for processing and validating transactions on behalf of users and dApps.
    • These transactions can be expensive when the network is under heavy load.
  • Arbitrum
    • Arbitrum is a suite of L2 scaling solutions for dApp developers.
    • Arbitrum One is an L2 chain that implements the Arbitrum Rollup protocol.
    • You can use Arbitrum One to build user-friendly dApps with high throughput, low latency, and low transaction costs while inheriting Ethereum's high security standards4.

Let's review our vending machine's Javascript implementation, then convert it into a Solidity smart contract, then deploy it to Arbitrum One.

We'll ask your smart contract for cupcakes using the vending machines on this page.

Review our Javascript vending machine

Here's our vending machine implemented as a Javascript class:

VendingMachine.js
   class VendingMachine {
// state variables = internal memory of the vending machine
cupcakeBalances = {};
cupcakeDistributionTimes = {};

// Vend a cupcake to the caller
giveCupcakeTo(userId) {
if (this.cupcakeDistributionTimes[userId] === undefined) {
this.cupcakeBalances[userId] = 0;
this.cupcakeDistributionTimes[userId] = 0;
}

// Rule 1: The vending machine will distribute a cupcake to anyone who hasn't recently received one.
const fiveSeconds = 5000;
const userCanReceiveCupcake = this.cupcakeDistributionTimes[userId] + fiveSeconds <= Date.now();
if (userCanReceiveCupcake) {
this.cupcakeBalances[userId]++;
this.cupcakeDistributionTimes[userId] = Date.now();
console.log(`Enjoy your cupcake, ${userId}!`);
return true;
} else {
console.error("HTTP 429: Too Many Cupcakes (you must wait at least 5 seconds between cupcakes)");
return false;
}
}

getCupcakeBalanceFor(userId) {
return this.cupcakeBalances[userId];
}
}

The VendingMachine class uses state variables and functions to implement predefined rules. This implementation is useful because it automates cupcake distribution, but there's a problem: it's hosted by a centralized server controlled by a third-party service provider.

Let's decentralize our vending machine's business logic and data by porting the above Javascript implementation into a Solidity smart contract.

Configure your project directory

Create a decentralized-cupcakes directory for your project and install hardhat using VS Code's integrated terminal:

mkdir decentralized-cupcakes
cd decentralized-cupcakes
yarn init -y
yarn add hardhat @nomicfoundation/hardhat-toolbox -D

This installs two packages: hardhat lets us write, test and deploy our smart contracts, and hardhat-toolbox is a bundle of popular Hardhat plugins that we'll use later.

Next, run yarn hardhat to configure Hardhat. Select Create a JavaScript project when prompted. Make sure you specify your decentralized-cupcakes directory as the project root when asked.

At this point, you should see the following items (among others) in your decentralized-cupcakes project directory:

ItemDescription
contracts/Contains your smart contracts. You should see the Lock.sol contract here.
scripts/Contains scripts that you can use to interact with your smart contracts. You should see deploy.js here.
hardhat.config.jsContains the configuration settings for Hardhat.

Replace the contents of hardhat.config.js with the following:

hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");

// NEVER record important private keys in your code - this is for demo purposes
const GOERLI_TESTNET_PRIVATE_KEY = "";
const ARBITRUM_MAINNET_TEMPORARY_PRIVATE_KEY = "";

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.18",
networks: {
hardhat: {
chainId: 1337
},
arbitrumGoerli: {
url: "https://goerli-rollup.arbitrum.io/rpc",
chainId: 421613,
//accounts: [GOERLI_TESTNET_PRIVATE_KEY]
},
arbitrumOne: {
url: "https://arb1.arbitrum.io/rpc",
//accounts: [ARBITRUM_MAINNET_TEMPORARY_PRIVATE_KEY]
}
}
};

Run yarn hardhat compile to compile the default contracts. You may be prompted to install additional dependencies - follow those instructions until this command runs successfully. You should see Compiled 1 Solidity file successfully in the terminal output. You should also see a new decentralized-cupcakes/artifacts/ directory. This directory contains the compiled smart contract.

Open scripts/deploy.js and replace its contents with the following:

scripts/deploy.js
const hre = require("hardhat");

async function main() {

const VendingMachineFactory = await hre.ethers.getContractFactory("VendingMachine");
const vendingMachine = await VendingMachineFactory.deploy();
await vendingMachine.deployed();

console.log(
`Cupcake vending machine deployed to ${vendingMachine.address}`
);
}

main().catch((error) => {
console.error(error);
process.exit(1);
});

We'll use this to deploy our smart contract in a moment. Next, delete contracts/Lock.sol and replace it with contracts/VendingMachine.sol, the smarter alternative to our Javascript implementation:

VendingMachine.sol
pragma solidity ^0.8.9;

// Rule 2: The vending machine's rules can't be changed by anyone.
contract VendingMachine {
// state variables = internal memory of the vending machine
mapping(address => uint) private _cupcakeBalances;
mapping(address => uint) private _cupcakeDistributionTimes;

function giveCupcakeTo(address userAddress) public returns (bool) {
// this code is unnecessary, but we're keeping it here so you can compare it to the JS implementation
if (_cupcakeDistributionTimes[userAddress] == 0) {
_cupcakeBalances[userAddress] = 0;
_cupcakeDistributionTimes[userAddress] = 0;
}

// Rule 1: The vending machine will distribute a cupcake to anyone who hasn't recently received one.
uint fiveSecondsFromLastDistribution = _cupcakeDistributionTimes[userAddress] + 5 seconds;
bool userCanReceiveCupcake = fiveSecondsFromLastDistribution <= block.timestamp;
if (userCanReceiveCupcake) {
_cupcakeBalances[userAddress]++;
_cupcakeDistributionTimes[userAddress] = block.timestamp;
return true;
} else {
revert("HTTP 429: Too Many Cupcakes (you must wait at least 5 seconds between cupcakes)");
}
}

// Getter function for the cupcake balance of a user
function getCupcakeBalanceFor(address userAddress) public view returns (uint) {
return _cupcakeBalances[userAddress];
}
}

Note that this smart contract is written in Solidity, a language that compiles to EVM bytecode. This means that it can be deployed to any Ethereum-compatible blockchain, including Ethereum mainnet, Arbitrum One, and Arbitrum Nova.

Run yarn hardhat compile again. You should see Compiled 1 Solidity file successfully in the terminal output. You should also see a new decentralized-cupcakes/artifacts/contracts/VendingMachine.sol directory.

Deploy the smart contract locally

To deploy our VendingMachine smart contract locally, we'll use two terminal windows and a wallet:

  1. We'll use the first terminal window to run Hardhat's built-in local Ethereum node
  2. We'll then configure a wallet so we can interact with our smart contract after it's deployed to (1)
  3. We'll then use the second terminal window to deploy our smart contract to (1)'s node

Run a local Ethereum network and node

Run yarn hardhat node from your decentralized-cupcakes directory to begin running a local Ethereum network powered by a single node. This will mimic Ethereum's behavior on your local machine by using Hardhat's built-in Hardhat Network.

You should see something along the lines of Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/ in your terminal. You should also see a number of test accounts automatically generated for you:

...
Account #0: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 (10000 ETH)
Private Key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
...
Never share your private keys

Your Ethereum Mainnet wallet's private key is the password to all of your money. Never share it with anyone; avoid copying it to your clipboard.

Note that in the context of this quickstart, "account" refers to a public wallet address and its associated private key5.

Configure Metamask

Next, open Metamask and create or import a wallet by following the displayed instructions. By default, Metamask will connect to Ethereum mainnet. To connect to our local "testnet", enable test networks for Metamask by clicking Show/hide test networks from the network selector dropdown. Then select the Localhost 8545 network:

Connect Metamask to Localhost 8545

Your mainnet wallet won't have a balance on your local testnet's node, but we can import one of the test accounts into Metamask to gain access to 10,000 fake ETH. Copy the private key of one of the test accounts (excluding the 0x prefix, so eg ac0..f80) and import it into Metamask:

Connect Metamask to Localhost 8545

You should see a balance of 10,000 ETH. Keep your private key handy; we'll use it again in a moment.

Next, click Metamask's network selector dropdown, and then click the Add Network button. Click "Add a network manually" and then provide the following information:

  • Network Name: Arbitrum Goerli
  • New RPC URL: https://goerli-rollup.arbitrum.io/rpc
  • Chain ID: 421613
  • Currency Symbol: AGOR

As we interact with our cupcake vending machine, we'll use Metamask's network selector dropdown to determine which network our cupcake transactions are sent to. For now, we'll leave the network set to Localhost 8545.

Deploy the smart contract to your local testnet

From another terminal instance, run yarn hardhat run scripts/deploy.js --network localhost. This command will deploy your smart contract to the local testnet's node. You should see something like Cupcake vending machine deployed to 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 in your terminal. 0xe7...512 is the address of your smart contract in your local testnet.

Ensure that the Localhost network is selected within Metamask. Then copy and paste your contract address below and click Get cupcake!. You should be prompted to sign a transaction that gives you a cupcake.

Free Cupcakes

web3-localhostRefresh balance 🧁

Cupcake balance:0

What's going on here?

Our first VendingMachine is labeled WEB2 because it demonstrates traditional n-tier web application architecture:

Architecture diagram

The WEB3-LOCALHOST architecture is similar to to the WEB2 architecture, with one key difference: with the WEB3 version, the business logic and data live in an (emulated for now) decentralized network of nodes instead of a centralized network of servers.

Let's take a closer look at the differences between our VendingMachine implementations:

WEB2 (the first one)WEB3-LOCALHOST (the latest one)WEB3-ARB-GOERLI (the next one)WEB3-ARB-MAINNET (the final one)
Data (cupcakes)Stored only in your browser. (Usually, stored by centralized infrastructure.)Stored on your device in an emulated Ethereum network (via smart contract).Stored on Ethereum's decentralized test network (via smart contract).Stored on Ethereum's decentralized mainnet network (via smart contract).
Logic (vending)Served from Offchain's servers. Executed by your browser.Stored and executed by your locally emulated Ethereum network (via smart contract).Stored and executed by Arbitrum's decentralized test network (via smart contract).Stored and executed by Arbitrum's decentralized mainnet network (via smart contract).
Presentation (UI)Served from Offchain's servers. Rendered and executed by your browser.<- same<- same<- same
MoneyDevs and users pay centralized service providers for server access using fiat currency.<- same, but only for the presentation-layer concerns (code that supports frontend UI/UX).<- same, but devs and users pay testnet $ETH to testnet validators.<- same, but instead of testnet $ETH, they use mainnet $ETH.

Next, we'll deploy our smart contract to a network of real nodes: Arbitrum's Goerli testnet.

Deploy the smart contract to the Arbitrum Goerli testnet

We were able to deploy to a local testnet for free because we were using Hardhat's built-in Ethereum network emulator. Arbitrum's Goerli testnet is powered by a real network of real nodes, so we'll need to pay a small transaction fee to deploy our smart contract. This fee can be paid with the Arbitrum Goerli testnet's token, $AGOR.

$AGOR IS SHORTHAND

"$AGOR" isn't a canonical term. It's just shorthand for "Arbitrum Goerli testnet $ETH" that we use for convenience.

First, update the hardhat.config.js file to specify the private key of the test account that you'll use to deploy your smart contract (and pay the transaction fee):

hardhat.config.js
// ...
const GOERLI_TESTNET_PRIVATE_KEY = ""; // <- this should **not** begin with "0x"
// ...
accounts: [GOERLI_TESTNET_PRIVATE_KEY] // <- uncomment this line
// ...
caution

Note that we're adding a private key to a config file. This is not a best practice; we're doing it here for convenience. Consider using environment variables when working on a real project.

Next, let's deposit some $AGOR into the wallet corresponding to the private key we added to hardhat.config.js. At the time of this quickstart's writing, the easiest way to acquire $AGOR is to bridge Goerli $ETH from Ethereum's L1 Goerli network to Arbitrum's L2 Goerli network:

  1. Use an L1 Goerli $ETH faucet like goerlifaucet.com to acquire some testnet $ETH on L1 Goerli.
  2. Bridge your L1 Goerli $ETH into Arbitrum L2 using the Arbitrum bridge.

Once you've acquired some $AGOR, you'll be able to deploy your smart contract to Arbitrum's Goerli testnet by issuing the following command:

yarn hardhat run scripts/deploy.js --network arbitrumGoerli

This tells hardhat to deploy the compiled smart contract through the RPC endpoint corresponding to arbitrumGoerli in hardhat.config.js. You should see the following output:

Cupcake vending machine deployed to 0xff825139321bd8fB8b720BfFC5b9EfDB7d6e9AB3

Congratulations! You've just deployed business logic and data to Arbitrum Goerli. This logic and data will be hashed and submitted within a transaction to Ethereum's L1 Goerli network, and then it will be mirrored across all nodes in the Goerli network6.

To view your smart contract in a blockchain explorer, visit https://goerli.arbiscan.io/address/0x...B3, but replace the 0x...B3 part of the URL with the full address of your deployed smart contract.

Select Arbitrum Goerli from Metamask's dropdown, paste your contract address into the VendingMachine below, and click Get cupcake!. You should be prompted to sign a transaction that gives you a cupcake.

Free Cupcakes

web3-arb-goerliRefresh balance 🧁

Cupcake balance:0

Deploy the smart contract to Arbitrum One Mainnet

Now that we've verified that our smart contract works on Arbitrum's Goerli testnet, we're ready to deploy it to Arbitrum One Mainnet. This is the same process as deploying to Arbitrum's Goerli testnet, except that we'll need to pay a transaction fee in real $ETH instead of $AGOR.

Expect to see inconsistent $ETH gas fees in this step - our Gas FAQs contain more information about how gas fees are determined for Arbitrum transactions.

First, update the hardhat.config.js file to specify the private key of the one-time-use deployment account that you'll use to deploy your smart contract (and pay the transaction fee):

hardhat.config.js
// ...
const ARBITRUM_MAINNET_TEMPORARY_PRIVATE_KEY = ""; // <- this should **not** begin with "0x"
// ...
accounts: [ARBITRUM_MAINNET_TEMPORARY_PRIVATE_KEY] // <- uncomment this line
// ...
caution

Note that we're adding a private key to a config file. This is not a best practice. Consider using environment variables instead.

Next, deposit some $ETH into the wallet corresponding to the private key we added to hardhat.config.js. You'll then be able to deploy your smart contract to Arbitrum One Mainnet by issuing the following command:

yarn hardhat run scripts/deploy.js --network arbitrumOne

You should see the following output:

Cupcake vending machine deployed to 0xff825139321bd8fB8b720BfFC5b9EfDB7d6e9AB3

Congratulations! You've just deployed business logic and data to Ethereum's decentralized network of nodes by way of Arbitrum One2.

To view your smart contract in a blockchain explorer, visit https://arbiscan.io/address/0x...B3, but replace the 0x...B3 part of the URL with the full address of your deployed smart contract.

Select Arbitrum One from Metamask's dropdown, paste your contract address into the VendingMachine below, and click Get cupcake!. You should be prompted to sign a transaction that gives you an immutable cupcake.

Free Cupcakes

web3-arb-oneRefresh balance 🧁

Cupcake balance:0

Summary

In this quickstart, we:

  • Identified two business rules: 1) fair and permissionless cupcake distribution, 2) immutable business logic and data.
  • Identified a challenge: These rules are difficult to follow in a centralized application.
  • Identified a solution: Arbitrum makes it easy for developers to decentralize business logic and data (using Ethereum mainnet as a settlement layer).
  • Converted a vending machine's Javascript business logic into a Solidity smart contract.
  • Deployed our smart contract to Hardhat's local development network, and then Arbitrum's Goerli testnet, and then Arbitrum One Mainnet.

If you have any questions or feedback, reach out to us on Discord and/or click the Request an update button at the top of this page - we're listening!

Next steps


  1. The vending machine example was inspired by Ethereum.org's "Introduction to Smart Contracts", which was inspired by Nick Szabo's "From vending machines to smart contracts".
  2. Although application front-ends are usually hosted by centralized services, smart contracts allow the underlying logic and data to be partially or fully decentralized. These smart contracts are hosted and executed by Ethereum's public, decentralized network of nodes. Arbitrum has its own network of nodes that use advanced cryptography techniques to "batch process" Ethereum transactions and then submit them to Ethereum L1, which significantly reduces the cost of using Ethereum. All without requiring developers to compromise on security or decentralization.
  3. There are multiple types of Ethereum nodes. The ones that earn ETH for processing and validating transactions are called validators. See Nodes and Networks for a beginner-friendly introduction to Ethereum's node types.
  4. When our VendingMachine contract is deployed to Ethereum, it'll be hosted by Ethereum's decentralized network of nodes. Generally speaking, we won't be able to modify the contract's code after it's deployed.
  5. To learn more about how Ethereum wallets work, see Ethereum.org's introduction to Ethereum wallets.
  6. Visit the Gentle Introduction to Arbitrum for a beginner-friendly introduction to Arbitrum's rollup protocol.