A Deep Dive Into Marketplace Smart Contracts

The importance of standards in tokenized item trading

Calvin Pak
The Notice Board
6 min readAug 10, 2018

--

Hail, Guildmates!

My name is Calvin, and I’m part of BitGuild’s engineering team. There’s a lot more to building a full blockchain gaming ecosystem than you’d think, so I’m here to give you a snapshot of the steps involved in creating complex features such as our new Marketplace.

BitGuild Marketplace

As a blockchain gaming portal, BitGuild provides players with commonly used features, such as user management, language preferences, social sharing, game promotions, analytics, pre-sale, and marketplace — all so developers can focus on refining their core gameplay, and integrate with BitGuild to instantly get all the other features.

Some of our partners have already developed in-game marketplaces, but not all have implemented the ERC721 token standard that is used for creating game items. While it is easier to develop an in-game marketplace without adhering to the standard, the benefits of complying with them are definitely worth the extra effort. The most obvious benefit is being able to reach more traders on third party marketplaces.

In the first part of our marketplace deep dive, I’ll cover marketplace design decisions and the ERC721 callback — an important feature that is often overlooked. We will cover how to implement buy-with-PLAT and deal with encoding and decoding bytes in Part (II)

“While it is easier to develop an in-game marketplace without adhering to the standard, the benefits of complying with them are definitely worth the extra effort. The most obvious benefit is being able to reach more traders on third party marketplaces.”

Open Source

The BitGuild marketplace beta is open source, you can read the entire Solidity source code on GitHub.

Since it’s a beta release, we expect bugs to exist and we want your help to find them! Join the bug bounty program and help us find what we’ve missed.

Marketplace Design Decisions

  1. Popular marketplaces only accept ETH as payment at the moment. On BitGuild, we need to also support PLAT — our official ERC20 token.
  2. We considered storing the entire transaction history in the smart contract in an array or mapping. However, since Solidity does not handle large arrays well—and the more complex the code, the higher the gas price is—we’ve decided to go lean. The BitGuild marketplace smart contract only stores current listings. Transaction history is recorded via transfer events on the blockchain anyways.
  3. Another debate we had is whether or not to allow users continue to use the game token when it is listed for sale. While it may sound like a good idea, it has unforeseeable consequences. What happens if a user lists the item on several marketplaces? And what happens if the item is sold while the seller is using it in-game? Because of this, the BitGuild marketplace requires game tokens be transferred to the marketplace smart contract when listed.
  4. To uniquely identify each item listed for sale, there are two parameters: game contract address and the token ID. There are two approaches to this:
    - Chained mapping:
    mapping(address => mapping(uint => Listing) public listings;
    - Hashed key mapping:
    i) hash the game contract address and token ID:
    byte32 key = keccak256(abi.encodedPacked(_contract, _tokenId);
    ii) and use the key in the mapping:
    mapping(byte32 => Listing) public listings;
    Both approaches are essentially the same: “hash and then hash again.” But what if we have 3 parameters? The chained mapping will turn ugly quickly — imagine if we need two addresses and a uint, the mapping will look like: mapping(address => mapping(address => mapping(uint => Listing) public listings; Hence, we decided to go with the hashed key mapping.

ERC721 Callback

The ERC721 Callback hasn’t been discussed much. And if you Google it, the details could be well hidden in some lengthy articles about the full implementation of the ERC721. But without the callback, life would be much harder!

Let’s take a real life example: How do we list an item for sale?

Life without ERC721 callback
Intuitively, we will assume listing is simple — call a listing method on the marketplace contract, passing the game contract address, token ID and the price: MarketplaceInstance.list(contractAddress, tokenId, price); Since the token ownership info is stored inside the game contract, the marketplace contract can call the game contract transfer method to authorize the transfer of the token from the user to the marketplace contract: ERC721(contractAddress).safeTransferFrom(msg.sender, address(this), tokenId);

However, when the game contract receives the call above, the caller, or the msg.sender to the game contract is the marketplace contract, not the user. The token is still owned by the user. So this transfer is not authorized. It will fail.

The only way we can transfer the token to the marketplace contract, is if the user calls the game contract: GameContractInstance.safeTransferFrom(user.walletAddress, marketplaceAddress, tokenId);

Then we need to make another call to the marketplace smart contract to provide the listing details: MarketplaceInstance.listItem(contractAddress, tokenId, price);

So to list the item, we will need two transactions, and two gas fees. But the blockchain is distributed — we do not want to send the listing details without first having the user transferred the token to the marketplace contract. To make the calls happen sequentially, we need to manipulate the nonce (we’re won’t be covering this here).

But what if the token transfer fails? We cannot submit the second transaction until the first transfer is successful, so we’ll have to check the transaction status and initiate the listing using raw transaction only when it succeeds. That is a lot of code for implementing a listing function!

Listing with ERC721 Callback
What is the ERC721 callback? As the ERC721 standard suggests:

A wallet/broker/auction application MUST implement the wallet interface if it will accept safe transfers.

interface ERC721TokenReceiver {
function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) external returns(bytes4);
}

And the callback is implemented inside the safeTransferFrom call:

function safeTransferFrom(address _from, address _to, uint _tokenId, bytes data) {
// ...... handles transfer
bytes4 retval = ERC721TokenReceiver(_to).onERC721Received(_operator, _from, _tokenId, data);
require(retval == 0x150b7a02);
}

In the marketplace contract, add a handler for the callback:

function onERC721Received(address _operator, address _from, uint _tokenId, bytes _extraData) external returns(bytes4) {
// ...... decode _extraData, handle listing
return 0x150b7a02;
}

With the ERC721 callback, now listing an item can be done in one transaction:

GameContractInstance.safeTransferFrom(
user.WalletAddress,
marketplaceAddress,
tokenId,
encode(price) // encode extra data to bytes
);

Here’s the entire flow: a seller calls the safeTransferFrom method of the game contract to transfer the token to the marketplace contract, and the safeTransferFrom method will call the marketplace contract with the ERC721 callback so the listing info can be stored on the marketplace contract. All in just one transaction.

Notes: Older version of the ERC721TokenReceiver has no _operator field. To support older games you may want to support both interfaces.

What’s Next?

We hope this post was useful for tou! In the second part of this post, we’ll cover buy-with-PLAT implementation and how to deal with encoding and decoding bytes. Stay tuned!

BitGuild’s mission is to revolutionize the global gaming industry by creating a platform for a brand new class of games that live on the blockchain. Blockchain games completely redefine the relationship between players and developers by facilitating full and true ownership of in-game assets, cheap & safe item trading, cross-game compatibility of items & currency, and more.

Join the community on Twitter, Discord, and Facebook.

--

--

Calvin Pak
The Notice Board

Creator of hackathon-winning projects Molecule Protocol (compliance tool), Credit3 (under-collateralized lending), and Claimable (token distribution tool)