Build Your Own Token Swap Contract in Solidity
Swap Tokens
In this tutorial, we’re gonna walk you through creating a dope token swap smart contract in Solidity. This bad boy lets users swap one ERC-20 token for another, all while collecting a little fee. Let’s get it!
Prerequisites
Before diving into the code, ensure you have the following tools installed:
Node.js and npm: Node.js allows you to run JavaScript code outside the browser, and npm helps manage packages.
Hardhat: A popular development environment for Ethereum, it helps compile and deploy smart contracts. Don’t stress; we’ll install it together.
Setting Up the Project
Alright, let’s kick things off by initializing our project. Open up your terminal and run the following commands:
mkdir token-swap
cd token-swap
npm init -y
npm install --save-dev hardhat
npx hardhat init
- OpenZeppelin Contracts: These are reusable, security-audited smart contracts for Ethereum. We'll use OpenZeppelin's ERC-20 token interface.
To install OpenZeppelin contracts, run:
npm install @openzeppelin/contracts
Swap Smart Contract Code
Let's now dive into the code of the token swap contract. We'll go through it step by step to explain its components.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract Swap is Ownable {
address public feeCollector; // Address to collect fees
uint32 public feePercent; // Fee percentage (in basis points, e.g., 1000 = 10%)
event TokensSwapped(
address indexed user,
address indexed tokenA,
address indexed tokenB,
uint256 amountIn,
uint256 amountOut,
uint256 fee
);
Explanation:
License Declaration: The
SPDX-License-Identifier: MIT
ensures the contract adheres to the MIT license, allowing others to use and modify it.Not sure about licenses? No sweat! You can totally leave it 'unlicensed' .Version Pragma: We’re rolling with Solidity version
^0.8.24
. The caret(^)
means that our contract can run on any compiler version that's0.8.24
or higher.Imports:
IERC20.sol
is the ERC-20 token standard interface from OpenZeppelin. It provides the necessary functions likebalanceOf
,transfer
, andtransferFrom
.Ownable.sol
allows for ownership control, ensuring that only the contract owner can perform certain actions, such as updating the fee.
State Variables:
feeCollector
: This holds the address of the entity that will collect the swap fees.feePercent
: The fee percentage is expressed in basis points (100 basis points = 1%). For example, afeePercent
of 1000 means a 10% fee on the swap.
Event Declaration: The
TokensSwapped
event emits details whenever a swap occurs. It logs the user's address, tokens involved, and the amount swapped.
Constructor and Initial Setup
constructor(uint32 _feePercent) Ownable(msg.sender) {
require(_feePercent <= 10000, "Fee too high");
feeCollector = msg.sender;
feePercent = _feePercent;
}
Explanation:
The constructor initializes the contract with a fee percentage and sets the contract deployer as the fee collector.
Input validation: We gotta make sure the fee isn’t over 100%. Can’t be greedy, ya know?
Token Swap Logic
function swapTokens(
address tokenA,
address tokenB,
uint256 amountIn
) external {
require(amountIn > 0, "AmountIn must be greater than 0");
// Calculate fee
uint256 fee = (amountIn * feePercent) / 10000;
uint256 amountOut = amountIn - fee;
require(IERC20(tokenB).balanceOf(address(this)) >= amountOut, "Insufficient token for swap, Try lesser Amount");
// Transfer tokenA from sender to this contract
IERC20(tokenA).transferFrom(msg.sender, address(this), amountIn);
// Send fee to fee collector
if (fee > 0) {
IERC20(tokenA).transfer(feeCollector, fee);
}
// Transfer tokenB to the receiver
IERC20(tokenB).transfer(msg.sender, amountOut);
// Emit event for transparency
emit TokensSwapped(msg.sender, tokenA, tokenB, amountIn, amountOut, fee);
}
Explanation:
Input Validation: The function ensures that the amount being swapped is greater than zero.
Fee Calculation:
- The fee is calculated as a percentage of the input amount (
amountIn
). The fee is deducted, leavingamountOut
, which will be sent to the user.
- The fee is calculated as a percentage of the input amount (
Check Token Balance: Before proceeding with the swap, the contract checks if it has enough of
tokenB
to complete the swap. If the contract doesn't have enough tokens, the swap will fail with the message:"Insufficient token for swap, Try lesser Amount"
.Transfer
tokenA
: ThetransferFrom
function is called to movetokenA
from the user's wallet to the contract. For this to work, the user must have already approved this contract to spend theirtokenA
tokens.Transfer Fee: The contract sends the calculated fee to the
feeCollector
.Transfer
tokenB
: After deducting the fee, the contract transfers the remainingamountOut
oftokenB
to the user.Emit Event: Finally, the event
TokensSwapped
is emitted for transparency, logging the swap details.
Updating the Swap Fee
function updateFee(uint32 newFeePercent) public onlyOwner {
require(msg.sender == feeCollector, "Not authorized");
require(newFeePercent <= 10000, "Fee too high");
feePercent = newFeePercent;
}
}
Explanation:
Access Control: Only the contract owner (via
Ownable
) can update the fee percentage.Fee Validation: We check that the new fee doesn’t exceed 100%. Gotta keep it in check!
Set New Fee: The
feePercent
is updated with the new value.
Complete Code
Here’s the full contract again for reference:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract Swap is Ownable {
address public feeCollector; // Address to collect fees
uint32 public feePercent; // Fee percentage (in basis points, e.g., 1000 = 10%)
event TokensSwapped(
address indexed user,
address indexed tokenA,
address indexed tokenB,
uint256 amountIn,
uint256 amountOut,
uint256 fee
);
constructor(uint32 _feePercent) Ownable(msg.sender) {
require(_feePercent <= 10000, "Fee too high");
feeCollector = msg.sender;
feePercent = _feePercent;
}
function swapTokens(
address tokenA,
address tokenB,
uint256 amountIn
) external {
require(amountIn > 0, "AmountIn must be greater than 0");
// Calculate fee
uint256 fee = (amountIn * feePercent) / 10000;
uint256 amountOut = amountIn - fee;
require(IERC20(tokenB).balanceOf(address(this)) >= amountOut, "Insufficient token for swap, Try lesser Amount");
// Transfer tokenA from sender to this contract
IERC20(tokenA).transferFrom(msg.sender, address(this), amountIn);
// Send fee to fee collector
if (fee > 0) {
IERC20(tokenA).transfer(feeCollector, fee);
}
// Transfer tokenB to the receiver
IERC20(tokenB).transfer(msg.sender, amountOut);
// Emit event for transparency
emit TokensSwapped(msg.sender, tokenA, tokenB, amountIn, amountOut, fee);
}
function updateFee(uint32 newFeePercent) public onlyOwner {
require(msg.sender == feeCollector, "Not authorized");
require(newFeePercent <= 10000, "Fee too high");
feePercent = newFeePercent;
}
}
Pro Tip 💡
Before you dive into that swap, just a quick reminder: make sure your swap contract has some liquidity! No one likes a dry pool, right? So, load it up with enough tokens to handle the swaps you want to initiate.
Let’s keep things flowing! 💧💰
Alright! Now that you’ve got the swap function down, how about taking it a step further? Try creating a new function to update the fee collector's address. Just a heads up: you'll want to use that onlyOwner
modifier to keep things secure—check out how it’s done in the updateFee
function for some inspo!
Let’s see what you can come up with! 🚀
Wrapping It Up 🎉
And there you have it! We’ve just built a simple token swap contract in Solidity. This contract lets users swap one ERC-20 token for another while adding a fee to the mix. We also covered how to set it up to collect fees and how to tweak the fee percentage using OpenZeppelin's Ownable contract.
Ready to take it up a notch? You can enhance this basic swap contract by integrating Chainlink's oracle price feeds for real-time pricing or by connecting it with Uniswap V3 for more dynamic trading options. Let’s level up!
Happy coding! ✌️
Subscribe to my newsletter
Read articles from Akshaya Gangatharan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by