My goal is to create a simple Ethereum smart contract that allows for an atomic swap between tokens. Initially it should've been a cross-chain swap but I am struggling to do the basics. I am using ERC20 tokens that i have created using the openzeppelin ERC20.sol and IERC20.sol contracts and using Remix-IDE to create the contracts.
When i try to transfer the tokens or use the claim()
function, I encounter errors such as
' ERC20: transfer amount exceeds balance'
' ERC20: insufficient allowance'
'Allowance must be greater than 0'
These errors occur even after i use the approve()
function in the AliceCoin and BobCoin contracts
Some Questions:
A: Do i need to deploy the tokens separately before i can use it in my contract?
B: Should all the ERC20 functions be in the same contract as my atomic swap contract?
C: I have create ETH addresses for owner, recipient and token address. But how to i utilize the Remix IDE addresses given to me in my contract?
Any help is much appreciated
Code:
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.1;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract AliceCoin is ERC20 {
constructor(uint256 supply) ERC20("AliceCoin", "ALI") {
_mint(msg.sender, supply);
}
}
contract BobCoin is ERC20{
constructor(uint256 supply) ERC20("BobCoin", "BOB"){
_mint(msg.sender, supply);
}
}
contract AtomicSwap{
ERC20 public tokenA;
ERC20 public tokenB;
// A constructor for the smart contract
constructor() payable {
//_owner = payable(msg.sender);
//_recipient = payable(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2);
tokenA = new AliceCoin(100000);
tokenB = new BobCoin(100000);
}
/*
Attributes of the Atomic Swap
-- Timelock
-- Hashlock
-- Address transfer from
-- Address transfer to
-- secret key
-- Token | That would use functions from the interface
--
*/
struct Swap {
// User who should recieve the contract
address payable recipient;
// Owner of the coin
address payable Owner;
// Address of the token
address tokenAddress;
// The amount of the tokens swapped
uint256 amount;
//uint256 amount; // An amount of the token wanting to be transferred
// Time stated for Swap to be executed
uint256 timelock;
// Cryptographic secret key
bytes32 Hashlock; //0xd218ef7a2461c961fdd5c0cd5a547f52b863d14db3e08e55f365e4cd0b4333c5;
// Secret key
string secret;
// Boolean to check if the owner has been refunded
bool refunded;
// Boolean to check if the token has been claimed
bool claimed;
}
mapping(address => mapping(address => uint256)) private _allowances;
mapping(bytes32 => Swap) public swaps;
event NewAtomicSwap(
bytes32 swapId,
address payable Owner,
address payable recipient,
address tokenAddress,
uint256 amount,
bytes32 Hashlock,
uint256 timelock
);
event Claimed(
bytes32 swapId
);
event Refunded(
bytes32 swapId
);
// Modifiers
modifier checkAllowance(address _token, address _Owner, uint256 _amount){
require(_amount > 0, "Token amount must be greater than 0");
require(
ERC20(_token).allowance(_Owner, address(this)) >= _amount,
"Allowance must be greater than 0"
);
_;
}
modifier futureTimelock(uint256 _time){
require(_time > block.timestamp, "timelock has to be set in the future");
_;
}
modifier claimable(bytes32 _swapId) {
require(swaps[_swapId].recipient == msg.sender, "This is not the right recipient");
require(swaps[_swapId].claimed == false, "already claimed");
require(swaps[_swapId].refunded == false, "already refunded");
_;
}
modifier matchingHashlocks(bytes32 _swapId, bytes32 _x){
require(
swaps[_swapId].Hashlock == keccak256(abi.encodePacked(_x)),
"incorrect hashlock"
);
_;
}
modifier existingContract(bytes32 _swapId) {
require(haveContract(_swapId), "contract does not exist");
_;
}
modifier refundable(bytes32 _swapId) {
require(swaps[_swapId].Owner == msg.sender, "Only the sender of this coin can refund");
require(swaps[_swapId].refunded == false, "Already refunded");
require(swaps[_swapId].claimed == false, "Already claimed");
require(swaps[_swapId].timelock <= block.timestamp, "Timelock not yet passed");
_;
}
function newSwap(
address payable _recipient,
bytes32 _Hashlock,
uint256 _timelock,
address _tokenAddress,
uint256 _amount
)
public // Visibility
payable
checkAllowance(_tokenAddress, msg.sender, _amount)
futureTimelock(_timelock)
returns(bytes32 swapId)
{
swapId = keccak256(
abi.encodePacked(
msg.sender,
_recipient,
_tokenAddress,
_amount,
_Hashlock,
_timelock
)
);
if(haveContract(swapId))
revert("Contract exists");
if(!AliceCoin(_tokenAddress).transfer(_recipient , _amount))
revert("transfer failed");
swaps[swapId] = Swap({
recipient : _recipient,
Owner : payable(msg.sender),
tokenAddress : _tokenAddress,
amount : msg.value,
timelock : getTimestamp() + 60000,
Hashlock : _Hashlock,
secret : "djkcoeuxhjkdf",
Open : false,
locked : false,
finished : false,
refunded : false,
claimed: false
});
emit NewAtomicSwap(
swapId,
payable(msg.sender),
_recipient,
_tokenAddress,
_amount,
_Hashlock,
_timelock
);
}
/* Function for recipient to claim token */
/* Only be claimed if Owner has opened the swap with _____ */
function claim(bytes32 _swapId, bytes32 _Hashlock)
public
payable
claimable(_swapId)
matchingHashlocks(_swapId, _Hashlock)
existingContract(_swapId)
returns (bool)
{
Swap storage s = swaps[_swapId];
s.Hashlock = _Hashlock;
s.claimed = true;
AliceCoin(s.tokenAddress).transfer(s.recipient, s.amount);
emit Claimed(_swapId);
return true;
}
function refund(bytes32 _swapId)
external
existingContract(_swapId)
refundable(_swapId)
returns (bool)
{
Swap storage s = swaps[_swapId];
s.refunded = true;
AliceCoin(s.tokenAddress).transfer(s.Owner, s.amount);
emit Refunded(_swapId);
return true;
}
function haveContract(bytes32 _swapId)
internal
view
returns (bool available)
{
available = (swaps[_swapId].Owner != address(0));
}