Part 2: Building a Decentralized Exchange with Solidity and React
Tags: javascript, web3, react, webdev
Published: 2022-06-09 05:03:26

Welcome back **frens**!

In [Part One](https://dev.to/sleepytenor/part-one-building-a-decentralized-exchange-with-solidity-and-react-2ne0) we got to setting up our base project and our constructor function for the DEX. Today we'll be working on our first token swap function and exploring testing with hardhat.

Here's a live [github repo](https://github.com/Geller99/DexterExchange.git) of the project to keep-up with the article and video series.

To understand more about PancakeRouter integration for this project, watch [Gabi's breakdown](https://www.youtube.com/watch?v=GXiSsJVc5V0)

## First Swap Function - $BNB

In our very first function, we'll be allowing the user to send Binance native tokens $BNB to the smart contract for exchange, giving the Router permission to transact the token for the swap, performing the actual token swap and emitting an event which says "Hey! Just swapped my BNB for some select tokens!" 

First, we need to declare an event which will be emitted when token swaps are successfully complete…

```
event SwapTransfer (address from, address to, address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut);
```
**but why?**

Turns out, events are Solidity's way of logging transaction details in smart contracts. Essentially, we declare an event that accepts certain parameters, and then we emit it in a function after a certain transaction has occurred. The event is basically an EVM dispatch signal we can listen to on the client side, with information about whatever transaction triggers it.

Next, we declare our function body, 
```
function swapExactBNBForTokens(uint amountOutMin, address tokenOut) external payable {
}
```

**external?** this indicates a function that can only by called by external parties, and not within the same smart contract.

**payable?** this modifier is used to indicate that a function can transact (receive and send) within a contract. 

Inside this function, we'll declare a fixed-size array that holds two addresses. The first address being that of the token we are trading in, and the second address being that of the token we would be receiving in return.

```
function swapExactBNBForTokens(uint amountOutMin, address tokenOut) external payable {
address[] memory path = new address[](2);
path[0] = pancakeRouter.WETH();  // technically wrappedBNB
path(1) =  tokenOut;   // address of selected token on frontend
}
```

Next, we approve the ``Router`` to transact our token so it can perform the swap. For this, we'll need a dependency, the IERC20 ``approve()`` function which allows another ``contract`` to transact ``tokens`` for a ``user``. 

**Here's what the IERC20 approval function looks like**

``approve(address spender, uint265 amount);``

Navigate to your terminal and install this dependency

``npm install @openzeppelin/contracts``

Then import this line at the top of your contract

``import "@openzeppelin/contracts/token/ERC20/IERC20.sol";``

Back in our function, we'll implement this function using our Router

```
IERC20(pancakeRouter.WETH()).approve(address(pancakeRouter), msg.value);
// technically wrappedBNB
```

This line means, the "wrapped" token-$BNB is approving ``pancakeRouter`` to transact a user provided amount of tokens . `` msg.value`` represents the user inputted amount.

> Note: WETH here means wrapped $ETH, but since we're on the Binance Smart Chain, it represents wrapped $BNB, the native token of BSC. 
> Now we write the swap!


```
pancakeRouter.swapExactETHForTokens{value: msg.value}(amountOutMin, path, msg.sender, block.timestamp + 60 * 10);

// technically its swapExactBNBForTokens
```

We call the function in ``pancakeRouter`` to swap our exact amount of BNB tokens for any other ``token`` we want. Let's examine the interface of this function to understand the parameters it accepts.
```
function swapExactETHForTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external payable returns (uint[] memory amounts);
```
the ``amountOutMin`` represents the minimum amount of tokens sent back to the user.
the ``path`` array holds the addresses of both tokens being swapped.
the ``address`` to refers to the destination of the token after it's swapped.
the ``deadline`` is a specified time limit in which a transaction fails, to avoid long pending transactions and inaccurate prices.
But holdon! There's a certain piece of syntax lost on us - ``{value: msg.value}``

This represents solidity's way of sending a ``blockchain's`` native token to a ``payable`` function. Without the ``payable`` keyword, this transaction will revert. So this is how we send the user's BNB to the contract.

## Moving On…

The next thing we need to do is get the ``amountsOut`` from the ``router``, we do so using an internal ``pancakeRouter`` function that accepts the user's input, the ``path`` array and returns two amounts -  the number of tokens sent in and number of ``tokens`` sent back to the user.

``uint256[] memory amounts = pancakeRouter.getAmountsOut(msg.value, path);``

We then log our token swap using the ``emit`` keyword and passing arguments to the event we created earlier.

```
emit SwapTransfer(address(pancakeRouter), msg.sender, pancakeRouter.WETH(), tokenOut, msg.value, amounts[1]);
```

Marvelous work! We've effectively sent BNB to our contract, approved the contract to transact our tokens, swapped the tokens and logged that transaction from the blockchain!

Your function should look like this now.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r7lz6zpe6dmhkef1kyt2.png)


## Testing our first Function with Hardhat and Moralis

We're ready to test our function and see it in action. So head over to the folder named ``tests`` in your project directory and open ``sample.js``. In there, you should find some sample test functions that come by default with a hardhat project. 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9glw231391dwm54clcjm.png)

We'll be changing the file name to ``TestDexter.js`` and updating the function descriptions inside the describe block.

```
...
describe("Dex Testing", () => {

  it("Should accept user's BNB and swap for Cake", async () => { }
}
      
```

Next, we'll setup our constants, including parameters for the swap function and then a ``beforeEach`` method to deploy the smart contract so we can run the swap function.



For our swap function, recall we need the contract address of both tokens.

**Note -  in production, these token addresses are automatically fetched by the ``DEX`` after user picks what token they wish to exchange for. But for the purpose of writing tests, we need to provide these addresses ourselves. **

Head over to [pancakeswap docs](https://docs.pancakeswap.finance/code/smart-contracts/pancakeswap-exchange/router-v2 ) and copy the contract address for the PancakeRouter.

And retrieve the contract address of the $cake token over [here](https://bscscan.com/token/0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82). 

Once you're done setting up constants, the next line you need is getting a ``signer`` with ethers.

``const accounts = await ethers.getSigners();``

``owner = accounts[0]``

> A Signer in Ethers.js is an object that represents an Ethereum account. It's used to send transactions to contracts and other accounts. Here we're getting a list of the accounts in the node we're connected to, which in this case is Hardhat Network, and only keeping the first/owner account for our test purposes - [source is Hardhat documentation on testing](https://hardhat.org/guides/waffle-testing)

Next, we create a contract factory for any instances of our ``exchange`` we need to deploy

``const DexterExchangeContract = await ethers.getContractFactory("DexterExchange");``

This line basically says, "from our smart contract, generate a factory and store in a variable" - this makes it easy to run tests on instances of the same smart contract. 

> read up on the ``factory`` pattern in OOP to gain more insight.

After creating the factory, we deploy the smart contract with ``ethers`` and log the address of the ``contract`` to the console. 

```
...

dexterExchange = await DexterExchangeContract.deploy(pancakeRouterAddress); 
await dexterExchange.deployed();
console.log(`dexterExchange deployed at ${dexterExchange.address}`);

```

For the actual test block,

```
it("Should accept user's BNB and swap for Cake", async () => {
     const bnb_cake_swap_tx = await dexterExchange.connect(owner).swapExactBNBForTokens
(0, 
cakeTokenAddress,
{value: ethers.utils.parseEther("500")});  
});

```

This code snippet gets the ``connect`` method from our ``exchange`` to connect the ``owner`` to the contract, then tests the ``swap`` function we wrote.

 For our function parameters, we will pass in zero as the``amountOutMin`` parameter, pass in the ``cakeTokenAddress`` for the ``tokenOut`` parameter and pass in the amount of tokens we wish to send to the contract using the ``utils.parseEther`` method.

At this point, your entire test file should look like this.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rdijo1xhspvu4umpid8o.png)


## Up Next: Setting up Hardhat Config

Head over to your ``hardhat.config.js`` file and replace the existing ``module.exports`` object with this 

```
...
module.exports = {
  solidity: "0.8.4",
  networks: {
    hardhat: {
      chainId: 31337,
    },
    localhost: {
      url: "https://localhost:8545",
      chainId: 31337,
    },
  },
};
```
This sets up our ``hardhat`` local chain id and sets up localhost as the destination url for the forked version of the BSC mainnet.



## Forking Binance Smartchain Mainnet From Moralis

Next, you want to head to [moralis](https://moralis.io/), create an account and head over to the admin dashboard. On the bottom-left side of the dashboard, click on "speedy nodes". This will give you a list of options.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0xzuyqwwjv5pw77q8a4i.png)

On the Binance Smart Chain, click on the button that says endpoints and it should reveal a list of endpoints.

Copy the url to the "mainnet archive", head back to your project terminal and enter the following command

```
npx hardhat node --fork "paste mainnet archive url here"

```

This command will complete a fork of the BSC mainnet and you should see the following result in your terminal.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wg8n8o2x7h58zej3i706.png)

Congrats! You now have a local running node of the Binance Smart Chain.

## Setting up Local Blockchain in Metamask

Now, we get to add our mainnet fork to Metamask so we can view changes in token amounts after running our test swaps.

If you haven't already, head over to the [download page](https://metamask.io/download/) and install metamask for your web browser of choice. Once you're done setting installing and setting up a wallet, head over to settings, click on ``networks`` and hit ``add network``. This should open up the following page

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e6rivibdv8z7zska6ck6.png)


Enter the following details next...


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hsg7mwrh6fspn2m8m7eo.png)

You might encounter an error here since we entered the values AFTER running the node. To fix this, close your current running node in the terminal and run the command again

```
npx hardhat node --fork "paste mainnet archive url here"

```
Then re-enter your chain id in the field and click  "save".

Voila! Your own local binance chain is running and recognized by metamask. You should be able to see all your wallet information now with the currency $BNB.



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hi8a44zuj0fik6fvts7m.png)

## Import a Wallet

To import one of the available free accounts on the BSC localhost fork, right click on the account button in the metamask extension.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/68xej6do8q0zacidqr4r.png)

head to the terminal in your project and copy the private key from the first account and paste in the 'private key' field.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e895wdr53oyse4n0996f.png)



## Running our Tests

Whew! Finally!

 To run your test function, type this command into the terminal

```
npx hardhat test --network localhost

```
If all's well, this should be your result!

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mhfo7m5oy1cmh2gltxlf.png)


Now head back to your metamask wallet and notice your native BNB balance is no longer 10,000 but now 



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q7k1ezshskx75ly06572.png)

Wait! Where's the token I swapped for? Where's my CAKE?!!!!!

> Well...see, we lied...

**JK**

Turns out you need to **import** a ``token`` for your wallet to recognize it.

So, on your wallet page, click "import token", head over to [coinmarketcap](https://coinmarketcap.com/currencies/pancakeswap/) and copy the contract address for the $CAKE token.

Paste it into your import form and it should automatically recognize the token.


Great work! You've officially performed the sexiest token swap in DeFi history, on your own local smart chain. Here's my result!

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8jaeji6z3jt0uq4ajypx.png)

[Tweet us about it!](https://ctt.ac/dlT6i)


## Rounding up - Next Lesson Preview

Hey! I know this was a much longer lesson but you did amazing, if you have any questions or would simply like to keep up with the series, reach [myself](https://twitter.com/elixir_js) or [Gabi](https://twitter.com/GabiDev98).

In the next lesson, we'll be writing and testing more swap functions for various types of tokens and discussing advanced solidity patterns. Thanks for joining us!