DUCATUR
9 min readAug 23, 2018

It all needed to start somewhere and it is here. We are glad to announce our first EOS Oracles wrapped in GUI — a simple EOS gateway to the outside world. The first deployable oracles for this network that we have created are crypto prices ones. The ability to infuse your smart-contracts with such type of data will significantly broaden the flexibility in DAPPs creation and open new ways of development and make new ideas for great products real.

Okay, enough celebration. Time to unleash your coding skills or call your dev friends. The first example we wanted to describe is betting on crypto prices. Let’s get going!

(This article describes process of implementation stox betting system with Ducatur Oracles platform)

Planning

Final system will have next components:
• Smart-contract based on EOSIO network
• Cryptocurrency price provider
• User betting interface

User flow will be as simple as possible. At the beginning of the process the authority of the smart-contract (creator) setups the question, source of truth and betting deadline (ex. Prognosticate movement direction of BTC price after a week). In graphical approach it will look like sequence of interactions:

Anyone before deadline could bet their EOS tokens to compete for the prize.

Preparation

Before we start, let’s check out the requirements.
• Download and install Docker at least v17.x.x.
• Download and install NodeJS at least v9.x.x.
• Install global node module eosic at least v0.0.4:

npm install — global DucaturFw/eosic

Setup the source of truth with Ducatur Admin panel
Open
Ducatur wizard and pass the instructions to generate EOS contract consuming Binance BTC price to operate.

1. Configure data provider
In case of market price of the currency we should select Cryptocurrency exchanges rates on first screen of Ducatur Contract Wizard.

Find and select BTC/USDT pair from first list and Binance from second. We aren’t interested in additional settings because of our contract design.

2. Generate contract
On the second step Wizard will provide boilerplate contract. Copy it and now it is time to create our development environment.

Development of the EOS Contract

1. Craete EOSIC project
Open terminal and create directory on your computer for future betting project:

mkdir ~/betting-contract

Open directory and initialize default eosic environment:

cd ~/betting-contract && eosic init && npm install

EOSIC will prepare file structure and base configuration for us:

- ~/betting-contract
- /contracts — folder with source codes of project contracts
- /migrate — JS files runs immediately after eosic start command
- /tests — Mocha&Chai test files
- eosic.json — EOSIC configuration
- config.ini — Local node configuration
- package.json — NPM dependencies

2. Create contract
In root directory of our project run command:

eosic contract betoraclize

Command creates folder and boilerplate

contracts/betoraclize/betoraclize.cpp

file for us.

Open .cpp file in any text editor and paste generated code.

NB: We recommend you using Visual Studio Code with CPP Extension

As a result we have a contract without our logic, but it’s ready to consume external data to operate with it.

Our logic could be split in three parts:
• Setup data provider
• Betting rules
• Finalize betting or withdrawal process

3. Setup data provider
To work with Ducatur oracles we should set up our contract to make data request inside master oraclize contract as well as provide administrator account name information.

In case of betting and one time request we will use smart-contract as administrator.

4. Change contract name
Find and replace YOUR_CONTRACT_NAME with betoraclize (three places: class name definition, constructor name and first argument of EOSIO_ABI macros).

5. Change setup method implementation
Find setup method and remove first argument (administrator) and change ask_data call with _self:

void setup(account_name oracle, account_name master, account_name registry)
{
// requires access to contract account to make changes
require_auth(_self);
// store oracle account
oracle_account(_self, _self).set(oracle, _self);
// store master account
oraclize_master(_self, _self).set(master, _self);
// make data request
ask_data(_self, registry, “0xa671e4d5c2daf92bd8b157e766e2c65010e55098cccde25fbb16eab53d8ae4e3”);
}

6. Additional logic in push method
By logic we should deactivate data pushing immediately after first data commitment. To manage state of the contract we should implement data structure and store it as singleton:

// @abi table state i64
struct state
{
// price at start of betting process
price price_start;
// price at end of betting
price price_end;
// time when betting started
uint64_t time_start;
// time when betting is over
uint64_t time_end;
// amount of EOS collected in raise bets
uint64_t total_raise;
// amount of EOS collected in fall bets
uint64_t total_fall;
EOSLIB_SERIALIZE(state, (price_start)(price_end)(time_start)(time_end)(total_raise)(total_fall))
};
// type definition to access state singleton with state_def shorthand
typedef singleton<N(state), state> state_def;

NB: Notice comment over structure definitions starts with // @abi. It’s important part of ABI generation.

Lets add private class fill current_state and initialize it in constructor as well as remove unnecessary eosusdt field (we will store prices inside our own structure):

private:
state_def current_state;
account_name known_master;
account_name known_oracle;
public:
using contract::contract;
betoralize(account_name s) : contract(s), current_state(_self, _self)
{
known_master = oraclize_master(_self, _self).get_or_create(_self, N(undefined));
known_oracle = oracle_account(_self, _self).get_or_create(_self, N(undefined));
}

To store prices in custom structure we will make changes inside pushprice. If current_state doesn’t exists yet we will create it and store price as start price, overwise we will modify state and store price as end, but only if time is over already:

if (strcmp(data_id.c_str(), “0x5d4e98a94bf3e6c7d539f4988cc5f7557fe12c8e53ec6a193b7c0ad92dafe188”) == 0) 
{
if (!current_state.exists())
{
state initial;
initial.price_start = data;
initial.time_start = now();
initial.time_end = initial.time_start + 60 * 60 * 24 * 7; // week after
current_state.set(initial, _self);
}
else if (current_state.get().time_end <= now())
{
state before = current_state.get();
before.price_end = data;
current_state.set(before, _self);
}
}

Last thing that we should add in pushprice is calling master to prevent pushing unnecessary pushing:

if (strcmp(data_id.c_str(), “0x5d4e98a94bf3e6c7d539f4988cc5f7557fe12c8e53ec6a193b7c0ad92dafe188”) == 0) 
{
<… state checks and modifications …>

action(permission_level{_self, N(active)},
known_master, N(stop),
std::make_tuple(_self, _self, data))
.send();
}

Now we are done with the data provider part and ready to go to the bettings rules.

Betting rules

As example of the tutorial, we implement the simplest betting rules: up/down. Each participant chooses where the price will go and makes a bid. In the end, the winners (participants whose bets turned out to be correct) can get a reward, overwise they lose their funds.

Bid reward is calculated by the formula: the total value of all bids multiplied by the ratio of bid value to the sum of all winning bets.

Example: let’s imagine five participants make their bids:

If price goes up:

Track incoming bids

EOSIO allows to hook transfer action of eosio.token to execute extra logic in action execution. To hook action we should replace EOSIO_ABI macros at last line of code with custom apply function:

extern “C” void apply(uint64_t receiver, uint64_t code, uint64_t action)
{
uint64_t self = receiver;
if (action == N(onerror))
{
eosio_assert(code == N(eosio), “onerror action’s are only valid from the \”eosio\” system account”);
}
betoraclize thiscontract(self); if (code == self || action == N(onerror))
{
switch (action)
{
EOSIO_API(betoraclize, (setup)(pushprice))
}
}
if (code == N(eosio.token) && action == N(transfer))
{
thiscontract.transfer(receiver, code);
}
}

NB: This code is almost same as generated with EOSIO_ABI(betoraclize, (setup)(pushprice)), but with additional check code == N(eosio.token) && action == N(transfer) to execute transfer method.

Transfer method implementation

First of all we will ignore all outgoing transfer actions:

void transfer(uint64_t self, uint64_t code)
{
auto data = unpack_action_data<currency::transfer>();
if (data.from == self || data.to != self)
{
return;
}
}

After that you need to add all important checks to prevent undesired behaviour:

eosio_assert(current_state.get().time_start > 0, “Start time isn’t setuped yet”);
eosio_assert(current_state.get().time_start <= now(), “Start time in future”);
eosio_assert(current_state.get().time_end > now(), “Deadline is achieved already”);
eosio_assert(code == N(eosio.token), “I reject your non-eosio.token deposit”);
eosio_assert(data.quantity.symbol == S(4, EOS), “I think you’re looking for another contract”);
eosio_assert(data.quantity.is_valid(), “Are you trying to corrupt me?”);
eosio_assert(data.quantity.amount > 0, “When pigs fly”);
require_auth(data.from);

Now we’re ready to work with transfer action data to create bet record. All bets will be stored as structs in multi_index table:

// @abi table bet i64
struct bet
{
account_name player;
uint64_t amount;
bool raise;
uint64_t primary_key() const
{
return player;
}
EOSLIB_SERIALIZE(bet, (player)(amount)(raise))
};
typedef singleton<N(state), state> state_def;

Each player could make only one bet and have no change to modify it or cancel. So, let’s add the initialize table in the constructor and store it as a private field:

private:
<..private fields..>
bets_def bets;
public:
betoraclize(account_name s) : contract(s),
current_state(_self, _self),
bets(_self, _self)
{
<..constructor body..>
}

Create a bet record in the transfer action after all checks:

auto itt = bets.find(data.from);
eosio_assert(itt == bets.end(), “Player already made decision”);
bool raise = data.memo;
bets.emplace(data.from, [&](bet &b) {
b.player = data.from;
b.amount = data.quantity.amount;
b.raise = raise;
});
auto state = current_state.get();
if (raise)
{
state.total_raise += data.quantity.amount;
}
else
{
state.total_fall += data.quantity.amount;
}
current_state.set(state, _self);

Important part is changing state with each bet. We avoid extra computation at the end of betting with tracking of total_raise and total_fall values.

Betting finalization and withdrawal rewards

Last, but not least task is the creation of the withdrawal process. Create empty withdrawal method with only one argument:

// @abi action
void withdrawal(account_name player)
{
// not implemented yet
}

Add this method to EOSIO_API macro at the bottom of contract:

EOSIO_API(betoraclize, (setup)(pushprice)(withdrawal))

Withdrawal action should check:
• Was betting started?
• Is betting over?
• Was end price pushed?
• Did player make a bid?

Add first checks:

void withdrawal(account_name player)
{
state end_state = current_state.get();
eosio_assert(end_state.time_start > 0, “Start time isn’t setuped yet”);
eosio_assert(end_state.time_end <= now(), “Deadline isn’t achieved already”);
eosio_assert(end_state.price_end.value > 0, “Price isn’t pushed yet”);
auto player_bet = bets.find(player);
eosio_assert(player_bet != bets.end(), “Player didn’t bet anything”);
}

Now if the player has won we should make action to eosio.token to transfer needed amount of tokens:

constexpr uint64_t DECIMAL_MULTIPLIER = 1e4;class betoraclize : public eosio::contract
{
<…>
void withdrawal(account_name player)
{
state end_state = current_state.get();
eosio_assert(end_state.time_start > 0, “Start time isn’t setuped yet”);
eosio_assert(end_state.time_end <= now(), “Deadline isn’t achieved already”);
eosio_assert(end_state.price_end.value > 0, “Price isn’t pushed yet”);
auto player_bet = bets.find(player);
eosio_assert(player_bet != bets.end(), “Player didn’t bet anything”);
bool actual_raise = end_state.price_start.value < end_state.price_end.value;
if (player_bet->raise == actual_raise)
{
uint64_t total = actual_raise ? end_state.total_raise : end_state.total_fall;
uint64_t stake = total * DECIMAL_MULTIPLIER / player_bet->amount;
uint64_t prize = (end_state.total_raise + end_state.total_fall) * stake / DECIMAL_MULTIPLIER;
bets.modify(player_bet, player_bet->player, [&](bet &b) {
b.amount = 0;
});
action(
permission_level{_self, N(active)},
N(eosio.token), N(transfer),
std::make_tuple(
_self,
player,
eosio::asset{static_cast<int64_t>(prize), S(4, EOS)}, string()))
.send();
}
}
}

Deploy and interact with contract

Now you need to deploy this contract and start interacting with it — the bet is ready. If you have encountered any troubles during the deployment process — feel free to ask at t.me/ducaturico, but the instruction is already being made and will be released soon.

Conclusion

Hope you enjoyed and made it through to implement your own products based on this structure or creating something new with a powerful tool we have released for the community.

If you have any ideas regarding DAPPs connected to crypto prices feel free to inform us right on our chat. Or if you have any questions you can address your requests on our Github. We would be glad to answer :)

DUCATUR

Generating new way of interaction between everyone and everyone. First Multichain Token (ETH, EOS, NEO): https://ducatur.com/