TradableAsset — Standard and Implementation Example

Overview

For your game to be compatible with the Expload Auction and Inventory, all in-game items (or assets) should be stored in the Pravda program that inherits either ITradableXGAsset interface or ITradableXPAsset interface.
The interface ITradableXGAsset is suitable for XGold Assets — assets that can only be sold for XGold on the Expload Auction. Similarly, ITradableXPAsset is suitable for XPlatinum Assets that can only be sold for XPlatinum. The logic and structure of these interfaces are identical, the only difference being the naming (e.g. GetXGAsset and GetXPAsset have the same purpose).

Let's have a closer look at these interfaces drawing on the example of XGold Asset implementation.

Code snippets from the implementation are solely included for the deeper understanding of the asset's inner logic, and reading through the snippets is not necessary. If your game doesn't need any specific mechanics for the releasing or or transferring of assets, you can just use the example implementation as linked above in your game, including as an API. Check out the asset's structure and method interface below and move on to Deploy and Implementation Setup.

Asset Structure

Each asset has the following fields:

long Id (or id) — the blockchain-id of the asset, that doesn't have any in-game meaning and is only used inside Pravda programs as a unique asset identifier.

Bytes Owner — the address of the current asset owner.

Bytes ItemClassId (or classId) — the in-game id of a specific asset class. For example, two in-game swords called "Deathbringer" may have different upgrades — one can be sharpened by a blacksmith and the other one can be enhanced by a wizard. This means that they have the same classId as they are the same class of an item, but their instances differ. Assets with the equal classId will be put in a single Expload Auction tab.

Bytes ItemInstanceId (or instanceId) — the in-game id of a specific item instance. For example, two in-game swords called "Deathbringer" may have different upgrades — one can be sharpened by a blacksmith and the other one can be enhanced by a wizard. This means that they have different instanceId as despite being the same item class, their instances differ.

Asset Operations

Asset Issuance

EmitXGAsset(Bytes owner, Bytes classId, Bytes instanceId) returns long id — releases assets with specified parameters and gives them to the user with the address Bytes owner. Returns the unique asset id and can only be called by the Pravda program owner.

Implementation example snippet Let's look into this method implementation (the comments are changed for the sake of convenience)
public long EmitXGAsset(Bytes owner, Bytes classId, Bytes instanceId){
    // Checking if the method caller
    // is the program owner
    AssertIsGameOwner();

    // _lastXGId - global variable storing 
    // the last id given to an asset
    // We increase it by 1
    var id = ++_lastXGId;

    // Create an asset object
    var asset = new Asset(id, owner, classId, instanceId);

    // Add the asset to _XGAssets mapping - main asset storage
    _XGAssets[id] = asset;

    /*
    In addition to the main asset storage, there is a special asset storage for each user, making it really easy to get all the assets owned by the user.
    */

    // Add the asset to the user’s storage.

    // Get the current amount of assets owned by the user
    // from _XGUsersAssetCount mapping
    var assetCount = _XGUsersAssetCount.GetOrDefault(owner, 0);

    // Store the asset id in the user storage
    _XGUsersAssetIds[GetUserAssetKey(owner, assetCount)] = id;
    // Increase user's asset count by 1
    _XGUsersAssetCount[owner] = assetCount + 1;
    // Add the asset serial number (its key in the user storage mapping) 
    // to the serial number storage
    // (so we don't have to iterate through all user's assets)
    _SerialNumbers[id] = assetCount;

    // Generate an event
    Log.Event("EmitXG", asset);

    // Return the asset's unique id
    return id;
}

Asset Transference

TransferXGAsset(long id, bytes to) — transfers the asset with the specified id to address to. Can only be called by the Expload Auction.

Implementation example snippet Let's look into this method implementation (the comments are changed for the sake of convenience)
public void TransferXGAsset(long id, Bytes to){
    // Check if the caller is the Expload Auction
    AssertIsAuction();

    // Get the asset with the specified id
    var asset = GetXGAsset(id);
    // Get the former asset owner's address
    var oldOwner  = asset.Owner;

    // Check if the asset actually exists
    // (if it has an owner)
    if(oldOwner == Bytes.VOID_ADDRESS){
        Error.Throw("This asset doesn't exist.");
    }

    // Change the asset owner
    asset.Owner = to;
    // Put the modified asset into the main storage
    _XGAssets[id] = asset;

    // Now we will change the user's assets storage

    // Deleting from the former owner's storage

    // Get the former owner's assets amount
    var oldOwnerAssetCount = _XGUsersAssetCount.GetOrDefault(oldOwner, 0);
    // Get the asset's serial number
    var oldOwnerSerialNumber = _SerialNumbers.GetOrDefault(id, 0);
    // Get the last asset in the former owner's storage
    var lastAsset = _XGUsersAssetIds.GetOrDefault(GetUserAssetKey(oldOwner, oldOwnerAssetCount-1), 0);
    // Put the last asset instead of the asset we're transferring
    _XGUsersAssetIds[GetUserAssetKey(oldOwner, oldOwnerSerialNumber)] = lastAsset;
    // Delete the last asset (as it is now in the place of the transferred asset)
    _XGUsersAssetIds[GetUserAssetKey(oldOwner,oldOwnerAssetCount-1)] = 0;
    // Decrease the asset count
    _XGUsersAssetCount[oldOwner] = oldOwnerAssetCount - 1;

    // Add to the new owner's storage

    // Get a new serial number
    var newSerialNumber = _XGUsersAssetCount.GetOrDefault(to, 0);
    // Put the id into the new owner's storage
    _XGUsersAssetIds[GetUserAssetKey(to, newSerialNumber)] = id;
    // Update the new owner's asset amount
    _XGUsersAssetCount[to] = newSerialNumber + 1;

    // Update the assets serial numbers
    _SerialNumbers[lastAsset] = oldOwnerSerialNumber;
    _SerialNumbers[id] = newSerialNumber;

    // Generate an event
    Log.Event("TransferXG", asset);
}

Getting Data from the Program

Get Asset Data by its id

GetXGAssetData(long id) returns Asset asset - returns the asset with the specified id.

Implementation example snippet Let's look into this method implementation (the comments are changed for the sake of convenience)
// A private method for getting an asset object from the storage
// (used in many different methods)
private Asset GetXGAsset(long id){
    return _XGAssets.GetOrDefault(id, new Asset());
}

public Asset GetXGAssetData(long id){
    // Calling the private method
    return GetXGAsset(id);
}

Get the Asset Owner by the Asset's id

GetXGAssetOwner(long id) returns Bytes owner — returns the address of the current asset owner with the specified id

Implementation example snippet Let's look into this method implementation (the comments are changed for the sake of convenience)
public Bytes GetXGAssetOwner(long id){
    // Using the private method as described above
    return GetXGAsset(id).Owner;
}

Get the Asset's classId by its id

GetXGAssetClassId(long id) returns Bytes classId — returns the classId of asset with specified id

Implementation example snippet Let's look into this method implementation (the comments are changed for the sake of convenience)
public Bytes GetXGAssetClassId(long id){
    // Using the private method as described above
    return GetXGAsset(id).ItemClassId;
}

Get the Amount of the User's Asset

GetUsersXGAssetCount(Bytes address) returns long assetCount — returns the amount of assets owned by the user with the specified address

Implementation example snippet Let's look into this method implementation (the comments are changed for the sake of convenience)
public long GetUsersXGAssetCount(Bytes address){\
    // Get the value from mapping
    return _XGUsersAssetCount.GetOrDefault(address, 0);
}

Get Asset's id by its Serial Number

GetUsersXGAssetId(Bytes address, long number) returns long id — returns the id of the asset with the serial number number and owned by the user with the specified address

Implementation example snippet Let's look into this method implementation (the comments are changed for the sake of convenience)
// Private method for getting the required id
// (Also used in other methods)
private long _getUsersXGAssetId(Bytes address, long number){
    // The serial number can't be bigger than asset amount
    if(number >= _XGUsersAssetCount.GetOrDefault(address, 0)){
        Error.Throw("This asset doesn't exist!");
    }

    // Get the required id from the user's asset storage
    var key = GetUserAssetKey(address, number);
    return _XGUsersAssetIds.GetOrDefault(key, 0);
}

public long GetUsersXGAssetId(Bytes address, long number){
    // Call the private method
    return _getUsersXGAssetId(address, number);
}

Request all the Assets Owned by the User

GetUsersAllXGAssetsData(Bytes address) returns Asset[] inventory — returns an array of assets owned by the user with the specified address.

Implementation example snippet Let's look into this method implementation (the comments are changed for the sake of convenience)
public Asset[] GetUsersAllXGAssetsData(Bytes address){
    // Get the user's assets amount
    int amount = (int)_XGUsersAssetCount.GetOrDefault(address, 0);
    // Create an empty array
    var result = new Asset[amount];

    // Fill it with assets
    for(int num = 0; num < amount; num++){
        // Get asset's id using private method described above,
        // then get the asset using id
        result[num] = GetXGAsset(_getUsersXGAssetId(address, num));
    }
    return result;
}

Deploy and Implementation Setup

Now that we have the understanding of the asset structure and interface, let's move on to the Pravda program setup. We will use XGold implementation example.

Pravda Program Deployment

# Clone the repository
git clone https://github.com/expload/auction

# Move to the implementation folder
cd auction/TradableAsset/source/XG

# Let's generate a Pravda wallet - wallet.json — we will use it to deploy pravda gen address -o wallet.json

# ! IMPORTANT !
# You need to replenish the wallet before moving on:
# https://faucet.dev.expload.com/ui

# Now let’s deploy the program to test-net:
dotnet publish -c Deploy

# program-wallet.json should appear in the directory.
# This is the 'admin wallet' - you will need it for
# program configuration.

The address from program-wallet.json should be forwarded to Expload’s team member, who will add the game to the database.

Program Configuration

The commands are called from the deploy directory (auction/TradableAsset/source/XG)

# We need to set up the Expload Auction address variable
# (so that the auction will have the right to transfer assets)

# You can ask an Expload team member for the current Auction address
# For the purposes of this tutorial, we will consider that the Expload Auction has address "A",
# program-wallet.json has address "B"

# ! IMPORTANT !
# Remember to replenish your program-wallet.json
# And insert a valid addresses before the execution

echo "push xA push \"SetAuction\" push xB push 2 pcall" | pravda compile asm | pravda broadcast run -w program-wallet.json -l 9000000
# You can also specify the fee charged by 
# your game when selling items on the Expload Auction
echo "push {percent} push {gameId} push true push \"SetGameCommission\" push xA push 4 pcall" | pravda compile asm | pravda broadcast run -w program-wallet.json -l 9000000

# Where {percent} is the fee percent.
# {gameId} is id (:long) your game's `gameId`, 
# given by an Expload team member.

# The Expload Auction has its inner fee too.
# E.g. if your game has a 5% feee, and the Expload Auction
# has a 5% fee too, there will be a total of 10% fee:
# If someone sells an item for 100 XG, they will receive 90 XG

Congrats! Your program is set up and ready to run.

The only thing you need to do now for your game to appear on the Expload Auction is to create a meta-server.

Developing Your Own Implementation

Expload supports custom asset implementations. Implementation must be inherited from either ITradableXGAsset interface or ITradableXPAsset interface (depending on the asset type).

There is a nuget-package including both interfaces.