Build Dapps
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.
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.
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.production
files 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.