My Crash Course in EOSIO dApp Development

Do repost and rate:

After I made Ethboard I got really into the Worldwide Asset eXchange (WAX) blockchain, getting started on WAX is super easy, you just need to go to the Wax Cloud Wallet (WCW) and sign up and BOOM! you have a wallet! If you're familiar with using an EOSIO wallet like Anchor or Scatter to generate keypairs, you can use those to create an account on Bloks.io instead but you'll have to fund it with a bit of EOS whereas with the WCW you'll have access to an wallet immediately. I'm not going to go into setting up a development environment for WAX/EOSIO however if you're interested in trying out the code in this article, head on over to the WAX Developer Hive. The main asset store on WAX is called the AtomicMarket, here you can buy and sell NFTs; my main goal to learn when I jumped into this was:

"How can I make a EOSIO smart contract that takes two NFTs of my choosing and merges them into a third rarer nft?"

I have a bunch of art assets I want to release as NFTs but I want to have a couple different rarity levels so I can gift/drop premium versions of them to people who support my artworks. Additionally, one of the features I want is for people to be able to take some of my assets and say, take 3 copies and merge them into a new rare version of that card; and likewise with the rare versions into a final cosmic version. Anyway, Let's begin with the code in its entirety and then we'll go through it line by line

#include #include using namespace eosio;using namespace std;CONTRACT mergenfts: public eosio::contract{public:    using contract::contract;/*Copied from the AtomicMarget github(https://github.com/pinknetworkx/atomicmarket-contract/blob/master/include/atomicmarket.hpp)This is the structure of the assets table for theatomicassets contract so we can read it*/struct [[eosio::table]] assets_s {uint64_t asset_id;name collection_name;name schema_name;int32_t template_id;name ram_payer;vector backed_tokens;vector immutable_serialized_data;vector mutable_serialized_data;uint64_t primary_key() const { return asset_id; }};typedef multi_index<"assets"_n, assets_s> atomics_t;/*Payable function to detect when AtomicHub assets aretransferred to this contract*/[[eosio::on_notify("atomicassets::transfer")]] void listen(name _owner, name _self, vector &ids, string memo) {// Make sure we're the ones receiving the asset(s)// Question, does this get called when ANYONE transfers an asset?if(_self != get_self()) return;// Check that we received EXACTLY two assetscheck(ids.size() == 2, msgRejectedAssetNum);// The assets obviously can't be the exact same asset which is an instant rejectcheck(ids[0] != ids[1], msgRejectedNotUnique);// Check that the collection each asset belongs to is validname col_a = atomics_t("atomicassets"_n, _self.value).require_find(ids[0], "One of the two supplied assets (A) doesn't appear to actually be owned by you!")->collection_name;name col_b = atomics_t("atomicassets"_n, _self.value).require_find(ids[1], "One of the two supplied assets (B) doesn't appear to actually be owned by you!")->collection_name;check(col_a == colName || col_b == colName, msgRejectedWrongCollection);// Check that the each asset is one of the ones that can be mergeduint64_t tmp_a = atomics_t("atomicassets"_n, _self.value).find(ids[0])->template_id;uint64_t tmp_b = atomics_t("atomicassets"_n, _self.value).find(ids[1])->template_id;check((tmp_a == b_id || tmp_a == a_id) || (tmp_b == b_id || tmp_b == a_id), msgRejectedWrongTemplate);// Check that each asset is a different onecheck(tmp_a != tmp_b, msgRejectedSameColor);// << NOTE At this point both assets are considered to be unique, valid, and owned by _owner >>// Burn the two assetsaction({ _self, "active"_n }, "atomicassets"_n,"burnasset"_n, tuple(_self, ids[0])).send();action({ _self, "active"_n }, "atomicassets"_n,"burnasset"_n, tuple(_self, ids[1])).send();// Mint the premium assetaction({ _self, "active"_n }, "atomicassets"_n, "mintasset"_n, tuple(_self, colName, schName, c_id, _owner, ""_n, ""_n, ""_n )).send();}/*Mints a new common token to new_owner*/ACTION mint(name new_owner, int8_t color){check(color < count, "Error, chose color index > max_colors");name _self = get_self();uint32_t sel = color == 0 ? b_id : a_id;action({ _self, "active"_n },"atomicassets"_n,"mintasset"_n,tuple(_self, colName, schName, sel, new_owner, ""_n, ""_n, ""_n )).send();}private:// Common assetsconst int8_t count = 2;const int32_t a_id = 59863;const int32_t b_id = 59864;// Premium assetsconst int32_t c_id = 59865;name colName = "cntestnfts12"_n;name schName = "cntestnfts2"_n;const string msgRejectedAssetNum = "Deposit rejected, send EXACTLY 2 CN assets";const string msgRejectedNotUnique = "Deposit rejected, same asset sent twice";const string msgRejectedWrongCollection = "Deposit rejected, one or both assets are not from the proper collection";const string msgRejectedWrongTemplate = "Deposit rejected, one or both assets are not the right color";const string msgRejectedSameColor = "Deposit rejected, both assets are the same color";};

If you've done an EOS contract development a lot of this should be easy to understand, but just for clarity let's go block by block

#include #include using namespace eosio;using namespace std;CONTRACT mergenfts: public eosio::contract{public:    using contract::contract;    .....}

Here we're just defining a standard EOSIO smart contract called Merge NFTs since that's what it does, it includes the proper files from the EOSIO library, and accesses the EOSIO namespace. This is a very basic smart contract, you could copy this and compile it using "eosio-cpp -abigen mergenfts.cpp -o mergenfts.wasm" and it would compile fine.

/*Copied from the AtomicMarget github(https://github.com/pinknetworkx/atomicmarket-contract/blob/master/include/atomicmarket.hpp)This is the structure of the assets table for theatomicassets contract so we can read it*/struct [[eosio::table]] assets_s {uint64_t asset_id;name collection_name;name schema_name;int32_t template_id;name ram_payer;vector backed_tokens;vector immutable_serialized_data;vector mutable_serialized_data;uint64_t primary_key() const { return asset_id; }};typedef multi_index<"assets"_n, assets_s> atomics_t;

Now we define a multi_index table, like the comment suggest this is a copy of the assets table from the AtomicMarket source code (you can also get it from the ABI on Bloks.io). What this will allow us to do is make a reference to the table contained in the AtomicAssets contract so we can query the marketplace.

/*Payable function to detect when AtomicHub assets aretransferred to this contract*/[[eosio::on_notify("atomicassets::transfer")]] void listen(name _owner, name _self, vector &ids, string memo) {// Make sure we're the ones receiving the asset(s)// Question, does this get called when ANYONE transfers an asset?if(_self != get_self()) return;// Check that we received EXACTLY two assetscheck(ids.size() == 2, msgRejectedAssetNum);// The assets obviously can't be the exact same asset which is an instant rejectcheck(ids[0] != ids[1], msgRejectedNotUnique);// Check that the collection each asset belongs to is validname col_a = atomics_t("atomicassets"_n, _self.value).require_find(ids[0], "One of the two supplied assets (A) doesn't appear to actually be owned by you!")->collection_name;name col_b = atomics_t("atomicassets"_n, _self.value).require_find(ids[1], "One of the two supplied assets (B) doesn't appear to actually be owned by you!")->collection_name;check(col_a == colName || col_b == colName, msgRejectedWrongCollection);// Check that the each asset is one of the ones that can be mergeduint64_t tmp_a = atomics_t("atomicassets"_n, _self.value).find(ids[0])->template_id;uint64_t tmp_b = atomics_t("atomicassets"_n, _self.value).find(ids[1])->template_id;check((tmp_a == b_id || tmp_a == a_id) || (tmp_b == b_id || tmp_b == a_id), msgRejectedWrongTemplate);// Check that each asset is a different onecheck(tmp_a != tmp_b, msgRejectedSameColor);// << NOTE At this point both assets are considered to be unique, valid, and owned by _owner >>// Burn the two assetsaction({ _self, "active"_n }, "atomicassets"_n,"burnasset"_n, tuple(_self, ids[0])).send();action({ _self, "active"_n }, "atomicassets"_n,"burnasset"_n, tuple(_self, ids[1])).send();// Mint the premium assetaction({ _self, "active"_n }, "atomicassets"_n, "mintasset"_n, tuple(_self, colName, schName, c_id, _owner, ""_n, ""_n, ""_n )).send();}

This is the meat of the contract, it's a standard payable function that watches for transfers from the AtomicAssets contract to the MergeNFTs contract; when it see's one it checks to see if it's actually the recipient. When then perform a series of checks to make sure we have the expected number of assets, they're from the proper collection, and they have the expected template ids. Once everything checks out, we burn the received assets and mint a rare premium asset back to the user! The pseudocode is easy, but the implementation was difficult due to never having written an EOSIO contract before. NEXT!

/*Mints a new common token to new_owner*/ACTION mint(name new_owner, int8_t color){check(color < count, "Error, chose color index > max_colors");name _self = get_self();uint32_t sel = color == 0 ? b_id : a_id;action({ _self, "active"_n },"atomicassets"_n,"mintasset"_n,tuple(_self, colName, schName, sel, new_owner, ""_n, ""_n, ""_n )).send();}

The second function in the contract basically allows the owner of the contract to mint common assets to people.

private:// Common assetsconst int8_t count = 2;const int32_t a_id = 59863;const int32_t b_id = 59864;// Premium assetsconst int32_t c_id = 59865;name colName = "cntestnfts12"_n;name schName = "cntestnfts2"_n;const string msgRejectedAssetNum = "Deposit rejected, send EXACTLY 2 CN assets";const string msgRejectedNotUnique = "Deposit rejected, same asset sent twice";const string msgRejectedWrongCollection = "Deposit rejected, one or both assets are not from the proper collection";const string msgRejectedWrongTemplate = "Deposit rejected, one or both assets are not the right color";const string msgRejectedSameColor = "Deposit rejected, both assets are the same color";

Just to be complete, the final section is just a bunch of private variables for the contract, we have a count of how many common cards we have. In a production scenario rather than have each id as a single int32 variable an array would probably be preferred. We then have all the template ids for each unique asset, as well as the collection name, and schema name. I left both of these as the actual values in my contract, if you'd like me to send you a few testnet tokens to try merging them just send me a message!

And that's it! In less than 100 lines of code we have a contract that will take 2 atomic assets and spit out a 3rd while burning the input assets. Please like and follow me for more content like this, till next time!

Regulation and Society adoption

Ждем новостей

Нет новых страниц

Следующая новость