Py substrate interface

Python Substrate Interface

Introduction

Python Substrate Interface library allows application developers to query a Phron node and interact with the node's Polkadot or Substrate features using a native Python interface. Here you will find an overview of the available functionalities and some commonly used code examples to get you started on interacting with Phron networks using Python Substrate Interface.

Checking Prerequisites

For the examples in this guide, you will need to have the following:

  • An account with funds. You can get DEV tokens for testing on Phron once every 24 hours from the Phron Faucet

  • To test out the examples in this guide on Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers

  • Have pip installed

Note !

The examples in this guide assume you have a MacOS or Ubuntu 22.04-based environment and will need to be adapted accordingly for Windows.

Installing Python Substrate Interface

You can install Python Substrate Interface library for your project through pip. Run the following command in your project directory:

pip install substrate-interface

Creating an API Provider Instance

Similar to ETH API libraries, you must first instantiate an API instance of Python Substrate Interface API. Create the WsProvider using the websocket endpoint of the Phron network you wish to interact with.

To test out the examples in this guide on Phron, you will need to have your own endpoint and API key, which you can get from one of the supported Endpoint Providers.

# Imports
from substrateinterface import SubstrateInterface

# Construct the API provider
ws_provider = SubstrateInterface(
    url="INSERT_WSS_API_ENDPOINT",
)

Querying for Information

In this section, you will learn how to query for on-chain information of Phron networks using Python Substrate Interface library.

Accessing Runtime Constants

All runtime constants, such as BlockWeights, DefaultBlocksPerRound and ExistentialDeposit, are provided in the metadata. You can use the get_metadata_constants method to see a list of available runtime constants within Phron network's metadata.

Runtime constants available in the metadata can be queried through the get_constant method.

# Imports
from substrateinterface import SubstrateInterface

# Construct the API provider
ws_provider = SubstrateInterface(
    url="wss://testnet.phron.ai",
)   

# List of available runtime constants in the metadata
constant_list = ws_provider.get_metadata_constants()
print(constant_list)

# Retrieve the Existential Deposit constant on Moonbeam, which is 0
constant = ws_provider.get_constant("Balances", "ExistentialDeposit")
print(constant.value)

Retrieving Blocks and Extrinsics

You can retrieve basic information about Phron networks, such as blocks and extrinsics, using the Python Substrate Interface API.

To retrieve a block, you can use the get_block method. You can also access extrinsics and their data fields inside a block object, which is simply a Python dictionary.

To retrieve a block header, you can use the get_block_header method.

# Imports
from substrateinterface import SubstrateInterface

# Construct the API provider
ws_provider = SubstrateInterface(
    url="wss://testnet.phron.ai",
)

# Retrieve the latest block
block = ws_provider.get_block()

# Retrieve the latest finalized block
block = ws_provider.get_block_header(finalized_only=True)

# Retrieve a block given its Substrate block hash
block_hash = "0xa499d4ebccdabe31218d232460c0f8b91bd08f72aca25f9b25b04b6dfb7a2acb"
block = ws_provider.get_block(block_hash=block_hash)

# Iterate through the extrinsics inside the block
for extrinsic in block["extrinsics"]:
    if "address" in extrinsic:
        signed_by_address = extrinsic["address"].value
    else:
        signed_by_address = None
    print(
        "\nPallet: {}\nCall: {}\nSigned by: {}".format(
            extrinsic["call"]["call_module"].name,
            extrinsic["call"]["call_function"].name,
            signed_by_address,
        )
    )

Note!

The block hash used in the above code sample is the Substrate block hash. The standard methods in Python Substrate Interface assume you are using the Substrate version of primitives, such as block or tx hashes.

Subscribing to New Block Headers

You can also adapt the previous example to use a subscription based model to listen to new block headers.

# Imports
from substrateinterface import SubstrateInterface

# Construct the API provider
ws_provider = SubstrateInterface(
    url="wss://testnet.phron.ai",
)


def subscription_handler(obj, update_nr, subscription_id):
    print(f"New block #{obj['header']['number']}")

    if update_nr > 10:
        return {
            "message": "Subscription will cancel when a value is returned",
            "updates_processed": update_nr,
        }


result = ws_provider.subscribe_block_headers(subscription_handler)

Querying for Storage Information

You can use the get_metadata_storage_functions to see a list of available storage functions within Phron network's metadata.

Chain states that are provided in the metadata through storage functions can be queried through the query method.

The Substrate system modules, such as System, Timestamp, and Balances, can be queried to provide basic information such as account nonce and balance. The available storage functions are read from the metadata dynamically, so you can also query for storage information on Phron custom modules, such as ParachainStaking and Democracy, for state information that's specific to Phron.

# Imports
from substrateinterface import SubstrateInterface

# Construct the API provider
ws_provider = SubstrateInterface(
    url="wss://testnet.phron.ai",
)

# List of available storage functions in the metadata
method_list = ws_provider.get_metadata_storage_functions()
print(method_list)

# Query basic account information
account_info = ws_provider.query(
    module="System",
    storage_function="Account",
    params=["0x578002f699722394afc52169069a1FfC98DA36f1"],
)
# Log the account nonce
print(account_info.value["nonce"])
# Log the account free balance
print(account_info.value["data"]["free"])

# Query candidate pool information from Moonbeam's Parachain Staking module
candidate_pool_info = ws_provider.query(
    module="ParachainStaking", storage_function="CandidatePool", params=[]
)
print(candidate_pool_info)

Signing and Transactions

Creating a Keypair

The keypair object in Python Substrate Interface is used in the signing of any data, whether it's a transfer, a message, or a contract interaction.

You can create a keypair instance from the shortform private key or from the mnemonic. For Phron networks, you also need to specify the KeypairType to be KeypairType.ECDSA.

# Imports
from substrateinterface import Keypair, KeypairType

# Define the shortform private key
privatekey = bytes.fromhex("INSERT_PRIVATE_KEY_WITHOUT_0X_PREFIX")

# Define the account mnenomic
mnemonic = "INSERT_MNEMONIC"

# Generate the keypair from shortform private key
keypair = Keypair.create_from_private_key(privatekey, crypto_type=KeypairType.ECDSA)

# Generate the keypair from mnemonic
keypair = Keypair.create_from_mnemonic(mnemonic, crypto_type=KeypairType.ECDSA)

Forming and Sending a Transaction

The compose_call method can be used to compose a call payload which can be used as an unsigned extrinsic or a proposal.

Then the payload can be signed using a keypair through the create_signed_extrinsic method.

The signed extrinsic can then be submitted using the submit_extrinsic method.

This method will also return an ExtrinsicReceipt object which contains information about the on-chain execution of the extrinsic. If you need to examine the receipt object, you can set the wait_for_inclusion to True when submitting the extrinsic to wait until the extrinsic is successfully included into the block.

The following sample code will show a complete example for sending a transaction.

# Imports
from substrateinterface import SubstrateInterface, Keypair, KeypairType
from substrateinterface.exceptions import SubstrateRequestException

# Construct the API provider
ws_provider = SubstrateInterface(
    url="wss://testnet.phron.ai",
)

# Define the shortform private key of the sending account
privatekey = bytes.fromhex("INSERT_PRIVATE_KEY_WITHOUT_0X_PREFIX")

# Generate the keypair
keypair = Keypair.create_from_private_key(privatekey, crypto_type=KeypairType.ECDSA)

# Form a transaction call
call = ws_provider.compose_call(
    call_module="Balances",
    call_function="transfer_allow_death",
    call_params={
        "dest": "0x44236223aB4291b93EEd10E4B511B37a398DEE55",
        "value": 1 * 10**18,
    },
)

# Form a signed extrinsic
extrinsic = ws_provider.create_signed_extrinsic(call=call, keypair=keypair)

# Submit the extrinsic
try:
    receipt = ws_provider.submit_extrinsic(extrinsic, wait_for_inclusion=True)
    print(
        "Extrinsic '{}' sent and included in block '{}'".format(
            receipt.extrinsic_hash, receipt.block_hash
        )
    )
except SubstrateRequestException as e:
    print("Failed to send: {}".format(e))

Offline Signing

You can sign transaction payloads or any arbitrary data using a keypair object through the sign method. This can be used for offline signing of transactions.

  1. First, generate the signature payload on an online machine:

    # Imports
    from substrateinterface import SubstrateInterface
    
    # Construct the API provider
    ws_provider = SubstrateInterface(
        url="wss://testnet.phron.ai",
    )
    
    # Construct a transaction call
    call = ws_provider.compose_call(
        call_module="Balances",
        call_function="transfer_allow_death",
        call_params={
            "dest": "0x44236223aB4291b93EEd10E4B511B37a398DEE55",
            "value": 1 * 10**18,
        },
    )
    
    # Generate the signature payload
    signature_payload = ws_provider.generate_signature_payload(call=call)
  2. On an offline machine, create a keypair with the private key of the sending account, and sign the signature payload:

    # Imports
    from substrateinterface import Keypair, KeypairType
    
    # Define the signature payload from the offline machine
    signature_payload = "INSERT_SIGNATURE_PAYLOAD"
    
    # Define the shortform private key of the sender account
    privatekey = bytes.fromhex("INSERT_PRIVATE_KEY_WITHOUT_0X_PREFIX")
    
    # Generate the keypair from shortform private key
    keypair = Keypair.create_from_private_key(privatekey, crypto_type=KeypairType.ECDSA)
    
    # Sign the signature_payload 
    signature = keypair.sign(signature_payload)
  3. On an online machine, create a keypair with the public key of the sending account, and submit the extrinsic with the generated signature from the offline machine:

    # Imports
    from substrateinterface import SubstrateInterface, Keypair, KeypairType
    
    # Construct the API provider
    ws_provider = SubstrateInterface(
        url="wss://testnet.phron.ai",
    )
    
    # Define the signature from the offline machine
    signature_payload = "INSERT_SIGNATURE_PAYLOAD"
    
    # Construct a keypair with the Ethereum style wallet address of the sending account
    keypair = Keypair(public_key="INSERT_ADDRESS_WITHOUT_0X", crypto_type=KeypairType.ECDSA)
    
    # Construct the same transaction call that was signed
    call = ws_provider.compose_call(
        call_module="Balances",
        call_function="transfer_allow_death",
        call_params={
            "dest": "0x44236223aB4291b93EEd10E4B511B37a398DEE55",
            "value": 1 * 10**18,
        },
    )
    
    # Construct the signed extrinsic with the generated signature
    extrinsic = ws_provider.create_signed_extrinsic(
        call=call, keypair=keypair, signature=signature
    )
    
    # Submit the signed extrinsic
    result = ws_provider.submit_extrinsic(extrinsic=extrinsic)
    
    # Print the execution result
    print(result.extrinsic_hash)

Custom RPC Requests

You can also make custom RPC requests with the rpc_request method.

This is particularly useful for interacting with Phron's Ethereum JSON-RPC endpoints or Phron's custom RPC endpoints.

The Consensus and Finality page has examples for using the custom RPC calls through Python Substrate Interface to check the finality of a transaction given its transaction hash.

This tutorial is for educational purposes only. As such, any contracts or code created in this tutorial should not be used in production.The information presented herein has been provided by third parties and is made available solely for general information purposes. Phron does not endorse any project listed and described on the Phron Doc Website (https://docs.Phron.ai/). Phron does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Phron disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Phron. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Phron has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Phron harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.

Last updated