Accepting NFT Deposits in a WAX Smart Contract

Accepting NFT Deposits in a WAX Smart Contract

When developing a WAX smart contract you may want to accept NFT deposits from your users. This quick tutorial will cover the basics of setting up an event listener for NFT transfers to a WAX smart contract and performing some actions on those NFTs.

This tutorial assumes you already have a basic understanding of developing and compiling WAX smart contracts. If not you can check out the Antelope docs for that.

What Are Events?

Events are a way for WAX smart contracts to notify accounts and other smart contracts of actions performed. For example, when a transfer of tokens happens the receiver account of the transfer will receive a notification of the transfer.

This allows incoming transfers to show up for the receiver in their transaction history but it also allows any smart contract deployed to that receiver account to hook into the transfer action and perform its own logic as needed.

Listening for Events

We use the on_notify attribute before our function to hook into these events in a smart contract. For an atomicassets transfer notification, it will look like this:

[[eosio::on_notify("atomicassets::transfer")]]

Our corresponding function needs to accept the same fields as the atomicassets transfer action, which are from, to, asset_ids, and memo.

[[eosio::on_notify("atomicassets::transfer")]]
void on_atomicassets_transfer(
  name from,
  name to,
  std::vector <uint64_t> asset_ids,
  std::string memo
) {

}

Great, now every atomicassets transfer that our smart contract is notified about we can do something with.

It's important to note that if your smart contract is deployed to the same account that created an NFT collection or the smart contract is on the notify list for a collection then it will receive notifications of every transfer for NFTs in that collection.

In this case, we only want to know about transfers to our smart contract account. So before we do any other logic we want to filter out the rest. We do this with a simple if statement and return.

If the to account is not us, don't do anything else.

[[eosio::on_notify("atomicassets::transfer")]]
void on_atomicassets_transfer(
  name from,
  name to,
  std::vector <uint64_t> asset_ids,
  std::string memo
) {
  // Ignore if not sending to us
  if (to != get_self()) {
    return;
  }

}

Processing the Assets

From here we can now cycle through each asset and perform any logic we like such as saving them to a table, burning them, or both!

To make things easier when dealing with atomicassets we can use the atomicassets-interface header file which is provided in the atomicassets contract repo. Simply include this file in your project and import.

#include "atomicassets-interface.hpp"

Any time you see a something prefixed with atomicassets:: in the below code examples it is referencing the namespace of the included file.

https://github.com/pinknetworkx/atomicassets-contract/blob/master/include/atomicassets-interface.hpp

#include <eosio/eosio.hpp>
#include "atomicassets-interface.hpp"

using namespace eosio;

CONTRACT depositcontract : public contract {
  public:
    using contract::contract;

    // This is a database model definition
    TABLE deposits_s {
       uint64_t    asset_id;
       int32_t     template_id;
       eosio::name schema_name;

       uint64_t primary_key() const { return asset_id; }
    };
    typedef eosio::multi_index<name("deposits"), deposits_s> deposits_t;

    [[eosio::on_notify("atomicassets::transfer")]]
    void on_atomicassets_transfer(
      eosio::name from,
      eosio::name to,
      std::vector <uint64_t> asset_ids,
      std::string memo
    ) {
      // Ignore if not sending to us
      if (to != get_self()) {
        return;
      }

      // Get our assets from atomicassets assets table
      atomicassets::assets_t assets = atomicassets::get_assets(get_self());

      for (auto asset_id: asset_ids) {
        // Sanity check, make sure we own this asset
        auto asset = assets.require_find(asset_id, std::string("User does not own asset " + std::to_string(asset_id)).c_str());

        // Example: checking that the collection is what we want
        check(asset->collection_name == "ourcol"_n, std::string("Assets from collection " + asset->collection_name.to_string() + " can not be deposited").c_str());

        // Example: storing the asset data into a table, scoped by form account
        deposits_t deposits(get_self(), from.value);
        deposits.emplace(get_self(), [&](auto& row) {
          row.asset_id = asset->asset_id;
          row.template_id = asset->template_id;
          row.schema_name = asset->schema_name;
        });

        // Example: burning the asset, for this to work your smart contract account needs the eosio.code permission added
        action(
          permission_level(get_self(), "active"_n),
          "atomicassets"_n,
          "burnasset"_n,
          std::make_tuple(
            get_self(), // asset_owner
            asset_id // asset_id
          )
        ).send();
      }
    }
};

I hope this article was helpful, if you have any questions there are plenty of helpful people, including myself, in the WAX Developers Telegram and on the official WAX Discord in the #get-dev-help channel.