Paying for Services with ERC20 Tokens

AlexL
Official Amulet
Published in
4 min readMar 16, 2018

An increasing number of ecosystems based on cryptocurrencies offer services in exchange for tokens. Paying with tokens for concert and movie tickets, auctions, online purchases, real estate services and even rent will become more and more common.

In this post, we’ll look at the following

  • How to pay for services using ERC20 tokens
  • How to refund ERC20 tokens

Throughout this post, we’ll be discussing two distinct contracts and how they’re connected: the Token Contract that defines the ERC20 token, and the Service Contract which provides some utility to a user in exchange for ERC20 tokens.

Fig 1

1. Sending tokens to a service contract

We’ll begin by looking at a function that’s often present in many ERC20 token contracts (but missing from the ERC20 standard itself).

Snippet (1)

When a user wants to send tokens to a service contract, they call this method with the following parameters:

  • _spender is the service contract address that receives the tokens
  • _value is the number of tokens being sent
  • _extraData is optional

approveAndCall() contains a call to receiveApproval() whose implementation seems to be missing from the token contract. Many developers starting with the basic Ethereum token example get confused by this.

To compile this token contract, we need to declare the interface in the token contract:

Snippet (2)

What will happen if we call approveAndCall()? The answer depends on the _spender parameter in (1). If _spender is a service contract with a properly implemented receiveApproval() function, everything is fine, otherwise the transaction will fail.

Let’s turn our focus to the service contract.

What is a proper implementation of receiveApproval()? Here is an example taken from one of our service contracts for the Amulet Platform. The Amulet Reputation contract receives Amulet Tokens from financial experts who use this platform.

Snippet (3)

ReceivedTokens() in (3) represents the related event.

When the user wants to pay for a service with approveAndCall(), receiveApproval() is triggered with the following parameters:

  • _from is the address of the user
  • _value is the tokens the user deposited to the service contract
  • _token is the address of the token contract
  • _extraData is additional data (optional)

receiveApproval() updates the stored balance of tokens associated with the user via the mapping:

Snippet (4)
Fig 2

To get the service contract compiled, we need to add the following contract to the service contract file:

Snippet (5)

The code still won’t get compiled unless “Token” in (3) is defined. Adding the following interface solves the problem and service contract will be compiled:

Snippet (6)

2. Returning tokens from the service contract to user accounts

We saw how to pay tokens for the service. In the example above, users send tokens via the token contract’s approveAndCall() function and these tokens go into the payers mapping of the service contract.

The same logic can be used for any service contract. Tokens will accumulate in mappings such as payers and be used to pay for services provided by the service contract (Fig. 2). The payers mapping serves the role of a wallet for each participant. When a user pays for the service, the cost of the service is subtracted from this wallet balance.

Let’s examine two scenarios when it comes to refunds:

  • User already paid for the service from their service contract wallet and the service is cancelled
  • Users request a refund before they’ve spent tokens

If the event (concert, movie, play or auction) gets cancelled tokens should be returned to the users from the service contract.

Fig 3

The following function is from Amulet Reputation contract:

Snippet (7)

In (7) services is an array of Service structures each containing meta information about the service and an array of payers. Payers are users who’ve paid for that particular service in tokens.

struct Service {    Payer [] payerInfo;}

and

struct Payer {    address payer;    uint256 tokensForService;}

ReturnTokens() in (7) stands for the related event.

When returnTokens() is called by the owner of the contract, tokens that have been spent on the service (stored in the payerInfo [i].tokensForService field) get returned to the user.

Now let’s try to compile the code with new function returnTokens(). Suddenly, we will get multiple compilation errors. They all refer to the transfer statement in (7). The compiler does not understand our transfer() function.

Fortunately, we can easily fix this. We need to expand interface in (6) to include the transfer() function from our token contract:

Snippet (8)

Now the code is successfully compiled.

Let’s look at the second refund scenario where users request a refund before spending any tokens on a service.

Fig 4

The function requestRefund() (Fig. 4) will provide this type of functionality:

Snippet (9)

ReturnTokensPerRequest() in (9) represents the related event.

--

--