Building a Telegram Crypto Wallet Management using Python and Fauna

Introduction to the Serverless Architecture

Written in connection with the Write with Fauna Program.

The serverless paradigm is a software design style that allows third-party services to host applications.

Servers are present but are abstracted from the app development process.

Serverless computing services are often classified into two categories:

  • backend-as-a-service (BaaS)
  • function-as-a-service (FaaS)

Cloud providers are an integral part of the serverless architecture. They take care of all the grunt work for developers so they can concentrate on the essential logic of their applications.

Our application's database layer will be built with Fauna — a serverless database provider. It eliminates layers of app code that were previously used to manually handle data anomalies, security, and scaling, giving you a better development experience and a better app experience for your users.

The Cryptography in Cryptocurrency

“Crypt” - comes from the Greek root “kryptos” , which means "hidden or concealed," and has been used to mean "secret or hidden." Cryptography is an integral part of cryptocurrency. We will be looking at three different methods of cryptography used in cryptocurrency:

Asymmetric Encryption: This type of encryption uses 2 different keys to encrypt data ( public and private key). The public key can be shared publicly, and can be used by anyone to encrypt data. A private key ( secret key ) is only known to the party that generated the key; this is the only key that can be used to decrypt data encrypted by the public key, hence the name, public key encryption. The private key cannot be derived from the public key.

Symmetric Encryption: In contrast to asymmetric encryption, data is encrypted and decrypted using the same cryptographic keys. The keys could be identical or there could be a simple transformation between them. When data encryption and decryption do not take place in the same location, this method of encryption becomes vulnerable. When using symmetric algorithms, the key exchange problem is a significant issue.

Hashing: Hashing is a cryptography technique used to verify the integrity of data. A hashing algorithm takes an arbitrary amount of data and returns a fixed output known as a hash. A minor change in the input data has a significant impact on the output, which is why hashing algorithms can be used to validate data.

Cryptocurrency Wallet

In contrast to physical wallets, which store cash, cryptocurrency wallets do not actually store your crypto assets. Crypto wallets provide the information required to interact with the blockchain, for example; the wallet address, private key, and public key. Your wallet address is an alphanumeric identifier that is generated from your public and private keys. It is your specific location on the blockchain. You can share your wallet address with others to receive crypto assets , but you should never share your private keys with anyone.

Your private keys provide access to your asset on the blockchain: as long as you have the private key associated with your address, you can access your crypto assets from any device.

Building a Wallet Management Application

We will be working on a wallet management application for the Tron blockchain. In this demonstration, we will be building it on Telegram.

Requirements

  • Fauna
  • Python
  • Python-telegram-bot
  • Telegram account and Telegram bot token
  • Tronapi

Application Structure

.env file: This file stores application secrets. Application secrets such as:

It is critical to keep these details hidden in your application. Each of them will be used in your application.

errors.py file: This file contains some custom python exceptions used in our application.

keyboards.py file: For our application's user interface, we are using Telegram: we sometimes need to create custom keyboards.

main.py file: This file defines the functions that our application will use to connect to Telegram.

message.json file: The messages we send to app users are saved and curated in this file.

utils.py file: This is our utility file which contains the core logic of our project.

To clone the project, run: git clone https://github.com/Bamimore-Tomi/fauna-wallet.git.

Installs all requirements using - “pip install -r requirements.txt”.

Communicating With Telegram

To communicate to users via Telegram, we need to create a Telegram Bot. To create a Telegram Bot:

  1. Create a Telegram account
  2. Go to BotFather on Telegram to create a new telegram bot. You can access bot father by searching for @botfather on Telegram.telegram-botfather
  3. Type /newbot and send telegram-botfather-newbot
  4. Follow the prompts and enter the information you are asked. The end result would be an access token for your bot. telegram-botfather-token

You can now change the value of the token in your .env file. The access token depicted in the preceding image is invalid. It was only shown here for illustration purposes.

Communicating With the Tron Blockchain

We can connect our application to numerous blockchains, including Bitcoin, Ethereum, and Ripple. In this demonstration, we will work with the Tron blockchain.

When we installed all the requirements, tronapi was among the packages: it interacts with the Tron blockchain in python.

Creating a New Wallet

We explored crypto wallets earlier. Now we will see how to generate a wallet address, a private key and a public key.

from tronapi import Tron
tron = Tron()
account = tron.create_account
address = account.address
fernet_key = _generate_fernet_key(os.getenv("MASTER"), os.getenv("SALT"))
encrypted_private_key = _encrypt_private_key(account.private_key, fernet_key)

We created the account in the account= tron.create_account statement. The account address is stored in the address variable. To protect our private key, I'm now employing the Fernet symmetric encryption algorithm. More information on the algorithm can be found here. It is critical to encrypt the private key before storing it so that even if another party gains unauthorized access to our database, they will not access our blockchain assets.

Checking Wallet Balance

from tronapi import Tron
tron = Tron()
tron.fromSun(tron.trx.get_balance(address))

Sending TRX From One Address to Another

def send_trx(sender_private_key: str, reciever_address: str, amount: int):
    fernet_key = _generate_fernet_key(os.getenv("MASTER"), os.getenv("SALT"))
    private_key = _decrypt_private_key(sender_private_key, fernet_key)
    tron = Tron()
    tron.private_key = private_key
    tron.defa3ult_address = tron.address.from_private_key(tron.private_key)["base58"]
    reciever_address = _validate_address(reciever_address)
    balance = get_balance(tron.default_address["base58"])

    if balance == 0 or amount > balance:
        raise errors.InsufficientBalance

    transaction = tron.trx.send(reciever_address, amount)

    return True

This code segment reinforces the importance of your private key. The function receives the encrypted private key.

Remember that we used a Fernet symmetric encryption algorithm to protect the keys.

We must now use the same algorithm to decrypt it. We can now request the blockchain to send the desired amount to the receiver address. If we have up to that amount, the transaction is complete — otherwise, it fails.

Setting up Fauna

Creating the Database

In Fauna's dashboard, we must create the database for the wallet application. If you haven't already done so, create an account on Fauna's website.

Click the NEW DATABASE button in the dashboard, give the database a name, and then press the SAVE button.

Creating a Fauna Collection

To store the data gathered in the database, we must now create a Fauna collection.

A collection is similar to SQL tables in that it contains identical properties, such as a wallet collection that includes information on users’ wallets. Click on the NEW COLLECTION button to create a collection. Next, you fill out the details of your collection.

Creating Fauna Indexes

We need to create an index for the database collection. A Fauna index allows us to search through data stored in a database collection. Navigate to Indexes and click on NEW_INDEX.

Terms indicates which field(s) the index is allowed to browse.

Creating a Database API Key

Navigate to security on the left navigation panel. Click on Security then click the NEW KEY button and complete the required information.

When you SAVE an API access key is generated.

You can now replace the token's value in your .env file. The access token shown in the image above is invalid - It was only shown for illustration purposes here.

Communicating With Fauna

def load_db():
    load_dotenv()
    client = FaunaClient(secret=os.getenv("FAUNA-KEY"))
    return client

This is the function where we initiate connection with our database. Let’s have a look at the CRUD operations:

Create:

def create_wallet(client: FaunaClient, user_id: int, wallet_name: str) -> bool:

    tron = Tron()

    account = tron.create_account

    address = account.address

    fernet_key = _generate_fernet_key(os.getenv("MASTER"), os.getenv("SALT"))

    encrypted_private_key = _encrypt_private_key(account.private_key, fernet_key)

    wallet = client.query(
        q.create(
            q.collection("wallet"),
            {
                "data": {
                    "user_id": user_id,
                    "wallet_name": wallet_name,
                    "encrypted_private_key": encrypted_private_key,
                    "public_key": account.public_key,
                    "wallet_address": dict(address),
                    "wallet_account_balance": 0.0,
                    "transactions": [],
                    "date_generated": time.time(),
                }
            },
        )
    )

    save_wallets(client)

    return address.base58

Read:

def get_wallets(client: FaunaClient, user_id: int, wallet_name: Optional[str] = None):

    wallets = client.query(
        q.paginate(q.match(q.index("wallet_index"), user_id), size="100_000")
    )

    if len(wallets["data"]) < 1:
        raise errors.WalletNotFound

    wallets_data = [
        q.get(q.ref(q.collection("wallet"), wallet.id())) for wallet in wallets["data"]
    ]

    wallets_data = client.query(wallets_data)

Update:

def record_transaction(
    client: FaunaClient, wallet: dict, type_: str, amount: int, address: str, tx_id: str
):

    tron = Tron()

    bot = telegram.Bot(token=os.getenv("TOKEN"))

    wallet = get_wallets(client, wallet["user_id"], wallet_name=wallet["wallet_name"])

    prev_transactions = wallet["transactions"]

    balance = wallet["wallet_account_balance"]

    if type_ == "credit":
        balance += amount

    if type_ == "debit":
        balance -= amount

    new = {
        "type": type_,
        "address": tron.address.from_hex(address).decode(),
        "amount": amount,
        "tx_id": tx_id,
        "time": time.time(),
    }

    prev_transactions.append(new)

    client.query(
        q.update(
            q.ref(q.collection("wallet"), wallet["ref"]),
            {
                "data": {
                    "transactions": prev_transactions,
                    "wallet_account_balance": balance,
                }
            },
        )
    )

Delete:

def delete_wallet(client: FaunaClient, user_id: int, wallet_ref: str):

    try:
        client.query(q.delete(q.ref(q.collection("wallet"), wallet_ref)))

    except NotFound:
        raise errors.WalletNotFound

Exploring the Application

Conclusion

We covered critical concepts in serverless computing architecture in this article.

We also used Fauna, a serverless database provider, to manage our application's database layer. We also explored CRUD operation in fauna from python.

The source code for the project can be found on Github. If you have any questions, please contact me via Twitter @forthecode__

17