Uniswap V2 fork

How to create uniswap fork and collect 0.05% fees for every trade.

Introduction

We will use Rinkeby test network. Deploy through Truffle and use these contracts, exept a section with Remix deployment, where will be a Ropsten network.

tx - short name for transaction

Table of contents

Deploy with Truffle

Core contracts

When replacing the code, you need to pay attention to the places where the prefix 0x is found, you have to save it and replace what follows it

  1. Create a new folder CoolSwap-core and copy there the code from Gihub repo. Create two files: .env (deployment data) and .secret (leave here your mnemonic phrase).

    Don't lose your private info like mnemonic phrase or API key. All this info in our case will be in dot files that are added to .gitignore file to exclude it from git tracking

    Add these variables in the `.env`:
    TESTNET_NETWORK_URL="https://rinkeby.infura.io/v3/3cb031735f9a46a69f2babab4fae3e0d"
    TESTNET_NETWORK_ID="4"
    EXPLORER_API_KEY="fill it with your key from the explorer account"
    And add your mnemonic phrase in the .secret file (just paste words). For mainnet values replace names prefixes with MAINNET_ and of course add your own links
  2. You can copy a core contracts template or make the same actions as described next. In the SwapFactory.sol contract was added a new line. After factory deployment we need to copy this parameter from the blockchain and paste it in Library:
    bytes32 public constant INIT_CODE_PAIR_HASH = keccak256(abi.encodePacked(type(SwapPair).creationCode));
    In the SwapERC20.sol contract we change Uniswap token names with ours:
    string public constant name = 'Swap-LP-Token';
    string public constant symbol = 'SWAP-LP';
    And were changed all log/file names with Swap name in SwapFactory.sol, SwapERC20.sol and SwapPair.sol
  3. At first we download Ganache where you can fast and easy make the first tests locally. After we've checked that on your machine everything is fine we can more slower deploy on testnet.

    Work with Nodejs 16.\* version

    Install dependencies:
    npm i
    Download Ganache and start it (quick start is enough). Correct development settings in the truffle-config.js if they doesn't match with Ganache settings. But probable it's fine by default.
  4. Go to 2_deploy_contracts.js and fill these variables (adminAddress address will be able to change himself (_feeToSetter) and feeCollectingAddress (_feeTo) in the deployed factory):
    const adminAddress = "";
    const feeCollectingAddress = "";
  5. Compile contracts (confirm truffle installation if it wasn't installed):
    npx truffle compile
    Deploy contracts on Ganache local blockchain:
    npx truffle migrate --reset --network development
    No you probably can see this error in the terminal/console:
    Error: Returned error: VM Exception while processing transaction: revert Swap: FORBIDDEN -- Reason given: Swap: FORBIDDEN.
    We set adminAddress not from local Ganache blockchain. So we can't use setFeeTo method, because we call this method we use Ganache blockchain address instead of adminAddress (if you didn't set this address from Ganache). If you want to bypass this error just change adminAddress with the first one from Ganache or comment this string: In the successful option you have to see something like this:
  6. Well for now it seems ok. It's more faster to debug/solve problems. Now we deploy it on testnet. Contracts are already compiled and we can just change a deploy command:
    npx truffle migrate --reset --network testnet
    You have to see similar output as with Ganache deployment, but you're able to check it on Ethereum testnet. Copy a factory address (from the explorer or truffle output) and save it. Let's verify the factory:
    npx truffle run verify SwapFactory --network testnet
    Now the contract is verified Go to a Read Contract tab, copy INIT_CODE_PAIR_HASH parameter and save it

Finally we have this parameters:


Factory:
0x6Bd5A1A63ffF10De3c6B7C667040E9AE1B47fDf2
INIT_CODE_PAIR_HASH:
0xaf88dd15a55596feb9d67243c727bfd6144af12453963809bc91f0cfcf8241bc

Periphery contracts

  1. This template has changed names/logs in SwapLibrary.sol, SwapRouterV1 and SwapRouterV2. And for now we have to change only init code hash value in a library:
  2. Copy .env and .secret files from the CoolSwap-core folder and install dependecies:
    npm i
  3. Go to 2_deploy_contracts.js and fill these variables:
    const factoryAddress = "";
    const WETH = "";
    For the testnet we can use this WETH contract: 0xc778417e063141139fce010982780140aa0cd5ab

    Use only trusted sources for main networks, like CoinGecko

    Compile contracts:
    npx truffle compile
  4. Let's do the same actions with Ganache as for core to be sure there're no errors. After this we deploy contracts on the testnet:
    npx truffle migrate --reset --network testnet
    And also verify the router contract:
    npx truffle run verify SwapRouterV2 --network testnet
  5. Here we need only a router address

    0xA4E1f3fD10E2397f58926E215Ed331D7cDA14056

Deploy with Remix

  1. Go to Remix editor and create there two files: CoolSwapFactory.sol, CoolSwapRouter.sol
  2. Let's begin with factory. Copy Uniswap factory code from the Etherscan: copy uniswap factory code And paste it into the new CoolSwapFactory file: paste the factory code in the new file
  3. Replace ERC20 contract name with CoolSwapERC20. Also replace two constants: name with CoolSwap LP, symbol with Cool-LP. It will be a bonus token which the user will receive for his liquidity: replace names for erc20 token in the new factory file
  4. Replace a pair contract name with CoolSwapPair. Also replace next to it ERC20 contract name: Also change it in the createPair method: replace other names for erc20 and pair contracts And finally just a little above change the factory name: replace the factory contract name
    You're able to replace all names (interfaces, logs and so on), but we won't do this
  5. Add the following line in the CoolSwapFactory contract:
    bytes32 public constant INIT_CODE_HASH = keccak256(abi.encodePacked(type(CoolSwapPair).creationCode));
    add an init code hash in the factory
  6. Go to "Solidity Compiler" tab. Change compiler version with 0.5.16 if it's different. Enable optimization option and click button "Compile": compile factory button
  7. Go to "Deploy & run transaction" tab. Select Injected Web3 in the ENVIRONMENT drop-down. It will use your external wallet. We will use Metamask wallet. If you didn't login in the wallet then login it now, reload page, come back and compile factory contract one more time. And again chose Injected Web3 option. Chose Ropsten network in the Metamask. Select CoolSwapFactory in the CONTRACT drop-down (probably it's already made by default). Set an address in the _feeToSetter field. Contracts will use feeToSetter variable. This variable contains the address that will be able to set an address feeTo which will get protocol commission. Also feeToSetter can change himself to a new one. Now you can see something like this in the deploy tab: settings on the factory deployment in the deploy tab If you won't add this address and deploy just with empty field then protocol fee will be disabled (you can set this address in future after deployment). So now click on "Deploy" button and confirm a modal window from external wallet with transaction fee: deploy option on the deploy tab
  8. After transaction has been mined you can see all details in the Remix terminal: info about transaction where we deployed the factory Copy a transaction hash and go to Ropsten etherscan. Paste this hash in the search field and go to a new contract page. Copy his address and save it somewhere. info about mined transaction on the Etherscan factory contract address on the Etherscan
  9. Come back into Remix deploy tab. Open our CoolSwapFactory contract and paste _feeTo address in the setFeeTob method. We will use 0x7371466090234bDE67bB6c8Ba55dB705Fcec4BCB address without funds. This address will receive protocol fee. Click on this method button and wait while tx will be mined. After that you can check this transaction on the same way as factory tx. set fee to address and call contract method
  10. Click on the last button INIT_CODE_HASH that is among CoolSwapFactory options. Copy it (without "bytes32") and save somewhere: copy init code pair hash value from deployed factory contract
  11. Go to "File explorers" tab and copy Uniswap router code into CoolSwapRouter.sol. Rename a router contract. rename router contract
  12. Replace a hex value in the pairFor method with the INIT_CODE_HASH that we got earlier (replace it without "0x" at the beginning): replace init code hash value in the router contract
  13. On the compiler tab enable optimization options and compile CoolSwapRouter file. Go to deploy tab. Pass two parameters in the Deploy method with a dot between them: factory address (that we just deployed) and WETH token address. Then deploy contract: deploy router contract
  14. Make the same actions as after factory deployment and copy CoolSwapRouter address.

Now we have this data:


CoolSwapFactory:
0x68d3A8bc9441eF1225521C1E5EC67C6a7509Ce58

CoolSwapRouter:
0xB2D64667df4C3608E0d4f3683f6e69914D185C87

INIT_CODE_HASH:
0x6be71632e8b409042bcda4b9b5dd96de1a924599be32bb83c12bce7d3355900e

Frontend

Main changes in this interface template:

  • Removed all (or almost all) info about first version of Uniswap
  • Removed UNI token info
  • Removed links to Project analytics (it is not included in this description). And saved only necessary for swap flow pages
  • Disabled Coinbase, Fortmatic and Portis options to connect external wallet. You can uncomment it if you have API keys
  • Changed source styles just a little. Now the interface is more like Uniswap V3 then V2
  • Some fixes, like warnings about no checksummed addresses, etc.

  1. Create a new directory: CoolSwap. Copy start template code in this directory and nstall dependencies (do not forget about Nodejs 16). Fill with the a network info .env file. In our case it's Rinkeby testnet:
    REACT_APP_NETWORK_URL="https://rinkeby.infura.io/v3/3cb031735f9a46a69f2babab4fae3e0d"
    REACT_APP_CHAIN_ID="4"
  2. Let's look at forks directory in the project root. We have to change Uniswap sdk to be able use it with our contracts. We will follow the least resistance and copy this builded package in our root directory. Change info directly in the sdk build and replace it in the node_modules directory after each dependency installation (automatically of course. NPM makes his job well). You also can do the same action as Uniswap and put your values in the new sdk and create own npm package. In our template we already have this package and npm script which will replace `node_modules` sdk with files from `forks`:
    "postinstall": "rm -rf ./node_modules/@uniswap/sdk; cp -r ./forks/@uniswap/sdk ./node_modules/@uniswap/sdk",
    All what we have to do it's replace values there with our saved info from the previous deployment. With our template and contracts we need to change only constants and INIT_CODE_HASH, because we use the same info for our ERC20 contract in the Truffle deployment. Open a src/constants/index.ts file. At first copy the old FACTORY_ADDRESS address. Here are our constants:
    export const FACTORY_ADDRESS
    export const ROUTER_ADDRESS
    export const LP_TOKEN_NAME
    export const LP_TOKEN_SYMBOL
  3. Find all his coincidences for whole project and replace it with our new factory (deploy with Truffle): 0x6Bd5A1A63ffF10De3c6B7C667040E9AE1B47fDf2. An example in Visual Studio Code search tab: For router address change it only in one place, in the same file. Our new router address: 0xA4E1f3fD10E2397f58926E215Ed331D7cDA14056. Now go to forks/@uniswap/sdk/dist/constants.d.ts and copy the old init code hash value. Do the same steps as for the factory. Find and replace with the new value: 0xaf88dd15a55596feb9d67243c727bfd6144af12453963809bc91f0cfcf8241bc. You probable have your token info. Well repeat the cycle, find old token name/symbol values and replace it. You can find your info in the Verified router contract:
  4. Install dependencies:
    npm i

    Do not forget to reinstall the node_modules directory if you change something in forks

    Start a server:
    npm start
  5. Now we can check that all works fine. We do not have any pools and can't make swaps. Connect your external wallet and go to Pool page. Click on a Create a pair button: We will use tokens available in the default list. In our example we're creating a new pool, ETH with DAI. Fill form fields. Click on a `Approve ` button and wait while transaction will be in the blockchain. After that you press a Supply button. Confirm your actions in the modal window and wait just a while:
  6. Go back to the Pool page and check your pool in the list: Let's make several swaps on the Swap page:
  7. It remains for us to check accumulated protocol fee liquidity. Go to the Pool page and remove some part or all liquidity from the pool: We see in our remove liquidity transaction the feeTo address, where were accumulated tokens sent: Switch in the wallet to the address for collecting commissions. You're able to see the same pool with the amounts that has been accumulated from previous swaps:

These were the minimum actions needed for the exchange to work and to collect the commission.

Additional changes

Token lists

Here is the main file with lists sources.

constants/lists.ts
All lists is available for Ethereum network. You can add your list address and it will be displayed here: The simple way to place your list is to add it on Github for example and copy his address from there. When you placed a list file you need to press a Raw button and copy a url address: It has to be the address of this type:
https://raw.githubusercontent.com/(organization or user name)/(repo name)/main/list.json

If there is no urls, then by default is used a Uniswap package with tokens: @uniswap/default-token-list. Replace it with some other package or add it manually in a json file and import them from it.

Language

How to add a new messages? Here we use a react-i18next package to translate information. Base settings file in the src/i18.ts and in the src/index.ts we just import it. After that we have to import a translation hook from a react-i18next package:
import { useTranslation } from 'react-i18next';
Now we need to get a t function from this hook somewhere inside your functional component:
const { t } = useTranslation();
And finally use this function with a translation key. For example: Now all what we need to do is add a swap key and his message value in translation files. They are here: Now we already have this message. If you don't just add a key and message as a value in all translation files. It will automatically detect browser language and show content in this language. Example of reflected changes:

User interface

Almost all UI color styles can be changed in this file
theme/index.tsx
If you want to add a new variables, don't forget add in types file:
theme/styled.d.ts
Logos in the Header component are used here
assets/svg/*

Naming

public/index.html
public/manifest.json
public/locales/*
components/vote/DelegateModal.tsx
connectors/index.ts
Change logs name if you change it in the contracts:
hooks/useSwapCallback.ts
Links:
components/PositionCard/index.tsx
components/swap/AdvancedSwapDetails.tsx