Skip to main content

Setting Up the Local Neon EVM Environment

This step-by-step guide describes how to install, configure, and test the local Solana cluster with Neon EVM onboard. It will help new developers create their own environment and run Ethereum programs wrapped into the Neon EVM.

The Neon EVM is a solution that performs transaction execution outside layer 1. The development process can be run on any modern Linux or Mac system, though this document is based on an Ubuntu 20.04 experience.

All the services in the Neon EVM environment presented by the docker-compose configuration files can be interchangeably substituted with their experimental versions in order to develop and test each service independently. To resolve issues, the Neon engineer has to be able to replace any service with a customized one according to the current requirements. You are welcome to change the following docker-compose configuration files based on your needs. Also, you can bring them altogether in a single docker-compose file, it is important to provide dependencies according to the following sequence.

Before you start to build your local environment, make sure you have all the prerequisites.

Prerequisitesā€‹

  • Node package manager ā€” Node.js/npm to interact with he Neon EVM with Web3 and Eth modules.

Setting up the Neon Local Workspace Environmentā€‹

Currently, the most flexible way is to use the set of independent docker containers sharing the common external network. To create the network called local that will be used over the docker containers, just input the following command:

docker network create local

If you want to bind some ports from the service to the host machine to be able to connect them and work with a service independently, just extend a docker-compose.yml configurations with the ports instruction. For example, you can bind the Solana (8899, 8900)- or Proxy (9090)-related ports to the host machine this way.

After establishing the local network, it's time to start the following containers:

1. Solana validator service

This service presents the Solana validator running inside the container

Once you deploy the environment, you'll have the Solana RPC endpoint working from the docker container at the 9090 port. The folder named "solana_state" will be created as well. It contains the Solana ledger to keep the state over restarts. If you need to reset the ledger, just remove this folder and it'll be recreated the next time you run docker-compose.

docker-compose.ymlā€‹

version: "3"

services:
solana:
container_name: solana
image: neonlabsorg/solana:${SOLANA_REVISION:-v1.9.12-testnet}
environment:
SOLANA_URL: http://solana:8899
RUST_LOG: solana_runtime::system_instruction_processor=trace,solana_runtime::message_processor=debug,solana_bpf_loader=debug,solana_rbpf=debug
expose:
- 8899
- 8900
networks:
- net
healthcheck:
test: [ CMD-SHELL, "solana cluster-version -u http://solana:8899" ]
interval: 5s
timeout: 10s
retries: 10
start_period: 10s
volumes:
- "./solana_state:/opt/solana/config/"

networks:
net:
external: yes
name: local

How to run it in bashā€‹

$ docker-compose -f solana/docker-compose.yml pull
$ docker-compose -f solana/docker-compose.yml up -d
2. EVM loader service

This container helps deploy the Neon EVM base contract onto Solana that listens for incoming connections on the port 8899. It's important to say that this container doesn't work as daemon, it just uploads the Neon EVM contract and finishes with zero return code.

docker-compose.ymlā€‹

version: "3"

services:
evm_loader:
container_name: evm_loader
image: neonlabsorg/evm_loader:latest
environment:
- SOLANA_URL=http://solana:8899
networks:
- net
command: bash -c "create-test-accounts.sh 1 && deploy-evm.sh && /opt/spl-token create-account HPsV9Deocecw3GeZv1FkAPNCBRfuVyfw9MMwjwRe1xaU && /opt/spl-token mint HPsV9Deocecw3GeZv1FkAPNCBRfuVyfw9MMwjwRe1xaU 1000000000 --owner /opt/evm_loader-keypair.json -- HX14J4Pp9CgSbWP13Dtpm8VLJpNxMYffLtRCRGsx7Edv"

networks:
net:
external: yes
name: local

How to Run It in Bashā€‹

   $ docker-compose -f evm-loader/docker-compose.yml pull
$ docker-compose -f evm-loader/docker-compose.yml up
3. Database services

This container aims to handle the database that stores all the relevant Ethereum processing metadata linked to each other: transactions, blocks, receipts, accounts etc. This data is consumed by the indexer service.

docker-compose.ymlā€‹

version: "3"

services:
postgres:
container_name: postgres
image: postgres:14.0
command: postgres -c 'max_connections=1000'
environment:
POSTGRES_DB: neon-db
POSTGRES_USER: neon-proxy
POSTGRES_PASSWORD: neon-proxy-pass
hostname: postgres
healthcheck:
test: [ CMD-SHELL, "pg_isready -h postgres -p 5432" ]
interval: 5s
timeout: 10s
retries: 10
start_period: 5s
networks:
- net
ports:
- "127.0.0.1:5432:5432"
expose:
- "5432"

dbcreation:
container_name: dbcreation
image: neonlabsorg/proxy:latest
environment:
SOLANA_URL: http://solana:8899
POSTGRES_DB: neon-db
POSTGRES_USER: neon-proxy
POSTGRES_PASSWORD: neon-proxy-pass
POSTGRES_HOST: postgres
entrypoint: proxy/run-dbcreation.sh
networks:
- net
depends_on:
postgres:
condition: service_healthy


networks:
net:
external: yes
name: local

How to Run It in Bashā€‹

$ docker-compose -f postgres/docker-compose.yml pull
$ docker-compose -f postgres/docker-compose.yml up -d
4. Indexer service

The indexer service indexes all the relevant Ethereum processing metadata consisting of signatures, transactions, blocks, receipts, accounts, etc. It gathers all this data from the Solana blockchain, filtering them by the EVM contract address. It also makes it possible to provide our users with the Ethereum API according to the data provided by the whole known operators.

docker-compose.ymlā€‹

version: "3"

services:
indexer:
container_name: indexer
image: neonlabsorg/proxy:latest
environment:
SOLANA_URL: http://solana:8899
POSTGRES_DB: neon-db
POSTGRES_USER: neon-proxy
POSTGRES_HOST: postgres
POSTGRES_PASSWORD: neon-proxy-pass
CONFIG: ci
START_SLOT: LATEST
hostname: indexer
entrypoint: proxy/run-indexer.sh

networks:
- net

networks:
net:
external: yes
name: local

How To Run It in Bashā€‹

$ docker-compose -f indexer/docker-compose.yml pull
$ docker-compose -f indexer/docker-compose.yml up -d
5. Proxy service
The Proxy service is a core service that allows Ethereum-like transactions to be processed on [Solana](https://docs.solana.com/introduction), taking full advantage of the functionality native to Solana, including the ability to execute transactions in parallel. It's available on 9090 port.

docker-compose.ymlā€‹

version: "3"

services:
proxy:
container_name: proxy
image: neonlabsorg/proxy:latest
environment:
- POSTGRES_DB=neon-db
- POSTGRES_USER=neon-proxy
- POSTGRES_PASSWORD=neon-proxy-pass
- POSTGRES_HOST=postgres
- SOLANA_URL=http://solana:8899
- EXTRA_GAS=5000
- EVM_LOADER=53DfF883gyixYNXnM7s5xhdeyV8mVk9T4i2hGV9vG9io
- CONFIG=ci
- LOG_NEON_CLI_DEBUG=YES
- USE_COMBINED_START_CONTINUE=yes
- NEON_CLI_TIMEOUT=60
- NEW_USER_AIRDROP_AMOUNT=0
- WRITE_TRANSACTION_COST_IN_DB=NO
- START_SLOT=LATEST
- PERM_ACCOUNT_LIMIT=16
hostname: proxy
entrypoint: ./proxy/run-proxy.sh
ports:
- "9090:9090"
expose:
- "9090"
networks:
- net

networks:
net:
external: yes
name: local

How to Run It in Bashā€‹

$ docker-compose -f proxy/docker-compose.yml pull
$ docker-compose -f proxy/docker-compose.yml up -d
6. Faucet service
The Faucet service provides the liquidity in `NEON` to all the accounts that are mentioned in the incoming requests.

docker-compose.ymlā€‹

version: "3"

services:

faucet:
container_name: faucet
image: neonlabsorg/faucet:latest
environment:
- FAUCET_RPC_BIND=0.0.0.0
- FAUCET_RPC_PORT=3333
- SOLANA_URL=http://solana:8899
- NEON_ETH_MAX_AMOUNT=50000
- EVM_LOADER=53DfF883gyixYNXnM7s5xhdeyV8mVk9T4i2hGV9vG9io
- FAUCET_RPC_ALLOWED_ORIGINS=["https://neonswap.live"]
- FAUCET_WEB3_ENABLE=false
- FAUCET_SOLANA_ENABLE=true
- NEON_OPERATOR_KEYFILE=/opt/faucet/id.json
- SOLANA_COMMITMENT=confirmed
entrypoint: /opt/faucet/faucet --config /opt/proxy/faucet.conf run
ports:
- 3333:3333
expose:
- "3333"
networks:
- net

networks:
net:
external: yes
name: local

How to Run It in Bashā€‹

$ docker-compose -f faucet/docker-compose.yml pull
$ docker-compose -f faucet/docker-compose.yml up -d
7. Full test suite service

The full test suite provides in general the OpenZeppelin tests to make sure the infrastructure deployed by this guide works properly. At the end, the full test suite outputs the result in the following form:

Full test passing - 1743
Full test threshold - 1700
Check if 1743 is greater or equal 1700

full_test_suite/docker-compose.ymlā€‹

version: "3"

services:

full_test_suite:
container_name: ${FTS_CONTAINER_NAME:-full_test_suite}
image: ${FTS_IMAGE:-neonlabsorg/full_test_suite:develop}
entrypoint: ./run-full-test-suite.sh 2>/dev/null
environment:
- NETWORK_NAME=${NETWORK_NAME}
- PROXY_URL=${PROXY_URL}
- NETWORK_ID=${NETWORK_ID}
- REQUEST_AMOUNT=${REQUEST_AMOUNT}
- FAUCET_URL=${FAUCET_URL}
- USE_FAUCET=${USE_FAUCET}
- SOLANA_URL=${SOLANA_URL}
- FTS_USERS_NUMBER=${FTS_USERS_NUMBER}
- FTS_JOBS_NUMBER=${FTS_JOBS_NUMBER}

networks:
- net

networks:
net:
external: yes
name: local

full_test_suite/local.envā€‹

NETWORK_NAME=local
PROXY_URL=http://proxy:9090/solana
NETWORK_ID=111
REQUEST_AMOUNT=20000
FAUCET_URL=http://faucet:3333/request_neon
USE_FAUCET=true
SOLANA_URL=http://solana:8899
FTS_USERS_NUMBER=15
FTS_JOBS_NUMBER=8

How to Run It in Bashā€‹

$ docker-compose -f full_test_suite/docker-compose.yml pull
$ docker-compose -f full_test_suite/docker-compose.yml --env-file full_test_suite/local.env up

Logsā€‹

After following the previous steps, you will have four running containers for the Neon EVM local environment: solana, postgres, proxy, indexer:

49c864f47ccd   neonlabsorg/solana:v1.9.12-testnet   "./run.sh"               About an hour ago   Up About an hour (healthy)   8003/udp, 0.0.0.0:8899-8900->8899-8900/tcp, :::8899-8900->8899-8900/tcp, 9900/tcp   solana
92f6b4492894 neonlabsorg/proxy:latest "./proxy/run-proxy.sh" 46 hours ago Up About an hour 0.0.0.0:9090->9090/tcp, :::9090->9090/tcp proxy
932d4d860629 neonlabsorg/proxy:latest "proxy/run-indexer.sh" 46 hours ago Up About an hour 9090/tcp indexer
5a7df37069fc postgres:14.0 "docker-entrypoint.sā€¦" 47 hours ago Up About an hour (healthy) 127.0.0.1:5432->5432/tcp postgres

To look for events or errors, just run the docker logs for either the solana or proxy container:

$ docker logs -f solana 2>&1 | grep -v "Program Vote111111111111111111111111111111111111111"
$ docker logs -f proxy

Remix and MetaMask with the Neon EVMā€‹

Set up the "MetaMask" Chromium extension to connect to the proxy via Custom RPC at http://localhost:9090/solana. The following image describes how to set up the local Solana connection:

Note: Once you create or import a new account in MetaMask, some NEONs will be airdropped into it.

Open Remix (also in Chromium) and select Injected Web3 environment. You can deploy EVM-wrapped smart contracts on Solana and input these instructions:

Truffle Suite with Neon EVMā€‹

Truffle is a popular platform to deploy and test Solidity programs. This section shows you how to check the compatibility of the Neon EVM and the Truffle suite.

In the new terminal, create a Truffle project and deploy contracts into EVM:

$ sudo npm install -g truffle
$ mkdir myproject && cd myproject
$ truffle init
$ npm install web3 @truffle/hdwallet-provider

Common Truffle Settingsā€‹

Put your truffle-config.js into the Truffle root:

$ echo 'const Web3 = require("web3");

const Web3eth = require("web3-eth");
const HDWalletProvider = require("@truffle/hdwallet-provider");

const web3eth = new Web3eth();
const accs = Array.from(Array(10), (_, x) => web3eth.accounts.create());
const privateKeys = accs.map((account) => account.privateKey);

module.exports = {
networks: {
solana: {
provider: new HDWalletProvider(privateKeys, "http://127.0.0.1:9090/solana"),
from: accs[0].publicKey,
network_id: "111",
gas: 3000000,
gasPrice: 1000000000,
}
},

compilers: {
solc: {
version: "0.8.9"
}
}
};' > truffle-config.js

Contract Creationā€‹

Create a trivial contract at contracts/Storage.sol:

$ echo '// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract Storage {
uint256 number;

function put(uint256 num) public {
number = num;
}

function get() public view returns (uint256) {
return number;
}
}' > contracts/Storage.sol

Testingā€‹

You can now start testing Storage invocations with Truffle facility:

$ echo 'const Storage = artifacts.require("Storage");

contract("Storage", (accounts) => {
let storage;

beforeEach(async () => {
storage = await Storage.new();
});

it("should store a value", async () => {
const setResult = await storage.put(248);
assert.equal(setResult.receipt.status, true);
const value = await storage.get();
assert.equal(value, 248);
})
})' > test/Storage.test.js

$ truffle test test/Storage.test.js --network solana

Possible Problemsā€‹

If for some reasons you remove the Solana container and run it again, then all related accounts stored in foreign systems get invalid from that moment. That's why you need to re-run the proxy container and reset the state of MetaMask and Truffle as well, to make all relations consistent.

To reset the MetaMask state, follow the steps at Settings, Advanced, Reset Account.

The Truffle state can be reset by redeploying it in the following way:

$ truffle compile --network solana --reset

Deploy your Solidity programs on the Solana-driven Neon EVM.