17
Building a Telegram Crypto Wallet Management using Python and Fauna
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.
“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.
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.
We will be working on a wallet management application for the Tron blockchain. In this demonstration, we will be building it on Telegram.
- Fauna
- Python
- Python-telegram-bot
- Telegram account and Telegram bot token
- Tronapi
.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”.
To communicate to users via Telegram, we need to create a Telegram Bot. To create a Telegram Bot:
- Create a Telegram account
- Go to BotFather on Telegram to create a new telegram bot. You can access bot father by searching for @botfather on Telegram.
- Type /newbot and send
- Follow the prompts and enter the information you are asked. The end result would be an access token for your bot.
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.
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.
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.
from tronapi import Tron
tron = Tron()
tron.fromSun(tron.trx.get_balance(address))
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.
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.
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.
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.
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.
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
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