Usage¶
Installation¶
To use robonomics-interface, first install it using pip:
$ pip3 install robonomics-interface
Examples¶
Initialization¶
from robonomicsinterface import Account
account = Account()
By default, you will only be able to fetch Chainstate info from Robonomics Polkadot parachain and use PubSub and ReqRes patterns.
You can specify another remote_ws
(e.g. local), seed
to sign extrinsics, custom type_registry
and crypto_type
.
account_local_dev_node = Account(remote_ws="ws://127.0.0.1:9944")
Address of the account may be obtained using get_address()
method if the account was initialed with a seed/private key.
This method will return ss58-address format of the created account address.
Service Functions¶
As have been said, when initiating account instance without a seed, you will be able to read any
Chainstate info from the Robonomics Polkadot parachain. This is possible by some dedicated below-mentioned classes and a
ServiceFunctions
’s method chainstate_query
allowing user to execute any query.
from robonomicsinterface import ServiceFunctions
service_functions = ServiceFunctions(account)
num_dt = service_functions.chainstate_query("DigitalTwin", "Total")
You can also specify an argument for the query. Several arguments should be put in a list. Block hash parameter is
also available via block_hash
argument if you want to make a query as of a specified block:
address = "4CqaroZnr25e43Ypi8Qe5NwbUYXzhxKqrfY5opnRzK4yG1mg"
index = 8
block_hash = "0x7bdd8ae3d9a2976a4d2a534071d076a5b8caf24f8f0447587d1cbc901f07892e"
some_record = service_functions.chainstate_query("Datalog", "DatalogItem", [address, index], block_hash=block_hash)
Providing seed (any, raw or mnemonic) while initializing will let you create and submit extrinsics:
account_with_seed = Account(seed="this is a testing seed phrase of specified length which equals twelve")
service_functions_seed = ServiceFunctions(account_with_seed)
hash_tr = service_functions_seed.extrinsic("DigitalTwin", "create")
hash_tr
here is the transaction hash of the succeeded extrinsic. You can also specify arguments for the extrinsic
as a dictionary and set transaction nonce.
from robonomicsinterface.utils import dt_encode_topic
dt_id = 0
topic_hashed = dt_encode_topic("topic 1")
source = account_with_seed.get_address()
nonce = 42
hash_tr = service_functions_seed.extrinsic("DigitalTwin", "set_source", {"id": dt_id, "topic": topic_hashed, "source": source}, nonce=nonce)
One nay also perform custom rpc calls:
def result_handler(data):
print(data)
service_functions.rpc_request("pubsub_peer", None, result_handler)
There are a lot of dedicated classes for the most frequently used queries, extrinsics and rpc calls. More on that below.
Chain Utils¶
This class is dedicated to some utilities to obtain valuable information from the node of the blockchain which is not module-specific. For example, transaction search or transforming a block hash in a block number and vice versa.
from robonomicsinterface import ChainUtils
cu = ChainUtils()
print(cu.get_block_number("0xef9ca7a02b8ab2df373b1f86f336474947df05455a1076a3c64b034319bd7152")) # 2875
print(cu.get_block_hash(2875)) # 0xef9ca7a02b8ab2df373b1f86f336474947df05455a1076a3c64b034319bd7152
Extrinsic search function here is implemented by get_extrinsic_in_block
method. It accepts block hash/number and
extrinsic hash/idx as arguments:
print(cu.get_extrinsic_in_block(1054910, 4)) # RWS call info.
print(cu.get_extrinsic_in_block("0x97ff645b2035a0ad62ed5f438ebd5ee91cbfe3d197ba221c6c03c614c6dc1dfe",
"0xbc2180c1773838ccf2f1e79302bec500c3c5ed7da8ea9471f5e40667574eed9f")) # The same.
Notice on Below-Listed Classes¶
It is worth to mention that any query in these classes may accept block_hash
argument and eny extrinsic may accept
nonce
argument. Also, if some method implies query, it name starts with get_
.
More that, each time one initialize a class, they may pass wait_for_inclusion=False
argument to avoid waiting for
future transactions to be included in block. It saves time, but one may not know if the transaction was not successful
(e.g. DigitalTwin.set_source was submitted by unauthorized account).
One more argument while initializing is return_block_num
. If Set to True
ALONG WITH wait_for_inclusion
, the
extrinsic
function will return a tuple of form (<extrinsic_hash>, <block_number-idx>)
.
If any extrinsic has failed, it well raise ExtrinsicFailedException
with an error message inside.
Common Functions¶
With Common Functions class one can send some tokens, get account nonce or some information about any other address.
from robonomicsinterface import CommonFunctions
common_functions = CommonFunctions(account_with_seed)
common_functions.get_account_info(account_with_seed.get_address())
common_functions.get_account_info() # Will make the same output as the one above
common_functions.get_account_nonce(account_with_seed.get_address())
common_functions.transfer_tokens("4CqaroZnr25e43Ypi8Qe5NwbUYXzhxKqrfY5opnRzK4yG1mg", 1000000000)
Datalog¶
With Datalog class one can record or erase datalog and read datalog records of any account.
from robonomicsinterface import Datalog
datalog = Datalog(account_with_seed)
datalog.record("Hello, world")
datalog.get_index(account_with_seed.get_address())
datalog.get_item(account_with_seed.get_address()) # If index was not provided here, the latest one will be used
datalog.erase()
Digital Twins¶
Digital Twins functionality is also supported.
from robonomicsinterface import DigitalTwin
dt = DigitalTwin(account_with_seed)
dt_it, tr_hash = dt.create()
# Here the topic is automatically encoded
topic_hashed, source_tr_hash = dt.set_source(dt_id, "topic 1", "4CqaroZnr25e43Ypi8Qe5NwbUYXzhxKqrfY5opnRzK4yG1mg")
dt.get_info(dt_id)
dt.get_owner(dt_id)
dt.get_total()
One may also find topic source by
dt.get_source(dt_id, "topic 1")
# >>> "4CqaroZnr25e43Ypi8Qe5NwbUYXzhxKqrfY5opnRzK4yG1mg"
Launch¶
With the help of a Launch class one may send launch commands with parameter to any other addresses. The parameter should
be 32 bytes long hex-string (0x...
), but it may also be an IPFS (Qm...
) CID as a string , which will be converted automatically.
from robonomicsinterface import Launch
launch = Launch(account_with_seed)
launch.launch("4CqaroZnr25e43Ypi8Qe5NwbUYXzhxKqrfY5opnRzK4yG1mg", "QmYA2fn8cMbVWo4v95RwcwJVyQsNtnEwHerfWR8UNtEwoE")
Liabilities¶
This package support Robonomics liability functionality. Here is a bit about the concept on Ethereum. It’s slightly different in Substrate.
With this package one can create liabilities, sign technical parameters messages, report completed liabilities, sign report messages, fetch information about current and completed liabilities:
from robonomicsinterface import Liability
promisee = Account(seed="<seed>")
promisor = Account(seed="<seed>")
promisee_liability = Liability(promisee)
promisor_liability = Liability(promisor)
task = "QmYA2fn8cMbVWo4v95RwcwJVyQsNtnEwHerfWR8UNtEwoE" # task parsing is on user side
reward = 10 * 10 ** 9 # 10 XRT
promisee_task_signature = promisee_liability.sign_liability(task, reward)
promisor_task_signature = promisor_liability.sign_liability(task, reward)
index, tr_hash = promisee_liability.create(
task, reward, promisee.get_address(), promisor.get_address(), promisee_task_signature, promisor_task_signature
)
print(index)
print(promisee_liability.get_agreement(index))
report = "Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z" # report parsing is on user side
promisor_liability.finalize(index, report) # this one signs report message automatically if no signature provided
print(promisor_liability.get_report(index))
Robonomics Web Services (RWS)¶
There are as well dedicated methods for convenient usage of RWS. - Chainstate functions to examine subscriptions and subscription auctions:
from robonomicsinterface import RWS
rws = RWS(account_with_seed)
rws.get_auction_queue()
rws.get_auction_next()
rws.get_auction(0)
rws.get_devices("4CqaroZnr25e43Ypi8Qe5NwbUYXzhxKqrfY5opnRzK4yG1mg")
rws.get_ledger("4CqaroZnr25e43Ypi8Qe5NwbUYXzhxKqrfY5opnRzK4yG1mg")
rws.get_days_left()
rws.is_in_sub(<owner_addr>, <device_addr>)
Extrinsincs: bid, set_devices
rws.bid(0, 10*10**9)
rws.set_devices([<ss58_addr>, <ss58_addr>])
- The
call
method is implemented differently. If you want to use any module/class with RWS subscription, pass the subscription owner address when initializing the class.
- The
datalog_rws = Datalog(account_seed, rws_sub_owner="4CqaroZnr25e43Ypi8Qe5NwbUYXzhxKqrfY5opnRzK4yG1mg")
datalog_rws.record("Hello, world via RWS")
Subscriptions¶
There is a subscriptions functional implemented. When initiated, processes new events with a user-passed
callback function. Pay attention that this callback may only accept one argument - the event data. Up to now, the supported
events are NewRecord
, NewLaunch
, Transfer
, TopicChanged
, NewDevices
, NewLiability
and NewReport
.
from robonomicsinterface import Subscriber, SubEvent
def callback(data):
print(data)
account = Account()
subscriber = Subscriber(account, SubEvent.MultiEvent, subscription_handler=callback)
<do stuff>
subscriber.cancel()
One may also pass a list of addresses or one address as a parameter to filter trigger situation. Another option is to set
pass_event_id
to get block number and event ID as a second callback
parameter.
There is a way to subscribe to multiple events by using side package aenum
.
from aenum import extend_enum
extend_enum(SubEvent, "MultiEvent", f"{SubEvent.NewRecord.value, SubEvent.NewLaunch.value}")
subscriber = Subscriber(acc, SubEvent.MultiEvent, subscription_handler=callback, addr=<ss58_addr>, pass_event_id=True)
IO¶
This package provides console prototyping tool such as robonomics io with slight differences:
$ robonomics_interface read datalog
$ echo "Hello, Robonomics" | robonomics_interface write datalog -s <seed>
$ robonomics_interface read launch
$ echo "ON" | robonomics_interface write launch -s <seed> -r <target_addr>
More info may be found with
$ robonomics_interface --help
ReqRes API¶
There is a functionality for a direct connection to server based on Robonomics node.
from robonomicsinterface import ReqRes
reqres = ReqRes(account)
reqres.p2p_get(<Multiaddr of server>,<GET request>)
reqres.p2p_ping(<Multiaddr of server>)
Example of usage¶
Download sample server here. Start this server with local ip (Rust (with cargo) installation process described here):
cargo run "/ip4/127.0.0.1/tcp/61240"
Then, in other terminal write small execute this script:
from robonomicsinterface import ReqRes, Account
account = Account(remote_ws="ws://127.0.0.1:9944") # requires local node
reqres = ReqRes(account)
reqres.p2p_get(<Multiaddr of server>,<GET request>)
print(reqres.p2p_get("/ip4/127.0.0.1/tcp/61240/<PeerId>","GET")) # PeerId - you will see in server logs
This code sample requires local node launched. PeerId
is obtained when launching server.
PubSub¶
There is a way to implement robonomics pubsub rpc calls. Below is a sample example of how to send messages from one script and listen to them on another one. For this two developer nodes on one machine were launched with:
./robonomics --dev --tmp -l rpc=trace
./robonomics --dev --tmp --ws-port 9991 -l rpc=trace
After that a subscriber and publisher scripts were created. Subscriber:
from robonomicsinterface import Account, PubSub
import time
def subscription_handler(obj, update_nr, subscription_id):
rawdata = obj['params']['result']['data']
for i in range(len(rawdata)):
rawdata[i] = chr(rawdata[i])
data = "".join(rawdata)
print(data)
remote_ws = "ws://127.0.0.1:9944"
account = Account(remote_ws=remote_ws)
pubsub = PubSub(account)
print(pubsub.listen("/ip4/127.0.0.1/tcp/44440"))
time.sleep(2)
print(pubsub.subscribe("topic_name", result_handler=subscription_handler))
Subscriber:
from robonomicsinterface import Account, PubSub
import time
remote_ws = "ws://127.0.0.1:9991"
account = Account(remote_ws=remote_ws)
pubsub = PubSub(account)
print(pubsub.connect("/ip4/127.0.0.1/tcp/44440"))
time.sleep(2)
while True:
print("publish:", pubsub.publish("topic_name", "message_" + str(time.time())))
time.sleep(2)
First, launch the subscriber script, then the publisher one. You should see published messages in listener’s script console.
Utils¶
Utils module provides some helpful functions, among which there are IPFS Qm...
hash encoding to 32 bytes length
string and vice-versa. One more is generating an auth tuple for Web3-Auth gateways (more on that
on Crust Wiki).
from robonomicsinterface.utils import ipfs_qm_hash_to_32_bytes, ipfs_32_bytes_to_qm_hash
ipfs_hash = "Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z"
bytes_32 = ipfs_qm_hash_to_32_bytes("Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z")
# >>> '0xcc2d976220820d023b7170f520d3490e811ed988ae3d6221474ee97e559b0361'
ipfs_hash_decoded = ipfs_32_bytes_to_qm_hash("0xcc2d976220820d023b7170f520d3490e811ed988ae3d6221474ee97e559b0361")
# >>> 'Qmc5gCcjYypU7y28oCALwfSvxCBskLuPKWpK4qpterKC7z'
auth = web_3_auth(tester_tokens_seed)