Getting Started
Overview
PyBand is a library written in Python used for interacting with BandChain. The library provides classes, methods, and protobuf classes for the ease of sending transactions, querying data, OBI encoding, and wallet management.
The library is implemented based on gRPC-web protocol which sends HTTP/1.5 or HTTP/2 requests to a gRPC proxy server, before serving them as HTTP/2 to gRPC server.
This library is only implemented on Python.
System Requirements
- Recommended Python version:
3.8.x
or below - MacOS, Windows (including WSL), and Linux are supported
Installation
This library is available on PyPI
pip install pyband
Basic Usages
Making an oracle request
This section describes the methods used to send a transaction containing an oracle request to BandChain
Step 1: Import pyband
and create a parameter: grpc_url
with the required <GRPC>
endpoint which can be found here. Then the client instance needs to be initialized in order to allow for the methods in client module to be used.
from pyband.client import Client
def main():
# Step 1
grpc_url = "<GRPC>" # ex.laozi-testnet6.bandchain.org(without https://)
c = Client(grpc_url)
if __name__ == "__main__":
main()
Step 2: As the sender's address is required for sending a transaction, we will have to initialize the address first. To do this, start by importing PrivateKey
from wallet module. In this example, we will get Mnemonic from environment variables.
from pyband.wallet import PrivateKey
MNEMONIC = os.getenv("MNEMONIC")
private_key = PrivateKey.from_mnemonic(MNEMONIC)
public_key = private_key.to_public_key()
sender_addr = public_key.to_address()
sender = sender_addr.to_acc_bech32()
After that, we will transform the private key to a public key, the public key to an address, and an address of type Address
to an address of type str
.
Step 3: Before constructing a transaction, additional information is needed.
As a transaction requires:
- messages
- sequence
- account_num
- chain_id
- fee
- gas
- memo
to be constructed, we will need to get those.
Messages
In this example, we will use MsgRequestData
with the following parameters as our message.
- oracle_script_id
<int>
: The oracle script ID. - calldata
<bytes>
: The calldata from a request (e.g., the hex string representing OBI-encoded value of{"symbols": ["ETH"], "multiplier": 100}
). - ask_count
<int>
: The number of validator required to process this transaction. - min_count
<int>
: The minimum number of validator required to process this transaction. - client_id
<str>
: Name of the client (can be any name or an empty string). - fee_limit <
Coin
>: The fee limit. - prepare_gas
<int>
: The amount of gas used in the preparation stage. - execute_gas
<int>
: The amount of gas used in the execution stage. - sender
<str>
: The sender's address.
from pyband.proto.oracle.v1.tx_pb2 import MsgRequestData
request_msg = MsgRequestData(
oracle_script_id=37,
calldata=bytes.fromhex("0000000200000003425443000000034554480000000000000064"),
ask_count=4,
min_count=3,
client_id="BandProtocol",
fee_limit=[Coin(amount="100", denom="uband")],
prepare_gas=50000,
execute_gas=200000,
sender=sender,
)
Instead from using bytes for the calldata, oracle binary encoding (obi) can also be used.
from pyband.obi import PyObi
obi = PyObi("{symbols:[string],multiplier:u64}/{rates:[u64]}")
calldata = obi.encode({"symbols": ["ETH"], "multiplier": 100})
The message can be any message as listed in Oracle Modules or Cosmos Based Messages. However, please note that our message should be imported from the generated protobuf files.
Sequence and Account Number
Sequence and account number can be retrieved by calling get_account
from the client module created in step 1.
account = c.get_account(sender)
account_num = account.account_number
sequence = account.sequence
Fee
Fee can be created by using Coin
from the generated protobuf file.
from pyband.proto.cosmos.base.v1beta1.coin_pb2 import Coin
fee = [Coin(amount="0", denom="uband")]
Step 4: Now we can construct a Transaction
from the transaction module.
from pyband.transaction import Transaction
txn = (
Transaction()
.with_messages(request_msg)
.with_sequence(sequence)
.with_account_num(account_num)
.with_chain_id(chain_id)
.with_gas(2000000)
.with_fee(fee)
.with_memo("")
)
Step 5: Preparing the transaction before sending
Call get_sign_doc
to get a signed transaction which we can use to get the signature from.
After that, we can get the raw transaction by calling get_tx_data
and putting the signature and public key as the parameters.
sign_doc = txn.get_sign_doc(public_key)
# Need to serialize sign_doc of type cosmos_tx_type.SignDoc to string
signature = private_key.sign(sign_doc.SerializeToString())
tx_raw_bytes = txn.get_tx_data(signature, public_key)
Step 6: After getting the raw transaction, the transaction can now be sent.
While there are 3 modes for sending the transaction, Block mode will be used in this example. We can call send_tx_block_mode
with the raw transaction as parameter.
import os
from pyband.client import Client
from pyband.transaction import Transaction
from pyband.wallet import PrivateKey
from pyband.proto.cosmos.base.v1beta1.coin_pb2 import Coin
from pyband.proto.oracle.v1.tx_pb2 import MsgRequestData
from google.protobuf.json_format import MessageToJson
def main():
# Step 1
grpc_url = "<GRPC>" # ex.laozi-testnet6.bandchain.org(without https://)
c = Client(grpc_url)
# Step 2
MNEMONIC = os.getenv("MNEMONIC")
private_key = PrivateKey.from_mnemonic(MNEMONIC)
public_key = private_key.to_public_key()
sender_addr = public_key.to_address()
sender = sender_addr.to_acc_bech32()
# Step 3
request_msg = MsgRequestData(
oracle_script_id=37,
calldata=bytes.fromhex("0000000200000003425443000000034554480000000000000064"),
ask_count=4,
min_count=3,
client_id="BandProtocol",
fee_limit=[Coin(amount="100", denom="uband")],
prepare_gas=50000,
execute_gas=200000,
sender=sender,
)
account = c.get_account(sender)
account_num = account.account_number
sequence = account.sequence
fee = [Coin(amount="0", denom="uband")]
chain_id = c.get_chain_id()
# Step 4
txn = (
Transaction()
.with_messages(request_msg)
.with_sequence(sequence)
.with_account_num(account_num)
.with_chain_id(chain_id)
.with_gas(2000000)
.with_fee(fee)
.with_memo("")
)
# Step 5
sign_doc = txn.get_sign_doc(public_key)
signature = private_key.sign(sign_doc.SerializeToString())
tx_raw_bytes = txn.get_tx_data(signature, public_key)
# Step 6
tx_block = c.send_tx_block_mode(tx_raw_bytes)
print(MessageToJson(tx_block))
if __name__ == "__main__":
main()
And the result should look like this.
{
"height": "603247",
"txhash": "587FF6D48E5CB8A23715389FE3CAC10262777B395E4D0C554916127461F63446",
"data": "0A090A0772657175657374",
"rawLog": "[{\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"request\"}]},{\"type\":\"raw_request\",\"attributes\":[{\"key\":\"data_source_id\",\"value\":\"61\"},{\"key\":\"data_source_hash\",\"value\":\"07be7bd61667327aae10b7a13a542c7dfba31b8f4c52b0b60bf9c7b11b1a72ef\"},{\"key\":\"external_id\",\"value\":\"6\"},{\"key\":\"calldata\",\"value\":\"BTC ETH\"},{\"key\":\"fee\"},{\"key\":\"data_source_id\",\"value\":\"57\"},{\"key\":\"data_source_hash\",\"value\":\"61b369daa5c0918020a52165f6c7662d5b9c1eee915025cb3d2b9947a26e48c7\"},{\"key\":\"external_id\",\"value\":\"0\"},{\"key\":\"calldata\",\"value\":\"BTC ETH\"},{\"key\":\"fee\"},{\"key\":\"data_source_id\",\"value\":\"62\"},{\"key\":\"data_source_hash\",\"value\":\"107048da9dbf7960c79fb20e0585e080bb9be07d42a1ce09c5479bbada8d0289\"},{\"key\":\"external_id\",\"value\":\"3\"},{\"key\":\"calldata\",\"value\":\"BTC ETH\"},{\"key\":\"fee\"},{\"key\":\"data_source_id\",\"value\":\"60\"},{\"key\":\"data_source_hash\",\"value\":\"2e588de76a58338125022bc42b460072300aebbcc4acaf55f91755c1c1799bac\"},{\"key\":\"external_id\",\"value\":\"5\"},{\"key\":\"calldata\",\"value\":\"huobipro BTC ETH\"},{\"key\":\"fee\"},{\"key\":\"data_source_id\",\"value\":\"59\"},{\"key\":\"data_source_hash\",\"value\":\"5c011454981c473af3bf6ef93c76b36bfb6cc0ce5310a70a1ba569de3fc0c15d\"},{\"key\":\"external_id\",\"value\":\"2\"},{\"key\":\"calldata\",\"value\":\"BTC ETH\"},{\"key\":\"fee\"},{\"key\":\"data_source_id\",\"value\":\"60\"},{\"key\":\"data_source_hash\",\"value\":\"2e588de76a58338125022bc42b460072300aebbcc4acaf55f91755c1c1799bac\"},{\"key\":\"external_id\",\"value\":\"4\"},{\"key\":\"calldata\",\"value\":\"binance BTC ETH\"},{\"key\":\"fee\"},{\"key\":\"data_source_id\",\"value\":\"60\"},{\"key\":\"data_source_hash\",\"value\":\"2e588de76a58338125022bc42b460072300aebbcc4acaf55f91755c1c1799bac\"},{\"key\":\"external_id\",\"value\":\"9\"},{\"key\":\"calldata\",\"value\":\"bittrex BTC ETH\"},{\"key\":\"fee\"},{\"key\":\"data_source_id\",\"value\":\"60\"},{\"key\":\"data_source_hash\",\"value\":\"2e588de76a58338125022bc42b460072300aebbcc4acaf55f91755c1c1799bac\"},{\"key\":\"external_id\",\"value\":\"7\"},{\"key\":\"calldata\",\"value\":\"kraken BTC ETH\"},{\"key\":\"fee\"},{\"key\":\"data_source_id\",\"value\":\"60\"},{\"key\":\"data_source_hash\",\"value\":\"2e588de76a58338125022bc42b460072300aebbcc4acaf55f91755c1c1799bac\"},{\"key\":\"external_id\",\"value\":\"8\"},{\"key\":\"calldata\",\"value\":\"bitfinex BTC ETH\"},{\"key\":\"fee\"},{\"key\":\"data_source_id\",\"value\":\"58\"},{\"key\":\"data_source_hash\",\"value\":\"7e6759fade717a06fb643392bfde837bfc3437da2ded54feed706e6cd35de461\"},{\"key\":\"external_id\",\"value\":\"1\"},{\"key\":\"calldata\",\"value\":\"BTC ETH\"},{\"key\":\"fee\"}]},{\"type\":\"request\",\"attributes\":[{\"key\":\"id\",\"value\":\"306633\"},{\"key\":\"client_id\",\"value\":\"BandProtocol\"},{\"key\":\"oracle_script_id\",\"value\":\"37\"},{\"key\":\"calldata\",\"value\":\"0000000200000003425443000000034554480000000000000064\"},{\"key\":\"ask_count\",\"value\":\"4\"},{\"key\":\"min_count\",\"value\":\"3\"},{\"key\":\"gas_used\",\"value\":\"111048\"},{\"key\":\"total_fees\"},{\"key\":\"validator\",\"value\":\"bandvaloper1zl5925n5u24njn9axpygz8lhjl5a8v4cpkzx5g\"},{\"key\":\"validator\",\"value\":\"bandvaloper17n5rmujk78nkgss7tjecg4nfzn6geg4cqtyg3u\"},{\"key\":\"validator\",\"value\":\"bandvaloper1p46uhvdk8vr829v747v85hst3mur2dzlhfemmz\"},{\"key\":\"validator\",\"value\":\"bandvaloper1ldtwjzsplhxzhrg3k5hhr8v0qterv05vpdxp9f\"}]}]}]",
"logs": [
{
"events": [
{
"type": "message",
"attributes": [{ "key": "action", "value": "request" }]
},
{
"type": "raw_request",
"attributes": [
{ "key": "data_source_id", "value": "61" },
{
"key": "data_source_hash",
"value": "07be7bd61667327aae10b7a13a542c7dfba31b8f4c52b0b60bf9c7b11b1a72ef"
},
{ "key": "external_id", "value": "6" },
{ "key": "calldata", "value": "BTC ETH" },
{ "key": "fee" },
{ "key": "data_source_id", "value": "57" },
{
"key": "data_source_hash",
"value": "61b369daa5c0918020a52165f6c7662d5b9c1eee915025cb3d2b9947a26e48c7"
},
{ "key": "external_id", "value": "0" },
{ "key": "calldata", "value": "BTC ETH" },
{ "key": "fee" },
{ "key": "data_source_id", "value": "62" },
{
"key": "data_source_hash",
"value": "107048da9dbf7960c79fb20e0585e080bb9be07d42a1ce09c5479bbada8d0289"
},
{ "key": "external_id", "value": "3" },
{ "key": "calldata", "value": "BTC ETH" },
{ "key": "fee" },
{ "key": "data_source_id", "value": "60" },
{
"key": "data_source_hash",
"value": "2e588de76a58338125022bc42b460072300aebbcc4acaf55f91755c1c1799bac"
},
{ "key": "external_id", "value": "5" },
{ "key": "calldata", "value": "huobipro BTC ETH" },
{ "key": "fee" },
{ "key": "data_source_id", "value": "59" },
{
"key": "data_source_hash",
"value": "5c011454981c473af3bf6ef93c76b36bfb6cc0ce5310a70a1ba569de3fc0c15d"
},
{ "key": "external_id", "value": "2" },
{ "key": "calldata", "value": "BTC ETH" },
{ "key": "fee" },
{ "key": "data_source_id", "value": "60" },
{
"key": "data_source_hash",
"value": "2e588de76a58338125022bc42b460072300aebbcc4acaf55f91755c1c1799bac"
},
{ "key": "external_id", "value": "4" },
{ "key": "calldata", "value": "binance BTC ETH" },
{ "key": "fee" },
{ "key": "data_source_id", "value": "60" },
{
"key": "data_source_hash",
"value": "2e588de76a58338125022bc42b460072300aebbcc4acaf55f91755c1c1799bac"
},
{ "key": "external_id", "value": "9" },
{ "key": "calldata", "value": "bittrex BTC ETH" },
{ "key": "fee" },
{ "key": "data_source_id", "value": "60" },
{
"key": "data_source_hash",
"value": "2e588de76a58338125022bc42b460072300aebbcc4acaf55f91755c1c1799bac"
},
{ "key": "external_id", "value": "7" },
{ "key": "calldata", "value": "kraken BTC ETH" },
{ "key": "fee" },
{ "key": "data_source_id", "value": "60" },
{
"key": "data_source_hash",
"value": "2e588de76a58338125022bc42b460072300aebbcc4acaf55f91755c1c1799bac"
},
{ "key": "external_id", "value": "8" },
{ "key": "calldata", "value": "bitfinex BTC ETH" },
{ "key": "fee" },
{ "key": "data_source_id", "value": "58" },
{
"key": "data_source_hash",
"value": "7e6759fade717a06fb643392bfde837bfc3437da2ded54feed706e6cd35de461"
},
{ "key": "external_id", "value": "1" },
{ "key": "calldata", "value": "BTC ETH" },
{ "key": "fee" }
]
},
{
"type": "request",
"attributes": [
{ "key": "id", "value": "306633" },
{ "key": "client_id", "value": "BandProtocol" },
{ "key": "oracle_script_id", "value": "37" },
{
"key": "calldata",
"value": "0000000200000003425443000000034554480000000000000064"
},
{ "key": "ask_count", "value": "4" },
{ "key": "min_count", "value": "3" },
{ "key": "gas_used", "value": "111048" },
{ "key": "total_fees" },
{
"key": "validator",
"value": "bandvaloper1zl5925n5u24njn9axpygz8lhjl5a8v4cpkzx5g"
},
{
"key": "validator",
"value": "bandvaloper17n5rmujk78nkgss7tjecg4nfzn6geg4cqtyg3u"
},
{
"key": "validator",
"value": "bandvaloper1p46uhvdk8vr829v747v85hst3mur2dzlhfemmz"
},
{
"key": "validator",
"value": "bandvaloper1ldtwjzsplhxzhrg3k5hhr8v0qterv05vpdxp9f"
}
]
}
]
}
],
"gasWanted": "2000000",
"gasUsed": "566496"
}
Sending BAND token
The process of sending BAND token is similar to making an oracle request , except we will use MsgSend
as our message.
The MsgSend
contains the following parameters:
- from_address
<str>
: The sender address which as a string. - to_address
<str>
: The receiver address which as a string. - amount
<int>
: The amount of BAND in Coin that you want to send. In this case, we want to send 1 BAND or 1000000 UBAND
from pyband.proto.cosmos.bank.v1beta1.tx_pb2 import MsgSend
msg = MsgSend(
from_address = sender,
to_address = "band1jrhuqrymzt4mnvgw8cvy3s9zhx3jj0dq30qpte",
amount = [Coin(amount="100", denom="uband")]
)
The final code should look as shown below.
import os
from pyband.client import Client
from pyband.transaction import Transaction
from pyband.wallet import PrivateKey
from pyband.proto.cosmos.base.v1beta1.coin_pb2 import Coin
from pyband.proto.cosmos.bank.v1beta1.tx_pb2 import MsgSend
from google.protobuf.json_format import MessageToJson
def main():
# Step 1
grpc_url = "<GRPC>" # ex.laozi-testnet6.bandchain.org(without https://)
c = Client(grpc_url)
# Step 2
MNEMONIC = os.getenv("MNEMONIC")
private_key = PrivateKey.from_mnemonic(MNEMONIC)
public_key = private_key.to_public_key()
sender_addr = public_key.to_address()
sender = sender_addr.to_acc_bech32()
# Step 3
send_msg = MsgSend(
from_address = sender,
to_address = "band1jrhuqrymzt4mnvgw8cvy3s9zhx3jj0dq30qpte",
amount = [Coin(amount="1000000", denom="uband")]
)
account = c.get_account(sender)
account_num = account.account_number
sequence = account.sequence
fee = [Coin(amount="0", denom="uband")]
chain_id = c.get_chain_id()
# Step 4
txn = (
Transaction()
.with_messages(send_msg)
.with_sequence(sequence)
.with_account_num(account_num)
.with_chain_id(chain_id)
.with_gas(2000000)
.with_fee(fee)
.with_memo("")
)
# Step 5
sign_doc = txn.get_sign_doc(public_key)
signature = private_key.sign(sign_doc.SerializeToString())
tx_raw_bytes = txn.get_tx_data(signature, public_key)
# Step 6
tx_block = c.send_tx_block_mode(tx_raw_bytes)
print(MessageToJson(tx_block))
if __name__ == "__main__":
main()
And the result should look like this.
{
"height": "603302",
"txhash": "815F488B3F05F2CBDD57C433DBEAF01FBFB06F378716A8ECDF5888095D6F7F7C",
"data": "0A060A0473656E64",
"rawLog": "[{\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"send\"},{\"key\":\"sender\",\"value\":\"band18p27yl962l8283ct7srr5l3g7ydazj07dqrwph\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"band1jrhuqrymzt4mnvgw8cvy3s9zhx3jj0dq30qpte\"},{\"key\":\"sender\",\"value\":\"band18p27yl962l8283ct7srr5l3g7ydazj07dqrwph\"},{\"key\":\"amount\",\"value\":\"1000000uband\"}]}]}]",
"logs": [
{
"events": [
{
"type": "message",
"attributes": [
{ "key": "action", "value": "send" },
{
"key": "sender",
"value": "band18p27yl962l8283ct7srr5l3g7ydazj07dqrwph"
},
{ "key": "module", "value": "bank" }
]
},
{
"type": "transfer",
"attributes": [
{
"key": "recipient",
"value": "band1jrhuqrymzt4mnvgw8cvy3s9zhx3jj0dq30qpte"
},
{
"key": "sender",
"value": "band18p27yl962l8283ct7srr5l3g7ydazj07dqrwph"
},
{ "key": "amount", "value": "1000000uband" }
]
}
]
}
],
"gasWanted": "2000000",
"gasUsed": "49029"
}
Getting reference data
This section shows an example on how to query data from BandChain. This example queries the standard price reference based on the given symbol pairs, min count, and ask count.
Step 1: Import pyband
and create a parameter: grpc_url
with the required <GRPC>
endpoint which can be found here. Then the client instance needs to be initialized in order to allow for the methods in client module to be used.
from pyband.client import Client
def main():
# Step 1
grpc_url = "<GRPC>" # ex.laozi-testnet6.bandchain.org(without https://)
if __name__ == "__main__":
main()
Step 2 After importing Client
, the function get_reference_data
can now be used to get the latest price.
The function contains the following parameters
- pairs
<List[str]>
: list of cryptocurrency pairs - min_count
<int>
: integer of min count - ask_count
<int>
: integer of ask count
from pyband.client import Client
def main():
# Step 1
grpc_url = "<GRPC>" # ex.laozi-testnet6.bandchain.org(without https://)
c = Client(grpc_url)
# Step 2
print(c.get_reference_data(["BTC/USD", "ETH/USD"], 3, 4))
if __name__ == "__main__":
main()
And running the code above should return a result that looks like this.
[
ReferencePrice(
(pair = "BTC/USD"),
(rate = 34614.1),
(updated_at = ReferencePriceUpdated((base = 1625655764), (quote = 1625715134)))
),
ReferencePrice(
(pair = "ETH/USD"),
(rate = 2372.53),
(updated_at = ReferencePriceUpdated((base = 1625655764), (quote = 1625715134)))
)
]