From 31af1d267e9697212afe081868dd1066c926d5ca Mon Sep 17 00:00:00 2001 From: cusma Date: Mon, 5 Dec 2022 09:46:51 +0100 Subject: [PATCH 1/9] CLI refactoring. --- .gitignore | 3 + README.md | 43 +-- src/actions.py | 497 ++++++++++++++++++++++++ src/algorealm.py | 953 +++++++++++------------------------------------ src/constants.py | 68 ++++ src/query.py | 193 ++++++++++ 6 files changed, 1005 insertions(+), 752 deletions(-) create mode 100644 src/actions.py create mode 100644 src/constants.py create mode 100644 src/query.py diff --git a/.gitignore b/.gitignore index b6e4761..b3bf820 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,6 @@ dmypy.json # Pyre type checker .pyre/ + +# AlgoRealm artifacts +*.gtxn diff --git a/README.md b/README.md index 7e5654d..307482c 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,10 @@ When nothing but Entropy was there. Then came the cryptographic Proof, And took it care. -Verifiability of randomness, +Verifiability of Randomness, Since genesis block, Brings Consensus over realm vastness, -So Algorand never fork. +So Algorand shall not fork. ``` ## Become a Majesty of Algorand @@ -30,6 +30,10 @@ The more generous you are, the harder will be to be dethroned. Join [AlgoRealm channel](https://t.me/algorealm)! +## Play with AlgoRealm CLI Web Emulator + +Play [AlgoRealm on CLI web emulator](https://algorealm.vercel.app/console) by [@aorumbayev](https://github.com/aorumbayev). + ## Play with AlgoRealm CLI @@ -59,32 +63,34 @@ AlgoRealm, only generous heart will ever rule over Algorand. (by cusma) Usage: algorealm.py poem - algorealm.py dynasty + algorealm.py dynasty [--test] + algorealm.py claim-majesty (--crown | --sceptre) [--test] + algorealm.py claim-card [--test] + algorealm.py buy-order [--notify] [--test] algorealm.py verify-order - algorealm.py claim-crown - algorealm.py claim-sceptre - algorealm.py claim-card - algorealm.py buy-order [--notify] - algorealm.py sell-card + algorealm.py sell-card [--test] algorealm.py [--help] Commands: poem AlgoRealm's poem. dynasty Print the glorious dynasty of AlgoRealm's Majesties. verify-order Verify the partially signed AlgoRealm Card buy order. - claim-crown Claim the Crown of Entropy, become the Randomic Majesty of Algorand. - claim-sceptre Claim the Sceptre of Proof, become the Verifiable Majesty of Algorand. + claim-majesty Claim the Crown of Entropy or the Sceptre of Proof, become Majesty of Algorand. claim-card Brake the spell and claim the AlgoRealm Card by AlgoWorld. buy-order Place an order for the AlgoRealm Card. sell-card Sell the AlgoRealm Card (paying a 10% royalty). Options: - -n --notify Notify the Seller about your buy order on-chain. - -h --help + -n, --notify Notify the Seller about your buy order on-chain. + -t, --test TestNet mode + -h, --help ``` ⚠️ Keep your `` safe! Although you will only use it on you local machine, is it strongly recommended to make use of a dedicated account just to play AlgoRealm! +> In case you want to give a try, you can play AlgoRealm on TestNet adding `-t` +> to CLI commands. + ### 3. AlgoRealm Dynasty Who are the Majesties of the Algorand realm? @@ -140,11 +146,7 @@ on Block: 14989913 donating: 4 microALGOs to the Rewards Pool. Chose your `` and become part of the Dynasty! Remember that to dethrone the current Majesties you must donate to the Algorand's Rewards Pool more `` than the last donation. ```shell -$ python3 algorealm.py claim-crown -``` - -```shell -$ python3 algorealm.py claim-sceptre +$ python3 claim-majesty (--crown | --sceptre) [--test] ``` ⚠️ Enter the the `` formatting it as: `"word_1 word_2 word_3 ... word_25"` and keep it safe! @@ -162,7 +164,7 @@ Only the generous heart of the [Great Majesty of Algorand](https://github.com/cu The AlgoRealm Card can be claimed **starting from block 16,250,000** using the command `claim-card`: hold strong both the Crown and the Sceptre and keep the throne until there! ```shell -$ python3 algorealm.py claim-card +$ python3 algorealm.py claim-card [--test] ``` ⚠️ Enter the the `` formatting it as: `"word_1 word_2 word_3 ... word_25"` and keep it safe! @@ -174,7 +176,7 @@ As a **Buyer** you can easily place a **buy-order** proposal to the **Seller** u Using the `--notify` option the **Seller** will receive a notification on-chain, being acknowledge about the new buy-order proposal. ```shell -$ python3 algorealm.py buy-order [--notify] +$ python3 algorealm.py buy-order [--notify] [--test] ``` ⚠️ Enter the the `` formatting it as: `"word_1 word_2 word_3 ... word_25"` and keep it safe! @@ -213,7 +215,7 @@ If you agree with the buy-order proposal you can sell the AlgoRealm Special Card As a **Seller**, if you agree with the buy-order proposal, you can sell your AlgoRealm Special Card using the command `sell-card`. ```shell -$ python3 algorealm.py sell-card +$ python3 algorealm.py sell-card [--test] ``` ⚠️ Enter the the `` formatting it as: `"word_1 word_2 word_3 ... word_25"` and keep it safe! @@ -355,7 +357,6 @@ Here you find the [AlgoRealm slide deck](https://docs.google.com/presentation/d/ Join [AlgoRealm channel](https://t.me/algorealm)! - ## ⭐️ Stargazers Special thanks to everyone who forked or starred the repository ❤️ diff --git a/src/actions.py b/src/actions.py new file mode 100644 index 0000000..f8a854a --- /dev/null +++ b/src/actions.py @@ -0,0 +1,497 @@ +import base64 +import math +import sys +import traceback +from getpass import getpass + +import msgpack +from algosdk.account import address_from_private_key +from algosdk.atomic_transaction_composer import ( + AccountTransactionSigner, + AtomicTransactionComposer, + AtomicTransactionResponse, + LogicSigTransactionSigner, + TransactionWithSigner, +) +from algosdk.constants import MNEMONIC_LEN +from algosdk.future.transaction import ( + ApplicationNoOpTxn, + AssetOptInTxn, + AssetTransferTxn, + LogicSigAccount, + LogicSigTransaction, + PaymentTxn, + Transaction, + wait_for_confirmation, + write_to_file, +) +from algosdk.mnemonic import to_private_key +from algosdk.util import microalgos_to_algos +from algosdk.v2client.algod import AlgodClient + +from constants import ( + ASA_STATE_OBSERVER_APP_ID, + CARD_ID, + CROWN_ID, + REWARDS_POOL, + ROYALTY_PERC, + SCEPTRE_ID, +) + + +def get_user() -> AccountTransactionSigner: + """ + Returns: + Algorand User Account + """ + mnemonic_phrase = getpass(prompt="Mnemonic (word_1 word_2 ... word_25):") + try: + assert len(mnemonic_phrase.split()) == MNEMONIC_LEN + except AssertionError: + quit('\n⚠️ Enter mnemonic phrase, formatted as: "word_1 ... word_25"') + return AccountTransactionSigner(to_private_key(mnemonic_phrase)) + + +def get_contract_account(program) -> LogicSigTransactionSigner: + """ + Args: + program: TEAL bytecode + + Returns: + Algorand Contract Account + """ + return LogicSigTransactionSigner( + lsig=LogicSigAccount(base64.decodebytes(program.encode())) + ) + + +def opt_in_algorealm_nft( + client: AlgodClient, + user: AccountTransactionSigner, + nft_id: int, +) -> AtomicTransactionResponse: + """ + Opt-In AlgoRealm NFT + + Args: + client: Algod Client + user: Algorand User Account + nft_id: AlgoRealm NFT ID (Crown, Sceptre, Card) + + Returns: + Execute NFT Opt-In transaction + """ + + user_address = address_from_private_key(user.private_key) + + atc = AtomicTransactionComposer() + params = client.suggested_params() + + opt_in_nft_txn = AssetOptInTxn( + sender=user_address, + sp=params, + index=nft_id, + ) + atc.add_transaction( + TransactionWithSigner( + txn=opt_in_nft_txn, + signer=user, + ) + ) + return atc.execute(client=client, wait_rounds=4) + + +def claim_algorealm_nft( + client: AlgodClient, + algorealm_app_id: int, + algorealm_law: LogicSigTransactionSigner, + user: AccountTransactionSigner, + current_owner: str, + claim_select: str, + majesty_name: str, + donation_amount: int, + nft_id: int, +) -> AtomicTransactionResponse: + """ + Claim AlgoRealm Majesty Title and NFT + + Args: + client: Algod Client + algorealm_app_id: AlgoRealm Application ID + algorealm_law: AlgoRealm Contract Account + user: Algorand User Account + current_owner: Address of current NFT owner (Crown, Sceptre) + claim_select: Title selector (Crown, Sceptre) + majesty_name: New Majesty nickname + donation_amount: New Majesty donation to the Rewards Pool + nft_id: AlgoRealm NFT ID (Crown, Sceptre) + + Returns: + + """ + assert claim_select == "Crown" or claim_select == "Sceptre" + + user_address = address_from_private_key(user.private_key) + + atc = AtomicTransactionComposer() + params = client.suggested_params() + + claim_nft_txn = ApplicationNoOpTxn( + sender=user_address, + sp=params, + index=algorealm_app_id, + app_args=[claim_select.encode(), majesty_name.encode()], + ) + atc.add_transaction( + TransactionWithSigner( + txn=claim_nft_txn, + signer=user, + ) + ) + + donation_txn = PaymentTxn( + sender=user_address, + sp=params, + receiver=REWARDS_POOL, + amt=donation_amount, + ) + atc.add_transaction( + TransactionWithSigner( + txn=donation_txn, + signer=user, + ) + ) + + nft_transfer = AssetTransferTxn( + sender=algorealm_law.lsig.address(), + sp=params, + receiver=user_address, + amt=1, + index=nft_id, + revocation_target=current_owner, + ) + atc.add_transaction( + TransactionWithSigner( + txn=nft_transfer, + signer=algorealm_law, + ) + ) + return atc.execute(client=client, wait_rounds=4) + + +def proof_asa_amount_eq_txn( + client: AlgodClient, + owner_address: str, + asa_id: int, + asa_amount: int, +) -> ApplicationNoOpTxn: + + params = client.suggested_params() + + method = "AsaAmountEq" + + return ApplicationNoOpTxn( + sender=owner_address, + sp=params, + index=ASA_STATE_OBSERVER_APP_ID, + app_args=[method.encode(), asa_amount], + foreign_assets=[asa_id], + accounts=[owner_address], + ) + + +def claim_card( + client: AlgodClient, + card_contract: LogicSigTransactionSigner, + user: AccountTransactionSigner, +) -> AtomicTransactionResponse: + + user_address = address_from_private_key(user.private_key) + + atc = AtomicTransactionComposer() + params = client.suggested_params() + + proof_crown_ownership = proof_asa_amount_eq_txn( + client=client, + owner_address=user_address, + asa_id=CROWN_ID, + asa_amount=1, + ) + atc.add_transaction(TransactionWithSigner(txn=proof_crown_ownership, signer=user)) + + proof_sceptre_ownership = proof_asa_amount_eq_txn( + client=client, + owner_address=user_address, + asa_id=SCEPTRE_ID, + asa_amount=1, + ) + atc.add_transaction(TransactionWithSigner(txn=proof_sceptre_ownership, signer=user)) + + nft_card_xfer = AssetTransferTxn( + sender=card_contract.lsig.address(), + sp=params, + receiver=user_address, + amt=1, + index=CARD_ID, + revocation_target=card_contract.lsig.address(), + ) + atc.add_transaction( + TransactionWithSigner( + txn=nft_card_xfer, + signer=card_contract, + ) + ) + return atc.execute(client=client, wait_rounds=4) + + +def card_order( + client: AlgodClient, + card_contract: LogicSigTransactionSigner, + buyer: AccountTransactionSigner, + seller_address: str, + royalty_collector_1_addr: str, + royalty_collector_2_addr: str, + price: int, +) -> list[Transaction]: + + buyer_address = address_from_private_key(buyer.private_key) + + proof_crown_ownership = proof_asa_amount_eq_txn( + client=client, + owner_address=seller_address, + asa_id=CROWN_ID, + asa_amount=1, + ) + + proof_sceptre_ownership = proof_asa_amount_eq_txn( + client=client, + owner_address=seller_address, + asa_id=SCEPTRE_ID, + asa_amount=1, + ) + + params = client.suggested_params() + + nft_card_payment = PaymentTxn( + sender=buyer_address, + sp=params, + receiver=seller_address, + amt=price, + ) + + royalty_amount = math.ceil(price * ROYALTY_PERC / 100) + + royalty_1_payment = PaymentTxn( + sender=seller_address, + sp=params, + receiver=royalty_collector_1_addr, + amt=royalty_amount, + ) + + royalty_2_payment = PaymentTxn( + sender=seller_address, + sp=params, + receiver=royalty_collector_2_addr, + amt=royalty_amount, + ) + + nft_card_xfer = AssetTransferTxn( + sender=card_contract.lsig.address(), + sp=params, + receiver=buyer_address, + amt=1, + index=CARD_ID, + revocation_target=seller_address, + ) + + trade_gtxn = [ + proof_crown_ownership, + proof_sceptre_ownership, + nft_card_payment, + royalty_1_payment, + royalty_2_payment, + nft_card_xfer, + ] + + sig_nft_card_payment = trade_gtxn[2].sign(buyer.private_key) + sig_nft_card_xfer = LogicSigTransaction(trade_gtxn[5], card_contract.lsig) + trade_gtxn[2] = sig_nft_card_payment + trade_gtxn[5] = sig_nft_card_xfer + write_to_file(trade_gtxn, "trade.gtxn", overwrite=True) + + return trade_gtxn + + +def notify( + client: AlgodClient, + user: AccountTransactionSigner, + seller_address: str, + trade_gtxn: list[Transaction], +) -> AtomicTransactionResponse: + user_address = address_from_private_key(user.private_key) + + atc = AtomicTransactionComposer() + params = client.suggested_params() + + note = { + "buy_order": "AlgoRealm Special Card", + "asset_id": CARD_ID, + "algo_amount": microalgos_to_algos(trade_gtxn[2].amt), + "algo_royalty": microalgos_to_algos(trade_gtxn[3].amt + trade_gtxn[4].amt), + "last_valid_block": trade_gtxn[2].last_valid_round, + } + + bytes_note = msgpack.packb(note) + + notification_txn = PaymentTxn( + sender=user_address, + sp=params, + receiver=seller_address, + amt=0, + note=bytes_note, + ) + atc.add_transaction( + TransactionWithSigner( + txn=notification_txn, + signer=user, + ) + ) + + return atc.execute(client=client, wait_rounds=4) + + +def sell_card( + client: AlgodClient, + user: AccountTransactionSigner, + trade_gtxn: list, +) -> dict: + + signed_crown_proof = trade_gtxn[0].sign(user.private_key) + signed_sceptre_proof = trade_gtxn[1].sign(user.private_key) + signed_royalty_1 = trade_gtxn[3].sign(user.private_key) + signed_royalty_2 = trade_gtxn[4].sign(user.private_key) + + trade_gtxn[0] = signed_crown_proof + trade_gtxn[1] = signed_sceptre_proof + trade_gtxn[3] = signed_royalty_1 + trade_gtxn[4] = signed_royalty_2 + + gtxn_id = client.send_transactions(trade_gtxn) + return wait_for_confirmation(client, gtxn_id, wait_rounds=4) + + +def verify_buy_order( + card_contract: LogicSigTransactionSigner, + seller_address: str, + royalty_collector_1_addr: str, + royalty_collector_2_addr: str, + trade_gtxn: list, +) -> list: + # Check TXN 0: Crown Proof of Ownership + try: + assert trade_gtxn[0].type == "appl" + assert trade_gtxn[0].index == ASA_STATE_OBSERVER_APP_ID + assert trade_gtxn[0].app_args[0] == b"AsaAmountEq" + assert trade_gtxn[0].app_args[1] == b"\x00\x00\x00\x00\x00\x00\x00\x01" + assert trade_gtxn[0].foreign_assets[0] == CROWN_ID + assert trade_gtxn[0].accounts[0] == seller_address + assert trade_gtxn[0].sender == seller_address + assert trade_gtxn[0].fee <= 1000 + assert trade_gtxn[0].rekey_to is None + except AssertionError: + _, _, tb = sys.exc_info() + tb_info = traceback.extract_tb(tb) + filename, line, func, text = tb_info[-1] + quit("Transaction 0 - Crown Proof of Ownership is invalid: {}".format(text)) + + # Check TXN 1: Sceptre Proof of Ownership + try: + assert trade_gtxn[1].type == "appl" + assert trade_gtxn[1].index == ASA_STATE_OBSERVER_APP_ID + assert trade_gtxn[1].app_args[0] == b"AsaAmountEq" + assert trade_gtxn[1].app_args[1] == b"\x00\x00\x00\x00\x00\x00\x00\x01" + assert trade_gtxn[1].foreign_assets[0] == SCEPTRE_ID + assert trade_gtxn[1].accounts[0] == seller_address + assert trade_gtxn[1].sender == seller_address + assert trade_gtxn[1].fee <= 1000 + assert trade_gtxn[1].rekey_to is None + except AssertionError: + _, _, tb = sys.exc_info() + tb_info = traceback.extract_tb(tb) + filename, line, func, text = tb_info[-1] + quit("Transaction 1 - Sceptre Proof of Ownership is invalid: {}".format(text)) + + # Check TXN 2: Card Payment + try: + assert trade_gtxn[2].transaction.type == "pay" + assert trade_gtxn[2].transaction.receiver == seller_address + except AssertionError: + _, _, tb = sys.exc_info() + tb_info = traceback.extract_tb(tb) + filename, line, func, text = tb_info[-1] + quit("Transaction 2 - Card Payment is invalid: {}".format(text)) + + # Check TXN 3: Royalty 1 Payment + try: + assert trade_gtxn[3].type == "pay" + assert trade_gtxn[3].sender == seller_address + assert trade_gtxn[3].receiver == royalty_collector_1_addr + assert trade_gtxn[3].fee <= 1000 + assert trade_gtxn[3].rekey_to is None + except AssertionError: + _, _, tb = sys.exc_info() + tb_info = traceback.extract_tb(tb) + filename, line, func, text = tb_info[-1] + quit("Transaction 3 - Royalty 1 Payment is invalid: {}".format(text)) + + # Check TXN 4: Royalty 3 Payment + try: + assert trade_gtxn[4].type == "pay" + assert trade_gtxn[4].sender == seller_address + assert trade_gtxn[4].receiver == royalty_collector_2_addr + assert trade_gtxn[4].fee <= 1000 + assert trade_gtxn[4].rekey_to is None + except AssertionError: + _, _, tb = sys.exc_info() + tb_info = traceback.extract_tb(tb) + filename, line, func, text = tb_info[-1] + quit("Transaction 4 - Royalty 2 Payment is invalid: {}".format(text)) + + # Check TXN 5: Card Transfer + try: + assert trade_gtxn[5].transaction.type == "axfer" + assert trade_gtxn[5].transaction.index == CARD_ID + assert trade_gtxn[5].transaction.amount == 1 + assert trade_gtxn[5].transaction.sender == card_contract.lsig.address() + assert trade_gtxn[5].transaction.receiver == trade_gtxn[2].transaction.sender + assert trade_gtxn[5].transaction.revocation_target == seller_address + assert trade_gtxn[5].transaction.fee <= 1000 + assert trade_gtxn[5].transaction.rekey_to is None + except AssertionError: + _, _, tb = sys.exc_info() + tb_info = traceback.extract_tb(tb) + filename, line, func, text = tb_info[-1] + quit("Transaction 5 - Card Transfer is invalid: {}".format(text)) + + return trade_gtxn + + +def order_summary(client: AlgodClient, trade_gtxn: list) -> str: + current_round = client.status()["last-round"] + last_valid_round = trade_gtxn[2].transaction.last_valid_round + remaning_rounds = last_valid_round - current_round + if remaning_rounds <= 0: + remaning_rounds = "Buy order expired!" + + return f""" + * =========================== ORDER SUMMARY =========================== * + + BUYER:\t{trade_gtxn[2].transaction.sender} + SELLER:\t{trade_gtxn[2].transaction.receiver} + AMOUNT:\t{trade_gtxn[2].transaction.amt / 10 ** 6} ALGO + ROYALTY:\t{(trade_gtxn[3].amt + trade_gtxn[4].amt) / 10 ** 6} ALGO + + BUY-ORDER VALIDITY REMAINING BLOCKS: {remaning_rounds} + + * ===================================================================== * + """ diff --git a/src/algorealm.py b/src/algorealm.py index ab32fc1..2d4c4a2 100644 --- a/src/algorealm.py +++ b/src/algorealm.py @@ -3,685 +3,81 @@ Usage: algorealm.py poem - algorealm.py dynasty + algorealm.py dynasty [--test] + algorealm.py claim-majesty (--crown | --sceptre) [--test] + algorealm.py claim-card + algorealm.py buy-order [--notify] algorealm.py verify-order - algorealm.py claim-crown - algorealm.py claim-sceptre - algorealm.py claim-card - algorealm.py buy-order [--notify] - algorealm.py sell-card + algorealm.py sell-card algorealm.py [--help] Commands: poem AlgoRealm's poem. dynasty Print the glorious dynasty of AlgoRealm's Majesties. - verify-order Verify the partially signed AlgoRealm Card buy order. - claim-crown Claim the Crown of Entropy, become the Randomic Majesty of Algorand. - claim-sceptre Claim the Sceptre of Proof, become the Verifiable Majesty of Algorand. + claim-majesty Claim the Crown of Entropy or the Sceptre of Proof, become Majesty of Algorand. claim-card Brake the spell and claim the AlgoRealm Card by AlgoWorld. buy-order Place an order for the AlgoRealm Card. + verify-order Verify the partially signed AlgoRealm Card buy order. sell-card Sell the AlgoRealm Card (paying a 10% royalty). Options: - -n --notify Notify the Seller about your buy order on-chain. - -h --help + -n, --notify Notify the Seller about your buy order on-chain. + -t, --test TestNet mode + -h, --help """ -import base64 -import dataclasses -import math -import sys -import time -import traceback - -import msgpack -from algosdk import account, mnemonic, util -from algosdk.error import AlgodHTTPError, IndexerHTTPError -from algosdk.future import transaction -from algosdk.v2client import algod, indexer -from docopt import docopt - - -@dataclasses.dataclass -class Account: - address: str - private_key: str - lsig: transaction.LogicSig = None - - def mnemonic(self) -> str: - return mnemonic.from_private_key(self.private_key) - - def is_lsig(self): - return not self.private_key and self.lsig - - @classmethod - def create_account(cls): - private_key, address = account.generate_account() - return cls(private_key=private_key, address=address) - - -# --- Config -MAX_CONNECTION_ATTEMPTS = 10 -CONNECTION_ATTEMPT_DELAY_SEC = 2 - -INDEXER_ADDRESS = "https://mainnet-idx.algonode.cloud" -ALGOD_ADDRESS = "https://mainnet-api.algonode.cloud" - -REWARDS_POOL = "737777777777777777777777777777777777777777777777777UFEJ2CI" - -ALGOREALM_FIRST_BLOCK = 13578170 -ALGOREALM_APP_ID = 137491307 -CROWN_ID = 137493252 -SCEPTRE_ID = 137494385 -ALGOREALM_LAW_BYTECODE = ( - "AiAIAwbr5sdBAQSE9sdB8f7HQegHJgEg/v////////////////////////////////////" - "////8yBCISMwAQIxIzABgkEhAQMwEQJRIzAQAzAAASEDMBBygSEBAzAhAhBBIzAhQzAQAS" - "EDMCESEFEjMCESEGEhEQMwISJRIQMwIBIQcOEDMCFTIDEhAzAiAyAxIQEA==" -) - -ALGOREALM_LAW_LSIG = transaction.LogicSig( - base64.decodebytes(ALGOREALM_LAW_BYTECODE.encode()) -) - -ALGOREALM_LAW = Account( - address=ALGOREALM_LAW_LSIG.address(), - private_key=None, - lsig=ALGOREALM_LAW_LSIG, -) - -ALGOREALM_CARD_FIRST_BLOCK = 16250000 -ROYALTY_PERC = 5 -ROYALTY_COLLECTOR_1 = Account( - address="H7N65NZIWBOKFDSRNPLLDGN72HVFKXT4RRSY7M66B6Y2PFLQFKLPLHU5JU", private_key="" -) -ROYALTY_COLLECTOR_2 = Account( - address="2PDM3E7WLVPMEKCCMNTHM3FCZNZM4CSJQUOC4SWHMFPAR3N4NXBLCQKHPE", private_key="" -) -ASA_STATE_OBSERVER_APP_ID = 321230622 -CARD_ID = 321172366 -CARD_CONTRACT_BYTECODE = ( - "AyAOAQMGBOgHnq6WmQGE9sdB8f7HQQVkjueSmQGQ6d8HAM7i0wcmAwtBc2FBbW91bnRFcS" - "A/2+63KLBcoo5Ra9axmb/R6lVefIxlj7PeD7GnlXAqliDTxs2T9l1ewihCY2Z2bKLLcs4K" - "SYUcLkrHYV4I7bxtwjIEIhJAAaIyBCMSQAD4MgQkEkAAAQAzABAkEjMBECQSEDMCECISED" - "MDECISEDMEECISEDMFECUSEDMFASEEDhAzBSAyAxIQMwUVMgMSEDMAGCEFEjcAGgAoEhA3" - "ABwBMwAAEhA3ADAAIQYSEDcAGgEiFhIQEDMBGCEFEjcBGgAoEhA3ARwBMwEAEhA3ATAAIQ" - "cSEDcBGgEiFhIQEDMAADMCBxIQMwEAMwIHEhAzAgAzBRQSEDMDADMCBxIQMwMHKRIQMwQA" - "MwIHEhAzBAcqEhAzAwgzBAgSEDMDCDMCCCEICyEJCg8QMwURIQoSEDMFEiISEDMFEzMCBx" - "IQMwUUMwIAEhBCANczABAkEjMBECQSEDMCECUSEDMCASEEDhAzAiAyAxIQMwIVMgMSEDMA" - "GCEFEjcAGgAoEhA3ABwBMwAAEhA3ADAAIQYSEDcAGgEiFhIQEDMBGCEFEjcBGgAoEhA3AR" - "wBMwEAEhA3ATAAIQcSEDcBGgEiFhIQEDMAADMCFBIQMwEAMwIUEhAzAgIhCw0QMwIRIQoS" - "EDMCEiISEDMCADMCExIQQgA0MRAlEjEBIQQOEDETMgMSEDEVMgMSEDEgMgMSEDERIQoSED" - "ESIQwSEDEAMRQSEDEEIQ0MEA==" -) +import sys -CARD_CONTRACT_LSIG = transaction.LogicSig( - base64.decodebytes(CARD_CONTRACT_BYTECODE.encode()) -) +from algosdk import util +from algosdk.error import AlgodHTTPError +from algosdk.future.transaction import retrieve_from_file +from algosdk.v2client.algod import AlgodClient +from algosdk.v2client.indexer import IndexerClient +from docopt import docopt -CARD_CONTRACT = Account( - address=CARD_CONTRACT_LSIG.address(), - private_key=None, - lsig=CARD_CONTRACT_LSIG, +import actions +import query +from constants import ( + ALGOD_ADDRESS, + ALGOREALM_APP_ID, + ALGOREALM_CARD_FIRST_BLOCK, + ALGOREALM_FIRST_BLOCK, + ALGOREALM_LAW_BYTECODE, + CARD_CONTRACT_BYTECODE, + CARD_ID, + CROWN_ID, + HEADER, + INDEXER_ADDRESS, + ROYALTY_COLLECTOR_1, + ROYALTY_COLLECTOR_2, + SCEPTRE_ID, + TEST_ALGOD_ADDRESS, + TEST_ALGOREALM_APP_ID, + TEST_ALGOREALM_FIRST_BLOCK, + TEST_ALGOREALM_LAW_BYTECODE, + TEST_CROWN_ID, + TEST_INDEXER_ADDRESS, + TEST_SCEPTRE_ID, ) -def wait_for_confirmation(client: algod.AlgodClient, txid: str): - """ - Utility function to wait until the transaction is confirmed before - proceeding. - """ - last_round = client.status().get("last-round") - txinfo = client.pending_transaction_info(txid) - - while not txinfo.get("confirmed-round", -1) > 0: - print(f"Waiting for transaction {txid} confirmation.") - last_round += 1 - client.status_after_block(last_round) - txinfo = client.pending_transaction_info(txid) - - print(f"Transaction {txid} confirmed in round {txinfo.get('confirmed-round')}.") - return txinfo - - -def sign(account: Account, txn: transaction.Transaction): - if account.is_lsig(): - return transaction.LogicSigTransaction(txn, account.lsig) - else: - assert account.private_key - return txn.sign(account.private_key) - +def build_algod_client( + api_address: str = ALGOD_ADDRESS, + test: bool = False, +) -> AlgodClient: + if test: + api_address = TEST_ALGOD_ADDRESS + return AlgodClient(algod_token="", algod_address=api_address, headers=HEADER) -def sign_send_wait(algod_client: algod.AlgodClient, account: Account, txn): - """Sign a transaction, submit it, and wait for its confirmation.""" - signed_txn = sign(account, txn) - tx_id = signed_txn.transaction.get_txid() - algod_client.send_transactions([signed_txn]) - wait_for_confirmation(algod_client, tx_id) - return algod_client.pending_transaction_info(tx_id) - -def group_and_sign(signers, txns): - assert len(signers) == len(txns) - - signed_group = [] - gid = transaction.calculate_group_id(txns) - - for signer, t in zip(signers, txns): - t.group = gid - signed_group.append(sign(signer, t)) - - return signed_group - - -def search_algorelm_calls(indexer_client: indexer.IndexerClient): - nexttoken = "" - numtx = 1 - calls = [] - while numtx > 0: - result = indexer_client.search_transactions( - limit=1000, - next_page=nexttoken, - application_id=ALGOREALM_APP_ID, - min_round=ALGOREALM_FIRST_BLOCK, - ) - calls += result["transactions"] - numtx = len(result["transactions"]) - if numtx > 0: - # pointer to the next chunk of requests - nexttoken = result["next-token"] - return calls - - -def search_algorelm_nft_txns(indexer_client: indexer.IndexerClient, nft_id: int): - nexttoken = "" - numtx = 1 - txns = [] - while numtx > 0: - result = indexer_client.search_asset_transactions( - asset_id=nft_id, - limit=1000, - next_page=nexttoken, - txn_type="axfer", - min_round=ALGOREALM_FIRST_BLOCK, - ) - txns += result["transactions"] - numtx = len(result["transactions"]) - if numtx > 0: - # pointer to the next chunk of requests - nexttoken = result["next-token"] - return txns - - -def history(indexer_client: indexer.IndexerClient): - attempts = 1 - algorealm_calls = None - while attempts <= MAX_CONNECTION_ATTEMPTS: - try: - algorealm_calls = search_algorelm_calls(indexer_client) - break - except IndexerHTTPError: - print( - f"Indexer Client connection attempt " - f"{attempts}/{MAX_CONNECTION_ATTEMPTS}" - ) - print("Trying to contact Indexer Client again...") - time.sleep(CONNECTION_ATTEMPT_DELAY_SEC) - finally: - attempts += 1 - if not algorealm_calls: - quit("❌ Unable to connect to Indexer Client. Check your API token!") - - claims_history = [] - name = "" - claim = "" - for call in algorealm_calls: - call_args = call["application-transaction"]["application-args"] - # Check is an NFT claim call - if len(call_args) == 2: - block = call["confirmed-round"] - nft = call_args[0].encode() - donation = call["global-state-delta"][0]["value"]["uint"] - # Check is a different claimer (2 elements in the state delta) - if len(call["global-state-delta"]) == 2: - name = base64.b64decode( - call["global-state-delta"][1]["value"]["bytes"] - ).decode() - if nft == base64.b64encode(b"Crown"): - claim = ( - f"👑 {name} claimed the Crown of Entropy\n" - f"on Block: {block} donating: {donation} microALGOs " - f"to the Rewards Pool.\n\n" - ) - elif nft == base64.b64encode(b"Sceptre"): - claim = ( - f"🪄 {name} claimed the Sceptre of Proof\n" - f"on Block: {block} donating: {donation} microALGOs " - f"to the Rewards Pool.\n\n" - ) - else: - pass - - claims_history += [claim] - - else: - pass - - return claims_history - - -def current_owner(indexer_client: indexer.IndexerClient, nft_id: int): - attempts = 1 - nft_txns = None - while attempts <= MAX_CONNECTION_ATTEMPTS: - try: - nft_txns = search_algorelm_nft_txns(indexer_client, nft_id) - break - except IndexerHTTPError: - print( - f"Indexer Client connection attempt " - f"{attempts}/{MAX_CONNECTION_ATTEMPTS}" - ) - print("Trying to contact Indexer Client again...") - time.sleep(CONNECTION_ATTEMPT_DELAY_SEC) - finally: - attempts += 1 - if not nft_txns: - quit("❌ Unable to connect to Indexer Client. Check your API token!") - - nft_txns.reverse() - for txn in nft_txns: - if txn["asset-transfer-transaction"]["amount"] == 1: - return txn["asset-transfer-transaction"]["receiver"] - - -def opt_in( - algod_client: algod.AlgodClient, - user: Account, - nft_id: int, -): - nft_name = algod_client.asset_info(nft_id)["params"]["name"] - optin = "" - while not optin: - optin = str( - input(f"Do you want to opt-in the {nft_name} (ID: {nft_id})? (Y/n) ") - ) - print("") - if optin.lower() == "y": - params = algod_client.suggested_params() - - opt_in_txn = transaction.AssetOptInTxn( - sender=user.address, - sp=params, - index=nft_id, - ) - return sign_send_wait(algod_client, user, opt_in_txn) - - elif optin.lower() == "n": - return - else: - optin = "" - - -def claim_nft( - algod_client: algod.AlgodClient, - indexer_client: indexer.IndexerClient, - claimer: Account, - claim_arg: str, - new_majesty: str, - donation_amount: int, - nft_id: int, -): - params = algod_client.suggested_params() - - claim_txn = transaction.ApplicationNoOpTxn( - sender=claimer.address, - sp=params, - index=ALGOREALM_APP_ID, - app_args=[claim_arg.encode(), new_majesty.encode()], - ) - - donation_txn = transaction.PaymentTxn( - sender=claimer.address, - sp=params, - receiver=REWARDS_POOL, - amt=donation_amount, - ) - - nft_transfer = transaction.AssetTransferTxn( - sender=ALGOREALM_LAW.address, - sp=params, - receiver=claimer.address, - amt=1, - index=nft_id, - revocation_target=current_owner(indexer_client, nft_id), - ) - - signed_group = group_and_sign( - [claimer, claimer, ALGOREALM_LAW], - [claim_txn, donation_txn, nft_transfer], - ) - - nft_name = algod_client.asset_info(nft_id)["params"]["name"] - - print( - f"Claiming the {nft_name} as {new_majesty}, " - f"donating {donation_amount / 10 ** 6} ALGO...\n" - ) - try: - gtxn_id = algod_client.send_transactions(signed_group) - wait_for_confirmation(algod_client, gtxn_id) - except AlgodHTTPError: - quit( - "\n☹️ Were you too stingy? Only generous hearts will rule over " - "Algorand Realm!\n️" - ) - - -def proof_asa_amount_eq_txn( - algod_client: algod.AlgodClient, - sender: Account, - asa_id: int, - asa_amount: int, -): - params = algod_client.suggested_params() - - proof_txn = transaction.ApplicationNoOpTxn( - sender=sender.address, - sp=params, - index=ASA_STATE_OBSERVER_APP_ID, - app_args=["AsaAmountEq".encode(), asa_amount], - foreign_assets=[asa_id], - accounts=[sender.address], - ) - return proof_txn - - -def claim_card(algod_client: algod.AlgodClient, claimer: Account): - params = algod_client.suggested_params() - - proof_crown_ownership = proof_asa_amount_eq_txn( - algod_client=algod_client, - sender=claimer, - asa_id=CROWN_ID, - asa_amount=1, - ) - - proof_sceptre_ownership = proof_asa_amount_eq_txn( - algod_client=algod_client, - sender=claimer, - asa_id=SCEPTRE_ID, - asa_amount=1, - ) - - nft_card_xfer = transaction.AssetTransferTxn( - sender=CARD_CONTRACT.address, - sp=params, - receiver=claimer.address, - amt=1, - index=CARD_ID, - revocation_target=CARD_CONTRACT.address, - ) - - signed_group = group_and_sign( - [claimer, claimer, CARD_CONTRACT], - [proof_crown_ownership, proof_sceptre_ownership, nft_card_xfer], - ) - - try: - gtxn_id = algod_client.send_transactions(signed_group) - wait_for_confirmation(algod_client, gtxn_id) - except AlgodHTTPError: - quit( - "\nOnly the generous heart of the Great Majesty of Algorand " - "can break the spell!\n" - "Conquer both the 👑 Crown of Entropy and the 🪄 Sceptre " - "of Proof first!\n" - ) - - -def card_order( - algod_client: algod.AlgodClient, - buyer: Account, - seller: Account, - price: int, -): - params = algod_client.suggested_params() - - proof_crown_ownership = proof_asa_amount_eq_txn( - algod_client=algod_client, - sender=seller, - asa_id=CROWN_ID, - asa_amount=1, - ) - - proof_sceptre_ownership = proof_asa_amount_eq_txn( - algod_client=algod_client, - sender=seller, - asa_id=SCEPTRE_ID, - asa_amount=1, - ) - - nft_card_payment = transaction.PaymentTxn( - sender=buyer.address, - sp=params, - receiver=seller.address, - amt=price, - ) - - royalty_amount = math.ceil(price * ROYALTY_PERC / 100) - - royalty_1_payment = transaction.PaymentTxn( - sender=seller.address, - sp=params, - receiver=ROYALTY_COLLECTOR_1.address, - amt=royalty_amount, - ) - - royalty_2_payment = transaction.PaymentTxn( - sender=seller.address, - sp=params, - receiver=ROYALTY_COLLECTOR_2.address, - amt=royalty_amount, - ) - - nft_card_xfer = transaction.AssetTransferTxn( - sender=CARD_CONTRACT.address, - sp=params, - receiver=buyer.address, - amt=1, - index=CARD_ID, - revocation_target=seller.address, - ) - - trade_gtxn = [ - proof_crown_ownership, - proof_sceptre_ownership, - nft_card_payment, - royalty_1_payment, - royalty_2_payment, - nft_card_xfer, - ] - - transaction.assign_group_id(trade_gtxn) - signed_nft_card_payment = trade_gtxn[2].sign(buyer.private_key) - trade_gtxn[2] = signed_nft_card_payment - trade_gtxn[5] = transaction.LogicSigTransaction(trade_gtxn[5], CARD_CONTRACT.lsig) - transaction.write_to_file(trade_gtxn, "trade_raw.gtxn", overwrite=True) - - print("📝 Partially signed trade group transaction saved as: 'trade.gtxn'\n") - - return trade_gtxn - - -def notify( - algod_client: algod.AlgodClient, user: Account, seller: Account, trade_gtxn: list -): - params = algod_client.suggested_params() - - note = { - "buy_order": "AlgoRealm Special Card", - "asset_id": CARD_ID, - "algo_amount": trade_gtxn[2].transaction.amt / 10**6, - "algo_royalty": (trade_gtxn[3].amt + trade_gtxn[4].amt) / 10**6, - "last_valid_block": trade_gtxn[2].transaction.last_valid_round, - } - - bytes_note = msgpack.packb(note) - - notification_txn = transaction.PaymentTxn( - sender=user.address, - sp=params, - receiver=seller.address, - amt=0, - note=bytes_note, - ) - - signed_txn = sign(user, notification_txn) - tx_id = signed_txn.transaction.get_txid() - print("✉️ Sending buy order notification to the Seller...\n") - algod_client.send_transactions([signed_txn]) - wait_for_confirmation(algod_client, tx_id) - print("\n📄 Buy order notification:\n" "https://algoexplorer.io/tx/" + tx_id) - - -def verify_buy_order(seller_address: str): - trade_gtxn = transaction.retrieve_from_file("trade_raw.gtxn") - - # Check TXN 0: Crown Proof of Ownership - try: - assert trade_gtxn[0].type == "appl" - assert trade_gtxn[0].index == ASA_STATE_OBSERVER_APP_ID - assert trade_gtxn[0].app_args[0] == b"AsaAmountEq" - assert trade_gtxn[0].app_args[1] == b"\x00\x00\x00\x00\x00\x00\x00\x01" - assert trade_gtxn[0].foreign_assets[0] == CROWN_ID - assert trade_gtxn[0].accounts[0] == seller_address - assert trade_gtxn[0].sender == seller_address - assert trade_gtxn[0].fee <= 1000 - assert trade_gtxn[0].rekey_to is None - except AssertionError: - _, _, tb = sys.exc_info() - tb_info = traceback.extract_tb(tb) - filename, line, func, text = tb_info[-1] - quit("Transaction 0 - Crown Proof of Ownership is invalid: {}".format(text)) - - # Check TXN 1: Sceptre Proof of Ownership - try: - assert trade_gtxn[1].type == "appl" - assert trade_gtxn[1].index == ASA_STATE_OBSERVER_APP_ID - assert trade_gtxn[1].app_args[0] == b"AsaAmountEq" - assert trade_gtxn[1].app_args[1] == b"\x00\x00\x00\x00\x00\x00\x00\x01" - assert trade_gtxn[1].foreign_assets[0] == SCEPTRE_ID - assert trade_gtxn[1].accounts[0] == seller_address - assert trade_gtxn[1].sender == seller_address - assert trade_gtxn[1].fee <= 1000 - assert trade_gtxn[1].rekey_to is None - except AssertionError: - _, _, tb = sys.exc_info() - tb_info = traceback.extract_tb(tb) - filename, line, func, text = tb_info[-1] - quit("Transaction 1 - Sceptre Proof of Ownership is invalid: {}".format(text)) - - # Check TXN 2: Card Payment - try: - assert trade_gtxn[2].transaction.type == "pay" - assert trade_gtxn[2].transaction.receiver == seller_address - except AssertionError: - _, _, tb = sys.exc_info() - tb_info = traceback.extract_tb(tb) - filename, line, func, text = tb_info[-1] - quit("Transaction 2 - Card Payment is invalid: {}".format(text)) - - # Check TXN 3: Royalty 1 Payment - try: - assert trade_gtxn[3].type == "pay" - assert trade_gtxn[3].sender == seller_address - assert trade_gtxn[3].receiver == ROYALTY_COLLECTOR_1.address - assert trade_gtxn[3].fee <= 1000 - assert trade_gtxn[3].rekey_to is None - except AssertionError: - _, _, tb = sys.exc_info() - tb_info = traceback.extract_tb(tb) - filename, line, func, text = tb_info[-1] - quit("Transaction 3 - Royalty 1 Payment is invalid: {}".format(text)) - - # Check TXN 4: Royalty 3 Payment - try: - assert trade_gtxn[4].type == "pay" - assert trade_gtxn[4].sender == seller_address - assert trade_gtxn[4].receiver == ROYALTY_COLLECTOR_2.address - assert trade_gtxn[4].fee <= 1000 - assert trade_gtxn[4].rekey_to is None - except AssertionError: - _, _, tb = sys.exc_info() - tb_info = traceback.extract_tb(tb) - filename, line, func, text = tb_info[-1] - quit("Transaction 4 - Royalty 2 Payment is invalid: {}".format(text)) - - # Check TXN 5: Card Transfer - try: - assert trade_gtxn[5].transaction.type == "axfer" - assert trade_gtxn[5].transaction.index == CARD_ID - assert trade_gtxn[5].transaction.amount == 1 - assert trade_gtxn[5].transaction.sender == CARD_CONTRACT.address - assert trade_gtxn[5].transaction.receiver == trade_gtxn[2].transaction.sender - assert trade_gtxn[5].transaction.revocation_target == seller_address - assert trade_gtxn[5].transaction.fee <= 1000 - assert trade_gtxn[5].transaction.rekey_to is None - except AssertionError: - _, _, tb = sys.exc_info() - tb_info = traceback.extract_tb(tb) - filename, line, func, text = tb_info[-1] - quit("Transaction 5 - Card Transfer is invalid: {}".format(text)) - - return trade_gtxn - - -def order_summary(algod_client: algod.AlgodClient, trade_gtxn: list): - - current_round = algod_client.status()["last-round"] - last_valid_round = trade_gtxn[2].transaction.last_valid_round - remaning_rounds = last_valid_round - current_round - if remaning_rounds <= 0: - remaning_rounds = "Buy order expired!" - - return f""" - * =========================== ORDER SUMMARY =========================== * - - BUYER:\t{trade_gtxn[2].transaction.sender} - SELLER:\t{trade_gtxn[2].transaction.receiver} - AMOUNT:\t{trade_gtxn[2].transaction.amt / 10 ** 6} ALGO - ROYALTY:\t{(trade_gtxn[3].amt + trade_gtxn[4].amt) / 10 ** 6} ALGO - - BUY-ORDER VALIDITY REMAINING BLOCKS: {remaning_rounds} - - * ===================================================================== * - """ - - -def sell_card(algod_client: algod.AlgodClient, user: Account): - trade_gtxn = transaction.retrieve_from_file("trade_raw.gtxn") - - signed_crown_proof = trade_gtxn[0].sign(user.private_key) - signed_sceptre_proof = trade_gtxn[1].sign(user.private_key) - signed_royalty_1 = trade_gtxn[3].sign(user.private_key) - signed_royalty_2 = trade_gtxn[4].sign(user.private_key) - - trade_gtxn[0] = signed_crown_proof - trade_gtxn[1] = signed_sceptre_proof - trade_gtxn[3] = signed_royalty_1 - trade_gtxn[4] = signed_royalty_2 - - print( - f"🤝 Selling the AlgoRealm Special Card for " - f"{trade_gtxn[2].transaction.amt / 10 ** 6} ALGO:\n" - ) - try: - gtxn_id = algod_client.send_transactions(trade_gtxn) - except AlgodHTTPError: - quit("You must hold the 👑 Crown and the 🪄 Scepter to sell the Card!\n") - else: - return wait_for_confirmation(algod_client, gtxn_id) +def build_indexer_client( + api_address: str = INDEXER_ADDRESS, + test: bool = False, +) -> IndexerClient: + if test: + api_address = TEST_INDEXER_ADDRESS + return IndexerClient(indexer_token="", indexer_address=api_address, headers=HEADER) def title(): @@ -710,10 +106,10 @@ def poem(): | Then came the cryptographic Proof, | | And took it care. | | | - | Verifiability of randomness, | + | Verifiability of Randomness, | | Since genesis block, | | Brings Consensus over realm vastness, | - | So Algorand never fork. | + | So Algorand shall not fork. | _| | (_/___________________(*)___________________/ \\ @@ -735,75 +131,103 @@ def main(): if args["poem"]: return print(poem()) - # Clients - header = {"User-Agent": "algosdk"} - - algod_client = algod.AlgodClient( - algod_token="", algod_address=ALGOD_ADDRESS, headers=header - ) - - indexer_client = indexer.IndexerClient( - indexer_token="", indexer_address=INDEXER_ADDRESS, headers=header - ) - - if args["verify-order"]: - summary = order_summary( - algod_client, verify_buy_order(args[""]) - ) - return print(summary) + # API + algod_client = build_algod_client(test=args["--test"]) + indexer_client = build_indexer_client(test=args["--test"]) + # CLI if args["dynasty"]: + if args["--test"]: + algorealm_app_id = TEST_ALGOREALM_APP_ID + algorealm_first_round = TEST_ALGOREALM_FIRST_BLOCK + else: + algorealm_app_id = ALGOREALM_APP_ID + algorealm_first_round = ALGOREALM_FIRST_BLOCK print( r""" *** DYNASTY *** """ ) - return print(*["\n", *history(indexer_client)]) - - # Checking mnemonic format - try: - assert len(args[""].split()) == 25 - except AssertionError: - quit( - "The mnemonic phrase must contain 25 words, " - 'formatted as: "word_1 word_2 ... word_25"\n' + return print( + *[ + "\n", + *query.history( + client=indexer_client, + algorealm_app_id=algorealm_app_id, + algorealm_first_round=algorealm_first_round, + ), + ] ) - private_key = mnemonic.to_private_key(args[""]) - - user = Account(account.address_from_private_key(private_key), private_key) + if args["claim-majesty"]: + majesty_name = args[""] - if args["claim-crown"]: - opt_in(algod_client, user, CROWN_ID) + if args["--test"]: + algorealm_first_round = TEST_ALGOREALM_FIRST_BLOCK + algorealm_contract = TEST_ALGOREALM_LAW_BYTECODE + algorealm_app_id = TEST_ALGOREALM_APP_ID + else: + algorealm_first_round = ALGOREALM_FIRST_BLOCK + algorealm_contract = ALGOREALM_LAW_BYTECODE + algorealm_app_id = ALGOREALM_APP_ID - name = args[""] + if args["--crown"]: + proclaim = ( + f"\n👑 Glory to {majesty_name}, the Randomic Majesty of Algorand! 🎉\n" + ) + claim_select = "Crown" + if args["--test"]: + nft_id = TEST_CROWN_ID + else: + nft_id = CROWN_ID + else: + proclaim = ( + f"\n🪄 Glory to {majesty_name}, the Verifiable Majesty of Algorand! 🎉\n" + ) + claim_select = "Sceptre" + if args["--test"]: + nft_id = TEST_SCEPTRE_ID + else: + nft_id = SCEPTRE_ID - claim_nft( - algod_client=algod_client, - indexer_client=indexer_client, - claimer=user, - claim_arg="Crown", - new_majesty=name, - donation_amount=int(args[""]), - nft_id=CROWN_ID, + user = actions.get_user() + algorealm_law = actions.get_contract_account(algorealm_contract) + current_owner = query.current_owner( + indexer_client, nft_id, algorealm_first_round ) - print(f"\n👑 Glory to {name}, the Randomic Majesty of Algorand! 🎉\n") - - elif args["claim-sceptre"]: - opt_in(algod_client, user, SCEPTRE_ID) + donation = int(args[""]) + nft_name = algod_client.asset_info(nft_id)["params"]["name"] - name = args[""] + opted_in = False + while not opted_in: + optin_choice = input( + f"Do you want to opt-in the {nft_name} (ID: {nft_id})? (Y/n)" + ) + if optin_choice.lower() == "y": + actions.opt_in_algorealm_nft(algod_client, user, nft_id) + opted_in = True + elif optin_choice.lower() == "n": + opted_in = True - claim_nft( - algod_client=algod_client, - indexer_client=indexer_client, - claimer=user, - claim_arg="Sceptre", - new_majesty=name, - donation_amount=int(args[""]), - nft_id=SCEPTRE_ID, - ) - print(f"\n🪄 Glory to {name}, the Verifiable Majesty of Algorand! 🎉\n") + print(f"Claiming the {nft_name} as donating {donation / 10 ** 6} ALGO...\n") + try: + actions.claim_algorealm_nft( + client=algod_client, + algorealm_app_id=algorealm_app_id, + algorealm_law=algorealm_law, + user=user, + current_owner=current_owner, + claim_select=claim_select, + majesty_name=majesty_name, + donation_amount=donation, + nft_id=nft_id, + ) + return print(proclaim) + except AlgodHTTPError: + quit( + "\n☹️ Were you too stingy? Only generous hearts will rule " + "over Algorand Realm!\n️" + ) elif args["claim-card"]: if algod_client.status()["last-round"] <= ALGOREALM_CARD_FIRST_BLOCK: @@ -812,9 +236,12 @@ def main(): f"{ALGOREALM_CARD_FIRST_BLOCK}... ⏳\n" ) - algorelm_card_contract = algod_client.account_info(CARD_CONTRACT.address) + user = actions.get_user() + card_contract = actions.get_contract_account(CARD_CONTRACT_BYTECODE) + card_contract_info = algod_client.account_info(card_contract.lsig.address()) + nft_name = algod_client.asset_info(CARD_ID)["params"]["name"] - assets = algorelm_card_contract["assets"] + assets = card_contract_info["assets"] card_nft = list(filter(lambda asset: asset["asset-id"] == CARD_ID, assets))[0] @@ -824,37 +251,101 @@ def main(): "The AlgoRealm Special Card has been claimed!\n" ) - opt_in(algod_client, user, CARD_ID) + opted_in = False + while not opted_in: + optin_choice = input( + f"Do you want to opt-in the {nft_name} (ID: {CARD_ID})? (Y/n)" + ) + if optin_choice.lower() == "y": + actions.opt_in_algorealm_nft(algod_client, user, CARD_ID) + opted_in = True + elif optin_choice.lower() == "n": + opted_in = True print("\n✨ Whispering words of wisdom...") - claim_card(algod_client=algod_client, claimer=user) - print( - f"\n � The spell has been broken! " - f"The AlgoRealm Special Card is yours! 🎉\n" - ) + try: + actions.claim_card(algod_client, card_contract, user) + return print( + f"\n � The spell has been broken! " + f"The AlgoRealm Special Card is yours! 🎉\n" + ) + except AlgodHTTPError: + quit( + "\nOnly the generous heart of the Great Majesty of Algorand " + "can break the spell!\n" + "Conquer both the 👑 Crown of Entropy and the 🪄 Sceptre " + "of Proof first!\n" + ) if args["buy-order"]: - opt_in(algod_client, user, CARD_ID) + user = actions.get_user() + card_contract = actions.get_contract_account(CARD_CONTRACT_BYTECODE) + nft_name = algod_client.asset_info(CARD_ID)["params"]["name"] + + opted_in = False + while not opted_in: + optin_choice = input( + f"Do you want to opt-in the {nft_name} (ID: {CARD_ID})? (Y/n)" + ) + if optin_choice.lower() == "y": + actions.opt_in_algorealm_nft(algod_client, user, CARD_ID) + opted_in = True + elif optin_choice.lower() == "n": + opted_in = True amount = int(args[""]) - print(f"✏️ Placing order of: {util.microalgos_to_algos(amount)} ALGO\n") + print(f"✏️ Placing order of: {util.microalgos_to_algos(amount)} ALGO\n") - seller = Account(address=current_owner(indexer_client, CARD_ID), private_key="") + seller_address = query.current_owner( + indexer_client, CARD_ID, algorealm_first_round + ) - trade_gtxn = card_order( - algod_client=algod_client, buyer=user, seller=seller, price=amount + trade_gtxn = actions.card_order( + client=algod_client, + card_contract=card_contract, + buyer=user, + seller_address=seller_address, + royalty_collector_1_addr=ROYALTY_COLLECTOR_1, + royalty_collector_2_addr=ROYALTY_COLLECTOR_2, + price=amount, ) + print("📝 Partially signed trade group transaction saved as: 'trade.gtxn'\n") if args["--notify"]: - notify(algod_client, user, seller, trade_gtxn) + print("✉️ Sending buy order notification to the Seller...\n") + result = actions.notify(algod_client, user, seller_address, trade_gtxn) + tx_id = result.tx_ids[0] + print("\n📄 Buy order notification:\n" "https://algoexplorer.io/tx/" + tx_id) + return print("📦 Send `trade.gtxn` file to the Seller to finalize the trade!\n") - return print( - "\n📦 Send `trade.gtxn` file to the Seller to finalize the trade!\n" + if args["verify-order"]: + card_contract = actions.get_contract_account(CARD_CONTRACT_BYTECODE) + trade_gtxn = retrieve_from_file("trade.gtxn") + + verified_buy_order = actions.verify_buy_order( + card_contract=card_contract, + seller_address=args[""], + royalty_collector_1_addr=ROYALTY_COLLECTOR_1, + royalty_collector_2_addr=ROYALTY_COLLECTOR_2, + trade_gtxn=trade_gtxn, ) + return print(actions.order_summary(algod_client, verified_buy_order)) if args["sell-card"]: - sell_card(algod_client, user) + trade_gtxn = retrieve_from_file("trade.gtxn") + + print( + f"🤝 Selling the AlgoRealm Special Card for " + f"{trade_gtxn[2].transaction.amt / 10 ** 6} ALGO:\n" + ) + + user = actions.get_user() + + try: + return actions.sell_card(algod_client, user, trade_gtxn) + except AlgodHTTPError: + quit("You must hold the 👑 Crown and the 🪄 Scepter to sell the Card!\n") else: quit("\nError: read AlgoRealm '--help'!\n") diff --git a/src/constants.py b/src/constants.py new file mode 100644 index 0000000..f14ce0f --- /dev/null +++ b/src/constants.py @@ -0,0 +1,68 @@ +# NETWORK +HEADER = {"User-Agent": "algosdk"} + +ALGOD_ADDRESS = "https://mainnet-api.algonode.cloud" +INDEXER_ADDRESS = "https://mainnet-idx.algonode.cloud" + +TEST_ALGOD_ADDRESS = "https://testnet-api.algonode.cloud" +TEST_INDEXER_ADDRESS = "https://testnet-idx.algonode.cloud" + +MAX_CONNECTION_ATTEMPTS = 10 +CONNECTION_ATTEMPT_DELAY_SEC = 2 + + +# ADDRESSES +REWARDS_POOL = "737777777777777777777777777777777777777777777777777UFEJ2CI" + +# ALGOREAM +ALGOREALM_FIRST_BLOCK = 13578170 +ALGOREALM_APP_ID = 137491307 +CROWN_ID = 137493252 +SCEPTRE_ID = 137494385 +ALGOREALM_LAW_BYTECODE = ( + "AiAIAwbr5sdBAQSE9sdB8f7HQegHJgEg/v////////////////////////////////////" + "////8yBCISMwAQIxIzABgkEhAQMwEQJRIzAQAzAAASEDMBBygSEBAzAhAhBBIzAhQzAQAS" + "EDMCESEFEjMCESEGEhEQMwISJRIQMwIBIQcOEDMCFTIDEhAzAiAyAxIQEA==" +) + +# TEST ALGOREALM +TEST_ALGOREALM_FIRST_BLOCK = 14739865 +TEST_ALGOREALM_APP_ID = 16258432 +TEST_CROWN_ID = 16258490 +TEST_SCEPTRE_ID = 16258497 +TEST_ALGOREALM_LAW_BYTECODE = ( + "AiAIAwaAq+AHAQS6q+AHwavgB+gHJgEg/v////////////////////////////////////" + "////8yBCISMwAQIxIQMwAYJBIQMwEQJRIQMwAAMwEAEhAzAQcoEhAzAhAhBBIQMwIUMwEA" + "EhAzAhEhBRIzAhEhBhIREDMCEiUSEDMCASEHDhAzAhUyAxIQMwIgMgMSEA==" +) + +# ALGOREALM CARD +ROYALTY_PERC = 5 +ALGOREALM_CARD_FIRST_BLOCK = 16250000 +CARD_ID = 321172366 +ROYALTY_COLLECTOR_1 = "H7N65NZIWBOKFDSRNPLLDGN72HVFKXT4RRSY7M66B6Y2PFLQFKLPLHU5JU" +ROYALTY_COLLECTOR_2 = "2PDM3E7WLVPMEKCCMNTHM3FCZNZM4CSJQUOC4SWHMFPAR3N4NXBLCQKHPE" +CARD_CONTRACT_BYTECODE = ( + "AyAOAQMGBOgHnq6WmQGE9sdB8f7HQQVkjueSmQGQ6d8HAM7i0wcmAwtBc2FBbW91bnRFcS" + "A/2+63KLBcoo5Ra9axmb/R6lVefIxlj7PeD7GnlXAqliDTxs2T9l1ewihCY2Z2bKLLcs4K" + "SYUcLkrHYV4I7bxtwjIEIhJAAaIyBCMSQAD4MgQkEkAAAQAzABAkEjMBECQSEDMCECISED" + "MDECISEDMEECISEDMFECUSEDMFASEEDhAzBSAyAxIQMwUVMgMSEDMAGCEFEjcAGgAoEhA3" + "ABwBMwAAEhA3ADAAIQYSEDcAGgEiFhIQEDMBGCEFEjcBGgAoEhA3ARwBMwEAEhA3ATAAIQ" + "cSEDcBGgEiFhIQEDMAADMCBxIQMwEAMwIHEhAzAgAzBRQSEDMDADMCBxIQMwMHKRIQMwQA" + "MwIHEhAzBAcqEhAzAwgzBAgSEDMDCDMCCCEICyEJCg8QMwURIQoSEDMFEiISEDMFEzMCBx" + "IQMwUUMwIAEhBCANczABAkEjMBECQSEDMCECUSEDMCASEEDhAzAiAyAxIQMwIVMgMSEDMA" + "GCEFEjcAGgAoEhA3ABwBMwAAEhA3ADAAIQYSEDcAGgEiFhIQEDMBGCEFEjcBGgAoEhA3AR" + "wBMwEAEhA3ATAAIQcSEDcBGgEiFhIQEDMAADMCFBIQMwEAMwIUEhAzAgIhCw0QMwIRIQoS" + "EDMCEiISEDMCADMCExIQQgA0MRAlEjEBIQQOEDETMgMSEDEVMgMSEDEgMgMSEDERIQoSED" + "ESIQwSEDEAMRQSEDEEIQ0MEA==" +) + +# TEST ALGOREALM CARD +TEST_ROYALTY_COLLECTOR_1 = "KKXHB5C4QMGQJ4WZQRRXGD7SJF5AKUUJ635U3SNBYKA6IILSJ6YVX2NQQU" +TEST_ROYALTY_COLLECTOR_2 = "HNVDQHZHQ76PDA7VQ54HFFHIYNNYTHZWJSSQVKNWMIDTDPUH7ME5W6CKIE" + +# ASA STATE OBSERVER +ASA_STATE_OBSERVER_APP_ID = 321230622 + +# TEST ASA STATE OBSERVER +TEST_ASA_STATE_OBSERVER_APP_ID = 24123396 diff --git a/src/query.py b/src/query.py new file mode 100644 index 0000000..bffef77 --- /dev/null +++ b/src/query.py @@ -0,0 +1,193 @@ +import base64 +import time + +from algosdk.error import IndexerHTTPError +from algosdk.v2client.indexer import IndexerClient + +from constants import CONNECTION_ATTEMPT_DELAY_SEC, MAX_CONNECTION_ATTEMPTS + + +def algorelm_calls( + client: IndexerClient, + algorealm_app_id: int, + algorealm_first_round: int, +) -> list: + """ + Search for all the Application Calls to AlgoRealm. + + Args: + client: Algorand Indexer Client + algorealm_app_id: AlgoRealm Application ID + algorealm_first_round: AlgoRealm first round on blockchain + + Returns: + List of all the Application Calls to AlgoRealm. + """ + nexttoken = "" + numtx = 1 + calls = [] + while numtx > 0: + result = client.search_transactions( + limit=1000, + next_page=nexttoken, + application_id=algorealm_app_id, + min_round=algorealm_first_round, + ) + calls += result["transactions"] + numtx = len(result["transactions"]) + if numtx > 0: + nexttoken = result["next-token"] + return calls + + +def algorelm_nft_txns( + client: IndexerClient, + nft_id: int, + algorealm_first_round: int, +) -> list: + """ + Search for all the Asset Transfer transactions involving AlgoRealm NFTs. + Args: + client: Algorand Indexer Client + nft_id: AlgoRealm NFT ID (Crown or Sceptre) + algorealm_first_round: AlgoRealm first round on blockchain + + Returns: + List of all the Asset Transfer transactions involving AlgoRealm NFTs. + """ + nexttoken = "" + numtx = 1 + nft_txns = [] + while numtx > 0: + result = client.search_asset_transactions( + asset_id=nft_id, + limit=1000, + next_page=nexttoken, + txn_type="axfer", + min_round=algorealm_first_round, + ) + nft_txns += result["transactions"] + numtx = len(result["transactions"]) + if numtx > 0: + nexttoken = result["next-token"] + return nft_txns + + +def history( + client: IndexerClient, + algorealm_app_id: int, + algorealm_first_round: int, +) -> list: + """ + Retrieve the AlgoRealm Majesties claims history from chain. + + Args: + client: Algorand Indexer Client + algorealm_app_id: AlgoRealm Application ID + algorealm_first_round: AlgoRealm first round on blockchain + + Returns: + Historical list of claims. + """ + attempts = 1 + calls = None + while attempts <= MAX_CONNECTION_ATTEMPTS: + try: + calls = algorelm_calls( + client=client, + algorealm_app_id=algorealm_app_id, + algorealm_first_round=algorealm_first_round, + ) + break + except IndexerHTTPError: + print( + f"Indexer Client connection attempt " + f"{attempts}/{MAX_CONNECTION_ATTEMPTS}" + ) + print("Trying to contact Indexer Client again...") + time.sleep(CONNECTION_ATTEMPT_DELAY_SEC) + finally: + attempts += 1 + if not calls: + quit("❌ Unable to connect to Indexer Client!") + + claims_history = [] + name = "" + claim = "" + for call in calls: + call_args = call["application-transaction"]["application-args"] + # Check is an NFT claim call + if len(call_args) == 2: + block = call["confirmed-round"] + nft = call_args[0].encode() + donation = call["global-state-delta"][0]["value"]["uint"] + # Check is a different claimer (2 elements in the state delta) + if len(call["global-state-delta"]) == 2: + name = base64.b64decode( + call["global-state-delta"][1]["value"]["bytes"] + ).decode() + if nft == base64.b64encode(b"Crown"): + claim = ( + f"👑 {name} claimed the Crown of Entropy\n" + f"on Block: {block} donating: {donation} microALGOs " + f"to the Rewards Pool.\n\n" + ) + elif nft == base64.b64encode(b"Sceptre"): + claim = ( + f"🪄 {name} claimed the Sceptre of Proof\n" + f"on Block: {block} donating: {donation} microALGOs " + f"to the Rewards Pool.\n\n" + ) + else: + pass + + claims_history += [claim] + + else: + pass + + return claims_history + + +def current_owner( + client: IndexerClient, + nft_id: int, + algorealm_first_round: int, +) -> str: + """ + Retrieve the current owner of an NFT. + + Args: + client: Algorand Indexer Client + nft_id: AlgoRealm NFT (Crown, Sceptre, Card) + algorealm_first_round: AlgoRealm first round on blockchain + + Returns: + Address of current NFT owner. + """ + attempts = 1 + nft_txns = None + while attempts <= MAX_CONNECTION_ATTEMPTS: + try: + nft_txns = algorelm_nft_txns( + client=client, + nft_id=nft_id, + algorealm_first_round=algorealm_first_round, + ) + break + except IndexerHTTPError: + print( + f"Indexer Client connection attempt " + f"{attempts}/{MAX_CONNECTION_ATTEMPTS}" + ) + print("Trying to contact Indexer Client again...") + time.sleep(CONNECTION_ATTEMPT_DELAY_SEC) + finally: + attempts += 1 + if not nft_txns: + quit("❌ Unable to connect to Indexer Client!") + + nft_txns.reverse() + for txn in nft_txns: + if txn["asset-transfer-transaction"]["amount"] == 1: + return txn["asset-transfer-transaction"]["receiver"] From 167b27c562ff583199cf1d750aafe337967f652e Mon Sep 17 00:00:00 2001 From: cusma Date: Mon, 5 Dec 2022 09:53:33 +0100 Subject: [PATCH 2/9] Renaming `constants` module. --- src/actions.py | 2 +- src/algorealm.py | 2 +- src/{constants.py => const.py} | 0 src/query.py | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename src/{constants.py => const.py} (100%) diff --git a/src/actions.py b/src/actions.py index f8a854a..bb24319 100644 --- a/src/actions.py +++ b/src/actions.py @@ -29,7 +29,7 @@ from algosdk.util import microalgos_to_algos from algosdk.v2client.algod import AlgodClient -from constants import ( +from const import ( ASA_STATE_OBSERVER_APP_ID, CARD_ID, CROWN_ID, diff --git a/src/algorealm.py b/src/algorealm.py index 2d4c4a2..b5b7f51 100644 --- a/src/algorealm.py +++ b/src/algorealm.py @@ -38,7 +38,7 @@ import actions import query -from constants import ( +from const import ( ALGOD_ADDRESS, ALGOREALM_APP_ID, ALGOREALM_CARD_FIRST_BLOCK, diff --git a/src/constants.py b/src/const.py similarity index 100% rename from src/constants.py rename to src/const.py diff --git a/src/query.py b/src/query.py index bffef77..f971fab 100644 --- a/src/query.py +++ b/src/query.py @@ -4,7 +4,7 @@ from algosdk.error import IndexerHTTPError from algosdk.v2client.indexer import IndexerClient -from constants import CONNECTION_ATTEMPT_DELAY_SEC, MAX_CONNECTION_ATTEMPTS +from const import CONNECTION_ATTEMPT_DELAY_SEC, MAX_CONNECTION_ATTEMPTS def algorelm_calls( From ffbaabf341c38f0d4a6880c1d73b351c7ccb6859 Mon Sep 17 00:00:00 2001 From: cusma Date: Mon, 5 Dec 2022 11:00:22 +0100 Subject: [PATCH 3/9] Add comments to AlgoRealm TEAL contracts --- src/contracts/algorealm_approval.teal | 75 +++++++++++++++++++++++++++ src/contracts/algorealm_clear.teal | 2 + src/contracts/algorealm_law.teal | 19 +++++++ 3 files changed, 96 insertions(+) diff --git a/src/contracts/algorealm_approval.teal b/src/contracts/algorealm_approval.teal index 4b5a84f..e0b43a5 100644 --- a/src/contracts/algorealm_approval.teal +++ b/src/contracts/algorealm_approval.teal @@ -1,63 +1,103 @@ +// AlgoRealm Application #pragma version 2 + +// If is an App Creation txn ApplicationID int 0 == +// Then jump to `algorealm_creation` bnz algorealm_creation + +// Else jump to `algorealm` txn OnCompletion int NoOp == bnz algorealm + +// Reject in any other case int 0 return + algorealm_creation: +// Initialize the `RandomicMajestyOfAlgorand` byte "RandomicMajestyOfAlgorand" byte "Silvio" app_global_put + +// Initialize the `VerifiableMajestyOfAlgorand` byte "VerifiableMajestyOfAlgorand" byte "Silvio" app_global_put + +// Initialize the `CrownOfEntropyDonation` byte "CrownOfEntropyDonation" int 0 app_global_put + +// Initialize the `SceptreOfProofDonation` byte "SceptreOfProofDonation" int 0 app_global_put + +// Approve int 1 return b end_algorealm + algorealm: +// If is a single txn global GroupSize int 1 == +// Then jump to `algorealm_law` setup bnz algorealm_law + +// Else if a groupo of 3 txns global GroupSize int 3 == +// Then jump to `algorealm_claims` bnz algorealm_claims + +// Reject in any other case int 0 return b end_algorealm + algorealm_law: +// Check if AlgoRealmLaw exists int 0 byte "AlgoRealmLaw" app_global_get_ex store 0 store 1 + +// If AlgoRealmLaw has been already promulgated load 0 +// Then jump to `promulgate_law_failure` bnz promulgate_law_failure + +// Else promulgate the AlgoRealmLaw byte "AlgoRealmLaw" txna ApplicationArgs 0 app_global_put b promulgate_law + promulgate_law_failure: +// Reject int 0 return b end_algorealm + promulgate_law: +// Approve int 1 return b end_algorealm + + algorealm_claims: +// If the first txn is an AppCall with 2 args gtxn 0 TypeEnum int appl == @@ -65,10 +105,14 @@ gtxn 0 NumAppArgs int 2 == && + +// And the second txn is a Payment donation gtxn 1 TypeEnum int pay == && + +// And the thirs txn must is an NFT AssetTransfer executed by the AlgoRealmLaw gtxn 2 TypeEnum int axfer == @@ -78,57 +122,88 @@ byte "AlgoRealmLaw" app_global_get == && + +// Then jump to `claims` bnz claims + +// Reject in any other case int 0 return b end_algorealm + claims: +// If first arg is "Crown" gtxna 0 ApplicationArgs 0 byte "Crown" == +// The jump to `claim_crown` bnz claim_crown + +// If first arg is "Sceptre" gtxna 0 ApplicationArgs 0 byte "Sceptre" == +// The jump to `claim_sceptre` bnz claim_sceptre + +// Reject in any other case int 0 return b end_algorealm + claim_crown: +// If `CrownOfEntropyDonation` is greater than previous one gtxn 1 Amount byte "CrownOfEntropyDonation" app_global_get > +// Then jump to `randomic_majesty` bnz randomic_majesty + +// Else reject int 0 return b end_algorealm + randomic_majesty: +// Set the `RandomicMajestyOfAlgorand` name to second args byte "RandomicMajestyOfAlgorand" gtxna 0 ApplicationArgs 1 app_global_put +// Set the `CrownOfEntropyDonation` to current donation byte "CrownOfEntropyDonation" gtxn 1 Amount app_global_put +// Approve int 1 return b end_algorealm + claim_sceptre: +// If `SceptreOfProofDonation` is greater than previous one gtxn 1 Amount byte "SceptreOfProofDonation" app_global_get > +// Then jump to `verifiable_majesty` bnz verifiable_majesty + +// Else reject int 0 return b end_algorealm + verifiable_majesty: +// Set the `VerifiableMajestyOfAlgorand` name to second args byte "VerifiableMajestyOfAlgorand" gtxna 0 ApplicationArgs 1 app_global_put +// Set the `SceptreOfProofDonation` to current donation byte "SceptreOfProofDonation" gtxn 1 Amount app_global_put +// Approve int 1 return + end_algorealm: diff --git a/src/contracts/algorealm_clear.teal b/src/contracts/algorealm_clear.teal index 6e0d8b8..0d67779 100644 --- a/src/contracts/algorealm_clear.teal +++ b/src/contracts/algorealm_clear.teal @@ -1,2 +1,4 @@ +// AlgoRealm Application #pragma version 2 +// Approve int 1 diff --git a/src/contracts/algorealm_law.teal b/src/contracts/algorealm_law.teal index 6343343..f615315 100644 --- a/src/contracts/algorealm_law.teal +++ b/src/contracts/algorealm_law.teal @@ -1,54 +1,73 @@ +// AlgoRealm Law #pragma version 2 + +// Must be a Group of 3 txns global GroupSize int 3 == + +// First txn must be an AppCall gtxn 0 TypeEnum int appl == +// To the AlgoRealm App gtxn 0 ApplicationID int 137491307 == && && + +// Second txn must be a Payment donation gtxn 1 TypeEnum int pay == +// From the AlgoRealm App caller gtxn 1 Sender gtxn 0 Sender == && +// To the Rewards Pool gtxn 1 Receiver addr 737777777777777777777777777777777777777777777777777UFEJ2CI == && && + +// Third txn must be the NFT AssetTransfer gtxn 2 TypeEnum int axfer == +// To the AlgoRealm donor gtxn 2 AssetReceiver gtxn 1 Sender == && +// Either of the Crown of Entropy gtxn 2 XferAsset int 137493252 == +// Or of the Sceptre of Proof gtxn 2 XferAsset int 137494385 == || && +// The transfer is of a unique NFT gtxn 2 AssetAmount int 1 == && +// Paying at most 1000 microAlgos as txn fee gtxn 2 Fee int 1000 <= && +// With no AssetCloseTo gtxn 2 AssetCloseTo global ZeroAddress == && +// With no RekeyTo gtxn 2 RekeyTo global ZeroAddress == From 1b6c63c83286ffbf17300dbd6918107f9792588b Mon Sep 17 00:00:00 2001 From: cusma Date: Mon, 5 Dec 2022 11:08:02 +0100 Subject: [PATCH 4/9] TEAL comments nit. --- src/contracts/algorealm_approval.teal | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/contracts/algorealm_approval.teal b/src/contracts/algorealm_approval.teal index e0b43a5..d1886e8 100644 --- a/src/contracts/algorealm_approval.teal +++ b/src/contracts/algorealm_approval.teal @@ -95,9 +95,8 @@ int 1 return b end_algorealm - algorealm_claims: -// If the first txn is an AppCall with 2 args +// First txn must be an AppCall with 2 args gtxn 0 TypeEnum int appl == @@ -106,13 +105,13 @@ int 2 == && -// And the second txn is a Payment donation +// Second txn must be a Payment donation gtxn 1 TypeEnum int pay == && -// And the thirs txn must is an NFT AssetTransfer executed by the AlgoRealmLaw +// Third txn must be an NFT AssetTransfer executed by the AlgoRealmLaw gtxn 2 TypeEnum int axfer == @@ -123,7 +122,7 @@ app_global_get == && -// Then jump to `claims` +// If so, jump to `claims` bnz claims // Reject in any other case From 1394ab0997041a324d6ba607bd4ab4fd39677faa Mon Sep 17 00:00:00 2001 From: cusma Date: Mon, 5 Dec 2022 13:25:25 +0100 Subject: [PATCH 5/9] TEAL comments nit. --- src/contracts/algorealm_approval.teal | 330 +++++++++++++------------- 1 file changed, 165 insertions(+), 165 deletions(-) diff --git a/src/contracts/algorealm_approval.teal b/src/contracts/algorealm_approval.teal index d1886e8..d167da8 100644 --- a/src/contracts/algorealm_approval.teal +++ b/src/contracts/algorealm_approval.teal @@ -19,190 +19,190 @@ int 0 return algorealm_creation: -// Initialize the `RandomicMajestyOfAlgorand` -byte "RandomicMajestyOfAlgorand" -byte "Silvio" -app_global_put - -// Initialize the `VerifiableMajestyOfAlgorand` -byte "VerifiableMajestyOfAlgorand" -byte "Silvio" -app_global_put - -// Initialize the `CrownOfEntropyDonation` -byte "CrownOfEntropyDonation" -int 0 -app_global_put - -// Initialize the `SceptreOfProofDonation` -byte "SceptreOfProofDonation" -int 0 -app_global_put - -// Approve -int 1 -return -b end_algorealm + // Initialize the `RandomicMajestyOfAlgorand` + byte "RandomicMajestyOfAlgorand" + byte "Silvio" + app_global_put + + // Initialize the `VerifiableMajestyOfAlgorand` + byte "VerifiableMajestyOfAlgorand" + byte "Silvio" + app_global_put + + // Initialize the `CrownOfEntropyDonation` + byte "CrownOfEntropyDonation" + int 0 + app_global_put + + // Initialize the `SceptreOfProofDonation` + byte "SceptreOfProofDonation" + int 0 + app_global_put + + // Approve + int 1 + return + b end_algorealm algorealm: -// If is a single txn -global GroupSize -int 1 -== -// Then jump to `algorealm_law` setup -bnz algorealm_law - -// Else if a groupo of 3 txns -global GroupSize -int 3 -== -// Then jump to `algorealm_claims` -bnz algorealm_claims - -// Reject in any other case -int 0 -return -b end_algorealm + // If is a single txn + global GroupSize + int 1 + == + // Then jump to `algorealm_law` setup + bnz algorealm_law + + // Else if a groupo of 3 txns + global GroupSize + int 3 + == + // Then jump to `algorealm_claims` + bnz algorealm_claims + + // Reject in any other case + int 0 + return + b end_algorealm algorealm_law: -// Check if AlgoRealmLaw exists -int 0 -byte "AlgoRealmLaw" -app_global_get_ex -store 0 -store 1 - -// If AlgoRealmLaw has been already promulgated -load 0 -// Then jump to `promulgate_law_failure` -bnz promulgate_law_failure - -// Else promulgate the AlgoRealmLaw -byte "AlgoRealmLaw" -txna ApplicationArgs 0 -app_global_put -b promulgate_law + // Check if AlgoRealmLaw exists + int 0 + byte "AlgoRealmLaw" + app_global_get_ex + store 0 + store 1 + + // If AlgoRealmLaw has been already promulgated + load 0 + // Then jump to `promulgate_law_failure` + bnz promulgate_law_failure + + // Else promulgate the AlgoRealmLaw + byte "AlgoRealmLaw" + txna ApplicationArgs 0 + app_global_put + b promulgate_law promulgate_law_failure: -// Reject -int 0 -return -b end_algorealm + // Reject + int 0 + return + b end_algorealm promulgate_law: -// Approve -int 1 -return -b end_algorealm + // Approve + int 1 + return + b end_algorealm algorealm_claims: -// First txn must be an AppCall with 2 args -gtxn 0 TypeEnum -int appl -== -gtxn 0 NumAppArgs -int 2 -== -&& - -// Second txn must be a Payment donation -gtxn 1 TypeEnum -int pay -== -&& - -// Third txn must be an NFT AssetTransfer executed by the AlgoRealmLaw -gtxn 2 TypeEnum -int axfer -== -&& -gtxn 2 Sender -byte "AlgoRealmLaw" -app_global_get -== -&& - -// If so, jump to `claims` -bnz claims - -// Reject in any other case -int 0 -return -b end_algorealm + // First txn must be an AppCall with 2 args + gtxn 0 TypeEnum + int appl + == + gtxn 0 NumAppArgs + int 2 + == + && + + // Second txn must be a Payment donation + gtxn 1 TypeEnum + int pay + == + && + + // Third txn must be an NFT AssetTransfer executed by the AlgoRealmLaw + gtxn 2 TypeEnum + int axfer + == + && + gtxn 2 Sender + byte "AlgoRealmLaw" + app_global_get + == + && + + // If so, jump to `claims` + bnz claims + + // Reject in any other case + int 0 + return + b end_algorealm claims: -// If first arg is "Crown" -gtxna 0 ApplicationArgs 0 -byte "Crown" -== -// The jump to `claim_crown` -bnz claim_crown - -// If first arg is "Sceptre" -gtxna 0 ApplicationArgs 0 -byte "Sceptre" -== -// The jump to `claim_sceptre` -bnz claim_sceptre - -// Reject in any other case -int 0 -return -b end_algorealm + // If first arg is "Crown" + gtxna 0 ApplicationArgs 0 + byte "Crown" + == + // The jump to `claim_crown` + bnz claim_crown + + // If first arg is "Sceptre" + gtxna 0 ApplicationArgs 0 + byte "Sceptre" + == + // The jump to `claim_sceptre` + bnz claim_sceptre + + // Reject in any other case + int 0 + return + b end_algorealm claim_crown: -// If `CrownOfEntropyDonation` is greater than previous one -gtxn 1 Amount -byte "CrownOfEntropyDonation" -app_global_get -> -// Then jump to `randomic_majesty` -bnz randomic_majesty - -// Else reject -int 0 -return -b end_algorealm + // If `CrownOfEntropyDonation` is greater than previous one + gtxn 1 Amount + byte "CrownOfEntropyDonation" + app_global_get + > + // Then jump to `randomic_majesty` + bnz randomic_majesty + + // Else reject + int 0 + return + b end_algorealm randomic_majesty: -// Set the `RandomicMajestyOfAlgorand` name to second args -byte "RandomicMajestyOfAlgorand" -gtxna 0 ApplicationArgs 1 -app_global_put -// Set the `CrownOfEntropyDonation` to current donation -byte "CrownOfEntropyDonation" -gtxn 1 Amount -app_global_put -// Approve -int 1 -return -b end_algorealm + // Set the `RandomicMajestyOfAlgorand` name to second args + byte "RandomicMajestyOfAlgorand" + gtxna 0 ApplicationArgs 1 + app_global_put + // Set the `CrownOfEntropyDonation` to current donation + byte "CrownOfEntropyDonation" + gtxn 1 Amount + app_global_put + // Approve + int 1 + return + b end_algorealm claim_sceptre: -// If `SceptreOfProofDonation` is greater than previous one -gtxn 1 Amount -byte "SceptreOfProofDonation" -app_global_get -> -// Then jump to `verifiable_majesty` -bnz verifiable_majesty - -// Else reject -int 0 -return -b end_algorealm + // If `SceptreOfProofDonation` is greater than previous one + gtxn 1 Amount + byte "SceptreOfProofDonation" + app_global_get + > + // Then jump to `verifiable_majesty` + bnz verifiable_majesty + + // Else reject + int 0 + return + b end_algorealm verifiable_majesty: -// Set the `VerifiableMajestyOfAlgorand` name to second args -byte "VerifiableMajestyOfAlgorand" -gtxna 0 ApplicationArgs 1 -app_global_put -// Set the `SceptreOfProofDonation` to current donation -byte "SceptreOfProofDonation" -gtxn 1 Amount -app_global_put -// Approve -int 1 -return + // Set the `VerifiableMajestyOfAlgorand` name to second args + byte "VerifiableMajestyOfAlgorand" + gtxna 0 ApplicationArgs 1 + app_global_put + // Set the `SceptreOfProofDonation` to current donation + byte "SceptreOfProofDonation" + gtxn 1 Amount + app_global_put + // Approve + int 1 + return end_algorealm: From 227b4b6bc63b10250c4a785b9f16e90a01569626 Mon Sep 17 00:00:00 2001 From: cusma Date: Mon, 5 Dec 2022 17:34:47 +0100 Subject: [PATCH 6/9] Nit, minor fix. --- README.md | 4 ++-- src/algorealm.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 307482c..7b99b09 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,7 @@ $ python3 algorealm.py claim-card [--test] As a **Buyer** you can easily place a **buy-order** proposal to the **Seller** using the `buy-order` command. You just need to choose the `` amount for the buy order proposal. -Using the `--notify` option the **Seller** will receive a notification on-chain, being acknowledge about the new buy-order proposal. +Using the `--notify` option the **Seller** will receive a notification on-chain, being acknowledged about the new buy-order proposal. ```shell $ python3 algorealm.py buy-order [--notify] [--test] @@ -181,7 +181,7 @@ $ python3 algorealm.py buy-order [--notify] [--test] ⚠️ Enter the the `` formatting it as: `"word_1 word_2 word_3 ... word_25"` and keep it safe! -As result, a *Partially Signed Trade Group Transaction* is created as `trade.gtx` file in the `algorealm.py` CLI directory. Note that there is **no counter-party risk** in this operation: as a **Buyer** you can safely send the `trade.gtxn` file to the **Seller**, being sure that the trade will be executed **if and only if** the Seller will transfers the AlgoRealm Special Card to you. +As result, a *Partially Signed Trade Group Transaction* is created as `trade.gtx` file in the `algorealm.py` CLI directory. Note that there is **no counter-party risk** in this operation: as a **Buyer** you can safely send the `trade.gtxn` file to the **Seller**, being sure that the trade will be executed **if and only if** the Seller will transfer the AlgoRealm Special Card to you. ### 7. Verify a buy-order diff --git a/src/algorealm.py b/src/algorealm.py index b5b7f51..d75bd48 100644 --- a/src/algorealm.py +++ b/src/algorealm.py @@ -298,7 +298,7 @@ def main(): print(f"✏️ Placing order of: {util.microalgos_to_algos(amount)} ALGO\n") seller_address = query.current_owner( - indexer_client, CARD_ID, algorealm_first_round + indexer_client, CARD_ID, ALGOREALM_CARD_FIRST_BLOCK ) trade_gtxn = actions.card_order( From d6bd9d4aa4e3e4c4c3b79474ee14dfd6bb1db954 Mon Sep 17 00:00:00 2001 From: cusma Date: Mon, 5 Dec 2022 18:35:22 +0100 Subject: [PATCH 7/9] Nit. --- README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 7b99b09..1353f34 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ Chose your `` and become part of the Dynasty! Remember that to det $ python3 claim-majesty (--crown | --sceptre) [--test] ``` -⚠️ Enter the the `` formatting it as: `"word_1 word_2 word_3 ... word_25"` and keep it safe! +⚠️ Enter the the `mnemonic` formatting it as: `"word_1 word_2 word_3 ... word_25"` and keep it safe! ### 5. Claim the AlgoRealm Special Card @@ -164,22 +164,22 @@ Only the generous heart of the [Great Majesty of Algorand](https://github.com/cu The AlgoRealm Card can be claimed **starting from block 16,250,000** using the command `claim-card`: hold strong both the Crown and the Sceptre and keep the throne until there! ```shell -$ python3 algorealm.py claim-card [--test] +$ python3 algorealm.py claim-card [--test] ``` -⚠️ Enter the the `` formatting it as: `"word_1 word_2 word_3 ... word_25"` and keep it safe! +⚠️ Enter the the `mnemonic` formatting it as: `"word_1 word_2 word_3 ... word_25"` and keep it safe! ### 6. Place a buy-order As a **Buyer** you can easily place a **buy-order** proposal to the **Seller** using the `buy-order` command. You just need to choose the `` amount for the buy order proposal. -Using the `--notify` option the **Seller** will receive a notification on-chain, being acknowledged about the new buy-order proposal. +Using the `--notify` option the **Seller** will receive a notification on-chain, being acknowledged about the new buy-order proposal. ```shell $ python3 algorealm.py buy-order [--notify] [--test] ``` -⚠️ Enter the the `` formatting it as: `"word_1 word_2 word_3 ... word_25"` and keep it safe! +⚠️ Enter the the `mnemonic` formatting it as: `"word_1 word_2 word_3 ... word_25"` and keep it safe! As result, a *Partially Signed Trade Group Transaction* is created as `trade.gtx` file in the `algorealm.py` CLI directory. Note that there is **no counter-party risk** in this operation: as a **Buyer** you can safely send the `trade.gtxn` file to the **Seller**, being sure that the trade will be executed **if and only if** the Seller will transfer the AlgoRealm Special Card to you. @@ -190,7 +190,7 @@ As a **Seller** you can review and verify the buy-order proposal, validating the The `verify-order` command requires your `` as argument. ```shell -$ python3 algorealm.py verify-order +$ python3 algorealm.py verify-order ``` Some compliancy checks are performed over the `trade.gtx` file before displaying the buy-order summary: @@ -218,8 +218,7 @@ As a **Seller**, if you agree with the buy-order proposal, you can sell your Alg $ python3 algorealm.py sell-card [--test] ``` -⚠️ Enter the the `` formatting it as: `"word_1 word_2 word_3 ... word_25"` and keep it safe! - +⚠️ Enter the the `mnemonic` formatting it as: `"word_1 word_2 word_3 ... word_25"` and keep it safe! ## Play with goal CLI From a619055908145dd3a0ee49c13bb127ee053a4eee Mon Sep 17 00:00:00 2001 From: cusma Date: Mon, 5 Dec 2022 18:37:56 +0100 Subject: [PATCH 8/9] Fix CLI usage in README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1353f34..c093951 100644 --- a/README.md +++ b/README.md @@ -65,19 +65,19 @@ Usage: algorealm.py poem algorealm.py dynasty [--test] algorealm.py claim-majesty (--crown | --sceptre) [--test] - algorealm.py claim-card [--test] - algorealm.py buy-order [--notify] [--test] + algorealm.py claim-card + algorealm.py buy-order [--notify] algorealm.py verify-order - algorealm.py sell-card [--test] + algorealm.py sell-card algorealm.py [--help] Commands: poem AlgoRealm's poem. dynasty Print the glorious dynasty of AlgoRealm's Majesties. - verify-order Verify the partially signed AlgoRealm Card buy order. claim-majesty Claim the Crown of Entropy or the Sceptre of Proof, become Majesty of Algorand. claim-card Brake the spell and claim the AlgoRealm Card by AlgoWorld. buy-order Place an order for the AlgoRealm Card. + verify-order Verify the partially signed AlgoRealm Card buy order. sell-card Sell the AlgoRealm Card (paying a 10% royalty). Options: From 1d238e4f677469fdb35eed39b51bb0e9a2dd80ef Mon Sep 17 00:00:00 2001 From: cusma Date: Mon, 5 Dec 2022 18:52:31 +0100 Subject: [PATCH 9/9] Bump project version. --- poetry.lock | 159 ++++++++++++++++++++++++++++++++++++++++++++++--- pyproject.toml | 4 +- 2 files changed, 153 insertions(+), 10 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1ab4377..8df619a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -152,16 +152,159 @@ content-hash = "05926e82d4f7483312cd284ec6467857bdd935dbdd66ff52b5a3573867adae85 [metadata.files] black = [] -cffi = [] -click = [] +cffi = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] +click = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] colorama = [] -docopt = [] -msgpack = [] -mypy-extensions = [] +docopt = [ + {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, +] +msgpack = [ + {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250"}, + {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:112b0f93202d7c0fef0b7810d465fde23c746a2d482e1e2de2aafd2ce1492c88"}, + {file = "msgpack-1.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:002b5c72b6cd9b4bafd790f364b8480e859b4712e91f43014fe01e4f957b8467"}, + {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35bc0faa494b0f1d851fd29129b2575b2e26d41d177caacd4206d81502d4c6a6"}, + {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4733359808c56d5d7756628736061c432ded018e7a1dff2d35a02439043321aa"}, + {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb514ad14edf07a1dbe63761fd30f89ae79b42625731e1ccf5e1f1092950eaa6"}, + {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c23080fdeec4716aede32b4e0ef7e213c7b1093eede9ee010949f2a418ced6ba"}, + {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:49565b0e3d7896d9ea71d9095df15b7f75a035c49be733051c34762ca95bbf7e"}, + {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aca0f1644d6b5a73eb3e74d4d64d5d8c6c3d577e753a04c9e9c87d07692c58db"}, + {file = "msgpack-1.0.4-cp310-cp310-win32.whl", hash = "sha256:0dfe3947db5fb9ce52aaea6ca28112a170db9eae75adf9339a1aec434dc954ef"}, + {file = "msgpack-1.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dea20515f660aa6b7e964433b1808d098dcfcabbebeaaad240d11f909298075"}, + {file = "msgpack-1.0.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e83f80a7fec1a62cf4e6c9a660e39c7f878f603737a0cdac8c13131d11d97f52"}, + {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c11a48cf5e59026ad7cb0dc29e29a01b5a66a3e333dc11c04f7e991fc5510a9"}, + {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1276e8f34e139aeff1c77a3cefb295598b504ac5314d32c8c3d54d24fadb94c9"}, + {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c9566f2c39ccced0a38d37c26cc3570983b97833c365a6044edef3574a00c08"}, + {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fcb8a47f43acc113e24e910399376f7277cf8508b27e5b88499f053de6b115a8"}, + {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:76ee788122de3a68a02ed6f3a16bbcd97bc7c2e39bd4d94be2f1821e7c4a64e6"}, + {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:0a68d3ac0104e2d3510de90a1091720157c319ceeb90d74f7b5295a6bee51bae"}, + {file = "msgpack-1.0.4-cp36-cp36m-win32.whl", hash = "sha256:85f279d88d8e833ec015650fd15ae5eddce0791e1e8a59165318f371158efec6"}, + {file = "msgpack-1.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:c1683841cd4fa45ac427c18854c3ec3cd9b681694caf5bff04edb9387602d661"}, + {file = "msgpack-1.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a75dfb03f8b06f4ab093dafe3ddcc2d633259e6c3f74bb1b01996f5d8aa5868c"}, + {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9667bdfdf523c40d2511f0e98a6c9d3603be6b371ae9a238b7ef2dc4e7a427b0"}, + {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11184bc7e56fd74c00ead4f9cc9a3091d62ecb96e97653add7a879a14b003227"}, + {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac5bd7901487c4a1dd51a8c58f2632b15d838d07ceedaa5e4c080f7190925bff"}, + {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1e91d641d2bfe91ba4c52039adc5bccf27c335356055825c7f88742c8bb900dd"}, + {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2a2df1b55a78eb5f5b7d2a4bb221cd8363913830145fad05374a80bf0877cb1e"}, + {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:545e3cf0cf74f3e48b470f68ed19551ae6f9722814ea969305794645da091236"}, + {file = "msgpack-1.0.4-cp37-cp37m-win32.whl", hash = "sha256:2cc5ca2712ac0003bcb625c96368fd08a0f86bbc1a5578802512d87bc592fe44"}, + {file = "msgpack-1.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:eba96145051ccec0ec86611fe9cf693ce55f2a3ce89c06ed307de0e085730ec1"}, + {file = "msgpack-1.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7760f85956c415578c17edb39eed99f9181a48375b0d4a94076d84148cf67b2d"}, + {file = "msgpack-1.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:449e57cc1ff18d3b444eb554e44613cffcccb32805d16726a5494038c3b93dab"}, + {file = "msgpack-1.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d603de2b8d2ea3f3bcb2efe286849aa7a81531abc52d8454da12f46235092bcb"}, + {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f5d88c99f64c456413d74a975bd605a9b0526293218a3b77220a2c15458ba9"}, + {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916c78f33602ecf0509cc40379271ba0f9ab572b066bd4bdafd7434dee4bc6e"}, + {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81fc7ba725464651190b196f3cd848e8553d4d510114a954681fd0b9c479d7e1"}, + {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5b5b962221fa2c5d3a7f8133f9abffc114fe218eb4365e40f17732ade576c8e"}, + {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:77ccd2af37f3db0ea59fb280fa2165bf1b096510ba9fe0cc2bf8fa92a22fdb43"}, + {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b17be2478b622939e39b816e0aa8242611cc8d3583d1cd8ec31b249f04623243"}, + {file = "msgpack-1.0.4-cp38-cp38-win32.whl", hash = "sha256:2bb8cdf50dd623392fa75525cce44a65a12a00c98e1e37bf0fb08ddce2ff60d2"}, + {file = "msgpack-1.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:26b8feaca40a90cbe031b03d82b2898bf560027160d3eae1423f4a67654ec5d6"}, + {file = "msgpack-1.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:462497af5fd4e0edbb1559c352ad84f6c577ffbbb708566a0abaaa84acd9f3ae"}, + {file = "msgpack-1.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2999623886c5c02deefe156e8f869c3b0aaeba14bfc50aa2486a0415178fce55"}, + {file = "msgpack-1.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f0029245c51fd9473dc1aede1160b0a29f4a912e6b1dd353fa6d317085b219da"}, + {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed6f7b854a823ea44cf94919ba3f727e230da29feb4a99711433f25800cf747f"}, + {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df96d6eaf45ceca04b3f3b4b111b86b33785683d682c655063ef8057d61fd92"}, + {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a4192b1ab40f8dca3f2877b70e63799d95c62c068c84dc028b40a6cb03ccd0f"}, + {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e3590f9fb9f7fbc36df366267870e77269c03172d086fa76bb4eba8b2b46624"}, + {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1576bd97527a93c44fa856770197dec00d223b0b9f36ef03f65bac60197cedf8"}, + {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:63e29d6e8c9ca22b21846234913c3466b7e4ee6e422f205a2988083de3b08cae"}, + {file = "msgpack-1.0.4-cp39-cp39-win32.whl", hash = "sha256:fb62ea4b62bfcb0b380d5680f9a4b3f9a2d166d9394e9bbd9666c0ee09a3645c"}, + {file = "msgpack-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:4d5834a2a48965a349da1c5a79760d94a1a0172fbb5ab6b5b33cbf8447e109ce"}, + {file = "msgpack-1.0.4.tar.gz", hash = "sha256:f5d869c18f030202eb412f08b28d2afeea553d6613aee89e200d7aca7ef01f5f"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] pathspec = [] platformdirs = [] py-algorand-sdk = [] -pycparser = [] +pycparser = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] pycryptodomex = [] -pynacl = [] -tomli = [] +pynacl = [ + {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, + {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, +] +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] diff --git a/pyproject.toml b/pyproject.toml index bbc29d2..da8d33d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,8 @@ [tool.poetry] name = "algorealm-cli" -version = "0.2.0" +version = "0.3.0" description = "An interactive game of randomness powered by Algorand blockchain (by cusma)" -authors = ["cosimo.bassi@algorand.com"] +authors = ["cosimo.bassi@gmail.com"] license = "MIT" [tool.poetry.dependencies]