This guide will introduce how to set up development tools on MacOS and develop Dapps on AxiomLedger. Here, we’ll use the well-known decentralized exchange Uniswap@V2 as an example.

Please familiarize yourself with the basic concepts of Uniswap before using the following tutorial for project deployment and experience!

The following steps will take the Taurus testnet as an example. If you want to try it on the mainnet or other testnets, you can view related resources on the Resources page.

Development Environment Configuration

First, you need to install some necessary development tools, including Git, NodeJs, etc.

Install Homebrew

Homebrew is a package manager for MacOS that can help us easily install and manage software. Install Homebrew:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Install Git

Install Git by Homebrew:

brew install git
git --version

Install NVM

Install NVM by curl or wget. Here we provide the curl command:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash

After installing NVM, close and reopen the Terminal, or run the following command to enable NVM:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
nvm --version

Install Node.js

Install Node.js v20 and Node.js v16 using NVM. Later, you may need to use different Node versions to deploy different Uniswap components.

nvm install 16
nvm install 20
node --version

Retrieve Uniswap@V2 Source Code

Since Uniswap v2 requires multiple components, you need to retrieve them in sequence from the official repository to form the following package structure:

Uniswap-v2
  default-token-list
  interface-2.6.5
  uniswap-contract
    contract
      lib
      v2-core
      v2-periphery
  uniswap-sdk

We’ve already organized the complete project code for you, which can be obtained from the test repository. The command is:

git clone https://github.com/axiomesh/embrace.git

Deploy Uniswap@V2 Contract

Init Hardhat

Initialize the Hardhat project under the uniswap-contract package and clone the relevant contract files from the official repository according to the package format mentioned above. If you are using our test repo, please route to the right package and run below code:

cd embrace/dapps/dexes/uniswap-v2/uniswap-contract
npm install
npx hardhat

At this point, you need to modify the networks field in the Hardhat configuration file to fit your network.

The account private key is used for deploying contracts on the chain. It can be safely used by setting it as an environment variable. Use the command export YOUR_PRIVATE_KEY = 'YOUR_PRIVATE_KEY' to write it to the environment variable. Use the Taurus testnet as an example, the Hardhat config is as follows:

const config: HardhatUserConfig = {
  defaultNetwork: "taurus",
  networks: {
    taurus: {
      url: "https://rpc4.taurus.axiomesh.io",
      accounts: [
        "YOUR_PRIVATE_KEY", // please config your private key through safe ways
      ]
    },
  },
};

Write Contract Deployment Scripts

Write the contract deployment script in the scripts package.

Here is the deployment script for the core contract of Uniswap@V2 that we have prepared for you. You can find the corresponding file named deployUniswapV2.js in the test repository.

const {ethers} = require("hardhat");

async function main() {

    // step 1 factory
    const [owner] = await ethers.getSigners();
    console.log("owner address:", owner.address)
    const UniswapV2Factory = await ethers.getContractFactory("UniswapV2Factory");
    uniswapV2Factory = await UniswapV2Factory.deploy(owner.address);
    console.log(`FACTORY_ADDR: ${uniswapV2Factory.target}`);
    const factoryAddr = uniswapV2Factory.target;
    await sleep(1000);
    const initCodePairHash = await uniswapV2Factory.INIT_CODE_PAIR_HASH();
    console.log("INIT_CODE_PAIR_HASH:", initCodePairHash);

    // step 2 router2
    const WETH9 = await ethers.getContractFactory("WETH9");
    weth9 = await WETH9.deploy();
    console.log(`WETH_ADDR: ${weth9.target}`);

    const weth9Addr = weth9.target
    const UniswapV2Router02 = await ethers.getContractFactory("UniswapV2Router02");
    router = await UniswapV2Router02.deploy(factoryAddr, weth9Addr);
    console.log(`Router02_ADDR: ${router.target}`);

    // step 3 router1
    const UniswapV2Router01 = await ethers.getContractFactory("UniswapV2Router01");
    router1 = await UniswapV2Router01.deploy(factoryAddr, weth9Addr);
    console.log(`Router01_ADDR: ${router1.target}`);

    // step 4 multicall
    const MulticallForUni = await ethers.getContractFactory("MulticallForUni");
    multicall = await MulticallForUni.deploy();
    console.log(`MULTICALL_ADDR: ${multicall.target}`);

}

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

async function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}

Write the token deployment script in the scripts package.

Here is the token contract deployment script that we have prepared for you. You can find the corresponding file named deployUniswapTokens.js in the test repository.

const {ethers} = require("hardhat");

async function main() {
    //get the account where the contract is deployed
    const [owner] = await ethers.getSigners();
    console.log(`owner address is ${owner.address}`)

    const mintAmount = ethers.getUint("1000000000000000000000000"); // 1000000 10^18

    const AxmToken1 = await ethers.getContractFactory("AxiomToken")
    const axmToken1 = await AxmToken1.deploy("AxiomToken1", "AX1", mintAmount)
    const axmToken2 = await AxmToken1.deploy("AxiomToken2", "AX2", mintAmount)
    const axmToken3 = await AxmToken1.deploy("AxiomToken3", "AX3", mintAmount)
    console.log(`AXMTOKEN1_ADDR: ${axmToken1.target}`);
    console.log(`AXMTOKEN2_ADDR: ${axmToken2.target}`);
    console.log(`AXMTOKEN3_ADDR: ${axmToken3.target}`);
}

main().catch((error) => {
    console.error(error);
    process.exitCode = 1;
});
contract AxiomToken is ERC20 {
    constructor(string memory _name, string memory _symbol, uint _totalSupply) ERC20(_name, _symbol) {
        if (_totalSupply > 0) {
            _mint(msg.sender, _totalSupply);
        }
    }

    // mint
    function mint(address recipient, uint256 amount) public {
        _mint(recipient, amount);
    }

    // burn
    function burn(address from, uint256 amount) public {
        _burn(from, amount);
    }
}

Deploy Contracts & Record Contract Address

Run the following command to deploy the contracts to the corresponding network. Here, we’ll deploy the contracts to the Taurus testnet as an example.

Please note: The first account configured in the hardhat network will be used as the deployment account. You need to ensure that this account has sufficient tokens. For information on how to obtain tokens from the faucet for testing, please refer to the Resources.
npx hardhat run scripts/deployUniswapV2.js --network taurus
npx hardhat run scripts/deployUniswapTokens.js --network taurus

After running the relevant deployment scripts, we can obtain the relevant Uniswap@V2 contract addresses。After running the relevant ERC20 token deployment script, we can obtain the associated Token contract addresses:

After running the contract deployment script and the ERC20 token deployment script, you will obtain the Uniswap@V2 contract address as well as the Token contract address. These addresses will be used in the next steps.

OWNER_ADDR
INIT_CODE_PAIR_HASH

FACTORY_ADDR
WETH_ADDR
ROUTER02_ADDR
ROUTER01_ADDR
MULTICALL_ADDR

AXMTOKEN1_ADDR
AXMTOKEN2_ADDR
AXMTOKEN3_ADDR

Usage

After deploying the aforementioned contracts, you can obtain the contract deployment addresses. These addresses are used to modify hardcoded values in subsequent frontend project, ensuring the smooth deployment of the frontend project.

The INIT_CODE_PAIR_HASH in the aforementioned contract is actually the hash value of the creationCode of the UniswapV2Pair contract. You need to manually modify the following code block. But if you are using our test repository, this part of the modification has already been completed.
function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
    (address token0, address token1) = sortTokens(tokenA, tokenB);
    pair = address(uint(keccak256(abi.encodePacked(
            hex'ff',
            factory,
            keccak256(abi.encodePacked(token0, token1)),
            hex'INIT_CODE_PAIR_HASH' // using INIT_CODE_PAIR_HASH produced by deploying contract
        ))));
}

Deploy Interface

Now, let’s modify the relevant configurations of the frontend project to locally deploy the Uniswap@V2 frontend poroject Interface-2.6.5.

Modify Uniswap-SDK

Retrieve the source code basing on the SDK version that the frontend depends on. You need to make some modifications. If you are using our test repository, the related source code has already been integrated in the test repository.

"@uniswap/sdk": "3.0.3",

Enter the uniswap-sdk directory, use nvm to switch the Node.js version to 16.13.2. Then execute the following command:

cd embrace/dapps/dexes/uniswap-v2/uniswap-sdk
nvm install 16
yarn install

Modify Network

Since currently Uniswap only supports the Ethereum mainnet and its testnets you need to modify the ChainId[TESTNET] in src/constants.ts to your desired chain ID. Here, we’ll take the AxiomLedger testnet Taurus as an example, and you can change the chain ID to 23412.

export enum ChainId {
  MAINNET = 65524,
  TESTNET = 23412,
}

Modify Contract Address

Make necessary changes to src/constant.ts based on the factory contract deployed earlier:

export const FACTORY_ADDRESS = 'FACTORY_ADDR' // using FACTORY_ADDR produced by deploying contract
export const INIT_CODE_HASH = 'INIT_CODE_PAIR_HASH' // using INIT_CODE_PAIR_HASH produced by deploying contract

Modify WETH Address

Now, modify the contract address corresponding to src/entities/token.ts.

Deploy the WETH contract on the testnet. Earlier, you’ve already deployed the WETH Token contract on the chain, enter the address into the WETH_ADDR in the following code:

export const WETH = {
  [ChainId.MAINNET]: new Token(ChainId.MAINNET, '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6', 18, 'WETH', 'Wrapped Ether'),
  [ChainId.TESTNET]: new Token(ChainId.TESTNET, 'WETH_ADDR', 18, 'WETH', 'Wrapped Ether')
}

Build SDK

Invoke the command under the SDK package in package.json to build SDK, or enter the following command in the corresponding package’s command line:

cd embrace/dapps/dexes/uniswap-v2/uniswap-sdk
npx tsdx build

At this point, a new artifact has produced. You can used it wherever the SDK is needed.

Default-token-list Modification

Retrieve the source code based on the default-token-list version that the frontend depends on, which requires some modifications. If you are using our test repository, the related source code has already been integrated in the test repository.

This library will help you generate the relevant token-lists.

Find the corresponding package and manually modify it according to the following format. Since WETH is the required contract for Uniswap@V2, you need to modify the address and chainId to your configuration after deployment.

Using src/tokens/testnet.json as an example, please fill in the corresponding fields with the WETH_ADDR, AXMTOKEN1_ADDR, etc., produced after deploying the token contract as mentioned above. The logoURI of the test token can be customized. Take Taurus testnet as an example, you can modify the chainId to 23412.

For other tokens, please read the token list description and fill in accordingly.

[
  {
    "name": "Wrapped Ether",
    "address": "WETH_ADDR",
    "symbol": "WETH",
    "decimals": 18,
    "chainId": 23412,
    "logoURI": "WETH_URL"
  },
  {
    "name": "AxmToken1",
    "address": "AXMTOKEN1_ADDR",
    "symbol": "AX1",
    "decimals": 18,
    "chainId": 23412,
    "logoURI": "AXMTOKEN1_URL"
  },
  //...
]

Execute the following command in the current package to construct uniswap-default.tokenlist.json, which can be configured and used in the frontend. For instance, see the file in the test repository: uniswap-v2/interface-2.6.5/src/constants/lists.ts.

cd embrace/dapps/dexes/uniswap-v2/default-token-list
npm install
npx rimraf build && mkdir -p build && node src/write.js > build/uniswap-default.tokenlist.json

This token list can be used in the Uniswap@V2 frontend project, allowing the frontend to quickly link to the relevant tokens already deployed on the chain. This is convenient for users to view the balances of various tokens, facilitating the swift establishment of liquidity pools.

Modify Interface

Next is the deployment and construction of the Uniswap@V2 frontend repository.

Retrieve source code from Uniswap’s official repository and switch to tag 2.6.5. Of course, our test project has already deployed the related source code for you.

Modify Contract Address

First, modify the ROUTER_ADDRESS contract address in src/constants/index.ts:

export const ROUTER_ADDRESS = 'ROUTER02_ADDRESS' // using ROUTER02_ADDRESS produced by deploying contract

Then modify the multi-signature contract address MULTICALL_ADDR in src/constants/multicall/index.ts:

const MULTICALL_NETWORKS: { [chainId in ChainId]: string } = {
  [ChainId.MAINNET]: 'MAINNET_MULTICALL_ADDR',
  [ChainId.TESTNET]: 'MULTICALL_ADDR' // using MULTICALL_ADDR produced by deploying contract
}

Modify Dependencies and Configurations

Modify the dependency package:

"@uniswap/default-token-list": "../default-token-list",
"@uniswap/sdk": "../uniswap-sdk",

Adjust the environment configuration. Taking AxiomLedger Taurus testnet as an example, modify the corresponding entries in the .env and .env.productionfiles to the following values:

REACT_APP_CHAIN_ID="23412"
REACT_APP_NETWORK_URL="https://rpc4.taurus.axiomesh.io"

Build and Launch

After making the necessary modifications, execute the following commands to start the Interface frontend locally: (using Node@16)

cd embrace/dapps/dexes/uniswap-v2/Interface
yarn install
npx react-scripts build
npx react-scripts start

Now you can experience Uniswap@V2 built on AxiomLedger Taurus testnet.

Miscellaneous - Build by Remix

Using Remix to build Dapps on AxiomLedger is a more convenient choice, for instance, deploying the Uniswap@v2.

Import the factory contract and the routing contract into Remix, and select the appropriate Solidity version in the compiler.

During the deployment phase, selecting ENVIRONMENT as Injected Provider - MetaMask allows for easy connection and deployment to AxiomLedger via MetaMask.

Finally, we can select the desired method from the contract method list in Remix to invoke.