Smart Contract for Token Swap Solidity, JavaScript
👤 Sharing: AI
Okay, let's craft a Solidity smart contract for a basic token swap, along with accompanying JavaScript code to interact with it.
**Solidity Smart Contract (TokenSwap.sol):**
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract TokenSwap is Ownable {
IERC20 public tokenA; // Address of Token A (ERC20)
IERC20 public tokenB; // Address of Token B (ERC20)
uint256 public rateAtoB; // Exchange Rate: How many token B you get for 1 token A (e.g., 2 means 1 A buys 2 B)
uint256 public rateBtoA; // Exchange Rate: How many token A you get for 1 token B
event SwapExecuted(address indexed user, address indexed tokenIn, address indexed tokenOut, uint256 amountIn, uint256 amountOut);
constructor(address _tokenA, address _tokenB, uint256 _rateAtoB, uint256 _rateBtoA) {
tokenA = IERC20(_tokenA);
tokenB = IERC20(_tokenB);
rateAtoB = _rateAtoB;
rateBtoA = _rateBtoA;
}
function setRateAtoB(uint256 _newRate) public onlyOwner {
rateAtoB = _newRate;
}
function setRateBtoA(uint256 _newRate) public onlyOwner {
rateBtoA = _newRate;
}
function swapAtoB(uint256 _amountA) public {
require(_amountA > 0, "Amount must be greater than zero.");
// 1. Transfer token A from user to this contract
tokenA.transferFrom(msg.sender, address(this), _amountA);
// 2. Calculate the amount of token B to send to the user.
uint256 amountB = _amountA * rateAtoB;
// 3. Transfer token B from this contract to the user.
require(tokenB.balanceOf(address(this)) >= amountB, "Insufficient balance of Token B in contract."); //Safety check
tokenB.transfer(msg.sender, amountB);
emit SwapExecuted(msg.sender, address(tokenA), address(tokenB), _amountA, amountB);
}
function swapBtoA(uint256 _amountB) public {
require(_amountB > 0, "Amount must be greater than zero.");
// 1. Transfer token B from user to this contract
tokenB.transferFrom(msg.sender, address(this), _amountB);
// 2. Calculate the amount of token A to send to the user.
uint256 amountA = _amountB * rateBtoA;
// 3. Transfer token A from this contract to the user.
require(tokenA.balanceOf(address(this)) >= amountA, "Insufficient balance of Token A in contract."); //Safety check
tokenA.transfer(msg.sender, amountA);
emit SwapExecuted(msg.sender, address(tokenB), address(tokenA), _amountB, amountA);
}
//Allow the owner to withdraw tokens from the contract (in case of errors or to manage funds)
function withdrawTokenA(uint256 _amount) public onlyOwner {
require(tokenA.balanceOf(address(this)) >= _amount, "Insufficient Token A balance in contract.");
tokenA.transfer(owner(), _amount);
}
function withdrawTokenB(uint256 _amount) public onlyOwner {
require(tokenB.balanceOf(address(this)) >= _amount, "Insufficient Token B balance in contract.");
tokenB.transfer(owner(), _amount);
}
// Fallback function to receive Ether (optional)
receive() external payable {}
}
```
**Explanation of the Solidity Contract:**
1. **`pragma solidity ^0.8.0;`**: Specifies the Solidity compiler version.
2. **`import "@openzeppelin/contracts/token/ERC20/IERC20.sol";`**: Imports the IERC20 interface from OpenZeppelin, which defines the standard functions for ERC20 tokens. This allows the contract to interact with any ERC20 compliant token.
3. **`import "@openzeppelin/contracts/access/Ownable.sol";`**: Imports the Ownable contract from OpenZeppelin. This provides basic access control, where only the contract owner can call certain functions.
4. **`contract TokenSwap is Ownable { ... }`**: Defines the `TokenSwap` contract, inheriting from `Ownable`.
5. **`IERC20 public tokenA;`**, **`IERC20 public tokenB;`**: Declare public variables to store the addresses of the two ERC20 tokens involved in the swap. They are declared as `IERC20` interfaces, allowing the contract to call the ERC20 functions.
6. **`uint256 public rateAtoB;`**, **`uint256 public rateBtoA;`**: Stores the exchange rates. For example, `rateAtoB = 2` means 1 Token A buys 2 Token B.
7. **`event SwapExecuted(...)`**: Defines an event that is emitted whenever a swap is successfully executed. This is important for off-chain monitoring and logging.
8. **`constructor(...)`**: The constructor is called when the contract is deployed. It takes the addresses of Token A, Token B, and the initial exchange rates as arguments. It initializes the corresponding state variables.
9. **`setRateAtoB(uint256 _newRate)`**, **`setRateBtoA(uint256 _newRate)`**: Allows the contract owner to update the exchange rates. This is important for adjusting the swap ratios based on market conditions. These functions are restricted to the owner using the `onlyOwner` modifier.
10. **`swapAtoB(uint256 _amountA)`**: This function allows a user to swap Token A for Token B.
* It requires that the amount to swap is greater than zero.
* It uses `tokenA.transferFrom(msg.sender, address(this), _amountA);` to transfer the specified amount of Token A from the user's address to the contract's address. **Important:** The user must first approve the contract to spend their Token A using the `approve` function of the ERC20 token contract.
* It calculates the amount of Token B to send to the user based on the `rateAtoB`.
* It uses `tokenB.transfer(msg.sender, amountB);` to transfer the calculated amount of Token B from the contract's address to the user's address. It also includes a safety check to ensure the contract has enough balance.
* It emits the `SwapExecuted` event.
11. **`swapBtoA(uint256 _amountB)`**: This function is similar to `swapAtoB`, but it allows a user to swap Token B for Token A.
12. **`withdrawTokenA(uint256 _amount)`**, **`withdrawTokenB(uint256 _amount)`**: These functions allow the contract owner to withdraw tokens from the contract. This is useful for recovering tokens in case of errors or for managing the contract's funds. They are restricted to the owner.
13. **`receive() external payable {}`**: This is the fallback function that allows the contract to receive Ether. This contract does not need to receive ether for swapping tokens but I added the code for completeness.
**Important Security Considerations:**
* **Reentrancy:** The `transfer` calls in the `swapAtoB` and `swapBtoA` functions could be vulnerable to reentrancy attacks if the Token A or Token B contracts have malicious callbacks. Consider using the "Checks-Effects-Interactions" pattern or OpenZeppelin's `ReentrancyGuard` to mitigate this risk. In this example, I am using `require` before calling the `transfer` function.
* **Slippage:** The exchange rate is fixed in this example. In a real-world scenario, you'd need to consider slippage (the difference between the expected price and the actual price due to market volatility). Implement a slippage tolerance mechanism to protect users from unfavorable price changes.
* **Front-Running:** Malicious actors could observe pending swap transactions and execute their own transactions before the original transaction to profit from the price movement. Mitigation techniques include using commit-reveal schemes or order matching systems.
* **Approval:** The user must first approve the contract to spend their tokens using the `approve` function of the ERC20 token contract. Make sure users understand this step.
**JavaScript Interaction (using ethers.js):**
```javascript
const { ethers } = require("ethers");
// Replace with your contract address, ABI, and token addresses
const contractAddress = "YOUR_CONTRACT_ADDRESS";
const tokenAAddress = "YOUR_TOKEN_A_ADDRESS";
const tokenBAddress = "YOUR_TOKEN_B_ADDRESS";
// Replace with the ABI of your TokenSwap contract
const contractABI = [
// Paste your contract ABI here (from the Solidity compiler output)
// Example (replace with your actual ABI):
{
"inputs": [
{
"internalType": "address",
"name": "_tokenA",
"type": "address"
},
{
"internalType": "address",
"name": "_tokenB",
"type": "address"
},
{
"internalType": "uint256",
"name": "_rateAtoB",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_rateBtoA",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_amountA",
"type": "uint256"
}
],
"name": "swapAtoB",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_amountB",
"type": "uint256"
}
],
"name": "swapBtoA",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_newRate",
"type": "uint256"
}
],
"name": "setRateAtoB",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_newRate",
"type": "uint256"
}
],
"name": "setRateBtoA",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_amount",
"type": "uint256"
}
],
"name": "withdrawTokenA",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_amount",
"type": "uint256"
}
],
"name": "withdrawTokenB",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]; // Corrected ABI Format
// Replace with your provider URL (e.g., Infura, Alchemy, local node)
const providerUrl = "YOUR_PROVIDER_URL"; // Example: "https://sepolia.infura.io/v3/YOUR_INFURA_PROJECT_ID";
// Replace with your private key
const privateKey = "YOUR_PRIVATE_KEY";
async function main() {
const provider = new ethers.providers.JsonRpcProvider(providerUrl);
const wallet = new ethers.Wallet(privateKey, provider);
const contract = new ethers.Contract(contractAddress, contractABI, wallet);
// --- 1. Approve the contract to spend your tokens ---
const tokenAContract = new ethers.Contract(tokenAAddress, [
"function approve(address spender, uint256 amount) external returns (bool)",
], wallet);
const amountToApprove = ethers.utils.parseUnits("100", 18); // Example: Approve 100 tokens (assuming 18 decimals)
console.log("Approving contract to spend Token A...");
const approveTx = await tokenAContract.approve(contractAddress, amountToApprove);
await approveTx.wait(); // Wait for the transaction to be mined
console.log("Approval successful!");
// --- 2. Swap tokens ---
const amountToSwap = ethers.utils.parseUnits("1", 18); // Swap 1 Token A (adjust decimals if needed)
console.log(`Swapping ${ethers.utils.formatUnits(amountToSwap, 18)} Token A for Token B...`);
const swapTx = await contract.swapAtoB(amountToSwap);
await swapTx.wait();
console.log("Swap successful!");
//--- 3. Update the rate (owner function)----
// Example: set rateAtoB to 3
const newRate = 3;
console.log(`Updating rate AtoB to ${newRate}...`);
const setRateTx = await contract.setRateAtoB(newRate);
await setRateTx.wait();
console.log("Rate Update successful!");
//--- 4. Withdraw tokens (owner function) ---
// Example: Withdraw 0.5 Token A from the contract (if contract has enough)
const amountToWithdraw = ethers.utils.parseUnits("0.5", 18);
console.log(`Withdrawing ${ethers.utils.formatUnits(amountToWithdraw, 18)} Token A...`);
const withdrawTx = await contract.withdrawTokenA(amountToWithdraw);
await withdrawTx.wait();
console.log("Withdrawal successful!");
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
```
**Explanation of the JavaScript Code:**
1. **`const { ethers } = require("ethers");`**: Imports the `ethers.js` library.
2. **Configuration:** Replace the placeholder values for `contractAddress`, `tokenAAddress`, `tokenBAddress`, `contractABI`, `providerUrl`, and `privateKey` with your actual values.
* **`contractAddress`**: The address of the deployed `TokenSwap` contract.
* **`tokenAAddress`**: The address of the Token A contract.
* **`tokenBAddress`**: The address of the Token B contract.
* **`contractABI`**: The Application Binary Interface (ABI) of the `TokenSwap` contract. You can get this from the Solidity compiler output. The ABI defines the functions and events of the contract. **Crucially, make sure this is correct and complete.**
* **`providerUrl`**: The URL of an Ethereum node provider (e.g., Infura, Alchemy, a local Ganache node).
* **`privateKey`**: The private key of the Ethereum account that will be used to interact with the contract. **Keep this private and secure!**
3. **`async function main() { ... }`**: Defines the main asynchronous function where the interaction logic resides.
4. **`const provider = new ethers.providers.JsonRpcProvider(providerUrl);`**: Creates a provider instance to connect to the Ethereum network.
5. **`const wallet = new ethers.Wallet(privateKey, provider);`**: Creates a wallet instance using the private key and provider. The wallet is used to sign transactions.
6. **`const contract = new ethers.Contract(contractAddress, contractABI, wallet);`**: Creates a contract instance, allowing you to call functions on the deployed smart contract.
7. **Approval Step:**
* **`const tokenAContract = new ethers.Contract(tokenAAddress, ...);`**: Creates a contract instance for Token A. We only need the `approve` function ABI for this step.
* **`const amountToApprove = ethers.utils.parseUnits("100", 18);`**: Specifies the amount of Token A that the contract is allowed to spend on behalf of the user. `ethers.utils.parseUnits` is used to convert the amount to the correct units (in this case, assuming 18 decimal places for the token).
* **`const approveTx = await tokenAContract.approve(contractAddress, amountToApprove);`**: Calls the `approve` function on the Token A contract, granting the `TokenSwap` contract permission to spend the user's tokens.
* **`await approveTx.wait();`**: Waits for the transaction to be mined and confirmed on the blockchain.
8. **Swap Step:**
* **`const amountToSwap = ethers.utils.parseUnits("1", 18);`**: Specifies the amount of Token A to swap.
* **`const swapTx = await contract.swapAtoB(amountToSwap);`**: Calls the `swapAtoB` function on the `TokenSwap` contract.
* **`await swapTx.wait();`**: Waits for the swap transaction to be mined.
9. **Example Owner Functions Calls:**
* `setRateAtoB` : Change the rate
* `withdrawTokenA` : Withdraw tokens from the contract
**To Run This Code:**
1. **Prerequisites:**
* Node.js and npm (Node Package Manager) installed.
* MetaMask or another Ethereum wallet.
2. **Install Dependencies:**
```bash
npm install ethers @openzeppelin/contracts
```
3. **Deploy the Contract:** Deploy the `TokenSwap.sol` contract to a test network (e.g., Ganache, Goerli, Sepolia) using Remix, Hardhat, or Truffle.
4. **Get Token Addresses:** Obtain the addresses of the Token A and Token B contracts. You'll need to deploy these as well (using standard ERC20 contracts).
5. **Fund the Contract:** Send some Token A and Token B to the `TokenSwap` contract's address so it has tokens to swap.
6. **Update JavaScript:** Replace the placeholder values in the JavaScript code with your actual contract address, token addresses, ABI, provider URL, and private key.
7. **Run the Script:**
```bash
node your_script_name.js (e.g., node swap.js)
```
Make sure you have enough ETH in your account to pay for gas fees. Use a test network for development and testing to avoid spending real ETH.
This example provides a basic framework. You'll need to adapt it based on the specific requirements of your token swap implementation, including error handling, input validation, and more advanced security measures. Good luck!
👁️ Viewed: 9
Comments