Receiving and sending payments in EOS contract (using IRESPO as example)

EDIT: the code examples are outdated and incompatible with CDT. The best is to use my dappscatalog contract as an example for sending and receiving transactions.

It’s quite a common task for a dApp to accept payments and send tokens or refunds, but the EOSIO Developer Portal does not cover this topic.

A typical ending for a contract source code is using EOSIO_ABI macro that dispatches the actions to your class methods. Let’s look at this macro definition in dispatcher.hpp:

  1. It defines “apply” function. This function is called whenever there’s an action where our contract is actor or recipient:
void apply( uint64_t receiver, uint64_t code, uint64_t action )

here, receiver is your contract that receives an action (including actions). Code is the account that owns the contract that is being executed during the initial action. Action is the name of the action encoded as 64-bit integer. For example, if someone sends EOS tokens to our contract, receiver will be our account, code will be N(eosio.token), and action will be N(transfer).

2. It checks that receiver and code are the same:

if( code == self || action == N(onerror) ) {

3. It initializes an instance of your class and executes a dispatcher statement that calls the corresponding method in the class:

TYPE thiscontract( self ); \
switch( action ) { \
} \

So, as we see, EOSIO_ABI macro does not allow our contract receive transfers from other contracts. What we need to do, is define our own apply function that would detect such transfers and act upon them.

Let’s have a look at the smart contract of iRespo ICO which is happening right now.

Their apply function at the bottom of the source file is instantiating the contract class and calling its apply method regardless of receiver and code values.

Then, the apply method checks if this is a transfer action. If not, it calls the regular dispatcher as in EOSIO_ABI macro. If it is a transfer, it starts processing it in transferReceived method.

It could actually be any kind of transfer. Any contract in EOS network may define a transfer action, and it may land in your contract. So, we need to determine if it’s a transfer of assets that we actually expect. So, we verify that the transfer is executed by eosio.token contract, as we want to receive EOS tokens only.

Then, the contract verifies that the asset is actually EOS. This is unnecessary, because the system account defines only one currency, and that is the EOS token. Also it actually prevents you from running your contract in a test network, because testnet native token is called SYS. So, in your contract, it is sufficient to verify that the transfer was executed by eosio.token contract, and your source code should be flexible enough to accept a different token symbol.

Then, there’s one more interesting thing: the iRespo ICO contract sends the IRESPO tokens back to the payer. The contract calculates the token price based on current USD/EOS rate, and then creates a new action object and sends it. The action is created with permission level {_self, N(active)}, which requires a valid signature. How do we achieve that, if the initial action was signed by the payer’s key only?

The answer is kindly provided by iRespo developers: when the contract executes an action, the current permission level is set to "eosio.code". So, we need to grant this permission the right to act on behalf of “active” key:

cleos -u set account permission \
irespoicoico active \
'{"threshold": 1, "keys":[{"key":"EOS5pELWiXKXcnjCukWgSTqeZkdwzqf37CFUueWH1MhLnmA8P6PYD", "weight":1}] , "accounts":[{"permission":{"actor":"irespoicoico","permission":"eosio.code"},"weight":1}], "waits":[] }' \
owner -p irespoicoico

This is a quite powerful delegation of rights, so you need to be careful. Now the contract is able to send any amount of tokens that is available on its account. This is why thorough testing is required before you deploy the contract in production network.

The creation procedure of new transfer action is a bit more clear in EOSVR airdrop code.

An alternative approach could be, that the contract keeps all due outgoing payments in a table, and manual transfers are done by the account owner. In this case, the contract will see the outgoing transfer events and clear corresponding entries from the debts table. It can also reject a transaction if the payment amount is not the same as the due amount.

Telegram: cc32d9, EOS account: "cc32dninexxx"

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store