Introducción a “Python Algorand SDK” integrando “FastAPI”

En el presente artículo, realizaremos una introducción acerca de la utilización del SDK Python de Algorand, y cómo este se integra para desarrollar aplicaciones con FastAPI.

Visión general

Advertencia de seguridad

Este proyecto no ha sido probado y nunca debe usarse en producción.

Requisitos

Debe tener Python 3 instalado en su sistema. También para este tutorial, necesitará python3-venv. Este paquete se puede instalar con el siguiente comando.

$ sudo apt-get install python3-venv

Este manual asume que Algorand Sandbox ya está instalado en su sistema, caso contrario instalarlo.

Empezar a trabajar

Primero, cree una carpeta raíz para el proyecto.

cd ~
mkdir algorand-fastAPI
cd algorand-fastAPI

Luego crea y activa un nuevo entorno virtual.

python3 -m venv Algorand-fastAPI
./Algorand-fastAPI/bin/activate

Ahora necesita instalar FastAPI, Uvicorn y py-algorand-sdk.

(Algorand-fastAPI) $ pip3 install fastapi
(Algorand-fastAPI) $ pip3 install uvicorn[standard]
(Algorand-fastAPI) $ pip3 install py-algorand-sdk

Cree dos archivos nuevos: main.py y schemes.py

touch main.py 
touch schemes.py

En el archivo main.py, conecte todos los módulos que necesitemos en el futuro, inicialice el cliente “algod” y el validador crea una instancia de la aplicación.

from typing import Optional
from fastapi import FastAPI

from algosdk import account, encoding , mnemonic 
from algosdk.future.transaction import PaymentTxn
from algosdk.future.transaction import AssetConfigTxn
from algosdk.error import WrongChecksumError ,WrongMnemonicLengthError
from algosdk.v2client import algod , indexer

from schemes import * 

algod_client=algod.AlgodClient("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","http://localhost:4001") #Initializing the algod client
indexer_client=indexer.IndexerClient("","http://localhost:8980") #initializing the validator

app = FastAPI() #Create a base application instance

Creando una cuenta

Ahora creamos un nuevo end point de nuestra API. Este end point será llamado por el método http GET.

En el cuerpo de nuestra función de end point, llamamos a la función account.generate_account(), que devuelve una tupla de una dirección de cuenta y una clave privada. Convertimos esto en una frase de contraseña, usando el método from_private_key de la clase mnemonic.

@app.get("/account")
def create_account():                                   #Create a function that creates an account
    private_key, address = account.generate_account()
    passphrase = mnemonic.from_private_key(private_key)

    return {"address":address,"passphrase":passphrase}

Ahora podemos ejecutar esta aplicación con el comando:

uvicorn main:app --reload

Después de descargar nuestra aplicación, la documentación interactiva estará disponible para nosotros en http://127.0.0.1:8000/docs#

Ahora, cuando envíe una solicitud de obtención, el servidor creará una nueva cuenta y enviará la dirección junto con la contraseña en la respuesta

Obtener información sobre una cuenta

Para hacer esto, crea un nuevo endpoint, que recibirá la dirección de la cuenta y devolverá información sobre ella.

@app.get("/account/{Address}")   
def get_account_info(Address:str):              #Create a function that returns information about an account by its ID
    info=algod_client.account_info(Address)

    return {"Address":info}

Podemos comprobar que nuestro “endpoint” funciona correctamente, con la documentación interactiva.

Actas

Ahora tenemos que volver al archivo schemes.py y crear un esquema para la transferencia y la transacción de creación de activos. El esquema es necesario para que el endpoint pueda comprobar, si los datos entrantes son del tipo esperado.

from pydantic import BaseModel

class Transaction(BaseModel):
    sender_address: str
    receiver_address: str
    passphrase: str
    amount: int
    note: str

class Asset(BaseModel):
    sender:str
    asset_name:str
    unit:str
    total:int
    default_frozen:bool
    decimals:int
    url:str
    manager:str
    reserve:str
    freeze:str
    clawback:str
    passphrase: str

Transferencia de fondos

Regrese al archivo main.py y cree un nuevo end point que reciba datos de acuerdo con el esquema de transacción:

@app.post("/transaction")
def create_transaction(transaction:Transaction):
    params = algod_client.suggested_params()

Ahora crea una transacción sin firmar e intente firmarla.

unsigned_txn = PaymentTxn(transaction.sender_address, params, transaction.receiver_address, transaction.amount, None, transaction.note.encode()) #Create an unsigned transaction

    if transaction.amount < 100000:                      #check that the volume of transferred funds is greater than the minimum
        return {"amount":"amount less than the minimum"}
    try:
        signed_txn = unsigned_txn.sign(mnemonic.to_private_key(transaction.passphrase)) #trying to sign a transaction

    except WrongChecksumError:
        return {"passphrase":"Checksum error"}

    except ValueError:
        return {"passphrase":"unknown word in the passphrase"}

    except WrongMnemonicLengthError:
        return {"passphrase":"Incorrect size of the passphrase"}

Una vez firmada la transacción podemos enviarla a blockchain, a través del cliente algod.

@app.post("/transaction")
def create_transaction(transaction:Transaction):
    params = algod_client.suggested_params()

Ahora crea una transacción sin firmar e intente firmarla:

unsigned_txn = PaymentTxn(transaction.sender_address, params, transaction.receiver_address, transaction.amount, None, transaction.note.encode()) #Create an unsigned transaction

    if transaction.amount < 100000:                      #check that the number of transferred funds is greater than the minimum
        return {"amount":"amount less than the minimum"}
    try:
        signed_txn = unsigned_txn.sign(mnemonic.to_private_key(transaction.passphrase)) #trying to sign a transaction

    except WrongChecksumError:
        return {"passphrase":"Checksum error"}

    except ValueError:
        return {"passphrase":"unknown word in the passphrase"}

    except WrongMnemonicLengthError:
        return {"passphrase":"Incorrect size of the passphrase"}

Cuando se firma la transacción podemos enviarla a la blockchain.

    transaction_id = algod_client.send_transaction(signed_txn) #send the signed transaction to the network

    return transaction_id 

A continuación, analicemos cómo crear un nuevo activo.

Creación de activos

El proceso de creación de un activo no es muy diferente de la transacción anterior, pero tiene muchos más parámetros:

  • Remitente (str) – dirección del remitente.
  • SP (SuggestedParams) – parámetros sugeridos de algod.
  • Ndex (int, opcional) – índice del activo.
  • Total (int, opcional): número total de unidades base de este activo creado.
  • Default_frozen (bool, opcional): si los espacios para este activo en las cuentas de usuario, están congelados de forma predeterminada.
  • Unit_name (str, opcional): sugerencia para el nombre de una unidad de este activo.
  • Asset_name (str, opcional) – pista para el nombre del activo.
  • Administrador (str, opcional) – dirección permitida para cambiar direcciones distintas de cero, para este activo.
  • Reserva (str, opcional): cuenta cuyas tenencias de este activo se deben informar como «no acuñadas».
  • Freeze (str, opcional): cuenta autorizada para cambiar el estado congelado de las tenencias, de este activo.
  • Clawback (str, opcional) – cuenta permitida, tomar unidades de este activo de cualquier cuenta.
  • URL (str, opcional): una URL donde se puede recuperar más información sobre el activo.
  • Strict_empty_address_check (bool, opcional): establézcalo en False (falso) si desea especificar direcciones vacías. De lo contrario, si se deja como True (verdadero), el valor predeterminado, tener direcciones vacías generará un error, lo que evitará eliminar accidentalmente el acceso de administrador a los activos, o eliminar el activo.
  • Decimales (int, opcional): número de dígitos que se utilizarán para mostrar después del decimal. Si se establece en 0, el activo no es divisible. Si se establece en 1, la unidad base del activo está en décimas. Debe estar entre 0 y 19, inclusive. El valor predeterminado es 0.

Este end point tendrá el siguiente código. Tenga en cuenta las similitudes entre ambos fragmentos:

@app.post("/asset")
def create_asset(asset:Asset):
    params = algod_client.suggested_params()
    unsigned_txn=AssetConfigTxn(sp=params,                                  #create an unsigned transaction to add a new asset
                                sender=asset.sender,
                                asset_name=asset.asset_name,
                                unit_name=asset.unit,
                                total=asset.total,
                                decimals=asset.decimals,
                                default_frozen=asset.default_frozen,
                                url=asset.url,
                                manager=asset.manager,
                                reserve=asset.reserve,
                                freeze=asset.freeze,
                                clawback=asset.clawback,
                                strict_empty_address_check=False)

    try:
        signed_txn = unsigned_txn.sign(mnemonic.to_private_key(asset.passphrase)) #trying to sign a transaction

    except WrongChecksumError:
        return {"passphrase":"Checksum error"}

    except ValueError:
        return {"passphrase":"unknown word in the passphrase"}

    transaction_id = algod_client.send_transaction(signed_txn) #send the signed transaction to the network
    return transaction_id

Ver todos los activos

@app.get("/assets")
def get_assets():                                   #Create a function that returns all existing assets
    return indexer_client.search_assets() 

Ver todas las transacciones de la cuenta

@app.get("/transactions/{account}")
def get_account_transactions(account:str):                         
    return indexer_client.search_transactions_by_address(account)

Endpoint para obtener información sobre una transacción por ID

@app.get("/transaction/{transaction_ID}")
def get_transaction_by_ID(transaction_ID:str):         
    return indexer_client.transaction(transaction_ID)

Filtrado de datos

Ahora podemos agregar la capacidad de filtrar transacciones por marca de tiempo y tipo de activo.

El marco FastAPI le permite agregar parámetros adicionales a la estructura de solicitud de end point:

– Agregue los parámetros para la marca de tiempo de inicio y finalización en formato “RFC 3339”, y el “ID” del activo

@app.get("/transactions/{account}")
def get_account_transactions(account:str, start_timestamp: Optional[str] = None , end_timestamp: Optional[str] = None , asset_id: Optional[int] = None):                          #Create a function that returns all transactions related to the account
    return indexer_client.search_transactions_by_address(account,start_time=start_timestamp,end_time=end_timestamp,asset_id=asset_id)

El end point, ahora puede emitir transacciones para una cuenta durante un cierto período de tiempo, relacionado con un activo específico.

Ahora creemos un end point para obtener una lista de transacciones relacionadas con un activo en particular, agregue parámetros opcionales para filtrar por marca de tiempo, como se muestra en el fragmento de código anterior.

@app.get("/assets/{asset_id}/transactions")
def get_assets(asset_id:int ,start_timestamp: Optional[str] = None , end_timestamp: Optional[str] = None):                                 
    return indexer_client.search_asset_transactions(asset_id , start_time = start_timestamp , end_time = end_timestamp)

Obtención de un balance de diferentes activos.

Comenzamos creando un end point con dos parámetros, dirección de cuenta e ID de activo. A continuación, obtenemos información sobre la cuenta por su dirección y comprobamos que el “ID del activo”, no sea igual a 0. De lo contrario, devolvemos el saldo de la cuenta por el activo base, en “micro-ALGO’s”.

Además, intentamos encontrar el activo que buscamos en la cuenta. Si encontramos el activo deseado en una cuenta, recuperamos el activo por su “ID” y formamos la respuesta. Si no se encuentra el activo, intentamos obtener el nombre del mismo. Si esta operación falla, informaremos al usuario que el activo no existe. De lo contrario, formamos la respuesta con un saldo cero en el activo buscado.

@app.get("/account/{account}/balance")
def get_assets(Address:str,asset_id:int):                                  
    info=algod_client.account_info(Address)
    if asset_id==0:
        balance=algod_client.account_info(Address)["amount"]
        return {"balance":balance,"asset-id":0,"asset_name":"microAlgos"}

    for asset in info["assets"]:
        if asset["asset-id"]==asset_id:
            asset_name=indexer_client.search_assets(asset_id=asset_id)["assets"][0]["params"]["name"]

            return {"balance":asset["amount"],"asset-id":asset["asset-id"],"asset_name":asset_name}
    try:
        asset_name=indexer_client.search_assets(asset_id=asset_id)["assets"][0]["params"]["name"]
    except:
        return {"ERROR":"Asset not found"}
    return {"balance":0,"asset-id":asset_id,"asset_name":asset_name}

Este es el balance general del activo subyacente.

Este es el saldo del activo que creamos anteriormente.

¡Eso es todo! Puede encontrar los documentos de FastAPI aquí.


Este artículo ha sido escrito originalmente por Algorand en el «solutions» de Algorand y traducido por AlgoLatam.

Original Article: https://developer.algorand.org/solutions/python-algorand-sdk-and-fastapi/

Deja una respuesta

Tu dirección de correo electrónico no será publicada.