Using Generalized Accounts

This page describe how to use the Python SDK to convert a basic account to a generalized one. For additional information about genrealized accounts visit the `protocol documentation`_

Example

The following example describe how to transform a basic account to a generalized one and how to execute a spend transaction from a generalized account.

from aeternity.node import NodeClient, Config
from aeternity.signing import Account
from aeternity.transactions import TxBuilder
from aeternity.compiler import CompilerClient
from aeternity import defaults, hashing, utils

import requests



def top_up_account(account_address):

    print()
    print(f"top up account {account_address} using the testnet.faucet.aepps.com app")
    r = requests.post(f"https://testnet.faucet.aepps.com/account/{account_address}").json()
    tx_hash = r.get("tx_hash")
    balance = utils.format_amount(r.get("balance"))
    print(f"account {account_address} has now a balance of {balance}")
    print(f"faucet transaction hash {tx_hash}")
    print()



def main():

    """
    This example is divided in 2 parts
    - the first one is about transforming a Basic account to Generalized
    - the second shows how to post transactions from GA accounts
    """

    #
    # PART 1: fro Basic to GA
    #
    print("Part #1 - transform a Basic account to a Generalized one")
    print()

    # first we create a new account
    # this will be the ga account
    print(f"generate a new account")
    ga_account = Account.generate()
    print(f"your basic account address is {ga_account.get_address()}")

    # use the faucet to top up your ga account
    top_up_account(ga_account.get_address())

    # Instantiate a node client
    node_url = "http://sdk-testnet.aepps.com"
    print(f"using node at {node_url}")
    # get the node client
    n = NodeClient(Config(
        external_url=node_url,
        blocking_mode=True # we want the client to verify the transactions are included in the chain
    ))


    # we are going to use a contract that will not be performing
    # any real authorization, just to provide a proof of concept
    # for the ga interaction
    #
    # DO NOT USE THIS CONTRACT IN A REAL SCENARIO
    # DO NOT USE THIS CONTRACT IN A REAL SCENARIO
    # DO NOT USE THIS CONTRACT IN A REAL SCENARIO
    #
    # this contract authorizes anybody to perform transactions
    # using the funds of the account!
    #
    blind_auth_contract = """contract BlindAuth =
    record state = { owner : address }

    entrypoint init(owner' : address) = { owner = owner' }

    stateful entrypoint authorize(r: int) : bool =
        // r is a random number only used to make tx hashes unique
        switch(Auth.tx_hash)
            None          => abort("Not in Auth context")
            Some(tx_hash) => true
    """

    # Instantiate a compiler client
    compiler_url = "https://compiler.aepps.com"
    print(f"using compiler at {compiler_url}")
    # get the node client
    c = CompilerClient(compiler_url=compiler_url)

    print()

    # compile the contract for the ga an retrieve the bytecode
    print("compile ga contract")
    bytecode = c.compile(blind_auth_contract).bytecode

    # prepare the calldata for the init function
    print("encode the init function calldata")
    init_calldata = c.encode_calldata(blind_auth_contract, "init", [ga_account.get_address()]).calldata

    # now we execute the first step, we'll be transforming the account into a ga
    print("execute the GaAttach transaction")
    ga_attach_tx = n.account_basic_to_ga(
        ga_account, # the ga account
        bytecode, # the bytecode of the ga contract
        init_calldata=init_calldata, # the encoded parameters of the init function
        auth_fun="authorize", # the name of the authentication function to use from the contract
        gas=1000
    )
    print(f"GaAttachTx hash is {ga_attach_tx.hash}")
    print(f"the account {ga_account.get_address()} is now generalized")

    #
    # END of PART 1
    #

    print()

    #
    # PART 2: posting a transaction from a GA account
    #
    # In this part we will be creating a spend transaction and we'll transfer 4AE
    # from the generalized account to a newly created account
    #
    print("Part #2 - Create a spend transaction from a GA account")
    print()

    # we will be using the compiler client and the node client from the PART 1

    # first we create a new account
    # this will be the recipient account
    rc_account_address = "ak_2iBPH7HUz3cSDVEUWiHg76MZJ6tZooVNBmmxcgVK6VV8KAE688"
    print(f"the recipient account address is {rc_account_address}")

    # then we prepare the parameters for a spend transaction
    sender_id = ga_account.get_address() # the ga sendder account
    amount = 4000000000000000000 # we will be sending 4.9AE
    payload = "" # we'll be sending an empty payload
    fee = defaults.FEE # we'll use the default fee (the client will generate the right fee for us)
    ttl = defaults.TX_TTL # we'll use the default ttl for the transaction
    nonce = defaults.GA_ACCOUNTS_NONCE # we'll use 0 as nonce since is is a special case

    # now we'll use the builder to prepare the spend transaction
    print(f"prepare a spend transaction from {sender_id} to {rc_account_address} of {utils.format_amount(amount)}")
    builder = TxBuilder()
    spend_tx = builder.tx_spend(sender_id, rc_account_address, amount, payload, fee, ttl, nonce)

    # now that we have the transaction we need to prepare the authentication data for the ga transaction
    print("encode the authorize function calldata")
    auth_calldata = c.encode_calldata(blind_auth_contract, "authorize", [hashing.randint()]).calldata

    # and use the sign_transaction with the auth_calldata to automatically
    # prepare the ga transaction for us
    print("execute the GaMeta transaction")
    ga_meta_tx = n.sign_transaction(ga_account, spend_tx, auth_data=auth_calldata)

    # and finally we can broadcast the transaction
    ga_meta_tx_hash = n.broadcast_transaction(ga_meta_tx)
    print(f"GaMetaTx hash is {ga_meta_tx_hash}")
    print(f"the account spend transaction has been executed")

    # note that you can verify all the steps above using the command line client
    print()
    print("Verify the steps using the command line client")
    print("1. Check the ga account:")
    print(f"aecli inspect {ga_account.get_address()}")
    print("2. Check the GaAttachTx:")
    print(f"aecli inspect {ga_attach_tx.hash}")
    print("3. Check the GaMetaTx:")
    print(f"aecli inspect {ga_meta_tx_hash}")


if __name__ == "__main__":
    main()