From 8ab33dd46db1f3d14364f8d22bdd0628fab7366e Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Thu, 16 Mar 2023 12:34:50 -0400 Subject: [PATCH 1/5] Documentation: Adding examples to be pulled in to docs (#441) * tmp * adding atomic transfers examples * use sandbox to fetch accounts * add examples for atc * adding asa examples * adding some account examples * Adding codec examples * fmt * improve kmd examples * adding tx fee override * adding more examples * tmp * adding app examples * remove check for creator on noop * Add recover account * add clear state * adding more lsig examples * fmt and asset config example * fmt with black==23.1.0 --------- Co-authored-by: Barbara Poon Co-authored-by: John Lee Co-authored-by: Jack Smith Co-authored-by: Lucky Baar Co-authored-by: Arthur Kepler <610274+excalq@users.noreply.github.com> --- _examples/account.py | 96 ++++++++ _examples/application/approval.teal | 100 +++++++++ .../application/approval_refactored.teal | 107 +++++++++ _examples/application/clear.teal | 3 + _examples/apps.py | 142 ++++++++++++ _examples/asa.py | 205 ++++++++++++++++++ _examples/atc.py | 103 +++++++++ _examples/atomic_transfers.py | 65 ++++++ _examples/calculator/approval.teal | 181 ++++++++++++++++ _examples/calculator/clear.teal | 3 + _examples/calculator/contract.json | 74 +++++++ _examples/codec.py | 66 ++++++ _examples/debug.py | 47 ++++ _examples/indexer.py | 55 +++++ _examples/kmd.py | 87 ++++++++ _examples/lsig.py | 140 ++++++++++++ _examples/lsig/sample_arg.teal | 5 + _examples/lsig/simple.teal | 3 + _examples/overview.py | 86 ++++++++ _examples/participation.py | 44 ++++ _examples/utils.py | 92 ++++++++ 21 files changed, 1704 insertions(+) create mode 100644 _examples/account.py create mode 100644 _examples/application/approval.teal create mode 100644 _examples/application/approval_refactored.teal create mode 100644 _examples/application/clear.teal create mode 100644 _examples/apps.py create mode 100644 _examples/asa.py create mode 100644 _examples/atc.py create mode 100644 _examples/atomic_transfers.py create mode 100644 _examples/calculator/approval.teal create mode 100644 _examples/calculator/clear.teal create mode 100644 _examples/calculator/contract.json create mode 100644 _examples/codec.py create mode 100644 _examples/debug.py create mode 100644 _examples/indexer.py create mode 100644 _examples/kmd.py create mode 100644 _examples/lsig.py create mode 100644 _examples/lsig/sample_arg.teal create mode 100644 _examples/lsig/simple.teal create mode 100644 _examples/overview.py create mode 100644 _examples/participation.py create mode 100644 _examples/utils.py diff --git a/_examples/account.py b/_examples/account.py new file mode 100644 index 00000000..5a34a206 --- /dev/null +++ b/_examples/account.py @@ -0,0 +1,96 @@ +from utils import get_accounts, get_algod_client +from algosdk import account, mnemonic +from algosdk import transaction + +# example: ACCOUNT_GENERATE +private_key, address = account.generate_account() +print(f"address: {address}") +print(f"private key: {private_key}") +print(f"mnemonic: {mnemonic.from_private_key(private_key)}") +# example: ACCOUNT_GENERATE + +# example: ACCOUNT_RECOVER_MNEMONIC +mn = "cost piano sample enough south bar diet garden nasty mystery mesh sadness convince bacon best patch surround protect drum actress entire vacuum begin abandon hair" +pk = mnemonic.to_private_key(mn) +print(f"Base64 encoded private key: {pk}") +addr = account.address_from_private_key(pk) +print(f"Address: {addr}") +# example: ACCOUNT_RECOVER_MNEMONIC + +accts = get_accounts() +account_1 = accts.pop() +account_2 = accts.pop() +account_3 = accts.pop() + +# example: MULTISIG_CREATE +version = 1 # multisig version +threshold = 2 # how many signatures are necessary +# create a Multisig given the set of participants and threshold +msig = transaction.Multisig( + version, + threshold, + [account_1.address, account_2.address, account_3.address], +) +print("Multisig Address: ", msig.address()) +# example: MULTISIG_CREATE + +algod_client = get_algod_client() +sp = algod_client.suggested_params() +ptxn = transaction.PaymentTxn( + account_1.address, sp, msig.address(), int(1e7) +).sign(account_1.private_key) +txid = algod_client.send_transaction(ptxn) +transaction.wait_for_confirmation(algod_client, txid, 4) +# dont check response, assume it worked + +# example: MULTISIG_SIGN +msig_pay = transaction.PaymentTxn( + msig.address(), sp, account_1.address, int(1e5) +) +msig_txn = transaction.MultisigTransaction(msig_pay, msig) +msig_txn.sign(account_2.private_key) +msig_txn.sign(account_3.private_key) +txid = algod_client.send_transaction(msig_txn) +result = transaction.wait_for_confirmation(algod_client, txid, 4) +print( + f"Payment made from msig account confirmed in round {result['confirmed-round']}" +) +# example: MULTISIG_SIGN + + +# example: ACCOUNT_REKEY +# Any kind of transaction can contain a rekey +rekey_txn = transaction.PaymentTxn( + account_1.address, sp, account_1.address, 0, rekey_to=account_2.address +) +signed_rekey = rekey_txn.sign(account_1.private_key) +txid = algod_client.send_transaction(signed_rekey) +result = transaction.wait_for_confirmation(algod_client, txid, 4) +print(f"rekey transaction confirmed in round {result['confirmed-round']}") + +# Now we should get an error if we try to submit a transaction +# signed with account_1s private key +expect_err_txn = transaction.PaymentTxn( + account_1.address, sp, account_1.address, 0 +) +signed_expect_err_txn = expect_err_txn.sign(account_1.private_key) +try: + txid = algod_client.send_transaction(signed_expect_err_txn) +except Exception as e: + print("Expected error: ", e) + +# But its fine if we sign it with the account we rekeyed to +signed_expect_err_txn = expect_err_txn.sign(account_2.private_key) +txid = algod_client.send_transaction(signed_expect_err_txn) +result = transaction.wait_for_confirmation(algod_client, txid, 4) +print(f"transaction confirmed in round {result['confirmed-round']}") + +# rekey account1 back to itself so we can actually use it later +rekey_txn = transaction.PaymentTxn( + account_1.address, sp, account_1.address, 0, rekey_to=account_1.address +) +signed_rekey = rekey_txn.sign(account_2.private_key) +txid = algod_client.send_transaction(signed_rekey) +result = transaction.wait_for_confirmation(algod_client, txid, 4) +print(f"rekey transaction confirmed in round {result['confirmed-round']}") +# example: ACCOUNT_REKEY diff --git a/_examples/application/approval.teal b/_examples/application/approval.teal new file mode 100644 index 00000000..eabde624 --- /dev/null +++ b/_examples/application/approval.teal @@ -0,0 +1,100 @@ +#pragma version 4 +// Handle each possible OnCompletion type. We don't have to worry about +// handling ClearState, because the ClearStateProgram will execute in that +// case, not the ApprovalProgram. +txn ApplicationID +int 0 +== +bnz handle_approve + +txn OnCompletion +int NoOp +== +bnz handle_noop + +txn OnCompletion +int OptIn +== +bnz handle_approve + +txn OnCompletion +int CloseOut +== +bnz handle_closeout + +txn OnCompletion +int UpdateApplication +== +bnz handle_updateapp + +txn OnCompletion +int DeleteApplication +== +bnz handle_deleteapp + +// Unexpected OnCompletion value. Should be unreachable. +err + +handle_noop: +// Handle NoOp + +// read global state +byte "counter" +dup +app_global_get + +// increment the value +int 1 ++ + +// store to scratch space +dup +store 0 + +// update global state +app_global_put + +// read local state for sender +int 0 +byte "counter" +app_local_get + +// increment the value +int 1 ++ +store 1 + +// update local state for sender +int 0 +byte "counter" +load 1 +app_local_put + +// load return value as approval +load 0 +return + + +handle_closeout: +// Handle CloseOut +//approval +int 1 +return + +handle_deleteapp: +// Check for creator +global CreatorAddress +txn Sender +== +return + +handle_updateapp: +// Check for creator +global CreatorAddress +txn Sender +== +return + +handle_approve: +int 1 +return diff --git a/_examples/application/approval_refactored.teal b/_examples/application/approval_refactored.teal new file mode 100644 index 00000000..1af5a7eb --- /dev/null +++ b/_examples/application/approval_refactored.teal @@ -0,0 +1,107 @@ +#pragma version 4 +// Handle each possible OnCompletion type. We don't have to worry about +// handling ClearState, because the ClearStateProgram will execute in that +// case, not the ApprovalProgram. + +txn ApplicationID +int 0 +== +bnz handle_approve + +txn OnCompletion +int NoOp +== +bnz handle_noop + +txn OnCompletion +int OptIn +== +bnz handle_approve + +txn OnCompletion +int CloseOut +== +bnz handle_closeout + +txn OnCompletion +int UpdateApplication +== +bnz handle_updateapp + +txn OnCompletion +int DeleteApplication +== +bnz handle_deleteapp + +// Unexpected OnCompletion value. Should be unreachable. +err + +handle_noop: +// Handle NoOp + +// read global state +byte "counter" +dup +app_global_get + +// increment the value +int 1 ++ + +// store to scratch space +dup +store 0 + +// update global state +app_global_put + +// read local state for sender +int 0 +byte "counter" +app_local_get + +// increment the value +int 1 ++ +store 1 + +// update local state for sender +// update "counter" +int 0 +byte "counter" +load 1 +app_local_put + +// update "timestamp" +int 0 +byte "timestamp" +txn ApplicationArgs 0 +app_local_put + +// load return value as approval +load 0 +return + +handle_closeout: +// Handle CloseOut +//approval +int 1 +return + +handle_deleteapp: +// Check for creator +global CreatorAddress +txn Sender +== +return + +handle_updateapp: +// Check for creator +global CreatorAddress +txn Sender +== +return + +handle_approve: +int 1 +return \ No newline at end of file diff --git a/_examples/application/clear.teal b/_examples/application/clear.teal new file mode 100644 index 00000000..d793651c --- /dev/null +++ b/_examples/application/clear.teal @@ -0,0 +1,3 @@ +#pragma version 4 +int 1 +return \ No newline at end of file diff --git a/_examples/apps.py b/_examples/apps.py new file mode 100644 index 00000000..de63a669 --- /dev/null +++ b/_examples/apps.py @@ -0,0 +1,142 @@ +import base64 +import datetime +from algosdk import transaction +from utils import get_algod_client, get_accounts + + +accts = get_accounts() +creator = accts.pop() +user = accts.pop() + +# example: APP_SCHEMA +# create schema for both global and local state, specifying +# how many keys of each type we need to have available +local_schema = transaction.StateSchema(num_uints=1, num_byte_slices=1) +global_schema = transaction.StateSchema(num_uints=1, num_byte_slices=1) +# example: APP_SCHEMA + +# example: APP_SOURCE +# read the `.teal` source files from disk +with open("application/approval.teal", "r") as f: + approval_program = f.read() + +with open("application/clear.teal", "r") as f: + clear_program = f.read() +# example: APP_SOURCE + +algod_client = get_algod_client() + +# example: APP_COMPILE +# pass the `.teal` files to the compile endpoint +# and b64 decode the result to bytes +approval_result = algod_client.compile(approval_program) +approval_binary = base64.b64decode(approval_result["result"]) + +clear_result = algod_client.compile(clear_program) +clear_binary = base64.b64decode(clear_result["result"]) +# example: APP_COMPILE + +# example: APP_CREATE +sp = algod_client.suggested_params() +# create the app create transaction, passing compiled programs and schema +app_create_txn = transaction.ApplicationCreateTxn( + creator.address, + sp, + transaction.OnComplete.NoOpOC, + approval_program=approval_binary, + clear_program=clear_binary, + global_schema=global_schema, + local_schema=local_schema, +) +# sign transaction +signed_create_txn = app_create_txn.sign(creator.private_key) +txid = algod_client.send_transaction(signed_create_txn) +result = transaction.wait_for_confirmation(algod_client, txid, 4) +app_id = result["application-index"] +print(f"Created app with id: {app_id}") +# example: APP_CREATE + +# example: APP_OPTIN +opt_in_txn = transaction.ApplicationOptInTxn(user.address, sp, app_id) +signed_opt_in = opt_in_txn.sign(user.private_key) +txid = algod_client.send_transaction(signed_opt_in) +optin_result = transaction.wait_for_confirmation(algod_client, txid, 4) +assert optin_result["confirmed-round"] > 0 +# example: APP_OPTIN + +# example: APP_NOOP +noop_txn = transaction.ApplicationNoOpTxn(user.address, sp, app_id) +signed_noop = noop_txn.sign(user.private_key) +txid = algod_client.send_transaction(signed_noop) +noop_result = transaction.wait_for_confirmation(algod_client, txid, 4) +assert noop_result["confirmed-round"] > 0 +# example: APP_NOOP + +# example: APP_READ_STATE +acct_info = algod_client.account_application_info(user.address, app_id) +# base64 encoded keys and values +print(acct_info["app-local-state"]["key-value"]) +# example: APP_READ_STATE + +# example: APP_UPDATE +with open("application/approval_refactored.teal", "r") as f: + approval_program = f.read() + +approval_result = algod_client.compile(approval_program) +approval_binary = base64.b64decode(approval_result["result"]) + + +sp = algod_client.suggested_params() +# create the app update transaction, passing compiled programs and schema +# note that schema is immutable, we cant change it after create +app_update_txn = transaction.ApplicationUpdateTxn( + creator.address, + sp, + app_id, + approval_program=approval_binary, + clear_program=clear_binary, +) +signed_update = app_update_txn.sign(creator.private_key) +txid = algod_client.send_transaction(signed_update) +update_result = transaction.wait_for_confirmation(algod_client, txid, 4) +assert update_result["confirmed-round"] > 0 +# example: APP_UPDATE + +# example: APP_CALL +now = datetime.datetime.now().strftime("%H:%M:%S") +app_args = [now.encode("utf-8")] +call_txn = transaction.ApplicationNoOpTxn(user.address, sp, app_id, app_args) + +signed_call = call_txn.sign(user.private_key) +txid = algod_client.send_transaction(signed_call) +call_result = transaction.wait_for_confirmation(algod_client, txid, 4) +assert call_result["confirmed-round"] > 0 + +# display results +print("Called app-id: ", call_result["txn"]["txn"]["apid"]) +if "global-state-delta" in call_result: + print("Global State updated :\n", call_result["global-state-delta"]) +if "local-state-delta" in call_result: + print("Local State updated :\n", call_result["local-state-delta"]) +# example: APP_CALL + +# example: APP_CLOSEOUT +close_txn = transaction.ApplicationCloseOutTxn(user.address, sp, app_id) +signed_close = close_txn.sign(user.private_key) +txid = algod_client.send_transaction(signed_close) +optin_result = transaction.wait_for_confirmation(algod_client, txid, 4) +assert optin_result["confirmed-round"] > 0 +# example: APP_CLOSEOUT + +# example: APP_DELETE +delete_txn = transaction.ApplicationDeleteTxn(creator.address, sp, app_id) +signed_delete = delete_txn.sign(creator.private_key) +txid = algod_client.send_transaction(signed_delete) +optin_result = transaction.wait_for_confirmation(algod_client, txid, 4) +assert optin_result["confirmed-round"] > 0 +# example: APP_DELETE + +# example: APP_CLEAR +clear_txn = transaction.ApplicationClearStateTxn(user.address, sp, app_id) +# .. sign, send, wait +# example: APP_CLEAR diff --git a/_examples/asa.py b/_examples/asa.py new file mode 100644 index 00000000..8faa2b7d --- /dev/null +++ b/_examples/asa.py @@ -0,0 +1,205 @@ +from typing import Dict, Any +from algosdk import transaction +from utils import get_accounts, get_algod_client + +# Setup +algod_client = get_algod_client() +accounts = get_accounts() +acct1 = accounts.pop() +acct2 = accounts.pop() +acct3 = accounts.pop() + + +# example: ASSET_CREATE +# Account 1 creates an asset called `rug` with a total supply +# of 1000 units and sets itself to the freeze/clawback/manager/reserve roles +sp = algod_client.suggested_params() +txn = transaction.AssetConfigTxn( + sender=acct1.address, + sp=sp, + default_frozen=False, + unit_name="rug", + asset_name="Really Useful Gift", + manager=acct1.address, + reserve=acct1.address, + freeze=acct1.address, + clawback=acct1.address, + url="https://path/to/my/asset/details", + total=1000, + decimals=0, +) + +# Sign with secret key of creator +stxn = txn.sign(acct1.private_key) +# Send the transaction to the network and retrieve the txid. +txid = algod_client.send_transaction(stxn) +print(f"Sent asset create transaction with txid: {txid}") +# Wait for the transaction to be confirmed +results = transaction.wait_for_confirmation(algod_client, txid, 4) +print(f"Result confirmed in round: {results['confirmed-round']}") + +# grab the asset id for the asset we just created +created_asset = results["asset-index"] +print(f"Asset ID created: {created_asset}") +# example: ASSET_CREATE + +# example: ASSET_CONFIG +sp = algod_client.suggested_params() +# Create a config transaction that wipes the +# reserve address for the asset +txn = transaction.AssetConfigTxn( + sender=acct1.address, + sp=sp, + manager=acct1.address, + reserve=None, + freeze=acct1.address, + clawback=acct1.address, + strict_empty_address_check=False, +) +# Sign with secret key of manager +stxn = txn.sign(acct1.private_key) +# Send the transaction to the network and retrieve the txid. +txid = algod_client.send_transaction(stxn) +print(f"Sent asset config transaction with txid: {txid}") +# Wait for the transaction to be confirmed +results = transaction.wait_for_confirmation(algod_client, txid, 4) +print(f"Result confirmed in round: {results['confirmed-round']}") +# example: ASSET_CONFIG + + +# example: ASSET_INFO +# Retrieve the asset info of the newly created asset +asset_info = algod_client.asset_info(created_asset) +asset_params: Dict[str, Any] = asset_info["params"] +print(f"Asset Name: {asset_params['name']}") +print(f"Asset params: {list(asset_params.keys())}") +# example: ASSET_INFO + + +# example: ASSET_OPTIN +sp = algod_client.suggested_params() +# Create opt-in transaction +# asset transfer from me to me for asset id we want to opt-in to with amt==0 +optin_txn = transaction.AssetOptInTxn( + sender=acct2.address, sp=sp, index=created_asset +) +signed_optin_txn = optin_txn.sign(acct2.private_key) +txid = algod_client.send_transaction(signed_optin_txn) +print(f"Sent opt in transaction with txid: {txid}") + +# Wait for the transaction to be confirmed +results = transaction.wait_for_confirmation(algod_client, txid, 4) +print(f"Result confirmed in round: {results['confirmed-round']}") + +acct_info = algod_client.account_info(acct2.address) +matching_asset = [ + asset + for asset in acct_info["assets"] + if asset["asset-id"] == created_asset +].pop() +assert matching_asset["amount"] == 0 +assert matching_asset["is-frozen"] is False +# example: ASSET_OPTIN + + +# example: ASSET_XFER +sp = algod_client.suggested_params() +# Create transfer transaction +xfer_txn = transaction.AssetTransferTxn( + sender=acct1.address, + sp=sp, + receiver=acct2.address, + amt=1, + index=created_asset, +) +signed_xfer_txn = xfer_txn.sign(acct1.private_key) +txid = algod_client.send_transaction(signed_xfer_txn) +print(f"Sent transfer transaction with txid: {txid}") + +results = transaction.wait_for_confirmation(algod_client, txid, 4) +print(f"Result confirmed in round: {results['confirmed-round']}") + +acct_info = algod_client.account_info(acct2.address) +matching_asset = [ + asset + for asset in acct_info["assets"] + if asset["asset-id"] == created_asset +].pop() +assert matching_asset["amount"] == 1 +# example: ASSET_XFER + +# example: ASSET_FREEZE +sp = algod_client.suggested_params() +# Create freeze transaction to freeze the asset in acct2 balance +freeze_txn = transaction.AssetFreezeTxn( + sender=acct1.address, + sp=sp, + index=created_asset, + target=acct2.address, + new_freeze_state=True, +) +signed_freeze_txn = freeze_txn.sign(acct1.private_key) +txid = algod_client.send_transaction(signed_freeze_txn) +print(f"Sent freeze transaction with txid: {txid}") + +results = transaction.wait_for_confirmation(algod_client, txid, 4) +print(f"Result confirmed in round: {results['confirmed-round']}") + +acct_info = algod_client.account_info(acct2.address) +matching_asset = [ + asset + for asset in acct_info["assets"] + if asset["asset-id"] == created_asset +].pop() +assert matching_asset["is-frozen"] is True +# example: ASSET_FREEZE + +# example: ASSET_CLAWBACK +sp = algod_client.suggested_params() +# Create clawback transaction to freeze the asset in acct2 balance +clawback_txn = transaction.AssetTransferTxn( + sender=acct1.address, + sp=sp, + receiver=acct1.address, + amt=1, + index=created_asset, + revocation_target=acct2.address, +) +signed_clawback_txn = clawback_txn.sign(acct1.private_key) +txid = algod_client.send_transaction(signed_clawback_txn) +print(f"Sent clawback transaction with txid: {txid}") + +results = transaction.wait_for_confirmation(algod_client, txid, 4) +print(f"Result confirmed in round: {results['confirmed-round']}") + +acct_info = algod_client.account_info(acct2.address) +matching_asset = [ + asset + for asset in acct_info["assets"] + if asset["asset-id"] == created_asset +].pop() +assert matching_asset["amount"] == 0 +assert matching_asset["is-frozen"] is True +# example: ASSET_CLAWBACK + +# example: ASSET_DELETE +sp = algod_client.suggested_params() +# Create asset destroy transaction to destroy the asset +destroy_txn = transaction.AssetDestroyTxn( + sender=acct1.address, + sp=sp, + index=created_asset, +) +signed_destroy_txn = destroy_txn.sign(acct1.private_key) +txid = algod_client.send_transaction(signed_destroy_txn) +print(f"Sent destroy transaction with txid: {txid}") + +results = transaction.wait_for_confirmation(algod_client, txid, 4) +print(f"Result confirmed in round: {results['confirmed-round']}") + +# now, trying to fetch the asset info should result in an error +try: + info = algod_client.asset_info(created_asset) +except Exception as e: + print("Expected Error:", e) +# example: ASSET_DELETE diff --git a/_examples/atc.py b/_examples/atc.py new file mode 100644 index 00000000..1556ddb2 --- /dev/null +++ b/_examples/atc.py @@ -0,0 +1,103 @@ +from algosdk.v2client import algod +from algosdk import transaction, abi +from utils import get_accounts + +# example: ATC_CREATE +from algosdk.atomic_transaction_composer import ( + AtomicTransactionComposer, + AccountTransactionSigner, + TransactionWithSigner, +) + +atc = AtomicTransactionComposer() +# example: ATC_CREATE + +accts = get_accounts() +acct = accts.pop() + +algod_address = "http://localhost:4001" +algod_token = "a" * 64 +algod_client = algod.AlgodClient(algod_token, algod_address) + +# example: ATC_ADD_TRANSACTION +addr, sk = acct.address, acct.private_key + +# Create signer object +signer = AccountTransactionSigner(sk) + +# Get suggested params from the client +sp = algod_client.suggested_params() + +# Create a transaction +ptxn = transaction.PaymentTxn(addr, sp, addr, 10000) + +# Construct TransactionWithSigner +tws = TransactionWithSigner(ptxn, signer) + +# Pass TransactionWithSigner to ATC +atc.add_transaction(tws) +# example: ATC_ADD_TRANSACTION + +# example: ATC_CONTRACT_INIT +with open("path/to/contract.json") as f: + js = f.read() +contract = abi.Contract.from_json(js) +# example: ATC_CONTRACT_INIT + + +# TODO: take it from contract object? +app_id = 123 + +# example: ATC_ADD_METHOD_CALL + +# Simple call to the `add` method, method_args can be any type but _must_ +# match those in the method signature of the contract +atc.add_method_call( + app_id, + contract.get_method_by_name("add"), + addr, + sp, + signer, + method_args=[1, 1], +) + +# This method requires a `transaction` as its second argument. +# Construct the transaction and pass it in as an argument. +# The ATC will handle adding it to the group transaction and +# setting the reference in the application arguments. +ptxn = transaction.PaymentTxn(addr, sp, addr, 10000) +txn = TransactionWithSigner(ptxn, signer) +atc.add_method_call( + app_id, + contract.get_method_by_name("txntest"), + addr, + sp, + signer, + method_args=[10000, txn, 1000], +) +# example: ATC_ADD_METHOD_CALL + + +# example: ATC_RESULTS +# Other options: +# txngroup = atc.build_group() +# txids = atc.submit(client) +result = atc.execute(algod_client, 4) +for res in result.abi_results: + print(res.return_value) +# example: ATC_RESULTS + + +my_method = contract.get_method_by_name("add_member()void") +# example: ATC_BOX_REF +atc = AtomicTransactionComposer() +atc.add_method_call( + app_id, + my_method, + addr, + sp, + signer, + method_args=[1, 5], + boxes=[[app_id, b"key"]], +) +# example: ATC_BOX_REF diff --git a/_examples/atomic_transfers.py b/_examples/atomic_transfers.py new file mode 100644 index 00000000..97243a58 --- /dev/null +++ b/_examples/atomic_transfers.py @@ -0,0 +1,65 @@ +from typing import Dict, Any +from algosdk import transaction +from algosdk.v2client import algod +from utils import get_accounts + +algod_address = "http://localhost:4001" +algod_token = "a" * 64 +algod_client = algod.AlgodClient(algod_token, algod_address) + +acct1, acct2, _ = get_accounts() +addr1, sk1 = acct1.address, acct1.private_key +addr2, sk2 = acct2.address, acct2.private_key + +suggested_params = algod_client.suggested_params() + +# example: ATOMIC_CREATE_TXNS + +# payment from account 1 to account 2 +txn_1 = transaction.PaymentTxn(addr1, suggested_params, addr2, 100000) +# payment from account 2 to account 1 +txn_2 = transaction.PaymentTxn(addr2, suggested_params, addr1, 200000) + +# example: ATOMIC_CREATE_TXNS + +# example: ATOMIC_GROUP_TXNS + +# Assign group id to the transactions (order matters!) +txn_1, txn_2 = transaction.assign_group_id([txn_1, txn_2]) + +# Or, equivalently + +# get group id and assign it to transactions +gid = transaction.calculate_group_id([txn_1, txn_2]) +txn_1.group = gid +txn_2.group = gid + +# example: ATOMIC_GROUP_TXNS + +# example: ATOMIC_GROUP_SIGN + +# sign transactions +stxn_1 = txn_1.sign(sk1) +stxn_2 = txn_2.sign(sk2) + +# example: ATOMIC_GROUP_SIGN + +# example: ATOMIC_GROUP_ASSEMBLE + +# combine the signed transactions into a single list +signed_group = [stxn_1, stxn_2] + +# example: ATOMIC_GROUP_ASSEMBLE + +# example: ATOMIC_GROUP_SEND + +# Only the first transaction id is returned +tx_id = algod_client.send_transactions(signed_group) + +# wait for confirmation +result: Dict[str, Any] = transaction.wait_for_confirmation( + algod_client, tx_id, 4 +) +print(f"txID: {tx_id} confirmed in round: {result.get('confirmed-round', 0)}") + +# example: ATOMIC_GROUP_SEND diff --git a/_examples/calculator/approval.teal b/_examples/calculator/approval.teal new file mode 100644 index 00000000..32acbb84 --- /dev/null +++ b/_examples/calculator/approval.teal @@ -0,0 +1,181 @@ +#pragma version 8 +intcblock 0 1 +bytecblock 0x151f7c75 +txn NumAppArgs +intc_0 // 0 +== +bnz main_l10 +txna ApplicationArgs 0 +pushbytes 0xfe6bdf69 // "add(uint64,uint64)uint64" +== +bnz main_l9 +txna ApplicationArgs 0 +pushbytes 0xe2f188c5 // "mul(uint64,uint64)uint64" +== +bnz main_l8 +txna ApplicationArgs 0 +pushbytes 0x78b488b7 // "sub(uint64,uint64)uint64" +== +bnz main_l7 +txna ApplicationArgs 0 +pushbytes 0x16e80f08 // "div(uint64,uint64)uint64" +== +bnz main_l6 +err +main_l6: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +txna ApplicationArgs 1 +btoi +store 9 +txna ApplicationArgs 2 +btoi +store 10 +load 9 +load 10 +callsub div_3 +store 11 +bytec_0 // 0x151f7c75 +load 11 +itob +concat +log +intc_1 // 1 +return +main_l7: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +txna ApplicationArgs 1 +btoi +store 6 +txna ApplicationArgs 2 +btoi +store 7 +load 6 +load 7 +callsub sub_2 +store 8 +bytec_0 // 0x151f7c75 +load 8 +itob +concat +log +intc_1 // 1 +return +main_l8: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +txna ApplicationArgs 1 +btoi +store 3 +txna ApplicationArgs 2 +btoi +store 4 +load 3 +load 4 +callsub mul_1 +store 5 +bytec_0 // 0x151f7c75 +load 5 +itob +concat +log +intc_1 // 1 +return +main_l9: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +txna ApplicationArgs 1 +btoi +store 0 +txna ApplicationArgs 2 +btoi +store 1 +load 0 +load 1 +callsub add_0 +store 2 +bytec_0 // 0x151f7c75 +load 2 +itob +concat +log +intc_1 // 1 +return +main_l10: +txn OnCompletion +intc_0 // NoOp +== +bnz main_l12 +err +main_l12: +txn ApplicationID +intc_0 // 0 +== +assert +intc_1 // 1 +return + +// add +add_0: +proto 2 1 +intc_0 // 0 +frame_dig -2 +frame_dig -1 ++ +frame_bury 0 +retsub + +// mul +mul_1: +proto 2 1 +intc_0 // 0 +frame_dig -2 +frame_dig -1 +* +frame_bury 0 +retsub + +// sub +sub_2: +proto 2 1 +intc_0 // 0 +frame_dig -2 +frame_dig -1 +- +frame_bury 0 +retsub + +// div +div_3: +proto 2 1 +intc_0 // 0 +frame_dig -2 +frame_dig -1 +/ +frame_bury 0 +retsub \ No newline at end of file diff --git a/_examples/calculator/clear.teal b/_examples/calculator/clear.teal new file mode 100644 index 00000000..e741f0e5 --- /dev/null +++ b/_examples/calculator/clear.teal @@ -0,0 +1,3 @@ +#pragma version 8 +pushint 0 // 0 +return \ No newline at end of file diff --git a/_examples/calculator/contract.json b/_examples/calculator/contract.json new file mode 100644 index 00000000..4b23fa17 --- /dev/null +++ b/_examples/calculator/contract.json @@ -0,0 +1,74 @@ +{ + "name": "Calculator", + "methods": [ + { + "name": "add", + "args": [ + { + "type": "uint64", + "name": "a" + }, + { + "type": "uint64", + "name": "b" + } + ], + "returns": { + "type": "uint64" + }, + "desc": "Add a and b, return the result" + }, + { + "name": "mul", + "args": [ + { + "type": "uint64", + "name": "a" + }, + { + "type": "uint64", + "name": "b" + } + ], + "returns": { + "type": "uint64" + }, + "desc": "Multiply a and b, return the result" + }, + { + "name": "sub", + "args": [ + { + "type": "uint64", + "name": "a" + }, + { + "type": "uint64", + "name": "b" + } + ], + "returns": { + "type": "uint64" + }, + "desc": "Subtract b from a, return the result" + }, + { + "name": "div", + "args": [ + { + "type": "uint64", + "name": "a" + }, + { + "type": "uint64", + "name": "b" + } + ], + "returns": { + "type": "uint64" + }, + "desc": "Divide a by b, return the result" + } + ], + "networks": {} +} \ No newline at end of file diff --git a/_examples/codec.py b/_examples/codec.py new file mode 100644 index 00000000..d65d7470 --- /dev/null +++ b/_examples/codec.py @@ -0,0 +1,66 @@ +import base64 +import os +from utils import get_algod_client, get_accounts +from algosdk import transaction +from algosdk import encoding + +# example: CODEC_ADDRESS +address = "4H5UNRBJ2Q6JENAXQ6HNTGKLKINP4J4VTQBEPK5F3I6RDICMZBPGNH6KD4" +pk = encoding.decode_address(address) +addr = encoding.encode_address(pk) + +assert addr == address +# example: CODEC_ADDRESS + +# example: CODEC_BASE64 +encoded_str = "SGksIEknbSBkZWNvZGVkIGZyb20gYmFzZTY0" +decoded_str = base64.b64decode(encoded_str).decode("utf-8") +print(decoded_str) +# example: CODEC_BASE64 + +# example: CODEC_UINT64 +val = 1337 +encoded_uint = val.to_bytes(8, "big") +decoded_uint = int.from_bytes(encoded_uint, byteorder="big") +assert decoded_uint == val +# example: CODEC_UINT64 + + +algod_client = get_algod_client() +acct = get_accounts().pop() +# example: CODEC_TRANSACTION_UNSIGNED +sp = algod_client.suggested_params() +pay_txn = transaction.PaymentTxn(acct.address, sp, acct.address, 10000) + +# Write message packed transaction to disk +with open("pay.txn", "w") as f: + f.write(encoding.msgpack_encode(pay_txn)) + +# Read message packed transaction and decode it to a Transaction object +with open("pay.txn", "r") as f: + recovered_txn = encoding.msgpack_decode(f.read()) + +print(recovered_txn.dictify()) +# example: CODEC_TRANSACTION_UNSIGNED + +os.remove("pay.txn") + +# example: CODEC_TRANSACTION_SIGNED +# Sign transaction +spay_txn = pay_txn.sign(acct.private_key) +# write message packed signed transaction to disk +with open("signed_pay.txn", "w") as f: + f.write(encoding.msgpack_encode(spay_txn)) + +# read message packed signed transaction into a SignedTransaction object +with open("signed_pay.txn", "r") as f: + recovered_signed_txn = encoding.msgpack_decode(f.read()) + +print(recovered_signed_txn.dictify()) +# example: CODEC_TRANSACTION_SIGNED + + +os.remove("signed_pay.txn") + +# example: CODEC_BLOCK +# example: CODEC_BLOCK diff --git a/_examples/debug.py b/_examples/debug.py new file mode 100644 index 00000000..c25e119c --- /dev/null +++ b/_examples/debug.py @@ -0,0 +1,47 @@ +import base64 +from utils import get_algod_client, get_accounts +from algosdk import ( + transaction, + encoding, + atomic_transaction_composer, + abi, + dryrun_results, +) + +algod_client = get_algod_client() +accts = get_accounts() + +acct1 = accts.pop() +acct2 = accts.pop() + +app_id = 123 +my_method = abi.Method( + name="cool_method", args=[], returns=abi.Returns("void") +) + +# example: DEBUG_DRYRUN_DUMP +sp = algod_client.suggested_params() + +atc = atomic_transaction_composer.AtomicTransactionComposer() +atc.add_method_call(app_id, my_method, acct1.address, sp, acct1.signer) +txns = atc.gather_signatures() + +drr = transaction.create_dryrun(algod_client, txns) + +# Write the file as binary result of msgpack encoding +with open("dryrun.msgp", "wb") as f: + f.write(base64.b64decode(encoding.msgpack_encode(drr))) +# example: DEBUG_DRYRUN_DUMP + +# example: DEBUG_DRYRUN_SUBMIT +# Create the dryrun request object +dryrun_request = transaction.create_dryrun(algod_client, txns) + +# Pass dryrun request to algod server +dryrun_result = algod_client.dryrun(dryrun_request) +drr = dryrun_results.DryrunResponse(dryrun_result) + +for txn in drr.txns: + if txn.app_call_rejected(): + print(txn.app_trace()) +# example: DEBUG_DRYRUN_SUBMIT diff --git a/_examples/indexer.py b/_examples/indexer.py new file mode 100644 index 00000000..81f5df0b --- /dev/null +++ b/_examples/indexer.py @@ -0,0 +1,55 @@ +import json +from algosdk.v2client import indexer + + +# example: CREATE_INDEXER_CLIENT +# instantiate indexer client +indexer_host = "http://localhost:8980" +indexer_token = "a" * 64 +myindexer = indexer.IndexerClient( + indexer_token=indexer_token, indexer_address=indexer_host +) +# example: CREATE_INDEXER_CLIENT + +# example: INDEXER_LOOKUP_ASSET +# lookup a single asset +asset_id = 2044572 +# by passing include_all, we specify that we want to see deleted assets as well +response = myindexer.asset_info(asset_id, include_all=True) +print(f"Asset Info: {json.dumps(response, indent=2,)}") +# example: INDEXER_LOOKUP_ASSET + +# example: INDEXER_SEARCH_MIN_AMOUNT +response = myindexer.search_transactions( + min_amount=10, min_round=1000, max_round=1500 +) +print(f"Transaction results: {json.dumps(response, indent=2)}") +# example: INDEXER_SEARCH_MIN_AMOUNT + +# example: INDEXER_PAGINATE_RESULTS + +nexttoken = "" +has_results = True +page = 0 + +# loop using next_page to paginate until there are +# no more transactions in the response +while has_results: + response = myindexer.search_transactions( + min_amount=10, min_round=1000, max_round=1500 + ) + + has_results = len(response["transactions"]) > 0 + + if has_results: + nexttoken = response["next-token"] + print(f"Tranastion on page {page}: " + json.dumps(response, indent=2)) + + page += 1 +# example: INDEXER_PAGINATE_RESULTS + +# example: INDEXER_PREFIX_SEARCH +note_prefix = "showing prefix".encode() +response = myindexer.search_transactions(note_prefix=note_prefix) +print(f"result: {json.dumps(response, indent=2)}") +# example: INDEXER_PREFIX_SEARCH diff --git a/_examples/kmd.py b/_examples/kmd.py new file mode 100644 index 00000000..a4c8b142 --- /dev/null +++ b/_examples/kmd.py @@ -0,0 +1,87 @@ +from algosdk import kmd, wallet, mnemonic, account + + +# example: KMD_CREATE_CLIENT +kmd_address = "http://localhost:4002" +kmd_token = "a" * 64 + +kmd_client = kmd.KMDClient(kmd_token=kmd_token, kmd_address=kmd_address) +# example: KMD_CREATE_CLIENT + + +def get_wallet_id_from_name(name: str): + wallets = kmd_client.list_wallets() + wallet_id = None + for w in wallets: + if w["name"] == name: + wallet_id = w["id"] + break + + if wallet_id is None: + raise Exception(f"No wallet with name {name} found") + + return wallet_id + + +# example: KMD_CREATE_WALLET +# create a wallet object which, if not available yet, also creates the wallet in the KMD +wlt = wallet.Wallet("MyNewWallet", "supersecretpassword", kmd_client) +# get wallet information +info = wlt.info() +print(f"Wallet name: {info['wallet']['name']}") + +backup = wlt.get_mnemonic() +print(f"mnemonic for master derivation key: {backup}") +# example: KMD_CREATE_WALLET + +# example: KMD_CREATE_ACCOUNT +# create an account using the wallet object +address = wlt.generate_key() +print(f"New account: {address}") +# example: KMD_CREATE_ACCOUNT + + +# example: KMD_RECOVER_WALLET +# Create the master derivation key from our backed up mnemonic +mdk = mnemonic.to_master_derivation_key(backup) + +# recover the wallet by passing mdk during creation +new_wallet = wallet.Wallet( + "MyNewWalletCopy", "testpassword", kmd_client, mdk=mdk +) + +info = new_wallet.info() +wallet_id = info["wallet"]["id"] +print(f"Created Wallet: {wallet_id}") + +rec_addr = wlt.generate_key() +print("Recovered account:", rec_addr) +# example: KMD_RECOVER_WALLET + +# example: KMD_EXPORT_ACCOUNT +# Get the id for the wallet we want to export an account from +wallet_id = get_wallet_id_from_name("MyNewWallet") +# Get a session handle for the wallet after providing password +wallethandle = kmd_client.init_wallet_handle(wallet_id, "supersecretpassword") +# Export the account key for the address passed +accountkey = kmd_client.export_key( + wallethandle, "supersecretpassword", address +) +# Print the mnemonic for the accounts private key +mn = mnemonic.from_private_key(accountkey) +print(f"Account mnemonic: {mn}") +# example: KMD_EXPORT_ACCOUNT + +# example: KMD_IMPORT_ACCOUNT +wallet_id = get_wallet_id_from_name("MyNewWallet") + +# Generate a new account client side +new_private_key, new_address = account.generate_account() +mn = mnemonic.from_private_key(new_private_key) +print(f"Account: {new_address} Mnemonic: {mn}") + +# Import the account to the wallet in KMD +wallethandle = kmd_client.init_wallet_handle(wallet_id, "supersecretpassword") +importedaccount = kmd_client.import_key(wallethandle, new_private_key) +print("Account successfully imported: ", importedaccount) +# example: KMD_IMPORT_ACCOUNT diff --git a/_examples/lsig.py b/_examples/lsig.py new file mode 100644 index 00000000..a1d66497 --- /dev/null +++ b/_examples/lsig.py @@ -0,0 +1,140 @@ +import base64 +import json +from pathlib import Path + +from algosdk import transaction, mnemonic, account + +from utils import get_algod_client, get_accounts + + +def compile_lsig(lsig_src_path: Path) -> str: + algod_client = get_algod_client() + # example: LSIG_COMPILE + # read teal program + data = open(lsig_src_path, "r").read() + # compile teal program + response = algod_client.compile(data) + print("Response Result = ", response["result"]) + print("Response Hash = ", response["hash"]) + # example: LSIG_COMPILE + return response["result"] + + +def get_lsig() -> transaction.LogicSigAccount: + compiled_program = compile_lsig(Path("lsig") / "simple.teal") + # example: LSIG_INIT + program = base64.b64decode(compiled_program) + lsig = transaction.LogicSigAccount(program) + # example: LSIG_INIT + return lsig + + +def lsig_args(): + compiled_program = compile_lsig() + # example: LSIG_PASS_ARGS + # string parameter + arg_str = "my string" + arg1 = arg_str.encode() + lsig = transaction.LogicSigAccount(compiled_program, args=[arg1]) + # OR integer parameter + arg1 = (123).to_bytes(8, "big") + lsig = transaction.LogicSigAccount(compiled_program, args=[arg1]) + # example: LSIG_PASS_ARGS + + +def contract_account_example(): + algod_client = get_algod_client() + + # Seed the lsig account address so the + # payment later works + accts = get_accounts() + seed_acct = accts.pop() + lsig_args_path = Path("lsig") / "sample_arg.teal" + compiled_program = compile_lsig(lsig_args_path) + lsig = transaction.LogicSigAccount(base64.b64decode(compiled_program)) + ptxn = transaction.PaymentTxn( + seed_acct.address, + algod_client.suggested_params(), + lsig.address(), + 10000000, + ) + txid = algod_client.send_transaction(ptxn.sign(seed_acct.private_key)) + transaction.wait_for_confirmation(algod_client, txid, 4) + + receiver = seed_acct.address + + # example: LSIG_SIGN_FULL + # Create an algod client + lsig_args_path = Path("lsig") / "sample_arg.teal" + compiled_program = compile_lsig(lsig_args_path) + program_binary = base64.b64decode(compiled_program) + arg1 = (123).to_bytes(8, "big") + lsig = transaction.LogicSigAccount(program_binary, args=[arg1]) + sender = lsig.address() + # Get suggested parameters + params = algod_client.suggested_params() + # Build transaction + amount = 10000 + # Create a transaction + txn = transaction.PaymentTxn(sender, params, receiver, amount) + # Create the LogicSigTransaction with contract account LogicSigAccount + lstx = transaction.LogicSigTransaction(txn, lsig) + + # Send raw LogicSigTransaction to network + txid = algod_client.send_transaction(lstx) + print("Transaction ID: " + txid) + # wait for confirmation + confirmed_txn = transaction.wait_for_confirmation(algod_client, txid, 4) + print( + "Result confirmed in round: {}".format( + confirmed_txn["confirmed-round"] + ) + ) + # example: LSIG_SIGN_FULL + + +def delegate_lsig_example(): + algod_client = get_algod_client() + accts = get_accounts() + + signer_acct = accts[0] + receiver_acct = accts[1] + + # example: LSIG_DELEGATE_FULL + lsig_args_path = Path("lsig") / "sample_arg.teal" + compiled_program = compile_lsig(lsig_args_path) + program_binary = base64.b64decode(compiled_program) + arg1 = (123).to_bytes(8, "big") + lsig = transaction.LogicSigAccount(program_binary, args=[arg1]) + + # Sign the logic signature with an account sk + lsig.sign(signer_acct.private_key) + + # Get suggested parameters + params = algod_client.suggested_params() + amount = 10000 + # Create a transaction where sender is the account that + # is the delegating account + txn = transaction.PaymentTxn( + signer_acct.address, params, receiver_acct.address, amount + ) + + # Create the LogicSigTransaction with contract account LogicSigAccount + lstx = transaction.LogicSigTransaction(txn, lsig) + + # Send raw LogicSigTransaction to network + txid = algod_client.send_transaction(lstx) + print("Transaction ID: " + txid) + + confirmed_txn = transaction.wait_for_confirmation(algod_client, txid, 4) + print( + "Result confirmed in round: {}".format( + confirmed_txn["confirmed-round"] + ) + ) + # example: LSIG_DELEGATE_FULL + + +if __name__ == "__main__": + contract_account_example() + delegate_lsig_example() diff --git a/_examples/lsig/sample_arg.teal b/_examples/lsig/sample_arg.teal new file mode 100644 index 00000000..8f21008e --- /dev/null +++ b/_examples/lsig/sample_arg.teal @@ -0,0 +1,5 @@ +#pragma version 5 +arg_0 +btoi +int 123 +== \ No newline at end of file diff --git a/_examples/lsig/simple.teal b/_examples/lsig/simple.teal new file mode 100644 index 00000000..d6298656 --- /dev/null +++ b/_examples/lsig/simple.teal @@ -0,0 +1,3 @@ +#pragma version 5 +int 1 +return \ No newline at end of file diff --git a/_examples/overview.py b/_examples/overview.py new file mode 100644 index 00000000..8a77a9a8 --- /dev/null +++ b/_examples/overview.py @@ -0,0 +1,86 @@ +from typing import Dict, Any +import json +from base64 import b64decode +from utils import get_accounts + +from algosdk import transaction +from algosdk.v2client import algod + + +# example: ALGOD_CREATE_CLIENT +# Create a new algod client, configured to connect to our local sandbox +algod_address = "http://localhost:4001" +algod_token = "a" * 64 +algod_client = algod.AlgodClient(algod_token, algod_address) + +# Or, if necessary, pass alternate headers + +# Create a new client with an alternate api key header +special_algod_client = algod.AlgodClient( + "", algod_address, headers={"X-API-Key": algod_token} +) +# example: ALGOD_CREATE_CLIENT + +accts = get_accounts() + +acct1 = accts.pop() +private_key, address = acct1.private_key, acct1.address + +acct2 = accts.pop() +address2 = acct2.address + +# example: ALGOD_FETCH_ACCOUNT_INFO +account_info: Dict[str, Any] = algod_client.account_info(address) +print(f"Account balance: {account_info.get('amount')} microAlgos") +# example: ALGOD_FETCH_ACCOUNT_INFO + + +# example: TRANSACTION_PAYMENT_CREATE +# grab suggested params from algod using client +# includes things like suggested fee and first/last valid rounds +params = algod_client.suggested_params() +unsigned_txn = transaction.PaymentTxn( + sender=address, + sp=params, + receiver=address2, + amt=1000000, + note=b"Hello World", +) +# example: TRANSACTION_PAYMENT_CREATE + +# example: TRANSACTION_PAYMENT_SIGN +# sign the transaction +signed_txn = unsigned_txn.sign(private_key) +# example: TRANSACTION_PAYMENT_SIGN + +# example: TRANSACTION_PAYMENT_SUBMIT +# submit the transaction and get back a transaction id +txid = algod_client.send_transaction(signed_txn) +print("Successfully submitted transaction with txID: {}".format(txid)) + +# wait for confirmation +txn_result = transaction.wait_for_confirmation(algod_client, txid, 4) + +print(f"Transaction information: {json.dumps(txn_result, indent=4)}") +print(f"Decoded note: {b64decode(txn_result['txn']['txn']['note'])}") +# example: TRANSACTION_PAYMENT_SUBMIT + +# example: TRANSACTION_FEE_OVERRIDE +suggested_params = algod_client.suggested_params() +suggested_params.fee = 2 * suggested_params.min_fee +# Important to set flat_fee = True here or the fee will be +# treated as fee-per-byte of the encoded transaction +suggested_params.flat_fee = True +# example: TRANSACTION_FEE_OVERRIDE + + +# example: SP_MIN_FEE +suggested_params = algod_client.suggested_params() +print(suggested_params.min_fee) +# example: SP_MIN_FEE + +# example: CONST_MIN_FEE +from algosdk import constants + +print(constants.MIN_TXN_FEE) +# example: CONST_MIN_FEE diff --git a/_examples/participation.py b/_examples/participation.py new file mode 100644 index 00000000..224a67b3 --- /dev/null +++ b/_examples/participation.py @@ -0,0 +1,44 @@ +from algosdk import transaction +from utils import get_algod_client + +algod_client = get_algod_client() + +# example: TRANSACTION_KEYREG_ONLINE_CREATE +# get suggested parameters +params = algod_client.suggested_params() + +votekey = "eXq34wzh2UIxCZaI1leALKyAvSz/+XOe0wqdHagM+bw=" +selkey = "X84ReKTmp+yfgmMCbbokVqeFFFrKQeFZKEXG89SXwm4=" + +num_rounds = int(1e5) # sets up keys for 100000 rounds +key_dilution = int(num_rounds**0.5) # dilution default is sqrt num rounds + +# create transaction +online_keyreg = transaction.KeyregTxn( + sender="EW64GC6F24M7NDSC5R3ES4YUVE3ZXXNMARJHDCCCLIHZU6TBEOC7XRSBG4", + votekey=votekey, + selkey=selkey, + votefst=params.first, + votelst=params.first + num_rounds, + votekd=key_dilution, + sp=params, +) +print(online_keyreg.dictify()) +# example: TRANSACTION_KEYREG_ONLINE_CREATE + +# example: TRANSACTION_KEYREG_OFFLINE_CREATE +# get suggested parameters +params = algod_client.suggested_params() + +# create keyreg transaction to take this account offline +offline_keyreg = transaction.KeyregTxn( + sender="EW64GC6F24M7NDSC5R3ES4YUVE3ZXXNMARJHDCCCLIHZU6TBEOC7XRSBG4", + sp=params, + votekey=None, + selkey=None, + votefst=None, + votelst=None, + votekd=None, +) +print(online_keyreg.dictify()) +# example: TRANSACTION_KEYREG_OFFLINE_CREATE diff --git a/_examples/utils.py b/_examples/utils.py new file mode 100644 index 00000000..b15f4427 --- /dev/null +++ b/_examples/utils.py @@ -0,0 +1,92 @@ +from dataclasses import dataclass + +from algosdk.v2client import algod +from algosdk.atomic_transaction_composer import AccountTransactionSigner +from algosdk.kmd import KMDClient +from algosdk.wallet import Wallet + +DEFAULT_KMD_ADDRESS = "http://localhost:4002" +DEFAULT_KMD_TOKEN = "a" * 64 +DEFAULT_KMD_WALLET_NAME = "unencrypted-default-wallet" +DEFAULT_KMD_WALLET_PASSWORD = "" + +DEFAULT_ALGOD_ADDRESS = "http://localhost:4001" +DEFAULT_ALGOD_TOKEN = "a" * 64 + + +def get_algod_client( + addr: str = DEFAULT_ALGOD_ADDRESS, token: str = DEFAULT_ALGOD_TOKEN +) -> algod.AlgodClient: + return algod.AlgodClient(algod_token=token, algod_address=addr) + + +def get_kmd_client() -> KMDClient: + """creates a new kmd client using the default sandbox parameters""" + return KMDClient( + kmd_token=DEFAULT_KMD_TOKEN, kmd_address=DEFAULT_KMD_ADDRESS + ) + + +def get_sandbox_default_wallet() -> Wallet: + """returns the default sandbox kmd wallet""" + return Wallet( + wallet_name=DEFAULT_KMD_WALLET_NAME, + wallet_pswd=DEFAULT_KMD_WALLET_PASSWORD, + kmd_client=get_kmd_client(), + ) + + +@dataclass +class SandboxAccount: + """SandboxAccount is a simple dataclass to hold a sandbox account details""" + + #: The address of a sandbox account + address: str + #: The base64 encoded private key of the account + private_key: str + #: An AccountTransactionSigner that can be used as a TransactionSigner + signer: AccountTransactionSigner + + +def get_accounts( + kmd_address: str = DEFAULT_KMD_ADDRESS, + kmd_token: str = DEFAULT_KMD_TOKEN, + wallet_name: str = DEFAULT_KMD_WALLET_NAME, + wallet_password: str = DEFAULT_KMD_WALLET_PASSWORD, +) -> list[SandboxAccount]: + """gets all the accounts in the sandbox kmd, defaults + to the `unencrypted-default-wallet` created on private networks automatically + """ + + kmd = KMDClient(kmd_token, kmd_address) + wallets = kmd.list_wallets() + + wallet_id = None + for wallet in wallets: + if wallet["name"] == wallet_name: + wallet_id = wallet["id"] + break + + if wallet_id is None: + raise Exception("Wallet not found: {}".format(wallet_name)) + + wallet_handle = kmd.init_wallet_handle(wallet_id, wallet_password) + + try: + addresses = kmd.list_keys(wallet_handle) + private_keys = [ + kmd.export_key(wallet_handle, wallet_password, addr) + for addr in addresses + ] + kmd_accounts = [ + SandboxAccount( + addresses[i], + private_keys[i], + AccountTransactionSigner(private_keys[i]), + ) + for i in range(len(private_keys)) + ] + finally: + kmd.release_wallet_handle(wallet_handle) + + return kmd_accounts From 1b8ad21e372bfbe30bb4b7c7d5c4ec3cb90ff6c5 Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Fri, 17 Mar 2023 19:48:10 -0400 Subject: [PATCH 2/5] minor fix for type annotation in exclude arg (#449) --- algosdk/v2client/algod.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/algosdk/v2client/algod.py b/algosdk/v2client/algod.py index c0ce20ea..90434ee8 100644 --- a/algosdk/v2client/algod.py +++ b/algosdk/v2client/algod.py @@ -128,7 +128,7 @@ def _assert_json_response( ) def account_info( - self, address: str, exclude: Optional[bool] = None, **kwargs: Any + self, address: str, exclude: Optional[str] = None, **kwargs: Any ) -> AlgodResponseType: """ Return account information. From 2a84227c48c41ce2cd229199d6f0608484822e8d Mon Sep 17 00:00:00 2001 From: Barbara Poon Date: Mon, 20 Mar 2023 10:55:26 -0400 Subject: [PATCH 3/5] Bumped version to v2.1.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ddba75fd..6a0dcca2 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ description="Algorand SDK in Python", author="Algorand", author_email="pypiservice@algorand.com", - version="2.1.0", + version="2.1.1", long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/algorand/py-algorand-sdk", From 97eec402aabb45376fc3907563c83a5a38195d48 Mon Sep 17 00:00:00 2001 From: Barbara Poon Date: Mon, 20 Mar 2023 10:57:24 -0400 Subject: [PATCH 4/5] Update Changelog --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d71d2bd..0e02846a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +# v2.1.1 + +## What's Changed +### Bugfixes +* Fix: Minor fix for `exclude` argument in `account_info` by @ahangsu in https://github.com/algorand/py-algorand-sdk/pull/449 +### Enhancements +* Documentation: Adding examples to be pulled in to docs by @barnjamin in https://github.com/algorand/py-algorand-sdk/pull/441 + + +**Full Changelog**: https://github.com/algorand/py-algorand-sdk/compare/v2.1.0...v2.1.1 + # v2.1.0 ## What's Changed From 8a633f4e063cc77356be0092bb08b807197c6f78 Mon Sep 17 00:00:00 2001 From: Barbara Poon Date: Mon, 20 Mar 2023 10:59:48 -0400 Subject: [PATCH 5/5] Remove extra space --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e02846a..1ed464ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,6 @@ ### Enhancements * Documentation: Adding examples to be pulled in to docs by @barnjamin in https://github.com/algorand/py-algorand-sdk/pull/441 - **Full Changelog**: https://github.com/algorand/py-algorand-sdk/compare/v2.1.0...v2.1.1 # v2.1.0