Building a NFT minting bot with Cardano, Telegram, and Python - part 2

Part 2: Our data storage backend for the bot

We need some sort of data storage for each bot session, so let's look at that.

Outline

Intro

There are a few different choices to make in regards to data storage during a bot session.  We need a way to store some variables on each session.  Mainly we are passing data between the bot's conversation points. The Telegram API allows for storing data within the user's context, but that is fairly limited to a basic key/value type data structure.  There are a few other data points needed and I felt that a simple database would work so I used the Python model sqlalchemy to read and write from the SQLite DB on the host.

The cardano-cli outputs to actual files for keys, and metadata. I tried to get around this, but that would have ended up with more reads and writes on the file system.  So I opted to just use the cardano-cli in its intended form.

OK, so let's walk through the db_model.py script to see how it works.

Walk through the db_model.py

First, we need to import the needed models from sqlalchemy.  We then build up an engine and the Session.

from sqlalchemy import Column, Integer, \
    String, DateTime, Boolean
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql.functions import current_timestamp
from sqlalchemy import create_engine

import config

engine = create_engine(config.SQLALCHEMY_DATABASE_URI)
Session = sessionmaker(bind=engine)
Base = declarative_base()

In the config.py file we setup sqlalchemy to save the database as a file tokens_testnet.db. You can send the data to a MySQL database if you want, but I am keeping it simple here.

import os

# Database
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
db_path = os.path.join(BASE_DIR, 'tokens_testnet.db')
SQLALCHEMY_DATABASE_URI = f'sqlite:///{db_path}'
SQLALCHEMY_TRACK_MODIFICATIONS = True

Next, we build out our Tokens table. See the inline comments for what the attributes are for.

class Tokens(Base):
    """ Individual Token data """
    __tablename__ = 'tokens'

    id = Column(
        Integer,
        primary_key=True,
        autoincrement=True
    )
    date_created = Column(
        DateTime,
        default=current_timestamp()
    )

    # Token Bot Session UUID
    session_uuid = Column(String())

    # Creator Details
    creator_username = Column(String())
    creator_pay_addr = Column(String())

    # Token Details
    token_ticker = Column(String(5))
    token_name = Column(String(128))
    token_desc = Column(String(128))
    token_number = Column(Integer, default=0)
    # Added, for future use with generic Native Tokens?
    token_amount = Column(Integer, default=1)
    token_ipfs_hash = Column(String(50))

    # Stake Keys and Payment Keys
    stake_keys_created = Column(Boolean, default=False)
    payment_keys_created = Column(Boolean, default=False)

    # The bot ADA address for funding
    bot_payment_addr = Column(String(128))

    # The blockchain protocol parameters
    protocol_params_created = Column(Boolean, default=False)

    policy_keys_created = Column(Boolean, default=False)
    policy_script_created = Column(Boolean, default=False)
    policy_keyhash = Column(String(64))
    policy_id = Column(String(64))

    # Our Slot information
    current_slot = Column(Integer)
    slot_cushion = Column(Integer)
    invalid_after_slot = Column(Integer)

    # Token metadata.json
    metadata_created = Column(Boolean, default=False)

    # UTXO to burn from bot payment_addr
    utxo_tx_hash = Column(String(64))
    utxo_tx_ix = Column(Integer)
    utxo_lovelace = Column(Integer)

    # Mint Transaction
    raw_tx_created = Column(Boolean, default=False)
    signed_tx_created = Column(Boolean, default=False)
    tx_submitted = Column(Boolean, default=False)
    token_tx_hash = Column(String(64))

    # We need atleast a uuid to initialize a session
    def __init__(self, session_uuid):
        self.session_uuid = session_uuid

    # Respond with the uuid and token ticker
    def __repr__(self):
        return f"{self.session_uuid} - {self.token_ticker}"

Special call out to the update() function as it takes a python dictionary, breaks it out, and sets each attribute in the table.  I found this on StackOverflow a long time ago and thought it was pretty clever, so I tend to include it in most of my Sqlalchemy sessions.  I will be using it later in the token_utils.py script.

    def update(self, **kwargs):
        """ Updates a Token information  """
        for key, value in kwargs.items():
            setattr(self, key, value)

Wrapping up

Finally, let's wrap up with the main() function that actually creates the DB.


def main():
    """ Creates the DB with Token table """
    Base.metadata.create_all(engine)
    # Create a session, and commit it
    session = Session()
    session.commit()
    session.close()
    print("Created DB")


if __name__ == '__main__':
    main()

When we run the script it creates a DB session and writes out the Tokens table.

cmd$ python db_model.py 
Created DB

OK, we have a database, let's recap working with IPFS next.