How to create a wrapped version of an existing ERC20 token

Problem: the current version of the token in question lacks many necessary functions such as the approve/transferFrom functions, so it still has to be wrapped for trading on most DEXes… Uniswap Dex and other Dexs for example.

LATX Contract as listed on etherscan

/**
 *Submitted for verification at Etherscan.io on 2017-10-03
*/

pragma solidity ^0.4.16;

contract LatiumX {
    string public constant name = "LatiumX";
    string public constant symbol = "LATX";
    uint8 public constant decimals = 8;
    uint256 public constant totalSupply =
        300000000 * 10 ** uint256(decimals);

    // owner of this contract
    address public owner;

    // balances for each account
    mapping (address => uint256) public balanceOf;

    // triggered when tokens are transferred
    event Transfer(address indexed _from, address indexed _to, uint _value);

    // constructor
    function LatiumX() {
        owner = msg.sender;
        balanceOf[owner] = totalSupply;
    }

    // transfer the balance from sender's account to another one
    function transfer(address _to, uint256 _value) {
        // prevent transfer to 0x0 address
        require(_to != 0x0);
        // sender and recipient should be different
        require(msg.sender != _to);
        // check if the sender has enough coins
        require(_value > 0 && balanceOf[msg.sender] >= _value);
        // check for overflows
        require(balanceOf[_to] + _value > balanceOf[_to]);
        // subtract coins from sender's account
        balanceOf[msg.sender] -= _value;
        // add coins to recipient's account
        balanceOf[_to] += _value;
        // notify listeners about this transfer
        Transfer(msg.sender, _to, _value);
    }
}

I am coding a wrapper contract that needs to wrap the current token for a more functional ERC20 version for use in Uniswaps LPs.

Proposed Current Code (Based off Wrapped ETH - needs additional code/ adjustments)

pragma solidity ^0.6.0;

import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol';

contract WLATX is ERC20 {
    constructor() ERC20("Wrapped LatiumX", "WLATX") public {}
    
    function mint() external payable {
        _mint(msg.sender, msg.value);
    }
    
    function burn(uint amount) external {
        msg.sender.transfer(amount);
        _burn(msg.sender, amount);
    }
}

QUESTION: What adjustments should I make to the code so that this contract represents a wrapped version of the token (0x2f85e502a988af76f7ee6d83b7db8d6c0a823bf9 - LATX)?

1 Like

Emmm, I think in your wrapped token contract, you should declare the underlying token, such as:

address underlyingToken = "0x2f85E502a988AF76f7ee6D83b7db8d6c0A823bf9";

And it seems like the LATX does not support mint.

1 Like

thanks for the reply - yes the underlying token definitely needs to be declared - should I put that line before or after the constructor line?

If LATX doesn’t support MINT - will this matter since it is only being wrapped? Does the wrapped token contract need to support an additional minting function?

This is helpful please keep the replies coming!

This seems to be the main issue, I’m not sure there’s a way to either react to a LatiumX transfer (tokensReceived) nor pull the token (approve + transferFrom) since it’s such a basic logic.

A rather complex, potentially bad idea is to have a wallet/escrow contract to which you send your LATX tokens and then the WLATX contract pulls from it to mint.

contract Wallet {
  private LatinumX latx = "0x..latx...";

  modifier onlyWlatx() {
    require(msg.sender == "0x..wrappedlatx..");
    _;
  }

  withdraw(uint256 amount) external onlyWlatx {
    latx.transfer(amount, wlatx);
  }
}

And then in WLATX you pull from the wallet contract

contract WLATX is ERC20 {
  ...

  function mint(uint256 amount, address wallet) external {
    uint256 previousBalance = latx.balanceOf(address(this));
    wallet.withdraw(amount);
    uint256 currentBalance = latx.balanceOf(address(this));
    require(previousBalance + amount == currentBalance);
    _mint(msg.sender, amount);
  }
}

The idea here is that the user would:

  1. have its own wallet/escrow hold their tokens, which plays nice with the wrapped token contract (to make it cheaper, you can deploy multiple proxies to the same wallet implementation with minimal proxies).
  2. send their tokens to the wallet contract
  3. and then call mint in the wrapped latx contract

Anyway, I’m just dumping this here but I don’t think it’s a good idea for many reasons:

  • I’m not aware of any contract doing this kind of dark magic, so as far as I know it’s unproven
  • Even though you can send the tokens to your own “private” wallet, anyone can detect it and frontrun you to claim the tokens => stolen funds :fire:
  • I really didn’t think this through, just throwing it out here for brainstorming
1 Like

Here is a TokenWrapper contract from Golem GNT that may offer some further insights as to how this can be achieved - any interpretation would be greatly appreciated.

/**
 *Submitted for verification at Etherscan.io on 2016-12-07
*/

/*
   Copyright 2016 Nexus Development, LLC

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

pragma solidity ^0.4.2;

// Token standard API
// https://github.com/ethereum/EIPs/issues/20

contract ERC20Constant {
    function totalSupply() constant returns (uint supply);
    function balanceOf( address who ) constant returns (uint value);
    function allowance(address owner, address spender) constant returns (uint _allowance);
}
contract ERC20Stateful {
    function transfer( address to, uint value) returns (bool ok);
    function transferFrom( address from, address to, uint value) returns (bool ok);
    function approve(address spender, uint value) returns (bool ok);
}
contract ERC20Events {
    event Transfer(address indexed from, address indexed to, uint value);
    event Approval( address indexed owner, address indexed spender, uint value);
}
contract ERC20 is ERC20Constant, ERC20Stateful, ERC20Events {}

contract ERC20Base is ERC20
{
    mapping( address => uint ) _balances;
    mapping( address => mapping( address => uint ) ) _approvals;
    uint _supply;
    function ERC20Base( uint initial_balance ) {
        _balances[msg.sender] = initial_balance;
        _supply = initial_balance;
    }
    function totalSupply() constant returns (uint supply) {
        return _supply;
    }
    function balanceOf( address who ) constant returns (uint value) {
        return _balances[who];
    }
    function transfer( address to, uint value) returns (bool ok) {
        if( _balances[msg.sender] < value ) {
            throw;
        }
        if( !safeToAdd(_balances[to], value) ) {
            throw;
        }
        _balances[msg.sender] -= value;
        _balances[to] += value;
        Transfer( msg.sender, to, value );
        return true;
    }
    function transferFrom( address from, address to, uint value) returns (bool ok) {
        // if you don't have enough balance, throw
        if( _balances[from] < value ) {
            throw;
        }
        // if you don't have approval, throw
        if( _approvals[from][msg.sender] < value ) {
            throw;
        }
        if( !safeToAdd(_balances[to], value) ) {
            throw;
        }
        // transfer and return true
        _approvals[from][msg.sender] -= value;
        _balances[from] -= value;
        _balances[to] += value;
        Transfer( from, to, value );
        return true;
    }
    function approve(address spender, uint value) returns (bool ok) {
        _approvals[msg.sender][spender] = value;
        Approval( msg.sender, spender, value );
        return true;
    }
    function allowance(address owner, address spender) constant returns (uint _allowance) {
        return _approvals[owner][spender];
    }
    function safeToAdd(uint a, uint b) internal returns (bool) {
        return (a + b >= a);
    }
}

contract ReducedToken {
    function balanceOf(address _owner) returns (uint256);
    function transfer(address _to, uint256 _value) returns (bool);
    function migrate(uint256 _value);
}

contract DepositBrokerInterface {
    function clear();
}

contract TokenWrapperInterface is ERC20 {
    function withdraw(uint amount);

    // NO deposit, must be done via broker! Sorry!
    function createBroker() returns (DepositBrokerInterface);

    // broker contracts only - transfer to a personal broker then use `clear`
    function notifyDeposit(uint amount);

    function getBroker(address owner) returns (DepositBrokerInterface);
}

contract DepositBroker is DepositBrokerInterface {
    ReducedToken _g;
    TokenWrapperInterface _w;
    function DepositBroker( ReducedToken token ) {
        _w = TokenWrapperInterface(msg.sender);
        _g = token;
    }
    function clear() {
        var amount = _g.balanceOf(this);
        _g.transfer(_w, amount);
        _w.notifyDeposit(amount);
    }
}

contract TokenWrapperEvents {
    event LogBroker(address indexed broker);
}

// Deposits only accepted via broker!
contract TokenWrapper is ERC20Base(0), TokenWrapperInterface, TokenWrapperEvents {
    ReducedToken _unwrapped;
    mapping(address=>address) _broker2owner;
    mapping(address=>address) _owner2broker;
    function TokenWrapper( ReducedToken unwrapped) {
        _unwrapped = unwrapped;
    }
    function createBroker() returns (DepositBrokerInterface) {
        DepositBroker broker;
        if( _owner2broker[msg.sender] == address(0) ) {
            broker = new DepositBroker(_unwrapped);
            _broker2owner[broker] = msg.sender;
            _owner2broker[msg.sender] = broker;
            LogBroker(broker);
        }
        else {
            broker = DepositBroker(_owner2broker[msg.sender]);
        }
        
        return broker;
    }
    function notifyDeposit(uint amount) {
        var owner = _broker2owner[msg.sender];
        if( owner == address(0) ) {
            throw;
        }
        _balances[owner] += amount;
        _supply += amount;
    }
    function withdraw(uint amount) {
        if( _balances[msg.sender] < amount ) {
            throw;
        }
        _balances[msg.sender] -= amount;
        _supply -= amount;
        _unwrapped.transfer(msg.sender, amount);
    }
    function getBroker(address owner) returns (DepositBrokerInterface) {
        return DepositBroker(_owner2broker[msg.sender]);
    }
}
1 Like