How to use SmartBCH-specific JSON-RPC methods in Python

0 102
Avatar for norphine
2 years ago

If you know something about Python and SmartBCH, then very probably you know about web3py. It's a powerful tool to develop SmartBCH-related projects using Python, but do you know that SmartBCH has their own methods not supported by web3py?

Although Ethereum methods works, there are interesting methods available only on SBCH, you can take a look at the specs here: https://docs.smartbch.org/smartbch/developers-guide/jsonrpc#sbch

I hope this can be useful for beginners, it's also a good way to learn how queries works.

The code:

import requests
import web3

class SBCH:
    ID = 0
    headers = {'Content-type': 'application/json'}
    payload = {"jsonrpc": "2.0",
               "method": "undefined",
               "params": [],
               "id": ID}
    session = requests.Session()
    topics = {"Transfer": web3.Web3.keccak(text="Transfer(address,address,uint256)").hex(),
              "Approval": web3.Web3.keccak(text="Approval(address,address,uint256)").hex(),
              "MinterAdded": web3.Web3.keccak(text="MinterAdded(address)").hex(),
              "MinterRemoved": web3.Web3.keccak(text="MinterRemoved(address)").hex()}

    def queryTxBySrc(self, address, start, end, txs_limit = 0):
        address = web3.Web3.toChecksumAddress(address)
        if type(start) == int:
            start = hex(start)
        if type(end) == int:
            end = hex(end)
        if type(txs_limit) == int:
            txs_limit = hex(txs_limit)
        self.payload["method"] = "sbch_queryTxBySrc"
        self.payload["params"] = [address, start, end, txs_limit]
        self.get_response()

    def queryTxByDst(self, address, start, end = 'latest', txs_limit = 0):
        address = web3.Web3.toChecksumAddress(address)
        if type(start) == int:
            start = hex(start)
        if type(end) == int:
            end = hex(end)
        if type(txs_limit) == int:
            txs_limit = hex(txs_limit)
        self.payload["method"] = "sbch_queryTxByDst"
        self.payload["params"] = [address, start, end, txs_limit]
        self.get_response()

    def queryTxByAddr(self, address, start, end = 'latest', txs_limit = 0):
        address = web3.Web3.toChecksumAddress(address)
        if type(start) == int:
            start = hex(start)
        if type(end) == int:
            end = hex(end)
        if type(txs_limit) == int:
            txs_limit = hex(txs_limit)
        self.payload["method"] = "sbch_queryTxByAddr"
        self.payload["params"] = [address, start, end, txs_limit]
        self.get_response()

    def queryLogs(self, address, start, topics_array = [], end = 'latest', txs_limit = 0):
        address = web3.Web3.toChecksumAddress(address)
        if type(start) == int:
            start = hex(start)
        if type(end) == int:
            end = hex(end)
        if type(txs_limit) == int:
            txs_limit = hex(txs_limit)
        if topics_array == []:
            topics_array = [SBCH.topics['Transfer']] # Get Transfer events logs by default
        self.payload["method"] = "sbch_queryLogs"
        self.payload["params"] = [address, topics_array, start, end, txs_limit]
        self.get_response()

    def getTxListByHeight(self, block_number):
        if type(block_number) == int:
            block_number = hex(block_number)
        self.payload["method"] = "sbch_getTxListByHeight"
        self.payload["params"] = [block_number]
        self.get_response()

    def getTxListByHeightWithRange(self, block_number, start_tx_index, end_tx_index = 0):
        if type(block_number) == int:
            block_number = hex(block_number)
        if type(start_tx_index) == int:
            start_tx_index = hex(start_tx_index)
        if type(end_tx_index) == int:
            end_tx_index = hex(end_tx_index)
        self.payload["method"] = "sbch_getTxListByHeightWithRange"
        self.payload["params"] = [block_number, start_tx_index, end_tx_index]
        self.get_response()

    def getAddressCount(self, query, address):
        # query must be "from", "to" or "both"
        address = web3.Web3.toChecksumAddress(address)
        self.payload["method"] = "sbch_getAddressCount"
        self.payload["params"] = [query, address]
        self.get_response()

    def getSep20AddressCount(self, query, contract_address, address):
        # query must be "from", "to" or "both"
        address = web3.Web3.toChecksumAddress(address)
        contract_address = web3.Web3.toChecksumAddress(contract_address)
        self.payload["method"] = "sbch_getSep20AddressCount"
        self.payload["params"] = [query, contract_address, address]
        self.get_response()

    def get_response(self):
        self.response = self.session.post('https://smartbch.fountainhead.cash/mainnet', json=self.payload, headers=self.headers).json()

    def __init__(self):
        SBCH.ID += 1
        self.payload["id"] = self.ID

This is the code. As you can see, you need to import requests to make the requests and web3.

  • Requests is used to make the request to the RPC server.

  • Web3, because we need to utils:

    • Web3.toChecksumAddress(address), we need addresses to be in checksum format and the script takes care of it.

    • Web3.keccak(text=string), this gives us the hash of a string, and it's needed for topics (more on this later).

Every instance has an unique ID, which you can see with my_instance.ID. If you want to see how is built the payload which is sent to the RPC server, just type my_instance.payload.

Using the code: getSep20AddressCount example

As you can see, there's a class called SBCH. Instead of directly running functions, we instantiate the class to get an instance.

getSep20AddressCount returns the times addr acts as a to-address or from-address of a SEP20 Transfer event at some contract.

Let's say you want to say how many times there's was a transfer from the LAW token smart contract to your address. LAW contract address is 0x0b00366fBF7037E9d75E4A569ab27dAB84759302

You have to do this once you have loaded the former code:

my_transaction = SBCH()
my_transaction.getSep20AddressCount("to", "0x0b00366fBF7037E9d75E4A569ab27dAB84759302", "0x0thisismyadress000000000")
my_transaction.response()

In this case, we obtain this response:

{'jsonrpc': '2.0', 'id': 1, 'result': '0x1'}

The result is given in hexadecimal and means 1. The RPC server loves hexadecimals numbers: the script has to convert integers to hexadecimal, like block number.

You can store the result in a new variable as an integer:

result = int(my_transaction.response["result"], 0)

Using the code: sbch_queryTxByAddr

This method returns the information about transactions requested by address (sender or recipient) and block range. What you get are the transactions hash, later you can query more information about every transaction using this info.

If you want to get a list of all the transactions related to your address from block 600,000 to the latest one:

my_transactions = SBCH()
my_transactions.queryTxByAddr("0x0thisismyaddress00000",650000)

Check 2 things:

  • If you don't specify otherwise, the end block by default is latest.

  • Also, the number of txs returned is set by default to 0, which is the default limit and I don't know exactly how many txs are.

If you're afraid of hitting the limit, you can query the current block height and iterate from 0 to the latest one.

Using the code: queryLogs example

This method query logs by address, topics and block range. Every event type has a topic associated to it. For example, for Transfer, the topic is 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. Where does this comes from? This is the Keccak 256 hash of the string "Transfer(address,address,uint256)". You can see inside the class a dictionary containing some common topics.

If you want to see the transfer events from the LAW token smart contract from block 600,000 to the latest one, just run:

law_transfers = SBCH()
law_transfers.queryLogs("0x0b00366fBF7037E9d75E4A569ab27dAB84759302", 600000)

By default, the end block is the latest and the topic is transfer. I tried to pass an array of topics with no success. Remember when passing a custom topic, pass it as a list, for example:

law_approvals = SBCH()
law_approvals.queryLogs("0x0b00366fBF7037E9d75E4A569ab27dAB84759302", [SBCH.topics["Approval"]],600000)

If you're not comfortable working with instances, you always can use the functions by themselves, just don't forget to remove the "self".

4
$ 26.23
$ 24.71 from @TheRandomRewarder
$ 1.00 from @ClearSky
$ 0.27 from @Telesfor
+ 2
Avatar for norphine
2 years ago

Comments