In this tutorial we will be using the Factom Python library to interact with the factomd and factom-walletd APIs in order to secure some data on the Factom blockchain, also known as factomizing. This tutorial is using Python 3.6.7 that comes with a fresh Ubuntu 18.04 install, but versions 3.4, 3.5, 3.7 most likely work as well.
Requirements
Python 3+
Git
Setup
Setting up the Factom sandbox
For development it is recommended to run a local installation of factomd and factom-walletd, there are a few reasons for this:
No need to spend money on Entry Credits
Getting slightly familiar with how a Factom node works.
Set your own block time, default for mainnet is 600 seconds (10 minutes pr. block)
With that said, if you don't have a Factom sandbox setup, head over to the Developer Sandbox Setup Guide and follow one of the three setup methods.
Setting up the Python environment
A good idea is to create a Python virtual environment to keep your Python packages isolated to your project. This will avoid any package version conflicts between different Python projects on your computer. First, make a directory containing your virtual environments, or navigate to it if you already have one.
mkdir ~/venvs
cd ~/venvs
Now create the project specific virtual environment:
python3 -m venv hello-world-fct
This will create a new directory in your current folder, named whatever you specified in the command. Here we named the virtual environment "hello-world-fct", you can change this to a different name that suits you. If the command fails to run, follow the instructions given in the command line and try again. Now activate the environment:
source hello-world-fct/bin/activate
Your environment is now active and we can move on to installing the Python package(s)!
Let’s check the wallet balances on both the FCT and EC addresses we imported and generated earlier. The FCT balance will be shown in Factoshis( Factoids * 10^-8) :
At the moment we have 20000 Factoids and 0 Entry Credits. We need to fund our Entry Credit address by purchasing some Entry Credits, as those are required in order to record data on-chain.
Purchasing Entry Credits using the imported Factoid address
The wallet daemon provides a method fct_to_ec() to burn Factoids and mint Entry Credits. We will use that method, but first we need to know what the Factoid to Entry Credit exchange rate is:
hello-world-fct.py
# Check the EC rate
ec_rate = factomd.entry_credit_rate()
print(ec_rate)
Result:
{'rate': 1000}
This tells us we'll need to burn 1000 Factoshis (0.00001 FCT) in exchange for 1 Entry Credit, so let's purchase 10 entry credits for 10000 Factoshis (0.0001 FCT).
hello-world-fct.py
# Purchase some EC with Factoids
r = walletd.fct_to_ec(factomd=factomd,
amount=10000,
fct_address=fct_address,
ec_address=ec_address)
print(r)
Result:
{'message': 'Successfully submitted the transaction', 'txid': 'c60e4fee59876ce5ee242d9da15d4764a2880f36efac8c18bc7306986b041fb5'}
The transaction was successfully submitted with txid: c60e4fee59876ce5ee242d9da15d4764a2880f36efac8c18bc7306986b041fb5. Let's check the balance again on our wallets as shown earlier. The result should be:
{'balance': 1999999978000}
{'balance': 10}
Note that the FCT balance has been reduced by 22K instead of 10K Factoshis due to the network transaction fees.
We've successfully funded our Entry Credit Address with 10 Entry Credits. Let's move on to creating chains and entries.
Creating chains and entries
The wallet client has a new_chain() method that handles everything needed to create a new chain. To make a new chain you need to provide:
Your factomd instance, where the creation message will be submitted
A list of external IDs
Entry content
Your Entry Credit address
hello-world-fct.py
# Create new chain!
new_chain = walletd.new_chain(factomd=factomd,
ext_ids=["Hello", "world", "FCT!"],
content="Hello world Factom in Python!",
ec_address=EC_ADDR)
print(new_chain)
Success! Lets head over to the local Factom explorer to see if our entry exists. Search for the chainid, found in the result above, in the search box as seen below:
If nothing shows up you might need to wait a few more seconds / minutes, depending on what you set your block time to.
Our chain is successfully created and is visible in the explorer, now lets add some entries to that chain.
hello-world-fct.py
# Chainid retrieved from the previous result
chain_id = 'e1ebf8bd4a4b20816c83b674895064bbe77bdc467967480daa7f851b1595d4dc'
# Add a new entry to existing chain
new_entry = walletd.new_entry(factomd=factomd,
chain_id=chain_id,
ext_ids=['Some', 'random', 'ext ids'],
content='Second entry in chain!',
ec_address=EC_ADDR)
print(new_entry)
Success! Let's check the explorer again to see if some new entries show up on the same chain:
As we see in the image above, both the initial entry and new entry exist on the chain! You can also read chains and entries created directly in Python!
Reading chains and entries created
Sometimes the entries in your chain may reference each other, thus you may want to scan the entire chain in order to very its integrity. The factomd client provides a read_chain() method which iterates over all entry-containing blocks and returns a list of entries in reverse order.
[{'chainid': 'e1ebf8bd4a4b20816c83b674895064bbe77bdc467967480daa7f851b1595d4dc', 'extids': ['Some', 'random', 'ext ids'], 'content': 'Second entry in chain!'}, {'chainid': 'e1ebf8bd4a4b20816c83b674895064bbe77bdc467967480daa7f851b1595d4dc', 'extids': ['Hello', 'world', 'FCT!'], 'content': 'Hello world Factom in Python!'}]
Success! The whole chain is returned with all of its entries. The latest entry is returned first.
Error handling
The Python library returns a number of different error codes in case of a problem. The API method you tried to call will raise a factom.exceptions.FactomAPIError with details about the error.
For example, if we try to create a new chain with the exact same parameters as a previously created chain you’ll see something similar to this:
Traceback (most recent call last):
File "/hello-world-fct/main.py", line 65, in <module>
ec_address=EC_ADDR)
File "/dev/hello-world-fct/lib/python3.6/site-packages/factom/client.py", line 256, in new_chain
'ecpub': ec_address or self.ec_address
File "/dev/hello-world-fct/lib/python3.6/site-packages/factom/client.py", line 73, in _request
handle_error_response(resp)
File "/dev/hello-world-fct/lib/python3.6/site-packages/factom/exceptions.py", line 19, in handle_error_response
raise codes[code](message=message, code=code, data=data, response=resp)
factom.exceptions.InvalidParams: -32602: Invalid params
Details about the error are attached to the exception instance, thus a good idea is to wrap each API call into try blocks such as:
hello-world-fct.py
from factom.exceptions import FactomAPIError
try:
new_chain = walletd.new_chain(factomd=factomd,
ext_ids=["Hello", "world", "FCT!"],
content="Hello world Factom in Python!",
ec_address=EC_ADDR)
except FactomAPIError as e:
print(e.data)
As this chain already exists, it'll return the following message:
For a full list of the error codes in the library see here.
Full code
Below is the full code featuring everything mentioned above, with some extra additions.
hello-world-fct.py
from time import sleep
from factom import Factomd, FactomWalletd
from factom.exceptions import FactomAPIError
def wait_for_entry(entryhash):
"""
Wait for an entry to be recorded on-chain.
This can be detected by making sure that the entry hash is not present
in the list of pending entries.
:param str entryhash: the hash of the entry to wait for
:returns: None
"""
while True:
r_pending_entries = factomd.pending_entries()
if any(entry["entryhash"] == entryhash for entry in r_pending_entries):
sleep(1)
else:
break
# Wallets, remember to import the FCT address and generate the EC address.
FCT_ADDR = 'FA2jK2HcLnRdS94dEcU27rF3meoJfpUcZPSinpb7AwQvPRY6RL1Q'
EC_ADDR = 'EC2qpLisdttfe6JHPmQ7DiZqYet2Ga5rzeWAbx2QWSMTMB5G5K4X'
if __name__ == "__main__":
# Default params
walletd = FactomWalletd()
# You can also specify fct and ec addresses, change host, or specify RPC credentials, for example:
factomd = Factomd(host='http://localhost:8088',
fct_address=FCT_ADDR,
ec_address=EC_ADDR,
username='rpc_username',
password='rpc_password')
# Check balance on wallets
fct_balance = factomd.factoid_balance(FCT_ADDR)
ec_balance = factomd.entry_credit_balance(EC_ADDR)
print(f'\n Factoshi (FCT*10^(⁻8)) balance: {fct_balance['balance']}')
print(f'Entry Credit (EC) balance: {ec_balance['balance']}\n')
# Check entry credit rate
ec_rate = factomd.entry_credit_rate()
# Print result
print(f'Entry credit rate is: {ec_rate["rate"]} Factoshis')
# Purchase some entry credits with our imported FCT wallet if our EC balance is less than 20.
if ec_balance['balance'] < 20:
print('Balance is less than 20 Entry Credits, purchasing 50 more.')
fct_to_ec = walletd.fct_to_ec(factomd, 50000, fct_address=FCT_ADDR, ec_address=EC_ADDR)
# Print result
print(fct_to_ec)
# Wait a little
print(f'Sleeping for 5 seconds..\n')
sleep(5)
# Check updated balances
fct_balance = factomd.factoid_balance(FCT_ADDR)
ec_balance = factomd.entry_credit_balance(EC_ADDR)
print(f'Factoshi (FCT*10^(⁻8)) balance: {fct_balance['balance']}')
print(f'Entry Credit (EC) balance: {ec_balance['balance']}\n')
# Initiate some variables
new_chain = None
chain_id = None
entry_hash = None
try:
# Create new chain!
# The cost of creating a new chain is 10 ECs.
new_chain = walletd.new_chain(factomd=factomd,
ext_ids=['Hello', 'world', 'FCT!'],
content='Hello world Factom in Python!',
ec_address=EC_ADDR)
# Store chain ID and entry hash
chain_id = new_chain['chainid']
entry_hash = new_chain['entryhash']
# Print result
print(f'New chain created with chain ID: {chain_id} and entry hash: {entry_hash}')
print('Waiting for the newly created chain to appear on the blockchain')
# Wait for the new chain to appear on the blockchain
wait_for_entry(entryhash=entry_hash)
except FactomAPIError as e:
print(f'{e.data}.\n')
# If chain already exists retrieve the chainid.
chain_id = e.data.split()[1]
new_entry = None
try:
# Create a new entry in the chain created above using the chainid
print(f'Creating a new entry to the chain with chain ID: {chain_id}.')
new_entry = walletd.new_entry(factomd=factomd,
chain_id=chain_id,
ext_ids=['Some', 'random', 'ext id'],
content='Second entry in chain!',
ec_address=EC_ADDR)
# Print result
print(f'{new_entry}\n')
except FactomAPIError as e:
print(e.data)
# Read the whole chain including the entries
chain_data = None
try:
print(f'Reading the whole chain:')
chain_data = factomd.read_chain(chain_id=chain_id)
# Print result
print(chain_data)
except FactomAPIError as e:
print(e.data)
Conclusion
You now have all the tools available and ready to start fully interacting with the Factom blockchain in Python!
Troubleshooting
This section will be filled out once users start reporting issues, problems or relevant questions they might have.