Skip to content
Advertisement

Displaying the transaction value of a custom ERC20 function in MetaMask

I have created a new contract on Polygon based on ERC20 with a couple of extra public functions. And am having trouble with communicating what the user is doing to MetaMask.

When I perform a normal ERC20 transfer() transaction, the signature popup correctly showing the value, in myNewToken, of the transaction plus MATIC gas. BUT if I use my new commissionTransfer() transaction, which, on the contract, sends a portion of the payment to the payee and a portion to “the house” as commission, the signature popup doesn’t show the value of the transaction, just the gas fee.

If the user signs the transaction it goes through OK, with the right number of tokens going to the right addresses, but I really need the user to be able to have visibility of what they are signing. It shows if I add a “value: amount” to the transaction but that turns the transaction into a MATIC transfer, not my token.

This is how I execute a commissionTransfer().

const tx = {
   from: userAddress,
   to: contractAddress,
   data: contract.methods.commissionTransfer(payeeAddress, 
         totalTransactionValue).encodeABI()
}
const sentTx = await web3.eth.sendTransaction(tx)

So. My question is, where does MetaMask get the transaction value it displays from? Is it from the transaction object? Is it from the Transfer event in the contract (so does the fact I have two calls to _transfer() in my transaction cause problems)? Is there a way of instructing MetaMask which value needs to be displayed to the user?

In case it’s helpful, here is the smart contract method. The commission value (in tenths of a percent) is global and set by another method.

function commissionTransfer(address recipient, uint256 amount) public virtual returns (bool) {
    address sender = _msgSender();
    uint256 senderBalance = balanceOf(sender);
    require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
    uint256 payAmount = (amount / 1000) * (1000 - commission);
    uint256 comAmount = amount - payAmount;
    _transfer(sender, recipient, payAmount);
    _transfer(sender, theHouse, comAmount);
    return true;
}

Solution used:

Following the response from NuMa below it seems that MetaMask uses the signature of the transfer() function to determine whether it is being used. That is derived from the name (“transfer”) and argument types (address, unit256) so any attempt to change the arguments failed to display on MetaMask.

One method that got around this was to remove 1 wei from the amount within my app and have the contract apply commission to transaction values if (amount % 100000) == 99999. This worked absolutely fine but was confusing for the user to see all those 9s when signing off the transaction.

The method I will probably go with is to create a new contract whose only job is to interface with my token and call the commissionTransfer() function from a transfer() function of its own. That way, in my app, I can control whether commission is applied by which contract is called. The code for the commission contract is:

interface IToken {
    function commissionTransfer(address, address, uint256) external returns (bool);
}

contract tokenCommission {
    address tokenAddress=0xetc;

    function transfer(address recipient, uint256 amount) public returns (bool) {
        return IToken(tokenAddress).commissionTransfer(msg.sender, recipient, amount);
    }

    function decimals() public view virtual returns (uint8) {
        return 18;
    }
}

I also had to change my commissionTransfer() function to include the sender address and validate that the msg.sender is the new commission contract.

The decimals() function appears to be used by MetaMask to show the correct order of token so that was required as well. Once that is done, MetaMask recognises the transfer() function as if it were from an ERC20 contract.

If more elegant solutions are found that would be great.

Advertisement

Answer

First of all, sending a value to a transaction won’t change anything, because as you said, that value is in ether (Matic in this case).

Metamask recognizes function calls by the function name. So if you try to call the approve() method, you will notice that the metamask popup is way different to a normal transfer() call.

So if you want it to display it as you want, you will need to make all your logic into the transfer() function. In this way, when you call the transfer() method, it will show the popup with the information you want. In fact you could make your comissionTransfer() function be called transfer(), and override the transfer() function, and everything will work as you want.

I hope you find this information helpful 🙂

User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement