Steem APIs from a Bitcoin perspective

in Steemit Dev Group2 years ago (edited)

With a view to encouraging more development as well as starting to build some Steem projects myself, I've been looking at the Steem ecosystem from the perspective of a potential developer finding Steem for the first time.

api-pic.png

Some historical perspective

After the introduction of the Bitcoin RPC API many years ago, it became the de facto standard API for every cryptocurrency. To a large extent, this was because the vast majority of altcoins were codebase forks of the Bitcoin wallet and supported the API out of the box without much work required from the coin developer.

This status quo persisted for quite a while, and it was a happy time for service providers: If you wanted to bring up your game / application / on-chain protocol on a different blockchain, all you had to do was install the relevant coin daemon next to your application, and configure RPC access. The API calls would work no matter what the underlying coin was.

I think Ethereum was the first major break with this norm. As a greenfield project that wasn't forking the Bitcoin codebase, the Bitcoin API wasn't supported.

The APIs were arranged in a different way, and suddenly you couldn't just deploy your app by connecting it to the daemon with no further effort.

What does this matter to Steem?

Steem was also a greenfield project, and doesn't support the Bitcoin API. It does, however, have its own very rich set of APIs.

There are quite a few cool games and other services that we could easily bring up on Steem if we had a nice clear mapping between certain Bitcoin API functions and Steem API functions.

Possibly more importantly, there are also a lot of developers who still design in terms of the Bitcoin API.

A direct mapping is impossible, but having some guidance on moving one's thinking between the two could really help with developer on-boarding.

Although the Bitcoin API contains over 120 functions, a tiny subset of these are typically all that's needed for a given use-case.

To see where the friction is, we'll pick a use-case and do a worked example of trying to approach Steem when coming from the Bitcoin API world.

Case Study: Bitcoin API dice game

As an example, let's consider a simple hypothetical dice game using the Bitcoin API.

Game specification

No user registration is required. When a potential player clicks Play Now on the game website, they are given a form to paste their address to receive winnings, and are shown a custom game address generated just for that play.

The game address and receive address are added to a local database.

To play, the player sends an amount of coins to the game address. The game server is monitoring for incoming transactions, and when it sees that the game address has received a transaction, makes a provably-fair dice roll using the transaction ID and the hash of the block the transaction appeared in.

If the player won, the server consults its database, and sends the rewards to the receive address the player entered.

(Note: In an even simpler variation, funds are automatically sent back to the address which held the first input / largest input of the play transaction. Historically, most games stopped doing this due to user confusion over inputs on change addresses.)

Bitcoin API calls

To implement the above flow using the classical Bitcoin API, only a few calls are needed.

To generate a game address for the player, getnewaddress would be used. This generates a fresh key pair using the wallet's HD master key, adds the private key to the wallet, and returns the new public address.

To list all incoming transactions, listtransactions is called. For each transaction, it returns the amount, in-wallet destination address, tx id, and (conveniently) the block hash the transaction appeared in.

Finally, sendtoaddress would be used to send winnings.

The functions getbalance and getreceivedbyaddress may also be helpful, but aren't strictly required for our example game in its simplest guise.

Steem API: Detecting players and rolling the dice

Our hypothetical experienced Bitcoin API developer coming to Steem will naturally dig into the Steem API docs looking for equivalents to the above functions, and may become a bit confused if looking for a direct mapping between the two APIs.

transfers_api.getTransfers looks almost perfect to replace listtransactions. For each incoming transaction, we have a timestamp, amount, and sender. We also get the memo field, a potentially useful way to pass arbitrary data in a transaction.

Note that getTransfers doesn't show us an actual wallet address like we might expect. In Steem land, that's perfectly fine. Usernames are unique, and effectively are addresses when it comes to transferring funds.

From the perspective of our example Dice game, that means we don't need to worry about game addresses or reward addresses at all. The user who transferred coins to the game is the user playing, and the user we need to pay if they win. Great, we don't need getnewaddress at all!

With no need to generate player addresses, the game's website's front end doesn't need to have any functionality. So it doesn't even need a website, beyond a post on Steem! Of course, it still needs a backend to check for transactions, roll the dice, and send out winnings.

It's a simple example, but this is a perfect demonstration of the differing paradigms between Steem and a traditional blockchain, and the way that those differences manifest at API level.

However, what is a bit of a problem for our Bitcoin API game design is that unfortunately, getTransfers doesn't show us transaction ids or another direct mapping down to the blockchain.

This means our Bitcoin API game's source of provable randomness isn't as readily available.

We do have options of course. Using blocks_api.getLastIrreversibleBlockNum and blocks_api.getTransactionsInBlock (which does show tx ids), we could keep an eye on the chain tip and parse through block transactions looking for transfers to the game account.

That's not very elegant though. Perhaps a better method: When we see a new incoming transfer with getTransfers, call blocks_api.getBlock with the current tip height, and simply use that block hash (returned as block_id) as our provably-fair random source.

That's still not ideal! What if tip has moved on several blocks since the transfer? A player could argue that the game operator is waiting a few blocks to see if one of them gives the house an advantageous roll.

This is where I come a little unstuck. So far, I haven't seen exactly how to link a transfer down to a blockchain transaction without continuously parsing chain tip myself. What am I missing, @steemchiller?!

Steem API: Sending funds (or posts, comments, votes...)

To send funds, someone used to the Bitcoin API may be initially stumped by the apparent lack of any API call to send coins. That's because on Steem, sending coins (a transfer) is only one of many operations that a given blockchain transaction might encode.

The resulting flow appears to be very similar to the Bitcoin API's raw transaction flow of createrawtransaction, signrawtransactionwithkey, sendrawtransaction, but I'm finding it hard to find a concrete example.

The rest of this post is just my current, untested hunch!

I suspect that we first create a transaction object containing our desired operation (in this case, transfer), value, sender, and recipient.

I think we then call database_api.get_required_signatures, but I'm unclear whether that actually signs the transaction for us if the node has those private keys, or just gives us the public keys that need to sign.

Once we've created and signed our transaction, we can push it out onto the network with network_broadcast_api.broadcast_transaction.

Disclaimer: All this only represents my current wooly understanding of the Steem APIs. I've also glossed over many things in the Dice example, including keeping track of which games have already been played/paid, the need (in the Bitcoin API version) to atomically couple blockchain and local DB operations, the need to occasionally sweep the wallet due to slowdown from all those single-use keypairs -- the list goes on. Don't implement something like this unless you're confident in what you're doing, or have access to battle-tested code.

I eagerly await corrections!

Sort:  

So far, I haven't seen exactly how to link a transfer down to a blockchain transaction without continuously parsing chain tip myself. What am I missing, @steemchiller?

There is no perfect way of achieving what you want currently (I will think about this for the next update of the SDS APIs).

I guess the best way to solve this with existing APIs would be to call getHistoryByTime to retrieve the exact block number + transaction index and then call the getTransactionsInBlock. Not perfect but probably the 'cheapest' solution for now.

You also could move completely to the Account History API for watching the incoming transfers. This would make the step to get the block number for a transfer obsolete.

Brilliant, thanks very much for the hints; I'll check all that out.

So, I may well have totally messed this up by looking for SDS API calls instead of the calls that would be exposed by a typical Jussi-enabled full node running various plugins on separate daemons...

Coin Marketplace

STEEM 0.17
TRX 0.16
JST 0.028
BTC 75751.82
ETH 2893.02
USDT 1.00
SBD 2.61