Steem Python with Beem tutorial - creating a stream to take commands in transfer memos

in #utopian-io5 years ago

Many Steem-based services use the Steem transfer memo as their command interface, with transfers to the account used to initiate actions by their associated programs. However, the Steem transaction format doesn't easily lend itself to this purpose, so I wanted to create a generic protocol for acquiring transactions and re-formatting them into a convenient object that would allow easy use for this purpose.

Repository

https://github.com/holgern/beem

What Will I Learn?

  • How to convert a full blockchain stream into a stream of only wanted transactions
  • How to read a Steem transfer transaction and re-encode it into a friendlier form

Requirements

  • Python 2.7 or later
  • The Beem repository, linked above.

It is also useful, but not required, to have a basic understanding of streaming the blockchain, as explained nicely in this tutorial by the invaluable @steempytutorials.

Difficulty

  • Basic

Tutorial Contents

You may download the source code for this tutorial here.

Steem Transfer Syntax

A transfer on the Steem blockchain as streamed into Beem looks like this, with line spacing added for readability:

{u'from': u'tcpolymath', 
u'timestamp': datetime.datetime(2019, 1, 13, 18, 23, 18, tzinfo=<UTC>), 
u'memo': u'example transfer', 
u'block_num': 29426514, 
u'to': u'tcpolymath', 
u'amount': {u'amount': u'1000', u'nai': u'@@000000021', u'precision': 3}, 
u'trx_num': 20, 
u'trx_id': u'c52ad75d50f274a004d1c3d04b097c50a4cb71be', 
u'_id': '01fb538737c88d795e7aa7472ca327bbe8144242', 
u'type': u'transfer'}



For the purposes of accepting commands and acting on them, much of this information is unnecessary, and some of it is kept in forms that are difficult to work with. The nai field, for instance, contains a serial number which indicates which blockchain asset the transaction concerns; we'd like to evaluate that and determine whether it's SBD or STEEM, then provide it in a more usable form.

In particular we would like to have a dictionary object which looks more like this:

{
"currency": "STEEM",
"amount": 1.0,
"from": "tcpolymath",
"memo": ["commands", "in", "a", "list"]
}



We don't need to because we're streaming only transactions to a single account. The from field remains the same, but currency and amount become easier to access, and the memo is parsed into a list so that each word separated by a space is accessible individually.

Method

First we import the Beem objects and methods we will need in order to accomplish this, get the current active nodes, and set up the Steem instance we'll be using throughout the program. These are basic startup tasks for any Beem program.

from beem import Steem
from beem.blockchain import Blockchain
from beem.instance import set_shared_steem_instance
from beem.nodelist import NodeList

nodes = NodeList()
nodes.update_nodes()
steem = Steem(node=nodes.get_nodes())
set_shared_steem_instance(steem)



Then we create the function transfer_command_stream, and set up a blockchain stream within it:

def transfer_command_stream(account):
        blockchain=Blockchain(mode="irreversable")
        stream=blockchain.stream(opNames=['transfer'], raw_ops=False)



This function takes as an argument the account we wish to stream transactions from. The Blockchain object's stream function allows us to get a real-time flow of all transactions made on the blockchain, which we can then ask it to automatically filter for transfers only. Further filtering we will have to do ourselves in the next step.

Filtering

 for transfer in stream:
        if transfer['to'] == account:
                amount = float(transfer['amount']['amount'])/(10**int(transfer['amount']['precision']))
                if transfer['amount']['nai'] == "@@000000021":
                      currency = "STEEM"
                elif transfer['amount']['nai'] == "@@000000013":
                      currency = "SBD"
                else:
                      currency = ""



This section of code checks to see if transfers are made to the account we're interested in, and then translates the amount and token ID fields into more useful formats; the amount in the raw transaction is converted into a whole-token number, and the ID is converted into either STEEM or SBD to make it easier to use later.

Then we construct the final dictionary which we will be sending back to our program:

output_dict = {"from": transfer['from'], "amount": amount, "currency": currency, "memo": transfer['memo'].split()}



The first sections simply organize what we've already done. "memo": transfer['memo'].split() uses a built-in Python function to take the long string of the memo and convert it into a list of words, which will make it easier to parse commands in our main program.

Then we transfer the output dictionary out of the function with the yield command:

yield output_dict



yield is like the more-familiar return except it doesn't end the function. We use it here to output a continuous stream; as eligible transfers come in from our blockchain stream, we translate them into our preferred format and send them back to our program in a smaller, more useful stream of their own.

Use

Once back in the program we can approach this as we would a blockchain stream:

commands = transfer_command_stream("tcpolymath")
for command in commands:
        print command



We initialize the transfer command stream with the account we want to receive commands, then begin a for loop which will take in transfers as they appear in the stream. Within the loop is where you build whatever it is you want to do with your commands. For testing purposes here I've chosen to simply print each one as it comes in; the output looks like:

{'currency': 'STEEM', 'amount': 1.0, 'from': u'tcpolymath', 'memo': [u'if', u"you're", u'watching', u'this', u"it's", u'probably', u'boring.']}



From here it's easy to deal with your commands; command["memo"][0] is the first word in the memo, command["memo"][1] the second, and so on.

Conclusion

I built this because I happen to be working on two transfer-controlled services at the same time this week, and felt like building a generic method for transfer commands was a better idea than two specific ones. You're welcome to pull my transfer command stream function into your own programs to make things easier. The repository and MIT license are below.

Proof of Work Done

https://github.com/tcpolymath/Steem-Transfer-Command-Stream

Steem.png

Sort:  

Thank you for your contribution @tcpolymath.
After reviewing your tutorial you suggest the following:

  • Your tutorial is quite short for a good tutorial. We recommend you aim for capturing at least 2-3 concepts.

  • We suggest you post comments in the code sections. Comments are important for the reader to interpret your code well.

Thanks for your development of this tutorial. We are waiting for your next tutorial.

Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.

To view those questions and the relevant answers related to your post, click here.


Need help? Chat with us on Discord.

[utopian-moderator]

Thank you for your review, @portugalcoin! Keep up the good work!

You can use the Amount class to simplify handling of the transfer amount:

from beem.amount import Amount
a = Amount({u'amount': u'1000', u'nai': u'@@000000021', u'precision': 3})
a
1.000 STEEM
a.symbol
'STEEM'
float(a)
1.0

I just heard about you via @joythewanderer and a comment I saw about @rewarding. Pretty cool stuff, I see you also have a fair knowledge of development, looking forward to see your work. Cheers

Thanks, I plan to improve @rewarding and also to add new features in the near future.

Hi @tcpolymath!

Your post was upvoted by @steem-ua, new Steem dApp, using UserAuthority for algorithmic post curation!
Your post is eligible for our upvote, thanks to our collaboration with @utopian-io!
Feel free to join our @steem-ua Discord server

Hey, @tcpolymath!

Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!

Get higher incentives and support Utopian.io!
Simply set @utopian.pay as a 5% (or higher) payout beneficiary on your contribution post (via SteemPlus or Steeditor).

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!

This post has been included in the latest edition of SOS Daily News - a digest of all you need to know about the State of Steem.



Coin Marketplace

STEEM 0.30
TRX 0.12
JST 0.034
BTC 64231.88
ETH 3128.59
USDT 1.00
SBD 3.95