Skip to main content

Writing dApps on Emerald

This tutorial will show you how to set up dApp development environment for Emerald to be able to write and deploy dApps on Oasis Emerald.

We will walk you through the Hardhat configuration and - for those who prefer a simpler web-only interface - the Remix IDE. Oasis Emerald exposes an EVM-compatible interface so writing dApps isn't much different compared to the original Ethereum Network!

Oasis Consensus Layer and Emerald ParaTime

Oasis Network consists of the consensus layer and a number of Layer 2 chains called the ParaTimes (to learn more, check the Oasis Network Overview chapter). Emerald is a ParaTime which implements the Ethereum Virtual Machine (EVM).

The minimum and also expected block time in Emerald is 6 seconds. Any Emerald transaction will require at least this amount of time to be executed.

The native Oasis addresses are Bech32-encoded (e.g. oasis1qpupfu7e2n6pkezeaw0yhj8mcem8anj64ytrayne) while Emerald supports both the Bech32-encoded and the Ethereum-compatible hex-encoded addresses (e.g. 0x90adE3B7065fa715c7a150313877dF1d33e777D5). The underlying algorithm for signing the transactions is Ed25519 on the Consensus layer and both Ed25519 and ECDSA in Emerald. The Ed25519 scheme is used mostly by the Emerald compute nodes for managing their computation rewards. For signing your dApp-related transactions on Emerald you will probably want to use ECDSA since this is the de facto scheme supported by Ethereum wallets and libraries.

Finally, the ParaTimes are not allowed to directly access your tokens stored in Consensus layer addresses. You will need to deposit tokens from your consensus account to Emerald. Consult the How to transfer ROSE into Emerald ParaTime chapter to learn more.

Testnet and Mainnet

The Oasis Network currently has, similar to some other blockchains, two major public deployments: the Mainnet and the Testnet. The native tokens are called ROSE and TEST respectively. Each deployment has its own state, a different set of validators and ParaTimes. The state of the Mainnet is considered immutable for indefinite time, while the data on the Testnet can be subject to wipe in the future.

The Emerald ParaTime is deployed similarly: the Emerald Mainnet is deployed on the Oasis Mainnet Network while the Emerald Testnet on the Oasis Testnet Network. The Emerald state on the Mainnet is stable. Testnet, apart from running the unstable version of the code and being prone to bugs, can have the state deliberately wiped either on the Emerald ParaTime layer or on the Oasis Testnet Network level.

Never deploy production service on Testnet

Because Testnet state can be wiped in the future, you should never deploy a production service on the Testnet!

tip

For testing purposes, visit our Testnet faucet to obtain some TEST which you can then use on the Emerald Testnet to pay for gas fees. The faucet supports sending TEST both to your Consensus layer address or to your address inside the ParaTime.

Running a Private Oasis Network Locally

For convenient development and testing of your dApps the Oasis team prepared the ghcr.io/oasisprotocol/emerald-localnet Docker image which brings you a complete Oasis stack to your desktop. This network is isolated from the Mainnet or Testnet and consists of:

  • single Oasis validator node with 1-second block time and 30-second epoch,
  • single Oasis client node,
  • three compute nodes running Oasis Emerald,
  • PostgreSQL instance,
  • Oasis Web3 gateway with transaction indexer,
  • helper script which populates initial test accounts for you.

To run the image, execute:

docker run -it -p8545:8545 -p8546:8546 ghcr.io/oasisprotocol/emerald-localnet

After a while, the tool will show you something like this:

emerald-localnet 2023-02-28-git84730b2 (oasis-core: 22.2.6, emerald-paratime: 10.0.0, oasis-web3-gateway: 3.2.0-git84730b2)

Starting oasis-net-runner with emerald...
Starting postgresql...
Starting oasis-web3-gateway...
Bootstrapping network and populating account(s) (this might take a minute)...

Available Accounts
==================
(0) 0x75eCF0d4496C2f10e4e9aF3D4d174576Ee9010E2 (100 ROSE)
(1) 0x903a7dce5a26a3f4DE2d157606c2191740Bc4BC9 (100 ROSE)
(2) 0xF149ad5CBFfD92ba84F5784106f6Cb071A32a1b8 (100 ROSE)
(3) 0x2315F40C1122400Df55483743B051D2997ef0a62 (100 ROSE)
(4) 0xf6FdcacbA93A428A07d27dacEf1fBF25E2C65B0F (100 ROSE)

Private Keys
==================
(0) 0x160f52faa5c0aecfa26c793424a04d53cbf23dcad5901ce15b50c2e85b9d6ca7
(1) 0x0ba685723b47d8e744b1b70a9bea9d4d968f60205385ae9de99865174c1af110
(2) 0xfa990cf0c22af455d2734c879a2a844ff99bd779b400bb0e2919758d1be284b5
(3) 0x3bf225ef73b1b56b03ceec8bb4dfb4830b662b073b312beb7e7fec3159b1bb4f
(4) 0xad0dd7ceb896fd5f5ddc76d56e54ee6d5c2a3ffeac7714d3ef544d3d6262512c

HD Wallet
==================
Mnemonic: bench remain brave curve frozen verify dream margin alarm world repair innocent
Base HD Path: m/44'/60'/0'/0/%d

WARNING: The chain is running in ephemeral mode. State will be lost after restart!

Listening on http://localhost:8545 and ws://localhost:8546

Those familiar with local dApp environments will find the output above similar to geth --dev or ganache-cli commands or the geth-dev-assistant npm package. emerald-localnet will spin up a private Oasis Network locally, generate and populate test accounts and make the following Web3 endpoints available for you to use:

  • http://localhost:8545
  • ws://localhost:8546
tip

If you prefer using the same mnemonics each time (e.g. for testing purposes) or to populate just a single wallet, use -to flag and pass the mnemonics or the wallet addresses. For example

docker run -it -p8545:8545 -p8546:8546 ghcr.io/oasisprotocol/emerald-localnet -to "bench remain brave curve frozen verify dream margin alarm world repair innocent"
docker run -it -p8545:8545 -p8546:8546 ghcr.io/oasisprotocol/emerald-localnet -to "0x75eCF0d4496C2f10e4e9aF3D4d174576Ee9010E2,0xbDA5747bFD65F08deb54cb465eB87D40e51B197E"
danger

emerald-localnet runs in ephemeral mode. Any smart contract and wallet balance will be lost after you quit the Docker container!

Create dApp on Emerald with Hardhat

Let's begin writing our dApp with Hardhat. We will lay out a base for a modern dApp including TypeScript bindings for tests and later for the frontend application.

First, make sure you installed Node.js and that you have npm and npx readily available. Then run:

npx hardhat init

Select the Create an advanced sample project that uses TypeScript option and enter the root directory for your project. You can leave other options as default. After a while Hardhat will finish downloading the dependencies and create a simple greeter dApp.

To compile, deploy and test the smart contract of your sample project locally, move to your project directory and type:

$ npx hardhat compile
Compiling 2 files with 0.8.4
Generating typings for: 2 artifacts in dir: typechain for target: ethers-v5
Successfully generated 5 typings!
Compilation finished successfully

$ npx hardhat test
No need to generate any newer typings.


Greeter
Deploying an Emerald Greeter with greeting: Hello, world!
Changing greeting from 'Hello, world!' to 'Hola, mundo!'
✓ Should return the new greeting once it's changed (613ms)


1 passing (614ms)

Hardhat already comes with a built-in EVM which is spun up from scratch each time we call hardhat test without parameters. It populates 20 accounts with ETH and registers them to the ethers.js instance used in the tests.

Next, let's look at how to configure Hardhat for Emerald. For convenience, we assign the PRIVATE_KEY environment variable a hex-encoded private key of your Emerald wallet containing tokens to pay for gas fees. If you are running emerald-localnet, use any of the five generated private keys.

export PRIVATE_KEY="YOUR_0x_EMERALD_PRIVATE_KEY"

Next, we configure three networks: emerald_local, emerald_testnet, and emerald_mainnet. Open hardhat.config.ts and replace the networks field to match the following:

networks: {
emerald_local: {
url: "http://localhost:8545",
accounts:
process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [],
},
emerald_testnet: {
url: "https://testnet.emerald.oasis.io",
accounts:
process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [],
},
emerald_mainnet: {
url: "https://emerald.oasis.io",
accounts:
process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [],
},
},

Next, we increase the default timeout for mocha tests from 20 seconds to 60 seconds. This step is not needed, if you will test your contracts solely on emerald-localnet, but is required for Testnet to avoid timeouts. Append the following block to the config object:

mocha: {
timeout: 60000
}
info

geth --dev and ganache-cli tools use a so-called "instant mining" mode. In this mode, a new block is mined immediately when a new transaction occurs in the mempool. Neither Oasis Mainnet and Testnet Networks nor emerald-localnet support such mode and the new block will always be mined at least after the 1 second block time elapsed.

Now deploy the contract to the local emerald-localnet Docker container by selecting the emerald_local network we configured above and run the tests:

$ npx hardhat run scripts/deploy.ts --network emerald_local
No need to generate any newer typings.
Greeter deployed to: 0x4e1de2f6cf4e57a8f55b4a5dd1fce770db734962

$ npx hardhat test --network emerald_local
No need to generate any newer typings.


Greeter
✓ Should return the new greeting once it's changed (6017ms)


1 passing (6s)

Next, you can try deploying the contract to the Testnet. Temporarily replace your PRIVATE_KEY environment variable with your Testnet one and deploy the contract by using the emerald_testnet network. Similarly, you can also run the tests.

$ PRIVATE_KEY="0xYOUR_TESTNET_PRIVATE_KEY" npx hardhat run scripts/deploy.ts --network emerald_testnet
No need to generate any newer typings.
Greeter deployed to: 0x735df9F166a2715bCA3D3A66B119CBef95a0D129

$ PRIVATE_KEY="0xYOUR_TESTNET_PRIVATE_KEY" npx hardhat test --network emerald_testnet
No need to generate any newer typings.


Greeter
✓ Should return the new greeting once it's changed (21016ms)


1 passing (6s)

Congratulations, you have just deployed your first smart contract to the public Emerald Testnet Network! If you are unsure, whether your contract was successfully deployed, you can monitor the transactions on the Emerald block explorer (Mainnet, Testnet). This tool indexes all Emerald accounts, blocks, transactions and even offers a neat user interface for browsing ETH-specifics like the ERC20 tokens and the ERC721 NFTs.

Emerald Block Explorer showing the latest transactions

Emerald Block Explorer showing our account 0x90adE3B7065fa715c7a150313877dF1d33e777D5 used for deploying the smart contract

Finally, by selecting the emerald_mainnet network and the corresponding private key, we can deploy the contract on the Mainnet:

$ PRIVATE_KEY="0xYOUR_MAINNET_PRIVATE_KEY" npx hardhat run scripts/deploy.ts --network emerald_mainnet
No need to generate any newer typings.
Greeter deployed to: 0x6e8e9e0DBCa4EF4a65eBCBe4032e7C2a6fb7C623

Create dApp on Emerald with Remix - Ethereum IDE

Remix is a popular web IDE for swift development, deployment and testing smart contracts on the Ethereum Network. We will use it in combination with MetaMask to access the network settings and your wallet to sign and submit the transactions.

If you haven't done it yet, first install the MetaMask extension for your browser. Import your wallet and configure Emerald Testnet and Mainnet Networks. If you wish to connect to emerald-localnet container, configure the local network as well.

When you open Remix for the first time, it automatically creates an example project. Let's open one of the contracts and compile it in the "Solidity compiler" tab.

The initial example project in Remix - Ethereum IDE

Solidity compiler tab

Next, in the "Deploy and Run Transactions" tab, select the "Injected Web3" environment. A MetaMask popup will appear and you will have to connect one or more accounts with Remix. Once the connection succeeds, click on the "Deploy" button. The MetaMask popup appears again and you will have to review the transaction, the gas options and finally confirm the transaction.

Metamask transaction confirmation

If everything goes well, your transaction will be deployed using the selected account in the MetaMask and the corresponding Emerald Network.

Successful contract deployment on Emerald Testnet with Remix

info

Sometimes the gas limit estimation function might compute a slightly lower value from the required one. In this case, try increasing the gas limit manually by 10% or 20%.

Congratulations! Now you can start developing your own smart contracts on the Oasis Emerald blockchain! Should you have any questions, do not hesitate to share them with us on the #emerald-paratime Discord channel.

Troubleshooting

Truffle Support

Sunsetting Truffle

Per Consensys announcement, Oasis will no longer support Truffle as of 2023-10-05 and encourage immediate migration to Hardhat. Please see our repository for the archived Truffle tutorial.

Deployment of my contract timed out on Testnet or Mainnet

Emerald validators, similar to Ethereum ones, order the execution of transactions by gas price. When deploying a contract and the deployment times out, first wait another few rounds to make sure that the contract will not be deployed eventually.

Next, check that your gasPrice is at least 10 nROSE which is a minimum required gas price on Emerald. This value should already be propagated automatically by the web3 endpoint, but your deployment configuration might have ignored it.

Finally, consider increasing the gasPrice parameter in the Hardhat config file by a fraction (e.g. 10% or 20%). This will require more ROSE from your wallet to deploy the contract, but you will also increase the chance of your transaction being included in the block.

Execution of my contract failed. How do I debug what went wrong?

If you are using Testnet or Mainnet, try to debug your transaction by finding it on the Emerald block explorer (Mainnet, Testnet):

Emerald block explorer showing a failed transaction

In some cases, the transaction result on Emerald block explorer might be stuck at Error: (Awaiting internal transactions for reason). In this case or in case of other Consensus layer ↔ ParaTime issues, try to find your Emerald transaction on the Oasis Scan (Mainnet, Testnet) which is primarily a Consensus layer explorer, but offers some introspection into ParaTime transactions as well. Once you find your failed Emerald transaction, the Status field should contain a more verbose error description, for example:

Oasis Scan showing the Out of gas error for a transaction on Emerald

See also

📄️ ParaTime Client Node

These instructions are for setting up a ParaTime client node which only observes ParaTime activity and can submit transactions. If you want to run a ParaTime node instead, see the instructions for running a ParaTime node. Similarly, if you want to run a validator or a non-validator node instead, see the instructions for running a validator node or instructions for running a non-validator node.

📄️ Web3 Gateway

Web3 gateway for Emerald and Sapphire ParaTimes