Skip to content

Hello World Tutorial with Python

Overview

We have developed a step-by-step python3 script that contains a BurstChain® Client class and a series of calls to execute each step of the Hello World tutorial. The BurstChain® Client could be reused in your own application and extended to other API calls (not all the available APIs were added to the class).

BurstIQ Hello World Python Script Download:

Download Here

Running the example

Python3 is required to be installed. Also, might have to install a dependency:

pip3 install argparse requests

The script has a usage output:

$ ./hello_world_example.py -h

--------------------------------------------------------------
Burst Chain Client - Hello World Demo
Copyright (c) 2015-2020 BurstIQ, Inc.
--------------------------------------------------------------

usage: hello_world_example.py [-h] [-s SERVER] -c CLIENT -u USERNAME -p PASSWORD
                          [--privateid PRIVATEID] [-q] [-v]

optional arguments:
  -h, --help            show this help message and exit
  -s SERVER, --server SERVER
                        The AIQ server; defaults to
                        https://aiq.burstiq.com
  -c CLIENT, --client CLIENT
                        The client name to interact with
  -u USERNAME, --username USERNAME
                        The username that is the admin account for the client
                        space
  -p PASSWORD, --password PASSWORD
                        The username password above
  --privateid PRIVATEID
                        An existing private id to use instead of generating a
                        new one
  -q, --quiet           log only warnings
  -v, --verbose         log low level details

When running the script for the first time, only 3 arguments are required: -c, -u, and -p. All three pieces of information should have been supplied when the account was established. This will execute the script and its 14 steps.

$ ./hello_world_example.py -c client_name -u admin@client.com -p 'mysecret'

If you want to run the hello world script again, and reuse the private id that was generated in the first run, simply add the –privateid argument with the value in the output of the first run.

Example output

--------------------------------------------------------------
Burst Chain Client - Hello World Demo
Copyright (c) 2015-2020 BurstIQ, Inc.
--------------------------------------------------------------

2019-05-23 13:22:21,833 [INFO] [MainProcess] [MainThread] : PUT dictionary response: The operation was successful
2019-05-23 13:22:21,834 [INFO] [MainProcess] [MainThread] : Using private id xxxxxxxxxxxxxxxx for this demo
2019-05-23 13:22:22,000 [INFO] [MainProcess] [MainThread] : Using public id 4be910dec39ace8a64711d0afe0467b9c9215602 for this demo
2019-05-23 13:22:22,069 [INFO] [MainProcess] [MainThread] : Asset created 4c84ab87-badd-471d-829c-a5fe069e3dca for this demo
2019-05-23 13:22:22,133 [INFO] [MainProcess] [MainThread] : Status response message accepted
2019-05-23 13:22:22,248 [INFO] [MainProcess] [MainThread] : ASSET:
 {
  "hash": "7f7d18b3ece351251a24e0dc6fb1a0fc747bbecda06af573cc2490f1625fd399",
  "timestamp": {
    "$date": "2019-05-23T19:22:24.197Z"
  },
  "type": "asset",
  "operation": "create",
  "owners": [
    "4be910dec39ace8a64711d0afe0467b9c9215602"
  ],
  "signer": "4be910dec39ace8a64711d0afe0467b9c9215602",
  "signature": "3195df7e78e479a7d001e35aef1836b2404da3e426a6d4e7fa5228842a6f1efddb5fb7a376855a7fe00ce0a76db058b36b10166a7e4f59ac47bbeefa2b323f17d9565663e5854044da87cd623404f88a1f5744f847e012069c8386b060c4d2d433d0af983c15658f835ebd280abccf499eca3fc04081a0c2f841d3682a45a60e49996192d917df14ad08cc29f2665776c5afa618ae7e79b8b72ec03ca5d159cea76bbb4fcc0621d86c99f14767ddee230902a0bdf0dfb2ba5294e350740fef2c97492cccb66ae4f3f3b2206d10d2bb681a1464b0e011ebb660d5aba3e86ba47a9c8a13b89d195f2c4140240e9c16543b0f2cac57beffe7eae2e9a6561cc88d26",
  "dictionary": "address",
  "asset": {
    "id": "7380",
    "addr1": "123 Main St",
    "city": "Nowhere",
    "state": "XX",
    "zip": "12345-0000"
  },
  "previous_hash": "3af4af78e023e31a24f8f8d4fd101e4d4dfe2b0ce555aa926a4c6a8656246908",
  "asset_id": "4c84ab87-badd-471d-829c-a5fe069e3dca",
  "asset_metadata": {
    "loaded by": "hello world demo"
  }
}
2019-05-23 13:22:22,311 [INFO] [MainProcess] [MainThread] : ASSET:
 {
  "hash": "7f7d18b3ece351251a24e0dc6fb1a0fc747bbecda06af573cc2490f1625fd399",
  "timestamp": {
    "$date": "2019-05-23T19:22:24.197Z"
  },
  "type": "asset",
  "operation": "create",
  "owners": [
    "4be910dec39ace8a64711d0afe0467b9c9215602"
  ],
  "signer": "4be910dec39ace8a64711d0afe0467b9c9215602",
  "signature": "3195df7e78e479a7d001e35aef1836b2404da3e426a6d4e7fa5228842a6f1efddb5fb7a376855a7fe00ce0a76db058b36b10166a7e4f59ac47bbeefa2b323f17d9565663e5854044da87cd623404f88a1f5744f847e012069c8386b060c4d2d433d0af983c15658f835ebd280abccf499eca3fc04081a0c2f841d3682a45a60e49996192d917df14ad08cc29f2665776c5afa618ae7e79b8b72ec03ca5d159cea76bbb4fcc0621d86c99f14767ddee230902a0bdf0dfb2ba5294e350740fef2c97492cccb66ae4f3f3b2206d10d2bb681a1464b0e011ebb660d5aba3e86ba47a9c8a13b89d195f2c4140240e9c16543b0f2cac57beffe7eae2e9a6561cc88d26",
  "dictionary": "address",
  "asset": {
    "id": "7380",
    "addr1": "123 Main St",
    "city": "Nowhere",
    "state": "XX",
    "zip": "12345-0000"
  },
  "previous_hash": "3af4af78e023e31a24f8f8d4fd101e4d4dfe2b0ce555aa926a4c6a8656246908",
  "asset_id": "4c84ab87-badd-471d-829c-a5fe069e3dca",
  "asset_metadata": {
    "loaded by": "hello world demo"
  }
}
2019-05-23 13:22:22,378 [INFO] [MainProcess] [MainThread] : Asset updated 4c84ab87-badd-471d-829c-a5fe069e3dca for this demo
2019-05-23 13:22:22,442 [INFO] [MainProcess] [MainThread] : ASSET:
 {
  "hash": "d61455af6be744da2778ee8cda0b1dd8f5833462ea0e61fffbe17b5556f8b434",
  "timestamp": {
    "$date": "2019-05-23T19:22:24.505Z"
  },
  "type": "asset",
  "operation": "update",
  "owners": [
    "4be910dec39ace8a64711d0afe0467b9c9215602"
  ],
  "signer": "4be910dec39ace8a64711d0afe0467b9c9215602",
  "signature": "1583c718d5320ee8eb7b3287d555b7d70986656ae67517a6ddc06dccf1caaadbb1e61664beb35a353beb23353c13b1103dcffb6a705fb453d701115a9ac5cb68e0bb15f009775cfccfd17bd6750abe6cec7f4cb09d929031e7b642c11337baed2279a5137755e160194dc7ce0ddc2a5807398a72f4c6f21a4992f79ca11ee9c76e0d4ac1f2752518595bb0a2ba5c9662060c32884a826e5e75af6f720fc19bb07fc4fe99f94a6288f95743bd9405133564f683871995a51dab1c6a404596bc1ac22f5a4a45a729daad060396a37e0b4f1f96bdd7a6110ed1d32a990ba68e3f85e2456a216b3438ff3adfb1f9a3ed38317d1d7eb17b4f292fff43b70bc2cda1b5",
  "dictionary": "address",
  "asset": {
    "id": "7380",
    "addr1": "123 Main St",
    "city": "Nowhere",
    "state": "CO",
    "zip": "12345-0000"
  },
  "previous_hash": "7f7d18b3ece351251a24e0dc6fb1a0fc747bbecda06af573cc2490f1625fd399",
  "asset_id": "4c84ab87-badd-471d-829c-a5fe069e3dca"
}
2019-05-23 13:22:22,629 [INFO] [MainProcess] [MainThread] : transferred asset id 4c84ab87-badd-471d-829c-a5fe069e3dca
2019-05-23 13:22:22,690 [INFO] [MainProcess] [MainThread] : ASSET should be NULL:
 null
2019-05-23 13:22:22,752 [INFO] [MainProcess] [MainThread] : ASSET:
 {
  "hash": "a07061a50e38d447d8911b6ccfbb5ebd07a8fe0e25072f5074a09b544c5bc261",
  "timestamp": {
    "$date": "2019-05-23T19:22:24.505Z"
  },
  "type": "asset",
  "operation": "transfer",
  "owners": [
    "942a3adce18388e4f45f521195b1356b6c51348f"
  ],
  "signer": "4be910dec39ace8a64711d0afe0467b9c9215602",
  "signature": "1583c718d5320ee8eb7b3287d555b7d70986656ae67517a6ddc06dccf1caaadbb1e61664beb35a353beb23353c13b1103dcffb6a705fb453d701115a9ac5cb68e0bb15f009775cfccfd17bd6750abe6cec7f4cb09d929031e7b642c11337baed2279a5137755e160194dc7ce0ddc2a5807398a72f4c6f21a4992f79ca11ee9c76e0d4ac1f2752518595bb0a2ba5c9662060c32884a826e5e75af6f720fc19bb07fc4fe99f94a6288f95743bd9405133564f683871995a51dab1c6a404596bc1ac22f5a4a45a729daad060396a37e0b4f1f96bdd7a6110ed1d32a990ba68e3f85e2456a216b3438ff3adfb1f9a3ed38317d1d7eb17b4f292fff43b70bc2cda1b5",
  "dictionary": "address",
  "asset": {
    "id": "7380",
    "addr1": "123 Main St",
    "city": "Nowhere",
    "state": "CO",
    "zip": "12345-0000"
  },
  "previous_hash": "d61455af6be744da2778ee8cda0b1dd8f5833462ea0e61fffbe17b5556f8b434",
  "asset_id": "4c84ab87-badd-471d-829c-a5fe069e3dca"
}
2019-05-23 13:22:22,815 [INFO] [MainProcess] [MainThread] : TQL 1 ASSET:
 [
  {
    "hash": "a07061a50e38d447d8911b6ccfbb5ebd07a8fe0e25072f5074a09b544c5bc261",
    "timestamp": {
      "$date": "2019-05-23T19:22:24.505Z"
    },
    "type": "asset",
    "operation": "transfer",
    "owners": [
      "942a3adce18388e4f45f521195b1356b6c51348f"
    ],
    "signer": "4be910dec39ace8a64711d0afe0467b9c9215602",
    "signature": "1583c718d5320ee8eb7b3287d555b7d70986656ae67517a6ddc06dccf1caaadbb1e61664beb35a353beb23353c13b1103dcffb6a705fb453d701115a9ac5cb68e0bb15f009775cfccfd17bd6750abe6cec7f4cb09d929031e7b642c11337baed2279a5137755e160194dc7ce0ddc2a5807398a72f4c6f21a4992f79ca11ee9c76e0d4ac1f2752518595bb0a2ba5c9662060c32884a826e5e75af6f720fc19bb07fc4fe99f94a6288f95743bd9405133564f683871995a51dab1c6a404596bc1ac22f5a4a45a729daad060396a37e0b4f1f96bdd7a6110ed1d32a990ba68e3f85e2456a216b3438ff3adfb1f9a3ed38317d1d7eb17b4f292fff43b70bc2cda1b5",
    "dictionary": "address",
    "asset": {
      "id": "7380",
      "addr1": "123 Main St",
      "city": "Nowhere",
      "state": "CO",
      "zip": "12345-0000"
    },
    "previous_hash": "d61455af6be744da2778ee8cda0b1dd8f5833462ea0e61fffbe17b5556f8b434",
    "asset_id": "4c84ab87-badd-471d-829c-a5fe069e3dca"
  }
]
2019-05-23 13:22:22,880 [INFO] [MainProcess] [MainThread] : TQL 2 ASSET:
 [
  {
    "owners": [],
    "asset": {
      "id": "7380"
    }
  }
]
2019-05-23 13:22:22,956 [INFO] [MainProcess] [MainThread] : MR ASSET:
 [
  {
    "hash": "a07061a50e38d447d8911b6ccfbb5ebd07a8fe0e25072f5074a09b544c5bc261",
    "timestamp": {
      "$date": "2019-05-23T19:22:24.505Z"
    },
    "type": "asset",
    "operation": "transfer",
    "owners": [
      "942a3adce18388e4f45f521195b1356b6c51348f"
    ],
    "signer": "4be910dec39ace8a64711d0afe0467b9c9215602",
    "signature": "1583c718d5320ee8eb7b3287d555b7d70986656ae67517a6ddc06dccf1caaadbb1e61664beb35a353beb23353c13b1103dcffb6a705fb453d701115a9ac5cb68e0bb15f009775cfccfd17bd6750abe6cec7f4cb09d929031e7b642c11337baed2279a5137755e160194dc7ce0ddc2a5807398a72f4c6f21a4992f79ca11ee9c76e0d4ac1f2752518595bb0a2ba5c9662060c32884a826e5e75af6f720fc19bb07fc4fe99f94a6288f95743bd9405133564f683871995a51dab1c6a404596bc1ac22f5a4a45a729daad060396a37e0b4f1f96bdd7a6110ed1d32a990ba68e3f85e2456a216b3438ff3adfb1f9a3ed38317d1d7eb17b4f292fff43b70bc2cda1b5",
    "dictionary": "address",
    "asset": {
      "id": "7380",
      "addr1": "123 Main St",
      "city": "Nowhere",
      "state": "CO",
      "zip": "12345-0000"
    },
    "previous_hash": "d61455af6be744da2778ee8cda0b1dd8f5833462ea0e61fffbe17b5556f8b434",
    "asset_id": "4c84ab87-badd-471d-829c-a5fe069e3dca"
  }
]
2019-05-23 13:22:22,957 [INFO] [MainProcess] [MainThread] : {"contract": "consents 4be910dec39ace8a64711d0afe0467b9c9215602 for address when asset.state = 'CO' until Date('2019-06-02 13:22:22')", "name": "first consent", "smart_contract_metadata": {"loaded by": "hello world demo"}, "smart_contract_type": "consent", "owners": ["eb6910c969cb6ca3"]}
2019-05-23 13:22:23,023 [INFO] [MainProcess] [MainThread] : Smart Contract (consent) asset id 7e4f2a52-1111-4cfd-a65e-629b63fee731 for this demo
2019-05-23 13:22:23,086 [INFO] [MainProcess] [MainThread] : ASSET should be VIEWABLE:
 {
  "hash": "a07061a50e38d447d8911b6ccfbb5ebd07a8fe0e25072f5074a09b544c5bc261",
  "timestamp": {
    "$date": "2019-05-23T19:22:24.505Z"
  },
  "type": "asset",
  "operation": "transfer",
  "owners": [
    "942a3adce18388e4f45f521195b1356b6c51348f"
  ],
  "signer": "4be910dec39ace8a64711d0afe0467b9c9215602",
  "signature": "1583c718d5320ee8eb7b3287d555b7d70986656ae67517a6ddc06dccf1caaadbb1e61664beb35a353beb23353c13b1103dcffb6a705fb453d701115a9ac5cb68e0bb15f009775cfccfd17bd6750abe6cec7f4cb09d929031e7b642c11337baed2279a5137755e160194dc7ce0ddc2a5807398a72f4c6f21a4992f79ca11ee9c76e0d4ac1f2752518595bb0a2ba5c9662060c32884a826e5e75af6f720fc19bb07fc4fe99f94a6288f95743bd9405133564f683871995a51dab1c6a404596bc1ac22f5a4a45a729daad060396a37e0b4f1f96bdd7a6110ed1d32a990ba68e3f85e2456a216b3438ff3adfb1f9a3ed38317d1d7eb17b4f292fff43b70bc2cda1b5",
  "dictionary": "address",
  "asset": {
    "id": "7380",
    "addr1": "123 Main St",
    "city": "Nowhere",
    "state": "CO",
    "zip": "12345-0000"
  },
  "previous_hash": "d61455af6be744da2778ee8cda0b1dd8f5833462ea0e61fffbe17b5556f8b434",
  "asset_id": "4c84ab87-badd-471d-829c-a5fe069e3dca"
}

Code

#!/usr/bin/env python3

"""
Copyright (c) 2015-2020 BurstIQ, Inc. - All Rights Reserved
Unauthorized copying of this file, via any medium is strictly prohibited.
Proprietary and confidential
"""

import argparse
import datetime
import json
import logging
import random
from typing import List, Tuple

import requests

logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s [%(levelname)s] [%(processName)s] [%(threadName)-10s] : %(message)s')

logger = logging.getLogger(__name__)


class BurstChainClient(object):
    """
    A simple client for BursChain; allows the caller to make programmatic function calls for rest
    base server API
    """

    def __init__(self, server: str, client: str):
        self._server = server
        self._client = client
        self._session = requests.Session()
        # disables ~/.netrc
        self._session.trust_env = False

    def _send(self,
              action: str,
              path: str,
              addl_headers: [None, dict],
              body: [None, dict],
              auth: [None, Tuple]) -> [None, dict]:
        """
        performs the base functionality for making http REST calls
        :param action:
        :param path:
        :param addl_headers:
        :param body:
        :param auth:
        :return:
        """
        headers = {'Content-Type': 'application/json', 'Content-Accept': 'application/json'}
        if addl_headers:
            headers.update(addl_headers)

        response = self._session.request(action, f'{self._server}{path}', headers=headers, json=body, auth=auth)

        if response.status_code != 200:
            logger.error(f'Call for {path} failed with code {response.status_code} and response of {response.text}')
            exit(1)

        if response.text:
            return json.loads(response.text)
        else:
            return None

    def _send_with_priv_id(self,
                           action: str,
                           path: str,
                           priv_id: str,
                           body: [None, dict]) -> [None, dict]:
        """
        builds the proper header for a private id field, vs basic auth
        :param action:
        :param path:
        :param priv_id:
        :param body:
        :return:
        """
        if priv_id:
            headers = {'Authorization': f'ID {priv_id}'}
        else:
            headers = None

        return self._send(action, path, headers, body, None)

    def put_metadata(self,
                     dictionary: dict,
                     username: str,
                     password: str) -> dict:
        """
        Puts the dictionary into the server
        :param dictionary:
        :param username:
        :param password:
        :return: response
        """
        return self._send('PUT', '/api/metadata/dictionary', None, dictionary, (username, password))

    def get_private_id(self) -> str:
        """
        get a new private id from the burst chain
        :return: private id
        """
        resp = self._send('GET', '/api/burstchain/id/private', None, None, None)
        return resp.get('private_id')

    def get_public_id(self, private_id: str) -> str:
        """
        get a new public id that is paired with this private id from the burst chain
        :return: public id
        """
        resp = self._send_with_priv_id('GET', '/api/burstchain/id/public', private_id, None)
        return resp.get('public_id')

    def create_asset(self,
                     private_id: str,
                     chain_name: str,
                     owners: List[str],
                     asset: dict,
                     asset_metadata: [None, dict]) -> str:
        """
        creates a new asset
        :param private_id:
        :param chain_name:
        :param owners:
        :param asset:
        :param asset_metadata:
        :return: the asset id
        """
        body = {
            'owners': owners,
            'asset': asset,
            'asset_metadata': asset_metadata
        }

        resp = self._send_with_priv_id('POST',
                                       f'/api/burstchain/{self._client}/{chain_name}/asset',
                                       private_id, body)
        return resp.get('asset_id')

    def get_asset_status(self, private_id: str, chain_name: str, asset_id: str) -> str:
        """
        gets the chains status of a specific asset
        :param private_id:
        :param chain_name:
        :param asset_id:
        :return:
        """
        resp = self._send_with_priv_id('GET',
                                       f'/api/burstchain/{self._client}/{chain_name}/{asset_id}/status',
                                       private_id, None)
        return resp.get('message')

    def get_asset_by_id(self,
                        private_id: str,
                        chain_name: str,
                        asset_id: str) -> [None, dict]:
        """

        :param private_id:
        :param chain_name:
        :param asset_id:
        :return:
        """
        return self._send_with_priv_id('GET',
                                       f'/api/burstchain/{self._client}/{chain_name}/{asset_id}/latest',
                                       private_id,
                                       None)

    def get_asset_by_hash(self,
                          private_id: str,
                          chain_name: str,
                          hash_value: str) -> [None, dict]:
        """

        :param private_id:
        :param chain_name:
        :param hash_value:
        :return:
        """
        return self._send_with_priv_id('GET',
                                       f'/api/burstchain/{self._client}/{chain_name}/{hash_value}',
                                       private_id,
                                       None)

    def update_asset(self,
                     private_id: str,
                     chain_name: str,
                     asset_id: str,
                     asset: dict,
                     asset_metadata: [None, dict]) -> str:
        """

        :param private_id:
        :param chain_name:
        :param asset_id:
        :param asset:
        :param asset_metadata:
        :return:
        """
        body = {
            'asset_id': asset_id,
            'asset': asset,
            'asset_metadata': asset_metadata
        }

        resp = self._send_with_priv_id('PUT',
                                       f'/api/burstchain/{self._client}/{chain_name}/asset',
                                       private_id,
                                       body)
        return resp.get('asset_id')

    def transfer_asset(self,
                       private_id: str,
                       chain_name: str,
                       asset_id: str,
                       owners: List[str],
                       new_owners: List[str],
                       new_signer_public_id: str) -> str:
        body = {
            'asset_id': asset_id,
            'owners': owners,
            'new_owners': new_owners,
            'new_signer_public_id': new_signer_public_id
        }

        resp = self._send_with_priv_id('POST',
                                       f'/api/burstchain/{self._client}/{chain_name}/transfer',
                                       private_id,
                                       body)
        return resp.get('asset_id')

    def query(self,
              private_id: str,
              chain_name: str,
              tql: str) -> [None, List[dict]]:
        body = {
            'queryTql': tql
        }

        resp = self._send_with_priv_id('POST',
                                       f'/api/burstchain/{self._client}/{chain_name}/assets/query',
                                       private_id,
                                       body)
        return resp.get('assets')

    def queryWhere(self,
                   private_id: str,
                   chain_name: str,
                   tql: str) -> [None, List[dict]]:
        body = {
            'tqlWhereClause': tql
        }

        resp = self._send_with_priv_id('POST',
                                       f'/api/burstchain/{self._client}/{chain_name}/assets/query',
                                       private_id,
                                       body)
        return resp.get('assets')

    def queryTqlFlow(self,
                     private_id: str,
                     chain_name: str,
                     tql: str) -> [None, List[dict]]:
        body = {
            'queryTqlFlow': tql
        }

        resp = self._send_with_priv_id('POST',
                                       f'/api/burstchain/{self._client}/{chain_name}/assets/query',
                                       private_id,
                                       body)
        return resp.get('assets')

    def create_consent(self,
                       private_id: str,
                       chain_name: str,
                       owners: List[str],
                       contract_name: str,
                       contract: str) -> str:
        body = {
            'contract': contract,
            'name': contract_name,
            'smart_contract_metadata': {'loaded by': 'hello world demo'},
            'smart_contract_type': 'consent',
            'owners': owners
        }

        logger.info(json.dumps(body))

        resp = self._send_with_priv_id('POST',
                                       f'/api/burstchain/{self._client}/{chain_name}/smartcontract',
                                       private_id,
                                       body)
        return resp.get('asset_id')


def parse_args() -> argparse.Namespace:
    """
    cmd line arguments parser for the demo
    :return:
    """
    _DEFAULT_SERVER = 'https://aiq.burstiq.com'

    # build command line arguments parser with command string
    args = argparse.ArgumentParser()

    # db args
    args.add_argument('-s', '--server',
                      dest='server',
                      default=_DEFAULT_SERVER,
                      help=f'The AIQ server; defaults to {_DEFAULT_SERVER}')
    args.add_argument('-c', '--client',
                      dest='client',
                      required=True,
                      help='The client name to interact with')
    args.add_argument('-u', '--username',
                      dest='username',
                      required=True,
                      help='The username that is the admin account for the client space')
    args.add_argument('-p', '--password',
                      dest='password',
                      required=True,
                      help='The username password above')
    args.add_argument('--privateid',
                      dest='privateid',
                      help='An existing private id to use instead of generating a new one')
    args.add_argument('-q', '--quiet',
                      dest='loglevel', action='store_const',
                      const=logging.WARN, default=logging.INFO,
                      help='log only warnings')
    args.add_argument('-v', '--verbose',
                      dest='loglevel', action='store_const',
                      const=logging.DEBUG, default=logging.INFO,
                      help='log low level details')

    # parse command line args
    return args.parse_args()


def main():
    logger.info('\n'
                + '--------------------------------------------------------------\n'
                + 'Burst Chain Client - Hello World Demo\n'
                + 'Copyright (c) 2015-2020 BurstIQ, Inc.\n'
                + '--------------------------------------------------------------\n')

    # parse the cmd line args
    opts = parse_args()

    # reset log level
    logging.getLogger().setLevel(opts.loglevel)
    if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
        from http.client import HTTPConnection
        HTTPConnection.debuglevel = 1
        requests_log = logging.getLogger("urllib3")
        requests_log.setLevel(logging.DEBUG)
        requests_log.propagate = True

    # create the burst chain client
    client = BurstChainClient(opts.server, opts.client)

    # ------------------------------------------------------------------------
    # STEP 1 - Setup the dictionary for a chain
    #
    dictionary = {
        'collection': 'address',

        'indexes': [{
            'unique': True,
            'attributes': ['id']
        }],

        'rootnode': {
            'attributes': [{
                'name': 'id',
                'required': True
            }, {
                'name': 'addr1'
            }, {
                'name': 'addr2'
            }, {
                'name': 'city'
            }, {
                'name': 'state'
            }, {
                'name': 'zip'
            }]
        }
    }

    resp = client.put_metadata(dictionary, opts.username, opts.password)
    logger.info(f"PUT dictionary response: {resp.get('message')}")

    # ------------------------------------------------------------------------
    # STEP 2 - get a private id
    #
    if opts.privateid:
        private_id = opts.privateid
    else:
        private_id = client.get_private_id()
    logger.info(f'Using private id {private_id} for this demo')

    # ------------------------------------------------------------------------
    # STEP 3 - get the public id
    #
    public_id = client.get_public_id(private_id)
    logger.info(f'Using public id {public_id} for this demo')

    # ------------------------------------------------------------------------
    # STEP 4 - create asset for the dictionary
    #
    asset = {
        'id': f'{random.randint(1000, 10000)}',
        'addr1': '123 Main St',
        'city': 'Nowhere',
        'state': 'XX',
        'zip': '12345-0000'
    }

    asset_metadata = {'loaded by': 'hello world demo'}

    first_asset_id = client.create_asset(private_id, dictionary['collection'], [public_id], asset, asset_metadata)
    logger.info(f'Asset created {first_asset_id} for this demo')

    # ------------------------------------------------------------------------
    # STEP 5 - get status of last asset
    #
    accepted_msg = client.get_asset_status(private_id, dictionary.get('collection'), first_asset_id)
    logger.info(f'Status response message {accepted_msg}')

    # ------------------------------------------------------------------------
    # STEP 6 - get asset via id
    #
    resp = client.get_asset_by_id(private_id, dictionary.get('collection'), first_asset_id)
    first_hash = resp.get('hash')
    logger.info(f'ASSET:\n {json.dumps(resp, indent=2)}')

    # ------------------------------------------------------------------------
    # STEP 7 - get asset via hash
    #
    resp = client.get_asset_by_hash(private_id, dictionary.get('collection'), first_hash)
    logger.info(f'ASSET:\n {json.dumps(resp, indent=2)}')

    # ------------------------------------------------------------------------
    # STEP 8 - update asset
    #
    asset['state'] = 'XX'

    tmp_id = client.update_asset(private_id, dictionary.get('collection'), first_asset_id, asset, None)
    logger.info(f'Asset updated {tmp_id} for this demo')

    # ------------------------------------------------------------------------
    # STEP 9 - get asset via id (again)
    #
    resp = client.get_asset_by_id(private_id, dictionary.get('collection'), first_asset_id)
    logger.info(f'ASSET:\n {json.dumps(resp, indent=2)}')

    # ------------------------------------------------------------------------
    # STEP 10 - transfer the asset to another owner
    #
    second_private_id = client.get_private_id()
    second_public_id = client.get_public_id(second_private_id)

    resp = client.transfer_asset(private_id, dictionary.get('collection'), first_asset_id,
                                 [public_id], [second_public_id], second_public_id)
    logger.info(f'transferred asset id {resp}')

    # the original owner should NO longer be able to see this asset
    resp = client.get_asset_by_id(private_id, dictionary.get('collection'), first_asset_id)
    logger.info(f'ASSET should be NULL:\n {json.dumps(resp, indent=2)}')

    # the new owner should be able to see this asset
    resp = client.get_asset_by_id(second_private_id, dictionary.get('collection'), first_asset_id)
    logger.info(f'ASSET:\n {json.dumps(resp, indent=2)}')

    # ------------------------------------------------------------------------
    # STEP 11 - query via TQL
    #

    tql = "WHERE asset.state = 'XX'"

    assets = client.queryWhere(second_private_id, dictionary.get('collection'), tql)
    logger.info(f'TQL 1 ASSET:\n {json.dumps(assets, indent=2)}')

    # ------------------------------------------------------------------------
    # STEP 12 - query via TQL
    #
    tql = "SELECT asset.id FROM address WHERE asset.state = 'XX'"

    assets = client.query(second_private_id, dictionary.get('collection'), tql)
    logger.info(f'TQL 2 ASSET:\n {json.dumps(assets, indent=2)}')

    # ------------------------------------------------------------------------
    # STEP 13 - query via TQLFlow
    #
    tql = "WHERE asset.state = 'XX'"

    assets = client.query(second_private_id, dictionary.get('collection'), tql)
    logger.info(f'TQL 2 ASSET:\n {json.dumps(assets, indent=2)}')

    # ------------------------------------------------------------------------
    # STEP 14 - consent contract
    #
    end_date = datetime.datetime.now() + datetime.timedelta(days=10)

    c = f"consents {public_id} " \
        f"for {dictionary.get('collection')} " \
        "when asset.state = 'XX' " \
        f"until Date('{end_date.strftime('%Y-%m-%d %H:%M:%S')}')"

    tmp_id = client.create_consent(second_private_id, dictionary.get('collection'), [second_private_id],
                                   'first consent', c)
    logger.info(f'Smart Contract (consent) asset id {tmp_id} for this demo')

    # the original owner should BE be able to see this asset now with the consent contract in place
    resp = client.get_asset_by_id(private_id, dictionary.get('collection'), first_asset_id)
    logger.info(f'ASSET should be VIEWABLE:\n {json.dumps(resp, indent=2)}')


if __name__ == '__main__':
    main()