{"id":25084206,"url":"https://github.com/realnimish/avalanche-amm","last_synced_at":"2025-07-08T12:03:43.833Z","repository":{"id":44248107,"uuid":"418208982","full_name":"realnimish/avalanche-amm","owner":"realnimish","description":"Learn how to build an Automated Market Maker (AMM) on an EVM-compatible blockchain system. Its tutorial is published on figment.io (https://learn.figment.io/tutorials/create-an-amm-on-avalanche)","archived":false,"fork":false,"pushed_at":"2021-11-11T20:35:59.000Z","size":10931,"stargazers_count":10,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-28T19:37:51.517Z","etag":null,"topics":["amm","automated-market-maker","avalanche","dapp","defi","ethereum","evm","reactjs","solidity","tutorial"],"latest_commit_sha":null,"homepage":"https://realnimish.github.io/avalanche-amm/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/realnimish.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-10-17T17:40:38.000Z","updated_at":"2024-05-04T16:46:28.000Z","dependencies_parsed_at":"2022-09-14T17:12:07.280Z","dependency_job_id":null,"html_url":"https://github.com/realnimish/avalanche-amm","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/realnimish%2Favalanche-amm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/realnimish%2Favalanche-amm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/realnimish%2Favalanche-amm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/realnimish%2Favalanche-amm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/realnimish","download_url":"https://codeload.github.com/realnimish/avalanche-amm/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249061551,"owners_count":21206526,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["amm","automated-market-maker","avalanche","dapp","defi","ethereum","evm","reactjs","solidity","tutorial"],"created_at":"2025-02-07T06:46:28.998Z","updated_at":"2025-04-15T11:36:02.583Z","avatar_url":"https://github.com/realnimish.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Introduction\n\nIn this tutorial, we will learn how to build a very basic AMM having features namely Provide, Withdraw \u0026 Swap with no incentive mechanism like trading fees. Also, we will not deal with ERC20 tokens instead, we will maintain our own mapping storing the balance of the accounts to keep things simple! We will build the smart contract in Solidity and the frontend of our application with the help of ReactJS.\n\n# Prerequisites\n\n* Basic familiarity with ReactJS and Solidity\n* Should've completed [Deploy a Smart Contract on Avalanche using Remix and MetaMask](https://learn.figment.io/network-documentation/avalanche/tutorials/deploy-a-smart-contract-on-avalanche-using-remix-and-metamask) tutorial\n\n# Requirements\n\n* [Node.js](https://nodejs.org/en/download/releases/) v10.18.0+\n* [Metamask extension](https://metamask.io/download.html) on your browser\n\n# What's an AMM?\n\nAutomated Market Maker(AMM) is a type of decentralized exchange which is based on a mathematical formula of price assets. It allows digital assets to be traded without any permissions and automatically by using liquidity pools instead of any traditional buyers and sellers which uses an order book that was used in traditional exchange, here assets are priced according to a pricing algorithm. \n\nFor example, Uniswap uses p * q = k, where p is the amount of one token in the liquidity pool, and q is the amount of the other. Here “k” is a fixed constant which means the pool’s total liquidity always has to remain the same. For further explanation let us take an example if an AMM has coin A and Coin B, two volatile assets, every time A is bought, the price of A goes up as there is less A in the pool than before the purchase. Conversely, the price of B goes down as there is more B in the pool. The pool stays in constant balance, where the total value of A in the pool will always equal the total value of B in the pool. The size will expand only when new liquidity providers join the pool.\n\n# Implementing the smart contract\n\nLet's start with the boilerplate code. We create a contract named `AMM` and import the SafeMath library from OpenZeppelin to perform mathematical operations with proper checks.\n\n```solidity\n// SPDX-License-Identifier: MIT\npragma solidity \u003e=0.7.0 \u003c0.9.0;\n\nimport \"@openzeppelin/contracts/utils/math/SafeMath.sol\";\n\ncontract AMM {\n    using SafeMath for uint256;\n}\n```\n\nNext, we define the state variables needed to operate the AMM. We will be using the same mathematical formula as used by Uniswap to determine the price of the assets (**K = totalToken1 * totalToken2**). For simplicity purposes, We are maintaining our own internal balance mapping (token1Balance \u0026 token2Balance) instead of dealing with the ERC-20 tokens. As Solidity doesn't support floating-point numbers, we will reserve the first six digits of an integer value to represent the decimal value after the dot. This is achieved by scaling the numbers by a factor of 10^6 (PRECISION).\n\n```solidity\nuint256 totalShares;  // Stores the total amount of share issued for the pool\nuint256 totalToken1;  // Stores the amount of Token1 locked in the pool\nuint256 totalToken2;  // Stores the amount of Token2 locked in the pool\nuint256 K;            // Algorithmic constant used to determine price (K = totalToken1 * totalToken2)\n\nuint256 constant PRECISION = 1_000_000;  // Precision of 6 decimal places\n\nmapping(address =\u003e uint256) shares;  // Stores the share holding of each provider\n\nmapping(address =\u003e uint256) token1Balance;  // Stores the available balance of user outside of the AMM\nmapping(address =\u003e uint256) token2Balance;\n```\n\nNow we will define modifiers that will be used to check the validity of the parameters passed to the functions and restrict certain activities when the pool is empty.\n\n```solidity\n// Ensures that the _qty is non-zero and the user has enough balance\nmodifier validAmountCheck(mapping(address =\u003e uint256) storage _balance, uint256 _qty) {\n    require(_qty \u003e 0, \"Amount cannot be zero!\");\n    require(_qty \u003c= _balance[msg.sender], \"Insufficient amount\");\n    _;\n}\n\n// Restricts withdraw, swap feature till liquidity is added to the pool\nmodifier activePool() {\n    require(totalShares \u003e 0, \"Zero Liquidity\");\n    _;\n}\n```\n\nThe following functions are used to get the present state of the smart contract\n\n```solidity\n// Returns the balance of the user\nfunction getMyHoldings() external view returns(uint256 amountToken1, uint256 amountToken2, uint256 myShare) {\n    amountToken1 = token1Balance[msg.sender];\n    amountToken2 = token2Balance[msg.sender];\n    myShare = shares[msg.sender];\n}\n\n// Returns the total amount of tokens locked in the pool and the total shares issued corresponding to it\nfunction getPoolDetails() external view returns(uint256, uint256, uint256) {\n    return (totalToken1, totalToken2, totalShares);\n}\n```\n\nAs we are not using the ERC-20 tokens and instead, maintaining a record of the balance ourselves; we need a way to allocate tokens to the new users so that they can interact with the dApp. Users can call the faucet function to get some tokens to play with!\n\n```solidity\n// Sends free token(s) to the invoker\nfunction faucet(uint256 _amountToken1, uint256 _amountToken2) external {\n    token1Balance[msg.sender] = token1Balance[msg.sender].add(_amountToken1);\n    token2Balance[msg.sender] = token2Balance[msg.sender].add(_amountToken2);\n}\n```\n\nNow we will start implementing the three core functionalities - Provide, Withdraw and Swap.\n\n## Provide\n\n`provide` function takes two parameters - amount of token1 \u0026 amount of token2 that the user wants to lock in the pool. If the pool is initially empty then the equivalence rate is set as **_amountToken1 : _amountToken2** and the user is issued 100 shares for it. Otherwise, it is checked whether the two amounts provided by the user have equivalent value or not. This is done by checking if the two amounts are in equal proportion to the total number of their respective token locked in the pool i.e. **_amountToken1 : totalToken1 :: _amountToken2 : totalToken2** should hold.\n\n```solidity\n// Adding new liquidity in the pool\n// Returns the amount of share issued for locking given assets\nfunction provide(uint256 _amountToken1, uint256 _amountToken2) external validAmountCheck(token1Balance, _amountToken1) validAmountCheck(token2Balance, _amountToken2) returns(uint256 share) {\n    if(totalShares == 0) { // Genesis liquidity is issued 100 Shares\n        share = 100*PRECISION;\n    } else{\n        uint256 share1 = totalShares.mul(_amountToken1).div(totalToken1);\n        uint256 share2 = totalShares.mul(_amountToken2).div(totalToken2);\n        require(share1 == share2, \"Equivalent value of tokens not provided...\");\n        share = share1;\n    }\n\n    require(share \u003e 0, \"Asset value less than threshold for contribution!\");\n    token1Balance[msg.sender] -= _amountToken1;\n    token2Balance[msg.sender] -= _amountToken2;\n\n    totalToken1 += _amountToken1;\n    totalToken2 += _amountToken2;\n    K = totalToken1.mul(totalToken2);\n\n    totalShares += share;\n    shares[msg.sender] += share;\n}\n```\n\n{% hint style=\"danger\" %}  \nCarefully notice the order of balance update we are performing in the above function. We are first deducting the tokens from the users' account and in the very last step, we are updating her share balance. This is done to prevent a reentrancy attack.  \n{% endhint %}\n\nThe given functions help the user get an estimate of the amount of the second token that they need to lock for the given token amount. Here again, we use the proportion **_amountToken1 : totalToken1 :: _amountToken2 : totalToken2** to determine the amount of token1 required if we wish to lock given amount of token2 and vice-versa.\n\n```solidity\n// Returns amount of Token1 required when providing liquidity with _amountToken2 quantity of Token2\nfunction getEquivalentToken1Estimate(uint256 _amountToken2) public view activePool returns(uint256 reqToken1) {\n    reqToken1 = totalToken1.mul(_amountToken2).div(totalToken2);\n}\n\n// Returns amount of Token2 required when providing liquidity with _amountToken1 quantity of Token1\nfunction getEquivalentToken2Estimate(uint256 _amountToken1) public view activePool returns(uint256 reqToken2) {\n    reqToken2 = totalToken2.mul(_amountToken1).div(totalToken1);\n}\n```\n\n## Withdraw\n\nWithdraw is used when a user wishes to burn a given amount of share to get back their tokens. Token1 and Token2 are released from the pool in proportion to the share burned with respect to total shares issued i.e. **share : totalShare :: amountTokenX : totalTokenX**.\n\n```solidity\n// Returns the estimate of Token1 \u0026 Token2 that will be released on burning given _share\nfunction getWithdrawEstimate(uint256 _share) public view activePool returns(uint256 amountToken1, uint256 amountToken2) {\n    require(_share \u003c= totalShares, \"Share should be less than totalShare\");\n    amountToken1 = _share.mul(totalToken1).div(totalShares);\n    amountToken2 = _share.mul(totalToken2).div(totalShares);\n}\n\n// Removes liquidity from the pool and releases corresponding Token1 \u0026 Token2 to the withdrawer\nfunction withdraw(uint256 _share) external activePool validAmountCheck(shares, _share) returns(uint256 amountToken1, uint256 amountToken2) {\n    (amountToken1, amountToken2) = getWithdrawEstimate(_share);\n    \n    shares[msg.sender] -= _share;\n    totalShares -= _share;\n\n    totalToken1 -= amountToken1;\n    totalToken2 -= amountToken2;\n    K = totalToken1.mul(totalToken2);\n\n    token1Balance[msg.sender] += amountToken1;\n    token2Balance[msg.sender] += amountToken2;\n}\n```\n\n## Swap\n\nTo swap from Token1 to Token2 we will implement three functions - `getSwapToken1Estimate`, `getSwapToken1EstimateGivenToken2` \u0026 `swapToken1`. The first two functions only determine the values of swap for estimation purposes while the last one does the conversion.\n\n`getSwapToken1Estimate` returns the amount of token2 that the user will get when depositing a given amount of token1. The amount of token2 is obtained from the equation **K = totalToken1 * totalToken2** where the **K** should remain the same before/after the operation. This gives us **K = (totalToken1 + amountToken1) * (totalToken2 - amountToken2)** and we get the value `amountToken2` from solving this equation. In the last line, we are ensuring that the pool is never drained completely from either side, which would make the equation undefined.\n\n```solidity\n// Returns the amount of Token2 that the user will get when swapping a given amount of Token1 for Token2\nfunction getSwapToken1Estimate(uint256 _amountToken1) public view activePool returns(uint256 amountToken2) {\n    uint256 token1After = totalToken1.add(_amountToken1);\n    uint256 token2After = K.div(token1After);\n    amountToken2 = totalToken2.sub(token2After);\n\n    // To ensure that Token2's pool is not completely depleted leading to inf:0 ratio\n    if(amountToken2 == totalToken2) amountToken2--;\n}\n```\n\n`getSwapToken1EstimateGivenToken2` returns the amount of token1 that the user should deposit to get a given amount of token2. Amount of token1 is similarly obtained by solving the following equation **K = (totalToken1 + amountToken1) * (totalToken2 - amountToken2)**.\n\n```solidity\n// Returns the amount of Token1 that the user should swap to get _amountToken2 in return\nfunction getSwapToken1EstimateGivenToken2(uint256 _amountToken2) public view activePool returns(uint256 amountToken1) {\n    require(_amountToken2 \u003c totalToken2, \"Insufficient pool balance\");\n    uint256 token2After = totalToken2.sub(_amountToken2);\n    uint256 token1After = K.div(token2After);\n    amountToken1 = token1After.sub(totalToken1);\n}\n```\n\n`swapToken1` actually swaps the amount instead of just giving an estimate.\n\n```solidity\n// Swaps given amount of Token1 to Token2 using algorithmic price determination\nfunction swapToken1(uint256 _amountToken1) external activePool validAmountCheck(token1Balance, _amountToken1) returns(uint256 amountToken2) {\n    amountToken2 = getSwapToken1Estimate(_amountToken1);\n\n    token1Balance[msg.sender] -= _amountToken1;\n    totalToken1 += _amountToken1;\n    totalToken2 -= amountToken2;\n    token2Balance[msg.sender] += amountToken2;\n}\n```\n\nSimilarly for Token2 to Token1 swap we implement the three functions - `getSwapToken2Estimate`, `getSwapToken2EstimateGivenToken1` \u0026 `swapToken2` as below.\n\n```solidity\n// Returns the amount of Token2 that the user will get when swapping a given amount of Token1 for Token2\nfunction getSwapToken2Estimate(uint256 _amountToken2) public view activePool returns(uint256 amountToken1) {\n    uint256 token2After = totalToken2.add(_amountToken2);\n    uint256 token1After = K.div(token2After);\n    amountToken1 = totalToken1.sub(token1After);\n\n    // To ensure that Token1's pool is not completely depleted leading to inf:0 ratio\n    if(amountToken1 == totalToken1) amountToken1--;\n}\n\n// Returns the amount of Token2 that the user should swap to get _amountToken1 in return\nfunction getSwapToken2EstimateGivenToken1(uint256 _amountToken1) public view activePool returns(uint256 amountToken2) {\n    require(_amountToken1 \u003c totalToken1, \"Insufficient pool balance\");\n    uint256 token1After = totalToken1.sub(_amountToken1);\n    uint256 token2After = K.div(token1After);\n    amountToken2 = token2After.sub(totalToken2);\n}\n\n// Swaps given amount of Token2 to Token1 using algorithmic price determination\nfunction swapToken2(uint256 _amountToken2) external activePool validAmountCheck(token2Balance, _amountToken2) returns(uint256 amountToken1) {\n    amountToken1 = getSwapToken2Estimate(_amountToken2);\n\n    token2Balance[msg.sender] -= _amountToken2;\n    totalToken2 += _amountToken2;\n    totalToken1 -= amountToken1;\n    token1Balance[msg.sender] += amountToken1;\n}\n```\n\nThis completes the smart contract implementation part. The complete code can be found at [contract/AMM.sol](contract/AMM.sol). Now we will deploy it on the Fuji C-Chain testnet.\n\n# Deploying the smart contract\n\n## Setting up Metamask\n\nLog in to MetaMask -\u003e Click the Network drop-down -\u003e Select Custom RPC\n\n![Metamask](images/metamask.png)\n\n**FUJI Testnet Settings:**\n\n* **Network Name**: Avalanche FUJI C-Chain\n* **New RPC URL**: [https://api.avax-test.network/ext/bc/C/rpc](https://api.avax-test.network/ext/bc/C/rpc)\n* **ChainID**: `43113`\n* **Symbol**: `C-AVAX`\n* **Explorer**: [https://cchain.explorer.avax-test.network](https://cchain.explorer.avax-test.network/)\n\nFund your address from the given [faucet](https://faucet.avax-test.network/).\n\n## Deploy using Remix\n\nOpen [Remix](https://remix.ethereum.org/) -\u003e Select Solidity\n\n![remix-preview](images/remix.png)\n\nCreate an `AMM.sol` file in the Remix file explorer, and paste the code [contract/AMM.sol](contract/AMM.sol)  \n\nNavigate to the Solidity compiler Tab on the left side navigation bar and click the blue button to compile the `AMM.sol` contract. Note down the `ABI` as it will be required in the next section.\n\nNavigate to Deploy Tab and open the “ENVIRONMENT” drop-down. Select \"Injected Web3\" (make sure Metamask is loaded) and click the \"Deploy\" button. \n\nApprove the transaction on Metamask pop-up interface. Once our contract is deployed successfully, make note of the `contract address`.\n\n{% hint style=\"info\" %}  \nAn Application Binary Interface (ABI) is a JSON object which stores the metadata about the methods of a contract like data type of input parameters, return data type \u0026 property of the method like payable, view, pure, etc. You can learn more about the ABI from the [solidity documentation](https://docs.soliditylang.org/en/latest/abi-spec.html)  \n{% endhint %}\n\n# Creating a frontend in React\nNow, we are going to create a react app and set up the front-end of the application. In the frontend, we represent token1 and token2 as KAR and KOTHI respectively.\n\nOpen a terminal and navigate to the directory where we will create the application.\n\n```text\ncd /path/to/directory\n```\n\nNow clone this github repository, move into the newly `avalance-amm` directory and install all the dependencies.\n\n```text\ngit clone https://github.com/realnimish/avalanche-amm.git\ncd avalanche-amm\nnpm install\n```\n\nIn our react application we keep all the React components in the `src/components` directory.\n\n* **BoxTemplate** :- \nIt renders the box containing the input field, its header, and the element on the right of the box, which can be a token name, a button, or is empty.\n\n* **FaucetComponent** :-\n Takes amount of token1 (KAR) and token2 (KOTHI) as input and funds the user address with that much amount.\n\n* **ProvideComponent** :-\nTakes amount of one token (KAR or KOTHI) fills in the estimated amount of the other token and helps provide liquidity to the pool.\n\n* **SwapComponent** :- \nHelps swap a token to another. It takes the amount of token in input field *From* and estimates the amount of token in input field *To* and vise versa.\n\n* **WithdrawComponent** :-\nHelps withdraw the share one has. Also enables to withdraw to his maximum limit.\n\n* **ContainerComponent** :- \nThis component renders the main body of our application which contains the center box containing the tabs to switch between the four components Swap, Provide, Faucet, Withdraw. And also renders the account details and pool details.\n\nNow it's time to run our React app. Use the following command to start the React app.\n```text\nnpm start\n```\n\nVisit [http://localhost:3000](http://localhost:3000) to interact with the AMM.\n\n# Walkthrough\n\nYoutube Link:  \n[![DEMO](https://img.youtube.com/vi/reTlVGURVok/0.jpg)](https://www.youtube.com/watch?v=reTlVGURVok \"DEMO\")\n\n# Conclusion\nCongratulations! We have successfully developed a working AMM model where users can swap tokens, provide \u0026 withdraw liquidity. As a next step, you can play around with the price formula, integrate the ERC20 standard, introduce fees as an incentive mechanism for providers or add slippage protection, and much more...\n\n# Troubleshooting\n\n**Transaction Failure**\n\n* Check if your account has sufficient balance at [fuji block-explorer](https://cchain.explorer.avax-test.network/). You can fund your address from the given [faucet](https://faucet.avax-test.network/)\n\n![Zero balance preview](images/zero_balance.jpeg)\n\n* Make sure that you have selected the correct account on Metamask if you have more than one account connected to the site.\n\n![Multiple account preview](images/multiple_accounts.jpeg)\n\n# About the Author(s)  \n\nThe tutorial was created by [Sayan Kar](https://github.com/SayanKar), [Yash Kothari](https://github.com/Yashkothari9), and [Nimish Agrawal](https://github.com/realnimish). You can reach out to them on [Figment Forum](https://community.figment.io/u/nimishagrawal100.in/) for any query regarding the tutorial.\n\n# References\n\n- [How Uniswap works](https://docs.uniswap.org/protocol/V2/concepts/protocol-overview/how-uniswap-works)\n\n- [Deploy a Smart Contract on Avalanche using Remix and MetaMask](https://docs.avax.network/build/tutorials/smart-contracts/deploy-a-smart-contract-on-avalanche-using-remix-and-metamask)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frealnimish%2Favalanche-amm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frealnimish%2Favalanche-amm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frealnimish%2Favalanche-amm/lists"}