Have you ever wondered how your users can spend ERC20 platform tokens to buy assets and other tokens from you? Or — in more technical terms — how to make sure a smart contract executes a particular function as soon as it receives tokens? This post is all about using ERC20
transferFrom() to allow your users to do much more than just keep tokens in a wallet.
But before jumping into the nitty-gritty, I’d like to present a common use case. Together, we will find a good way to implement it with smart contracts.
Alice wants to pay 20 Eve Tokens (ET) to Bob’s Lemon Juice Stand and receive 20 Lemon Juice Tokens (LJT) in return.
Bob can only give Alice her LJT if he can verify that he has received the correct amount of ET from her in a single transaction. For that, the smart contract needs proof of payment.
Let’s assume that Alice has a token wallet at
alice_address, and Bob’s Lemon Juice Stand has a smart contract called
bob_address. There’s also an ET contract contract called
eve and a LJT contract called
If Alice uses
eve.transfer(bob_address, 20 ether), the amount will appear at Bob’s Lemon Juice Stand address and it can be verified with
eve.balanceOf(bob_address). His balance will have increased by 20 ET. But just checking
eve.balanceOf(bob_address) won’t allow Bob to check where the tokens came from. So how can Alice prove that she transferred the tokens?
To put it another way, Bob’s Lemon Juice Stand’s ET balance could also increase by 20 ET if two people send each him 10 ET. There is simply no functionality in an ERC20 compatible token that allows someone to trace the origin of the funds. We have to find a different way to achieve this while still staying in the realm of smart contracts — which is also called on-chain. Let’s take a closer look at what that means.
The term on-chain describes all the functionality that can be implemented using smart contracts on the Ethereum Virtual Machine. Off-chain describes anything that is not run on the Ethereum Virtual Machine but still uses the Ethereum block chain as a data source and verifiable transaction log.
If we want to verify that Alice’s payment took place using a program — implemented off-chain — there are a few options. We can easily listen for ET ERC20 events using, for example, a web3.js filter. The two possible events are
Approval, as is documented here. Assuming that our contract emits an event called
Transfer(from, to, amount), we can listen for any event matching
Transfer(alice_address, bob_address, 20 ether) and we can be immediately notified when Alice transfers her tokens.
Inside a smart contract, we can’t listen for events like this. We need to look for an alternative solution that allows us to send tokens and executes some piece of smart contract code in Bob’s Lemon Juice Contract all in one transaction on-chain.
For that we need to use
eve.approve(receiver, amount) and
eve.transferFrom(from, amount). With
eve.approve(), we can give someone permission to withdraw a certain amount of tokens from our address. This is called an allowance. Bob must then have a function in his smart contract called
bob.giveToken() that does the following:
- Check the token allowance
alice_addressin this example).
- Calculate the correct amount of LJT to be paid out.
- Ensure that Bob’s Lemon Juice Stand has enough tokens by checking
eve.transferFrom(alice_address, 20 ether)and Assert that the call was successful.
ljt.transfer(alice_address, 20 ether)and Assert that the call was successful.
If Bob’s Lemon Juice Stand contract provides the
bob.giveToken() function, the process is easy. If Alice now wants to receive LJT in return for her ET, the following two transactions are necessary:
- Alice calls
eve.approve(bob_address, 20 ether). No token balances will change.
- Alice calls
bob.giveToken(). If the transaction is validated, Alice now has 20 more LJT and 20 fewer ET.
So we can’t solve this problem in less than two transactions. With ERC20, there is no way to transfer tokens and call smart contract functionality at the same time. When it comes to token transactions, simple and secure is better. Newer standards such as ERC223 or EIP777 provide this functionality.