From 6e881cfd19072508cc29dac27beb6d2c5f316c9b Mon Sep 17 00:00:00 2001 From: Vandyck Date: Tue, 10 Oct 2023 23:57:44 +0800 Subject: [PATCH] Co-authored-by: Looi Wei En Co-authored-by: Shaun Lim --- README.md | 4 + backend/smart_contracts/algoaid/contract.py | 120 ++++ .../smart_contracts/algoaid/deploy_config.py | 167 +++++ backend/smart_contracts/algoaid/nft.py | 319 +++++++++ .../artifacts/algo-aid/application.json | 90 +++ .../artifacts/algo-aid/approval.teal | 266 ++++++++ .../artifacts/algo-aid/clear.teal | 3 + .../artifacts/algo-aid/client.py | 639 ++++++++++++++++++ .../artifacts/algo-aid/contract.json | 39 ++ frontend/src/App.tsx | 49 +- frontend/src/main.tsx | 15 + frontend/src/pages/CategoryPage.tsx | 171 +++++ frontend/src/pages/DashboardPage.tsx | 6 +- frontend/src/pages/DonationHistoryPage.tsx | 178 +++++ frontend/src/pages/ProfilePage.tsx | 190 ++++++ frontend/src/providers/CategoryProvider.tsx | 40 ++ frontend/src/providers/GroupProvider.tsx | 201 ++++++ 17 files changed, 2477 insertions(+), 20 deletions(-) create mode 100644 backend/smart_contracts/algoaid/contract.py create mode 100644 backend/smart_contracts/algoaid/deploy_config.py create mode 100644 backend/smart_contracts/algoaid/nft.py create mode 100644 backend/smart_contracts/artifacts/algo-aid/application.json create mode 100644 backend/smart_contracts/artifacts/algo-aid/approval.teal create mode 100644 backend/smart_contracts/artifacts/algo-aid/clear.teal create mode 100644 backend/smart_contracts/artifacts/algo-aid/client.py create mode 100644 backend/smart_contracts/artifacts/algo-aid/contract.json create mode 100644 frontend/src/providers/CategoryProvider.tsx create mode 100644 frontend/src/providers/GroupProvider.tsx diff --git a/README.md b/README.md index 2e8056e..a5320e2 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ This starter full stack project has been generated using AlgoKit. See below for default getting started instructions. + +## Deployed Smart Contract + +[https://testnet.algoexplorer.io/application/428125640](https://testnet.algoexplorer.io/application/428125640) ## Setup ### Initial setup diff --git a/backend/smart_contracts/algoaid/contract.py b/backend/smart_contracts/algoaid/contract.py new file mode 100644 index 0000000..79e3417 --- /dev/null +++ b/backend/smart_contracts/algoaid/contract.py @@ -0,0 +1,120 @@ +from algosdk import atomic_transaction_composer as a_t_c +from algosdk import v2client, account +from algosdk import transaction as tx + + +from pyteal import * +from beaker import * +import beaker +import pyteal as pt + +app = beaker.Application("algo-aid") + + +@app.external +def initAidNft(*, output: abi.Uint64): + return Seq( + # Create an Aid NFT + InnerTxnBuilder.Execute( + { + TxnField.type_enum: TxnType.AssetConfig, + TxnField.config_asset_name: Bytes("WWF"), + TxnField.config_asset_total: Int(1000), + TxnField.config_asset_decimals: Int(0), + TxnField.config_asset_url: Bytes( + "https://logowik.com/content/uploads/images/753_wwf.jpg" + ), + TxnField.config_asset_default_frozen: Int(0), + TxnField.config_asset_reserve: Global.current_application_address(), + TxnField.config_asset_manager: Global.current_application_address(), + TxnField.config_asset_freeze: Global.current_application_address(), + TxnField.config_asset_clawback: Global.current_application_address(), + } + ), + # Output created Aid NFT ID + output.set(InnerTxn.created_asset_id()), + ) + + +@app.external +def purchaseAidNft(asset_id: abi.Uint64, *, output: abi.Uint64): + return Seq( + # Check that supplied ASA is an Aid NFT + creatorCheck := AssetParam.creator(Txn.assets[0]), + Assert(creatorCheck.hasValue() == Int(1), comment="ASA supplied must exist"), + Assert( + creatorCheck.value() == Global.current_application_address(), + comment="ASA supplied must be created by Freeda Play app", + ), + # Other basic checks + Assert( + Gtxn[0].type_enum() == TxnType.Payment, + comment="First Txn in Group must be Payment", + ), + Assert( + Gtxn[0].receiver() == Global.current_application_address(), + comment="Receiver of Payment must be Application", + ), + # Send the Aid NFT to Txn.sender() + InnerTxnBuilder.Execute( + { + TxnField.type_enum: TxnType.AssetTransfer, + TxnField.sender: Global.current_application_address(), + TxnField.asset_receiver: Txn.sender(), + TxnField.asset_amount: Int(1), + TxnField.xfer_asset: Txn.assets[0], + } + ), + # Freeze the sent asset if the season is active + InnerTxnBuilder.Execute( + { + TxnField.type_enum: TxnType.AssetFreeze, + TxnField.freeze_asset: Txn.assets[0], + TxnField.freeze_asset_account: Txn.sender(), + } + ), + # Return the new Aid NFT balance of Txn.sender() + balCheck := AssetHolding.balance(Txn.sender(), asset_id.get()), + output.set(balCheck.value()), + ) + + +@app.external +def sellAidNft(*, output: abi.Uint64): + return Seq( + # Has to be a clawback txn since the asset is usually frozen + InnerTxnBuilder.Execute( + { + TxnField.type_enum: TxnType.AssetTransfer, + TxnField.asset_sender: Txn.sender(), + TxnField.sender: Global.current_application_address(), + TxnField.asset_receiver: Global.current_application_address(), + TxnField.asset_amount: Int(1), + TxnField.xfer_asset: Txn.assets[0], + } + ), + # Reimburse Txn.sender() with proper Algo amount + InnerTxnBuilder.Execute( + { + TxnField.type_enum: TxnType.Payment, + TxnField.receiver: Txn.sender(), + } + ), + # Return the new Aid NFT balance of Txn.sender() + balCheck := AssetHolding.balance(Txn.sender(), Txn.assets[0]), + output.set(balCheck.value()), + ) + + +@app.external +def unlockAsset(): + return Seq( + InnerTxnBuilder.Execute( + { + TxnField.type_enum: TxnType.AssetFreeze, + TxnField.freeze_asset: Txn.assets[0], + TxnField.freeze_asset_account: Txn.sender(), + TxnField.freeze_asset_frozen: Int(0), + } + ), + ) diff --git a/backend/smart_contracts/algoaid/deploy_config.py b/backend/smart_contracts/algoaid/deploy_config.py new file mode 100644 index 0000000..6ccefa2 --- /dev/null +++ b/backend/smart_contracts/algoaid/deploy_config.py @@ -0,0 +1,167 @@ +import logging + +import algokit_utils +from algosdk.v2client.algod import AlgodClient +from algosdk.v2client.indexer import IndexerClient + +logger = logging.getLogger(__name__) + + +def demo( + deployedOnPublicNet=False, + acc_addr=None, + acc_privkey=None, + acc_signer=None, + algod_client=None, + app_client=None, + app_addr=None, +): + # Setup - if `deployedOnPublicNet` is True, then all other params must be supplied + acc_addr = ( + sandbox.get_accounts()[0].address if not deployedOnPublicNet else acc_addr + ) + acc_privkey = ( + sandbox.get_accounts()[0].private_key + if not deployedOnPublicNet + else acc_privkey + ) + acc_signer = ( + sandbox.get_accounts()[0].signer if not deployedOnPublicNet else acc_signer + ) + algod_client = ( + sandbox.get_algod_client() if not deployedOnPublicNet else algod_client + ) + + app_client = ( + client.ApplicationClient( + client=algod_client, + app=FreedaPlay(version=8), + signer=acc_signer, + ) + if not deployedOnPublicNet + else app_client + ) + + if not deployedOnPublicNet: + print("Deploying app...") + app_id, app_addr, txid = app_client.create() + print( + f"""Deployed app in txid {txid} + App ID: {app_id} + Address: {app_addr}\n""" + ) + + # Get suggested params before testing + s_params = algod_client.suggested_params() + + # Begin testing + print("Funding contract...\n") + app_client.fund(1000000) + + print("Opting in to the contract...\n") + app_client.opt_in() + + print("Creating aid nft...") + callb = app_client.call(method=FreedaPlay.initAidNft) + asa_id = callb.return_value + + print("The created ASA ID is " + str(asa_id) + "\n") + + print("Opting into created ASA...\n") + optin_tx = tx.AssetOptInTxn(sender=acc_addr, sp=s_params, index=asa_id) + algod_client.send_transaction(optin_tx.sign(acc_privkey)) + + print("Activating season...") + calla = app_client.call(method=FreedaPlay.toggleSeason, foreign_assets=[asa_id]) + print("Season status after 1st toggle: " + str(calla.return_value) + "\n") + + print("Checking aid nft value...") + call0 = app_client.call(method=FreedaPlay.getAidNftValue) + print( + "The value of an aid nft is " + str(call0.return_value / 1000000) + " Algo(s)\n" + ) + + print("Buying aid nft...") + pay_tx = tx.PaymentTxn( + sender=acc_addr, sp=s_params, receiver=app_addr, amt=1000000 + 2000 + ) # add 2000 for fees + pay_tx_signer = a_t_c.TransactionWithSigner(txn=pay_tx, signer=acc_signer) + fundAndCall = a_t_c.AtomicTransactionComposer() + fundAndCall.add_transaction(pay_tx_signer) + call = app_client.call( + atc=fundAndCall, + method=FreedaPlay.purchaseAidNft, + asset_id=asa_id, + foreign_assets=[asa_id], + ) + print("Aid NFT balance after buy: " + str(call.return_value) + "\n") + + print("Selling aid nft...") + call2 = app_client.call(method=FreedaPlay.sellAidNft, foreign_assets=[asa_id]) + print("Aid NFT balance after sell: " + str(call2.return_value) + "\n") + + print("Deactivating season...") + call3 = app_client.call(method=FreedaPlay.toggleSeason, foreign_assets=[asa_id]) + print("Season status after 2nd toggle: " + str(call3.return_value) + "\n") + + print("Unlocking asset when season is inactive...\n") + app_client.call(method=FreedaPlay.unlockAsset, foreign_assets=[asa_id]) + + print("Demo finished!") + + +# define deployment behaviour based on supplied app spec +def deploy( + algod_client: AlgodClient, + indexer_client: IndexerClient, + app_spec: algokit_utils.ApplicationSpecification, + deployer: algokit_utils.Account, +) -> None: + from smart_contracts.artifacts.smart_contract.client import ( + SmartContractClient, + ) + + app_client = SmartContractClient( + algod_client, + creator=deployer, + indexer_client=indexer_client, + ) + app_client.deploy( + on_schema_break=algokit_utils.OnSchemaBreak.AppendApp, + on_update=algokit_utils.OnUpdate.AppendApp, + ) + + # # Initialize account + # acc_privkey = "city month away course stem treat kiwi basket alpha news siege atom vast latin appear shoe miss search idle ghost pool alley spot absent rail" + # acc_addr = account.address_from_private_key(acc_privkey) + # signer = a_t_c.AccountTransactionSigner(acc_privkey) + + # # Initialize client + # algod_token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + # algod_address = "https://node.testnet.algoexplorerapi.io" # Node URLs provided as sample; any valid node URLs will work + # if not deployOnTestnet: + # input( + # "\nAre you sure you want to deploy Freeda Play application to Algorand MAINNET using account " + # + acc_addr + # + "?\nPress ENTER to deploy and control + C to cancel. " + # ) + # algod_address = "https://node.algoexplorerapi.io" + + # algod_client = v2client.algod.AlgodClient(algod_token, algod_address) + + # app_client = client.ApplicationClient( + # client=algod_client, + # app=FreedaPlay(version=8), + # signer=signer, + # ) + + # print("\nDeploying app with account " + acc_addr + "...") + # app_id, app_addr, txid = app_client.create() + # print( + # f"""Deployed app in txid {txid} + # App ID: {app_id} + # Address: {app_addr}\n""" + # ) + + # if run_demo: + # demo(True, acc_addr, acc_privkey, signer, algod_client, app_client, app_addr) diff --git a/backend/smart_contracts/algoaid/nft.py b/backend/smart_contracts/algoaid/nft.py new file mode 100644 index 0000000..f0dd6e4 --- /dev/null +++ b/backend/smart_contracts/algoaid/nft.py @@ -0,0 +1,319 @@ +from algosdk import atomic_transaction_composer as a_t_c +from algosdk import v2client, account +from algosdk import transaction as tx + +from pyteal import * +from beaker import * + + +class AidNFT(Application): + adminAccount = ApplicationStateValue( + stack_type=TealType.bytes, + descr="The admin account. He can configure various settings in the application.", + ) + + aidNftValue = ApplicationStateValue( + stack_type=TealType.uint64, + static=True, + descr="Value of an AlgoAid NFT in microAlgos", + ) + + isSeasonActive = ApplicationStateValue( + stack_type=TealType.uint64, + default=Int(0), + descr="If the football season currently active (0 = inactive, 1 = active)", + ) + + @create + def create(self): + return Seq( + self.initialize_application_state(), + self.adminAccount.set(Txn.sender()), + self.isSeasonActive.set(Int(0)), + self.aidNftValue.set(Int(1000000)), # 1 Algo + ) + + @opt_in + def opt_in(self): + return self.initialize_account_state() + + @external + def initAidNft(self, *, output: abi.Uint64): + return Seq( + # Create an Aid NFT + InnerTxnBuilder.Execute( + { + TxnField.type_enum: TxnType.AssetConfig, + TxnField.config_asset_name: Bytes("WWF"), + TxnField.config_asset_total: Int(1000), + TxnField.config_asset_decimals: Int(0), + TxnField.config_asset_url: Bytes( + "https://logowik.com/content/uploads/images/753_wwf.jpg" + ), + TxnField.config_asset_default_frozen: Int(0), + TxnField.config_asset_reserve: Global.current_application_address(), + TxnField.config_asset_manager: Global.current_application_address(), + TxnField.config_asset_freeze: Global.current_application_address(), + TxnField.config_asset_clawback: Global.current_application_address(), + } + ), + # Output created Aid NFT ID + output.set(InnerTxn.created_asset_id()), + ) + + @external + def purchaseAidNft(self, asset_id: abi.Uint64, *, output: abi.Uint64): + return Seq( + # Check that supplied ASA is an Aid NFT + creatorCheck := AssetParam.creator(Txn.assets[0]), + Assert( + creatorCheck.hasValue() == Int(1), comment="ASA supplied must exist" + ), + Assert( + creatorCheck.value() == Global.current_application_address(), + comment="ASA supplied must be created by Freeda Play app", + ), + # Other basic checks + Assert( + self.isSeasonActive == Int(1), + comment="Football season must be active (not currently active)", + ), + Assert( + Gtxn[0].type_enum() == TxnType.Payment, + comment="First Txn in Group must be Payment", + ), + Assert( + Gtxn[0].receiver() == Global.current_application_address(), + comment="Receiver of Payment must be Application", + ), + Assert( + Gtxn[0].amount() >= self.aidNftValue, + comment=f"Must transfer at least {self.aidNftValue} microAlgos to purchase NFT", + ), + # Send the Aid NFT to Txn.sender() + InnerTxnBuilder.Execute( + { + TxnField.type_enum: TxnType.AssetTransfer, + TxnField.sender: Global.current_application_address(), + TxnField.asset_receiver: Txn.sender(), + TxnField.asset_amount: Int(1), + TxnField.xfer_asset: Txn.assets[0], + } + ), + # Freeze the sent asset if the season is active + InnerTxnBuilder.Execute( + { + TxnField.type_enum: TxnType.AssetFreeze, + TxnField.freeze_asset: Txn.assets[0], + TxnField.freeze_asset_account: Txn.sender(), + TxnField.freeze_asset_frozen: self.isSeasonActive.get(), + } + ), + # Return the new Aid NFT balance of Txn.sender() + balCheck := AssetHolding.balance(Txn.sender(), asset_id.get()), + output.set(balCheck.value()), + ) + + @external + def sellAidNft(self, *, output: abi.Uint64): + return Seq( + Assert( + self.isSeasonActive == Int(1), + comment="Football season must be active (not currently active)", + ), + # Has to be a clawback txn since the asset is usually frozen + InnerTxnBuilder.Execute( + { + TxnField.type_enum: TxnType.AssetTransfer, + TxnField.asset_sender: Txn.sender(), + TxnField.sender: Global.current_application_address(), + TxnField.asset_receiver: Global.current_application_address(), + TxnField.asset_amount: Int(1), + TxnField.xfer_asset: Txn.assets[0], + } + ), + # Reimburse Txn.sender() with proper Algo amount + InnerTxnBuilder.Execute( + { + TxnField.type_enum: TxnType.Payment, + TxnField.amount: self.aidNftValue, + TxnField.receiver: Txn.sender(), + } + ), + # Return the new Aid NFT balance of Txn.sender() + balCheck := AssetHolding.balance(Txn.sender(), Txn.assets[0]), + output.set(balCheck.value()), + ) + + # When the seasons ends, users may want to unfreeze their assets + @external + def unlockAsset(self): + return Seq( + Assert(self.isSeasonActive == Int(0), comment="Season must not be active"), + InnerTxnBuilder.Execute( + { + TxnField.type_enum: TxnType.AssetFreeze, + TxnField.freeze_asset: Txn.assets[0], + TxnField.freeze_asset_account: Txn.sender(), + TxnField.freeze_asset_frozen: Int(0), + } + ), + ) + + @external + def toggleSeason(self, *, output: abi.Uint64): + return Seq( + Assert(Txn.sender() == self.adminAccount, comment="Sender must be admin"), + If(self.isSeasonActive == Int(0)) + .Then(self.isSeasonActive.set(Int(1))) + .Else(self.isSeasonActive.set(Int(0))), + output.set(self.isSeasonActive), + ) + + @external(read_only=True) + def getAidNftValue(self, *, output: abi.Uint64): + return output.set(self.aidNftValue) + + +def demo( + deployedOnPublicNet=False, + acc_addr=None, + acc_privkey=None, + acc_signer=None, + algod_client=None, + app_client=None, + app_addr=None, +): + # Setup - if `deployedOnPublicNet` is True, then all other params must be supplied + acc_addr = ( + sandbox.get_accounts()[0].address if not deployedOnPublicNet else acc_addr + ) + acc_privkey = ( + sandbox.get_accounts()[0].private_key + if not deployedOnPublicNet + else acc_privkey + ) + acc_signer = ( + sandbox.get_accounts()[0].signer if not deployedOnPublicNet else acc_signer + ) + algod_client = ( + sandbox.get_algod_client() if not deployedOnPublicNet else algod_client + ) + + app_client = ( + client.ApplicationClient( + client=algod_client, + app=FreedaPlay(version=8), + signer=acc_signer, + ) + if not deployedOnPublicNet + else app_client + ) + + if not deployedOnPublicNet: + print("Deploying app...") + app_id, app_addr, txid = app_client.create() + print( + f"""Deployed app in txid {txid} + App ID: {app_id} + Address: {app_addr}\n""" + ) + + # Get suggested params before testing + s_params = algod_client.suggested_params() + + # Begin testing + print("Funding contract...\n") + app_client.fund(1000000) + + print("Opting in to the contract...\n") + app_client.opt_in() + + print("Creating aid nft...") + callb = app_client.call(method=FreedaPlay.initAidNft) + asa_id = callb.return_value + + print("The created ASA ID is " + str(asa_id) + "\n") + + print("Opting into created ASA...\n") + optin_tx = tx.AssetOptInTxn(sender=acc_addr, sp=s_params, index=asa_id) + algod_client.send_transaction(optin_tx.sign(acc_privkey)) + + print("Activating season...") + calla = app_client.call(method=FreedaPlay.toggleSeason, foreign_assets=[asa_id]) + print("Season status after 1st toggle: " + str(calla.return_value) + "\n") + + print("Checking aid nft value...") + call0 = app_client.call(method=FreedaPlay.getAidNftValue) + print( + "The value of an aid nft is " + str(call0.return_value / 1000000) + " Algo(s)\n" + ) + + print("Buying aid nft...") + pay_tx = tx.PaymentTxn( + sender=acc_addr, sp=s_params, receiver=app_addr, amt=1000000 + 2000 + ) # add 2000 for fees + pay_tx_signer = a_t_c.TransactionWithSigner(txn=pay_tx, signer=acc_signer) + fundAndCall = a_t_c.AtomicTransactionComposer() + fundAndCall.add_transaction(pay_tx_signer) + call = app_client.call( + atc=fundAndCall, + method=FreedaPlay.purchaseAidNft, + asset_id=asa_id, + foreign_assets=[asa_id], + ) + print("Aid NFT balance after buy: " + str(call.return_value) + "\n") + + print("Selling aid nft...") + call2 = app_client.call(method=FreedaPlay.sellAidNft, foreign_assets=[asa_id]) + print("Aid NFT balance after sell: " + str(call2.return_value) + "\n") + + print("Deactivating season...") + call3 = app_client.call(method=FreedaPlay.toggleSeason, foreign_assets=[asa_id]) + print("Season status after 2nd toggle: " + str(call3.return_value) + "\n") + + print("Unlocking asset when season is inactive...\n") + app_client.call(method=FreedaPlay.unlockAsset, foreign_assets=[asa_id]) + + print("Demo finished!") + + +def deploy(deployOnTestnet, run_demo): + # Initialize account + acc_privkey = "city month away course stem treat kiwi basket alpha news siege atom vast latin appear shoe miss search idle ghost pool alley spot absent rail" + acc_addr = account.address_from_private_key(acc_privkey) + signer = a_t_c.AccountTransactionSigner(acc_privkey) + + # Initialize client + algod_token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + algod_address = "https://node.testnet.algoexplorerapi.io" # Node URLs provided as sample; any valid node URLs will work + if not deployOnTestnet: + input( + "\nAre you sure you want to deploy Freeda Play application to Algorand MAINNET using account " + + acc_addr + + "?\nPress ENTER to deploy and control + C to cancel. " + ) + algod_address = "https://node.algoexplorerapi.io" + + algod_client = v2client.algod.AlgodClient(algod_token, algod_address) + + app_client = client.ApplicationClient( + client=algod_client, + app=FreedaPlay(version=8), + signer=signer, + ) + + print("\nDeploying app with account " + acc_addr + "...") + app_id, app_addr, txid = app_client.create() + print( + f"""Deployed app in txid {txid} + App ID: {app_id} + Address: {app_addr}\n""" + ) + + if run_demo: + demo(True, acc_addr, acc_privkey, signer, algod_client, app_client, app_addr) + + +if __name__ == "__main__": + demo() diff --git a/backend/smart_contracts/artifacts/algo-aid/application.json b/backend/smart_contracts/artifacts/algo-aid/application.json new file mode 100644 index 0000000..517ecf4 --- /dev/null +++ b/backend/smart_contracts/artifacts/algo-aid/application.json @@ -0,0 +1,90 @@ +{ + "hints": { + "initAidNft()uint64": { + "call_config": { + "no_op": "CALL" + } + }, + "purchaseAidNft(uint64)uint64": { + "call_config": { + "no_op": "CALL" + } + }, + "sellAidNft()uint64": { + "call_config": { + "no_op": "CALL" + } + }, + "unlockAsset()void": { + "call_config": { + "no_op": "CALL" + } + } + }, + "source": { + "approval": "I3ByYWdtYSB2ZXJzaW9uIDgKaW50Y2Jsb2NrIDAgMSA0IDUKYnl0ZWNibG9jayAweDE1MWY3Yzc1CnR4biBOdW1BcHBBcmdzCmludGNfMCAvLyAwCj09CmJueiBtYWluX2wxMAp0eG5hIEFwcGxpY2F0aW9uQXJncyAwCnB1c2hieXRlcyAweDRhYzgwNGZkIC8vICJpbml0QWlkTmZ0KCl1aW50NjQiCj09CmJueiBtYWluX2w5CnR4bmEgQXBwbGljYXRpb25BcmdzIDAKcHVzaGJ5dGVzIDB4OGZkODcyNGIgLy8gInB1cmNoYXNlQWlkTmZ0KHVpbnQ2NCl1aW50NjQiCj09CmJueiBtYWluX2w4CnR4bmEgQXBwbGljYXRpb25BcmdzIDAKcHVzaGJ5dGVzIDB4MjVlNzI3NDcgLy8gInNlbGxBaWROZnQoKXVpbnQ2NCIKPT0KYm56IG1haW5fbDcKdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMApwdXNoYnl0ZXMgMHg5Y2JlOWQzNiAvLyAidW5sb2NrQXNzZXQoKXZvaWQiCj09CmJueiBtYWluX2w2CmVycgptYWluX2w2Ogp0eG4gT25Db21wbGV0aW9uCmludGNfMCAvLyBOb09wCj09CnR4biBBcHBsaWNhdGlvbklECmludGNfMCAvLyAwCiE9CiYmCmFzc2VydApjYWxsc3ViIHVubG9ja0Fzc2V0Y2FzdGVyXzcKaW50Y18xIC8vIDEKcmV0dXJuCm1haW5fbDc6CnR4biBPbkNvbXBsZXRpb24KaW50Y18wIC8vIE5vT3AKPT0KdHhuIEFwcGxpY2F0aW9uSUQKaW50Y18wIC8vIDAKIT0KJiYKYXNzZXJ0CmNhbGxzdWIgc2VsbEFpZE5mdGNhc3Rlcl82CmludGNfMSAvLyAxCnJldHVybgptYWluX2w4Ogp0eG4gT25Db21wbGV0aW9uCmludGNfMCAvLyBOb09wCj09CnR4biBBcHBsaWNhdGlvbklECmludGNfMCAvLyAwCiE9CiYmCmFzc2VydApjYWxsc3ViIHB1cmNoYXNlQWlkTmZ0Y2FzdGVyXzUKaW50Y18xIC8vIDEKcmV0dXJuCm1haW5fbDk6CnR4biBPbkNvbXBsZXRpb24KaW50Y18wIC8vIE5vT3AKPT0KdHhuIEFwcGxpY2F0aW9uSUQKaW50Y18wIC8vIDAKIT0KJiYKYXNzZXJ0CmNhbGxzdWIgaW5pdEFpZE5mdGNhc3Rlcl80CmludGNfMSAvLyAxCnJldHVybgptYWluX2wxMDoKdHhuIE9uQ29tcGxldGlvbgppbnRjXzAgLy8gTm9PcAo9PQpibnogbWFpbl9sMTIKZXJyCm1haW5fbDEyOgp0eG4gQXBwbGljYXRpb25JRAppbnRjXzAgLy8gMAo9PQphc3NlcnQKaW50Y18xIC8vIDEKcmV0dXJuCgovLyBpbml0QWlkTmZ0CmluaXRBaWROZnRfMDoKcHJvdG8gMCAxCmludGNfMCAvLyAwCml0eG5fYmVnaW4KcHVzaGludCAzIC8vIGFjZmcKaXR4bl9maWVsZCBUeXBlRW51bQpwdXNoYnl0ZXMgMHg1NzU3NDYgLy8gIldXRiIKaXR4bl9maWVsZCBDb25maWdBc3NldE5hbWUKcHVzaGludCAxMDAwIC8vIDEwMDAKaXR4bl9maWVsZCBDb25maWdBc3NldFRvdGFsCmludGNfMCAvLyAwCml0eG5fZmllbGQgQ29uZmlnQXNzZXREZWNpbWFscwpwdXNoYnl0ZXMgMHg2ODc0NzQ3MDczM2EyZjJmNmM2ZjY3NmY3NzY5NmIyZTYzNmY2ZDJmNjM2ZjZlNzQ2NTZlNzQyZjc1NzA2YzZmNjE2NDczMmY2OTZkNjE2NzY1NzMyZjM3MzUzMzVmNzc3NzY2MmU2YTcwNjcgLy8gImh0dHBzOi8vbG9nb3dpay5jb20vY29udGVudC91cGxvYWRzL2ltYWdlcy83NTNfd3dmLmpwZyIKaXR4bl9maWVsZCBDb25maWdBc3NldFVSTAppbnRjXzAgLy8gMAppdHhuX2ZpZWxkIENvbmZpZ0Fzc2V0RGVmYXVsdEZyb3plbgpnbG9iYWwgQ3VycmVudEFwcGxpY2F0aW9uQWRkcmVzcwppdHhuX2ZpZWxkIENvbmZpZ0Fzc2V0UmVzZXJ2ZQpnbG9iYWwgQ3VycmVudEFwcGxpY2F0aW9uQWRkcmVzcwppdHhuX2ZpZWxkIENvbmZpZ0Fzc2V0TWFuYWdlcgpnbG9iYWwgQ3VycmVudEFwcGxpY2F0aW9uQWRkcmVzcwppdHhuX2ZpZWxkIENvbmZpZ0Fzc2V0RnJlZXplCmdsb2JhbCBDdXJyZW50QXBwbGljYXRpb25BZGRyZXNzCml0eG5fZmllbGQgQ29uZmlnQXNzZXRDbGF3YmFjawppdHhuX3N1Ym1pdAppdHhuIENyZWF0ZWRBc3NldElECmZyYW1lX2J1cnkgMApyZXRzdWIKCi8vIHB1cmNoYXNlQWlkTmZ0CnB1cmNoYXNlQWlkTmZ0XzE6CnByb3RvIDEgMQppbnRjXzAgLy8gMAp0eG5hIEFzc2V0cyAwCmFzc2V0X3BhcmFtc19nZXQgQXNzZXRDcmVhdG9yCnN0b3JlIDEKc3RvcmUgMApsb2FkIDEKaW50Y18xIC8vIDEKPT0KLy8gQVNBIHN1cHBsaWVkIG11c3QgZXhpc3QKYXNzZXJ0CmxvYWQgMApnbG9iYWwgQ3VycmVudEFwcGxpY2F0aW9uQWRkcmVzcwo9PQovLyBBU0Egc3VwcGxpZWQgbXVzdCBiZSBjcmVhdGVkIGJ5IEZyZWVkYSBQbGF5IGFwcAphc3NlcnQKZ3R4biAwIFR5cGVFbnVtCmludGNfMSAvLyBwYXkKPT0KLy8gRmlyc3QgVHhuIGluIEdyb3VwIG11c3QgYmUgUGF5bWVudAphc3NlcnQKZ3R4biAwIFJlY2VpdmVyCmdsb2JhbCBDdXJyZW50QXBwbGljYXRpb25BZGRyZXNzCj09Ci8vIFJlY2VpdmVyIG9mIFBheW1lbnQgbXVzdCBiZSBBcHBsaWNhdGlvbgphc3NlcnQKaXR4bl9iZWdpbgppbnRjXzIgLy8gYXhmZXIKaXR4bl9maWVsZCBUeXBlRW51bQpnbG9iYWwgQ3VycmVudEFwcGxpY2F0aW9uQWRkcmVzcwppdHhuX2ZpZWxkIFNlbmRlcgp0eG4gU2VuZGVyCml0eG5fZmllbGQgQXNzZXRSZWNlaXZlcgppbnRjXzEgLy8gMQppdHhuX2ZpZWxkIEFzc2V0QW1vdW50CnR4bmEgQXNzZXRzIDAKaXR4bl9maWVsZCBYZmVyQXNzZXQKaXR4bl9zdWJtaXQKaXR4bl9iZWdpbgppbnRjXzMgLy8gYWZyegppdHhuX2ZpZWxkIFR5cGVFbnVtCnR4bmEgQXNzZXRzIDAKaXR4bl9maWVsZCBGcmVlemVBc3NldAp0eG4gU2VuZGVyCml0eG5fZmllbGQgRnJlZXplQXNzZXRBY2NvdW50Cml0eG5fc3VibWl0CnR4biBTZW5kZXIKZnJhbWVfZGlnIC0xCmFzc2V0X2hvbGRpbmdfZ2V0IEFzc2V0QmFsYW5jZQpzdG9yZSAyCmZyYW1lX2J1cnkgMApyZXRzdWIKCi8vIHNlbGxBaWROZnQKc2VsbEFpZE5mdF8yOgpwcm90byAwIDEKaW50Y18wIC8vIDAKaXR4bl9iZWdpbgppbnRjXzIgLy8gYXhmZXIKaXR4bl9maWVsZCBUeXBlRW51bQp0eG4gU2VuZGVyCml0eG5fZmllbGQgQXNzZXRTZW5kZXIKZ2xvYmFsIEN1cnJlbnRBcHBsaWNhdGlvbkFkZHJlc3MKaXR4bl9maWVsZCBTZW5kZXIKZ2xvYmFsIEN1cnJlbnRBcHBsaWNhdGlvbkFkZHJlc3MKaXR4bl9maWVsZCBBc3NldFJlY2VpdmVyCmludGNfMSAvLyAxCml0eG5fZmllbGQgQXNzZXRBbW91bnQKdHhuYSBBc3NldHMgMAppdHhuX2ZpZWxkIFhmZXJBc3NldAppdHhuX3N1Ym1pdAppdHhuX2JlZ2luCmludGNfMSAvLyBwYXkKaXR4bl9maWVsZCBUeXBlRW51bQp0eG4gU2VuZGVyCml0eG5fZmllbGQgUmVjZWl2ZXIKaXR4bl9zdWJtaXQKdHhuIFNlbmRlcgp0eG5hIEFzc2V0cyAwCmFzc2V0X2hvbGRpbmdfZ2V0IEFzc2V0QmFsYW5jZQpzdG9yZSAzCmZyYW1lX2J1cnkgMApyZXRzdWIKCi8vIHVubG9ja0Fzc2V0CnVubG9ja0Fzc2V0XzM6CnByb3RvIDAgMAppdHhuX2JlZ2luCmludGNfMyAvLyBhZnJ6Cml0eG5fZmllbGQgVHlwZUVudW0KdHhuYSBBc3NldHMgMAppdHhuX2ZpZWxkIEZyZWV6ZUFzc2V0CnR4biBTZW5kZXIKaXR4bl9maWVsZCBGcmVlemVBc3NldEFjY291bnQKaW50Y18wIC8vIDAKaXR4bl9maWVsZCBGcmVlemVBc3NldEZyb3plbgppdHhuX3N1Ym1pdApyZXRzdWIKCi8vIGluaXRBaWROZnRfY2FzdGVyCmluaXRBaWROZnRjYXN0ZXJfNDoKcHJvdG8gMCAwCmludGNfMCAvLyAwCmNhbGxzdWIgaW5pdEFpZE5mdF8wCmZyYW1lX2J1cnkgMApieXRlY18wIC8vIDB4MTUxZjdjNzUKZnJhbWVfZGlnIDAKaXRvYgpjb25jYXQKbG9nCnJldHN1YgoKLy8gcHVyY2hhc2VBaWROZnRfY2FzdGVyCnB1cmNoYXNlQWlkTmZ0Y2FzdGVyXzU6CnByb3RvIDAgMAppbnRjXzAgLy8gMApkdXAKdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMQpidG9pCmZyYW1lX2J1cnkgMQpmcmFtZV9kaWcgMQpjYWxsc3ViIHB1cmNoYXNlQWlkTmZ0XzEKZnJhbWVfYnVyeSAwCmJ5dGVjXzAgLy8gMHgxNTFmN2M3NQpmcmFtZV9kaWcgMAppdG9iCmNvbmNhdApsb2cKcmV0c3ViCgovLyBzZWxsQWlkTmZ0X2Nhc3RlcgpzZWxsQWlkTmZ0Y2FzdGVyXzY6CnByb3RvIDAgMAppbnRjXzAgLy8gMApjYWxsc3ViIHNlbGxBaWROZnRfMgpmcmFtZV9idXJ5IDAKYnl0ZWNfMCAvLyAweDE1MWY3Yzc1CmZyYW1lX2RpZyAwCml0b2IKY29uY2F0CmxvZwpyZXRzdWIKCi8vIHVubG9ja0Fzc2V0X2Nhc3Rlcgp1bmxvY2tBc3NldGNhc3Rlcl83Ogpwcm90byAwIDAKY2FsbHN1YiB1bmxvY2tBc3NldF8zCnJldHN1Yg==", + "clear": "I3ByYWdtYSB2ZXJzaW9uIDgKcHVzaGludCAwIC8vIDAKcmV0dXJu" + }, + "state": { + "global": { + "num_byte_slices": 0, + "num_uints": 0 + }, + "local": { + "num_byte_slices": 0, + "num_uints": 0 + } + }, + "schema": { + "global": { + "declared": {}, + "reserved": {} + }, + "local": { + "declared": {}, + "reserved": {} + } + }, + "contract": { + "name": "algo-aid", + "methods": [ + { + "name": "initAidNft", + "args": [], + "returns": { + "type": "uint64" + } + }, + { + "name": "purchaseAidNft", + "args": [ + { + "type": "uint64", + "name": "asset_id" + } + ], + "returns": { + "type": "uint64" + } + }, + { + "name": "sellAidNft", + "args": [], + "returns": { + "type": "uint64" + } + }, + { + "name": "unlockAsset", + "args": [], + "returns": { + "type": "void" + } + } + ], + "networks": {} + }, + "bare_call_config": { + "no_op": "CREATE" + } +} \ No newline at end of file diff --git a/backend/smart_contracts/artifacts/algo-aid/approval.teal b/backend/smart_contracts/artifacts/algo-aid/approval.teal new file mode 100644 index 0000000..fab2587 --- /dev/null +++ b/backend/smart_contracts/artifacts/algo-aid/approval.teal @@ -0,0 +1,266 @@ +#pragma version 8 +intcblock 0 1 4 5 +bytecblock 0x151f7c75 +txn NumAppArgs +intc_0 // 0 +== +bnz main_l10 +txna ApplicationArgs 0 +pushbytes 0x4ac804fd // "initAidNft()uint64" +== +bnz main_l9 +txna ApplicationArgs 0 +pushbytes 0x8fd8724b // "purchaseAidNft(uint64)uint64" +== +bnz main_l8 +txna ApplicationArgs 0 +pushbytes 0x25e72747 // "sellAidNft()uint64" +== +bnz main_l7 +txna ApplicationArgs 0 +pushbytes 0x9cbe9d36 // "unlockAsset()void" +== +bnz main_l6 +err +main_l6: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +callsub unlockAssetcaster_7 +intc_1 // 1 +return +main_l7: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +callsub sellAidNftcaster_6 +intc_1 // 1 +return +main_l8: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +callsub purchaseAidNftcaster_5 +intc_1 // 1 +return +main_l9: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +callsub initAidNftcaster_4 +intc_1 // 1 +return +main_l10: +txn OnCompletion +intc_0 // NoOp +== +bnz main_l12 +err +main_l12: +txn ApplicationID +intc_0 // 0 +== +assert +intc_1 // 1 +return + +// initAidNft +initAidNft_0: +proto 0 1 +intc_0 // 0 +itxn_begin +pushint 3 // acfg +itxn_field TypeEnum +pushbytes 0x575746 // "WWF" +itxn_field ConfigAssetName +pushint 1000 // 1000 +itxn_field ConfigAssetTotal +intc_0 // 0 +itxn_field ConfigAssetDecimals +pushbytes 0x68747470733a2f2f6c6f676f77696b2e636f6d2f636f6e74656e742f75706c6f6164732f696d616765732f3735335f7777662e6a7067 // "https://logowik.com/content/uploads/images/753_wwf.jpg" +itxn_field ConfigAssetURL +intc_0 // 0 +itxn_field ConfigAssetDefaultFrozen +global CurrentApplicationAddress +itxn_field ConfigAssetReserve +global CurrentApplicationAddress +itxn_field ConfigAssetManager +global CurrentApplicationAddress +itxn_field ConfigAssetFreeze +global CurrentApplicationAddress +itxn_field ConfigAssetClawback +itxn_submit +itxn CreatedAssetID +frame_bury 0 +retsub + +// purchaseAidNft +purchaseAidNft_1: +proto 1 1 +intc_0 // 0 +txna Assets 0 +asset_params_get AssetCreator +store 1 +store 0 +load 1 +intc_1 // 1 +== +// ASA supplied must exist +assert +load 0 +global CurrentApplicationAddress +== +// ASA supplied must be created by Freeda Play app +assert +gtxn 0 TypeEnum +intc_1 // pay +== +// First Txn in Group must be Payment +assert +gtxn 0 Receiver +global CurrentApplicationAddress +== +// Receiver of Payment must be Application +assert +itxn_begin +intc_2 // axfer +itxn_field TypeEnum +global CurrentApplicationAddress +itxn_field Sender +txn Sender +itxn_field AssetReceiver +intc_1 // 1 +itxn_field AssetAmount +txna Assets 0 +itxn_field XferAsset +itxn_submit +itxn_begin +intc_3 // afrz +itxn_field TypeEnum +txna Assets 0 +itxn_field FreezeAsset +txn Sender +itxn_field FreezeAssetAccount +itxn_submit +txn Sender +frame_dig -1 +asset_holding_get AssetBalance +store 2 +frame_bury 0 +retsub + +// sellAidNft +sellAidNft_2: +proto 0 1 +intc_0 // 0 +itxn_begin +intc_2 // axfer +itxn_field TypeEnum +txn Sender +itxn_field AssetSender +global CurrentApplicationAddress +itxn_field Sender +global CurrentApplicationAddress +itxn_field AssetReceiver +intc_1 // 1 +itxn_field AssetAmount +txna Assets 0 +itxn_field XferAsset +itxn_submit +itxn_begin +intc_1 // pay +itxn_field TypeEnum +txn Sender +itxn_field Receiver +itxn_submit +txn Sender +txna Assets 0 +asset_holding_get AssetBalance +store 3 +frame_bury 0 +retsub + +// unlockAsset +unlockAsset_3: +proto 0 0 +itxn_begin +intc_3 // afrz +itxn_field TypeEnum +txna Assets 0 +itxn_field FreezeAsset +txn Sender +itxn_field FreezeAssetAccount +intc_0 // 0 +itxn_field FreezeAssetFrozen +itxn_submit +retsub + +// initAidNft_caster +initAidNftcaster_4: +proto 0 0 +intc_0 // 0 +callsub initAidNft_0 +frame_bury 0 +bytec_0 // 0x151f7c75 +frame_dig 0 +itob +concat +log +retsub + +// purchaseAidNft_caster +purchaseAidNftcaster_5: +proto 0 0 +intc_0 // 0 +dup +txna ApplicationArgs 1 +btoi +frame_bury 1 +frame_dig 1 +callsub purchaseAidNft_1 +frame_bury 0 +bytec_0 // 0x151f7c75 +frame_dig 0 +itob +concat +log +retsub + +// sellAidNft_caster +sellAidNftcaster_6: +proto 0 0 +intc_0 // 0 +callsub sellAidNft_2 +frame_bury 0 +bytec_0 // 0x151f7c75 +frame_dig 0 +itob +concat +log +retsub + +// unlockAsset_caster +unlockAssetcaster_7: +proto 0 0 +callsub unlockAsset_3 +retsub \ No newline at end of file diff --git a/backend/smart_contracts/artifacts/algo-aid/clear.teal b/backend/smart_contracts/artifacts/algo-aid/clear.teal new file mode 100644 index 0000000..e741f0e --- /dev/null +++ b/backend/smart_contracts/artifacts/algo-aid/clear.teal @@ -0,0 +1,3 @@ +#pragma version 8 +pushint 0 // 0 +return \ No newline at end of file diff --git a/backend/smart_contracts/artifacts/algo-aid/client.py b/backend/smart_contracts/artifacts/algo-aid/client.py new file mode 100644 index 0000000..ebbe4ec --- /dev/null +++ b/backend/smart_contracts/artifacts/algo-aid/client.py @@ -0,0 +1,639 @@ +# flake8: noqa +# fmt: off +# mypy: disable-error-code="no-any-return, no-untyped-call" +# This file was automatically generated by algokit-client-generator. +# DO NOT MODIFY IT BY HAND. +# requires: algokit-utils@^1.2.0 +import base64 +import dataclasses +import decimal +import typing +from abc import ABC, abstractmethod + +import algokit_utils +import algosdk +from algosdk.atomic_transaction_composer import ( + AtomicTransactionComposer, + AtomicTransactionResponse, + TransactionSigner, + TransactionWithSigner +) + +_APP_SPEC_JSON = r"""{ + "hints": { + "initAidNft()uint64": { + "call_config": { + "no_op": "CALL" + } + }, + "purchaseAidNft(uint64)uint64": { + "call_config": { + "no_op": "CALL" + } + }, + "sellAidNft()uint64": { + "call_config": { + "no_op": "CALL" + } + }, + "unlockAsset()void": { + "call_config": { + "no_op": "CALL" + } + } + }, + "source": { + "approval": "I3ByYWdtYSB2ZXJzaW9uIDgKaW50Y2Jsb2NrIDAgMSA0IDUKYnl0ZWNibG9jayAweDE1MWY3Yzc1CnR4biBOdW1BcHBBcmdzCmludGNfMCAvLyAwCj09CmJueiBtYWluX2wxMAp0eG5hIEFwcGxpY2F0aW9uQXJncyAwCnB1c2hieXRlcyAweDRhYzgwNGZkIC8vICJpbml0QWlkTmZ0KCl1aW50NjQiCj09CmJueiBtYWluX2w5CnR4bmEgQXBwbGljYXRpb25BcmdzIDAKcHVzaGJ5dGVzIDB4OGZkODcyNGIgLy8gInB1cmNoYXNlQWlkTmZ0KHVpbnQ2NCl1aW50NjQiCj09CmJueiBtYWluX2w4CnR4bmEgQXBwbGljYXRpb25BcmdzIDAKcHVzaGJ5dGVzIDB4MjVlNzI3NDcgLy8gInNlbGxBaWROZnQoKXVpbnQ2NCIKPT0KYm56IG1haW5fbDcKdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMApwdXNoYnl0ZXMgMHg5Y2JlOWQzNiAvLyAidW5sb2NrQXNzZXQoKXZvaWQiCj09CmJueiBtYWluX2w2CmVycgptYWluX2w2Ogp0eG4gT25Db21wbGV0aW9uCmludGNfMCAvLyBOb09wCj09CnR4biBBcHBsaWNhdGlvbklECmludGNfMCAvLyAwCiE9CiYmCmFzc2VydApjYWxsc3ViIHVubG9ja0Fzc2V0Y2FzdGVyXzcKaW50Y18xIC8vIDEKcmV0dXJuCm1haW5fbDc6CnR4biBPbkNvbXBsZXRpb24KaW50Y18wIC8vIE5vT3AKPT0KdHhuIEFwcGxpY2F0aW9uSUQKaW50Y18wIC8vIDAKIT0KJiYKYXNzZXJ0CmNhbGxzdWIgc2VsbEFpZE5mdGNhc3Rlcl82CmludGNfMSAvLyAxCnJldHVybgptYWluX2w4Ogp0eG4gT25Db21wbGV0aW9uCmludGNfMCAvLyBOb09wCj09CnR4biBBcHBsaWNhdGlvbklECmludGNfMCAvLyAwCiE9CiYmCmFzc2VydApjYWxsc3ViIHB1cmNoYXNlQWlkTmZ0Y2FzdGVyXzUKaW50Y18xIC8vIDEKcmV0dXJuCm1haW5fbDk6CnR4biBPbkNvbXBsZXRpb24KaW50Y18wIC8vIE5vT3AKPT0KdHhuIEFwcGxpY2F0aW9uSUQKaW50Y18wIC8vIDAKIT0KJiYKYXNzZXJ0CmNhbGxzdWIgaW5pdEFpZE5mdGNhc3Rlcl80CmludGNfMSAvLyAxCnJldHVybgptYWluX2wxMDoKdHhuIE9uQ29tcGxldGlvbgppbnRjXzAgLy8gTm9PcAo9PQpibnogbWFpbl9sMTIKZXJyCm1haW5fbDEyOgp0eG4gQXBwbGljYXRpb25JRAppbnRjXzAgLy8gMAo9PQphc3NlcnQKaW50Y18xIC8vIDEKcmV0dXJuCgovLyBpbml0QWlkTmZ0CmluaXRBaWROZnRfMDoKcHJvdG8gMCAxCmludGNfMCAvLyAwCml0eG5fYmVnaW4KcHVzaGludCAzIC8vIGFjZmcKaXR4bl9maWVsZCBUeXBlRW51bQpwdXNoYnl0ZXMgMHg1NzU3NDYgLy8gIldXRiIKaXR4bl9maWVsZCBDb25maWdBc3NldE5hbWUKcHVzaGludCAxMDAwIC8vIDEwMDAKaXR4bl9maWVsZCBDb25maWdBc3NldFRvdGFsCmludGNfMCAvLyAwCml0eG5fZmllbGQgQ29uZmlnQXNzZXREZWNpbWFscwpwdXNoYnl0ZXMgMHg2ODc0NzQ3MDczM2EyZjJmNmM2ZjY3NmY3NzY5NmIyZTYzNmY2ZDJmNjM2ZjZlNzQ2NTZlNzQyZjc1NzA2YzZmNjE2NDczMmY2OTZkNjE2NzY1NzMyZjM3MzUzMzVmNzc3NzY2MmU2YTcwNjcgLy8gImh0dHBzOi8vbG9nb3dpay5jb20vY29udGVudC91cGxvYWRzL2ltYWdlcy83NTNfd3dmLmpwZyIKaXR4bl9maWVsZCBDb25maWdBc3NldFVSTAppbnRjXzAgLy8gMAppdHhuX2ZpZWxkIENvbmZpZ0Fzc2V0RGVmYXVsdEZyb3plbgpnbG9iYWwgQ3VycmVudEFwcGxpY2F0aW9uQWRkcmVzcwppdHhuX2ZpZWxkIENvbmZpZ0Fzc2V0UmVzZXJ2ZQpnbG9iYWwgQ3VycmVudEFwcGxpY2F0aW9uQWRkcmVzcwppdHhuX2ZpZWxkIENvbmZpZ0Fzc2V0TWFuYWdlcgpnbG9iYWwgQ3VycmVudEFwcGxpY2F0aW9uQWRkcmVzcwppdHhuX2ZpZWxkIENvbmZpZ0Fzc2V0RnJlZXplCmdsb2JhbCBDdXJyZW50QXBwbGljYXRpb25BZGRyZXNzCml0eG5fZmllbGQgQ29uZmlnQXNzZXRDbGF3YmFjawppdHhuX3N1Ym1pdAppdHhuIENyZWF0ZWRBc3NldElECmZyYW1lX2J1cnkgMApyZXRzdWIKCi8vIHB1cmNoYXNlQWlkTmZ0CnB1cmNoYXNlQWlkTmZ0XzE6CnByb3RvIDEgMQppbnRjXzAgLy8gMAp0eG5hIEFzc2V0cyAwCmFzc2V0X3BhcmFtc19nZXQgQXNzZXRDcmVhdG9yCnN0b3JlIDEKc3RvcmUgMApsb2FkIDEKaW50Y18xIC8vIDEKPT0KLy8gQVNBIHN1cHBsaWVkIG11c3QgZXhpc3QKYXNzZXJ0CmxvYWQgMApnbG9iYWwgQ3VycmVudEFwcGxpY2F0aW9uQWRkcmVzcwo9PQovLyBBU0Egc3VwcGxpZWQgbXVzdCBiZSBjcmVhdGVkIGJ5IEZyZWVkYSBQbGF5IGFwcAphc3NlcnQKZ3R4biAwIFR5cGVFbnVtCmludGNfMSAvLyBwYXkKPT0KLy8gRmlyc3QgVHhuIGluIEdyb3VwIG11c3QgYmUgUGF5bWVudAphc3NlcnQKZ3R4biAwIFJlY2VpdmVyCmdsb2JhbCBDdXJyZW50QXBwbGljYXRpb25BZGRyZXNzCj09Ci8vIFJlY2VpdmVyIG9mIFBheW1lbnQgbXVzdCBiZSBBcHBsaWNhdGlvbgphc3NlcnQKaXR4bl9iZWdpbgppbnRjXzIgLy8gYXhmZXIKaXR4bl9maWVsZCBUeXBlRW51bQpnbG9iYWwgQ3VycmVudEFwcGxpY2F0aW9uQWRkcmVzcwppdHhuX2ZpZWxkIFNlbmRlcgp0eG4gU2VuZGVyCml0eG5fZmllbGQgQXNzZXRSZWNlaXZlcgppbnRjXzEgLy8gMQppdHhuX2ZpZWxkIEFzc2V0QW1vdW50CnR4bmEgQXNzZXRzIDAKaXR4bl9maWVsZCBYZmVyQXNzZXQKaXR4bl9zdWJtaXQKaXR4bl9iZWdpbgppbnRjXzMgLy8gYWZyegppdHhuX2ZpZWxkIFR5cGVFbnVtCnR4bmEgQXNzZXRzIDAKaXR4bl9maWVsZCBGcmVlemVBc3NldAp0eG4gU2VuZGVyCml0eG5fZmllbGQgRnJlZXplQXNzZXRBY2NvdW50Cml0eG5fc3VibWl0CnR4biBTZW5kZXIKZnJhbWVfZGlnIC0xCmFzc2V0X2hvbGRpbmdfZ2V0IEFzc2V0QmFsYW5jZQpzdG9yZSAyCmZyYW1lX2J1cnkgMApyZXRzdWIKCi8vIHNlbGxBaWROZnQKc2VsbEFpZE5mdF8yOgpwcm90byAwIDEKaW50Y18wIC8vIDAKaXR4bl9iZWdpbgppbnRjXzIgLy8gYXhmZXIKaXR4bl9maWVsZCBUeXBlRW51bQp0eG4gU2VuZGVyCml0eG5fZmllbGQgQXNzZXRTZW5kZXIKZ2xvYmFsIEN1cnJlbnRBcHBsaWNhdGlvbkFkZHJlc3MKaXR4bl9maWVsZCBTZW5kZXIKZ2xvYmFsIEN1cnJlbnRBcHBsaWNhdGlvbkFkZHJlc3MKaXR4bl9maWVsZCBBc3NldFJlY2VpdmVyCmludGNfMSAvLyAxCml0eG5fZmllbGQgQXNzZXRBbW91bnQKdHhuYSBBc3NldHMgMAppdHhuX2ZpZWxkIFhmZXJBc3NldAppdHhuX3N1Ym1pdAppdHhuX2JlZ2luCmludGNfMSAvLyBwYXkKaXR4bl9maWVsZCBUeXBlRW51bQp0eG4gU2VuZGVyCml0eG5fZmllbGQgUmVjZWl2ZXIKaXR4bl9zdWJtaXQKdHhuIFNlbmRlcgp0eG5hIEFzc2V0cyAwCmFzc2V0X2hvbGRpbmdfZ2V0IEFzc2V0QmFsYW5jZQpzdG9yZSAzCmZyYW1lX2J1cnkgMApyZXRzdWIKCi8vIHVubG9ja0Fzc2V0CnVubG9ja0Fzc2V0XzM6CnByb3RvIDAgMAppdHhuX2JlZ2luCmludGNfMyAvLyBhZnJ6Cml0eG5fZmllbGQgVHlwZUVudW0KdHhuYSBBc3NldHMgMAppdHhuX2ZpZWxkIEZyZWV6ZUFzc2V0CnR4biBTZW5kZXIKaXR4bl9maWVsZCBGcmVlemVBc3NldEFjY291bnQKaW50Y18wIC8vIDAKaXR4bl9maWVsZCBGcmVlemVBc3NldEZyb3plbgppdHhuX3N1Ym1pdApyZXRzdWIKCi8vIGluaXRBaWROZnRfY2FzdGVyCmluaXRBaWROZnRjYXN0ZXJfNDoKcHJvdG8gMCAwCmludGNfMCAvLyAwCmNhbGxzdWIgaW5pdEFpZE5mdF8wCmZyYW1lX2J1cnkgMApieXRlY18wIC8vIDB4MTUxZjdjNzUKZnJhbWVfZGlnIDAKaXRvYgpjb25jYXQKbG9nCnJldHN1YgoKLy8gcHVyY2hhc2VBaWROZnRfY2FzdGVyCnB1cmNoYXNlQWlkTmZ0Y2FzdGVyXzU6CnByb3RvIDAgMAppbnRjXzAgLy8gMApkdXAKdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMQpidG9pCmZyYW1lX2J1cnkgMQpmcmFtZV9kaWcgMQpjYWxsc3ViIHB1cmNoYXNlQWlkTmZ0XzEKZnJhbWVfYnVyeSAwCmJ5dGVjXzAgLy8gMHgxNTFmN2M3NQpmcmFtZV9kaWcgMAppdG9iCmNvbmNhdApsb2cKcmV0c3ViCgovLyBzZWxsQWlkTmZ0X2Nhc3RlcgpzZWxsQWlkTmZ0Y2FzdGVyXzY6CnByb3RvIDAgMAppbnRjXzAgLy8gMApjYWxsc3ViIHNlbGxBaWROZnRfMgpmcmFtZV9idXJ5IDAKYnl0ZWNfMCAvLyAweDE1MWY3Yzc1CmZyYW1lX2RpZyAwCml0b2IKY29uY2F0CmxvZwpyZXRzdWIKCi8vIHVubG9ja0Fzc2V0X2Nhc3Rlcgp1bmxvY2tBc3NldGNhc3Rlcl83Ogpwcm90byAwIDAKY2FsbHN1YiB1bmxvY2tBc3NldF8zCnJldHN1Yg==", + "clear": "I3ByYWdtYSB2ZXJzaW9uIDgKcHVzaGludCAwIC8vIDAKcmV0dXJu" + }, + "state": { + "global": { + "num_byte_slices": 0, + "num_uints": 0 + }, + "local": { + "num_byte_slices": 0, + "num_uints": 0 + } + }, + "schema": { + "global": { + "declared": {}, + "reserved": {} + }, + "local": { + "declared": {}, + "reserved": {} + } + }, + "contract": { + "name": "algo-aid", + "methods": [ + { + "name": "initAidNft", + "args": [], + "returns": { + "type": "uint64" + } + }, + { + "name": "purchaseAidNft", + "args": [ + { + "type": "uint64", + "name": "asset_id" + } + ], + "returns": { + "type": "uint64" + } + }, + { + "name": "sellAidNft", + "args": [], + "returns": { + "type": "uint64" + } + }, + { + "name": "unlockAsset", + "args": [], + "returns": { + "type": "void" + } + } + ], + "networks": {} + }, + "bare_call_config": { + "no_op": "CREATE" + } +}""" +APP_SPEC = algokit_utils.ApplicationSpecification.from_json(_APP_SPEC_JSON) +_TReturn = typing.TypeVar("_TReturn") + + +class _ArgsBase(ABC, typing.Generic[_TReturn]): + @staticmethod + @abstractmethod + def method() -> str: + ... + + +_TArgs = typing.TypeVar("_TArgs", bound=_ArgsBase[typing.Any]) + + +@dataclasses.dataclass(kw_only=True) +class _TArgsHolder(typing.Generic[_TArgs]): + args: _TArgs + + +def _filter_none(value: dict | typing.Any) -> dict | typing.Any: + if isinstance(value, dict): + return {k: _filter_none(v) for k, v in value.items() if v is not None} + return value + + +def _as_dict(data: typing.Any, *, convert_all: bool = True) -> dict[str, typing.Any]: + if data is None: + return {} + if not dataclasses.is_dataclass(data): + raise TypeError(f"{data} must be a dataclass") + if convert_all: + result = dataclasses.asdict(data) + else: + result = {f.name: getattr(data, f.name) for f in dataclasses.fields(data)} + return _filter_none(result) + + +def _convert_transaction_parameters( + transaction_parameters: algokit_utils.TransactionParameters | None, +) -> algokit_utils.CommonCallParametersDict: + return typing.cast(algokit_utils.CommonCallParametersDict, _as_dict(transaction_parameters)) + + +def _convert_call_transaction_parameters( + transaction_parameters: algokit_utils.TransactionParameters | None, +) -> algokit_utils.OnCompleteCallParametersDict: + return typing.cast(algokit_utils.OnCompleteCallParametersDict, _as_dict(transaction_parameters)) + + +def _convert_create_transaction_parameters( + transaction_parameters: algokit_utils.TransactionParameters | None, + on_complete: algokit_utils.OnCompleteActionName, +) -> algokit_utils.CreateCallParametersDict: + result = typing.cast(algokit_utils.CreateCallParametersDict, _as_dict(transaction_parameters)) + on_complete_enum = on_complete.replace("_", " ").title().replace(" ", "") + "OC" + result["on_complete"] = getattr(algosdk.transaction.OnComplete, on_complete_enum) + return result + + +def _convert_deploy_args( + deploy_args: algokit_utils.DeployCallArgs | None, +) -> algokit_utils.ABICreateCallArgsDict | None: + if deploy_args is None: + return None + + deploy_args_dict = typing.cast(algokit_utils.ABICreateCallArgsDict, _as_dict(deploy_args)) + if isinstance(deploy_args, _TArgsHolder): + deploy_args_dict["args"] = _as_dict(deploy_args.args) + deploy_args_dict["method"] = deploy_args.args.method() + + return deploy_args_dict + + +@dataclasses.dataclass(kw_only=True) +class InitAidNftArgs(_ArgsBase[int]): + @staticmethod + def method() -> str: + return "initAidNft()uint64" + + +@dataclasses.dataclass(kw_only=True) +class PurchaseAidNftArgs(_ArgsBase[int]): + asset_id: int + + @staticmethod + def method() -> str: + return "purchaseAidNft(uint64)uint64" + + +@dataclasses.dataclass(kw_only=True) +class SellAidNftArgs(_ArgsBase[int]): + @staticmethod + def method() -> str: + return "sellAidNft()uint64" + + +@dataclasses.dataclass(kw_only=True) +class UnlockAssetArgs(_ArgsBase[None]): + @staticmethod + def method() -> str: + return "unlockAsset()void" + + +class Composer: + + def __init__(self, app_client: algokit_utils.ApplicationClient, atc: AtomicTransactionComposer): + self.app_client = app_client + self.atc = atc + + def build(self) -> AtomicTransactionComposer: + return self.atc + + def execute(self) -> AtomicTransactionResponse: + return self.app_client.execute_atc(self.atc) + + def init_aid_nft( + self, + *, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + ) -> "Composer": + """Adds a call to `initAidNft()uint64` ABI method + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns Composer: This Composer instance""" + + args = InitAidNftArgs() + self.app_client.compose_call( + self.atc, + call_abi_method=args.method(), + transaction_parameters=_convert_call_transaction_parameters(transaction_parameters), + **_as_dict(args, convert_all=True), + ) + return self + + def purchase_aid_nft( + self, + *, + asset_id: int, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + ) -> "Composer": + """Adds a call to `purchaseAidNft(uint64)uint64` ABI method + + :param int asset_id: The `asset_id` ABI parameter + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns Composer: This Composer instance""" + + args = PurchaseAidNftArgs( + asset_id=asset_id, + ) + self.app_client.compose_call( + self.atc, + call_abi_method=args.method(), + transaction_parameters=_convert_call_transaction_parameters(transaction_parameters), + **_as_dict(args, convert_all=True), + ) + return self + + def sell_aid_nft( + self, + *, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + ) -> "Composer": + """Adds a call to `sellAidNft()uint64` ABI method + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns Composer: This Composer instance""" + + args = SellAidNftArgs() + self.app_client.compose_call( + self.atc, + call_abi_method=args.method(), + transaction_parameters=_convert_call_transaction_parameters(transaction_parameters), + **_as_dict(args, convert_all=True), + ) + return self + + def unlock_asset( + self, + *, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + ) -> "Composer": + """Adds a call to `unlockAsset()void` ABI method + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns Composer: This Composer instance""" + + args = UnlockAssetArgs() + self.app_client.compose_call( + self.atc, + call_abi_method=args.method(), + transaction_parameters=_convert_call_transaction_parameters(transaction_parameters), + **_as_dict(args, convert_all=True), + ) + return self + + def create_bare( + self, + *, + on_complete: typing.Literal["no_op"] = "no_op", + transaction_parameters: algokit_utils.CreateTransactionParameters | None = None, + ) -> "Composer": + """Adds a call to create an application using the no_op bare method + + :param typing.Literal[no_op] on_complete: On completion type to use + :param algokit_utils.CreateTransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns Composer: This Composer instance""" + + self.app_client.compose_create( + self.atc, + call_abi_method=False, + transaction_parameters=_convert_create_transaction_parameters(transaction_parameters, on_complete), + ) + return self + + def clear_state( + self, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + app_args: list[bytes] | None = None, + ) -> "Composer": + """Adds a call to the application with on completion set to ClearState + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :param list[bytes] | None app_args: (optional) Application args to pass""" + + self.app_client.compose_clear_state(self.atc, _convert_transaction_parameters(transaction_parameters), app_args) + return self + + +class AlgoAidClient: + """A class for interacting with the algo-aid app providing high productivity and + strongly typed methods to deploy and call the app""" + + @typing.overload + def __init__( + self, + algod_client: algosdk.v2client.algod.AlgodClient, + *, + app_id: int = 0, + signer: TransactionSigner | algokit_utils.Account | None = None, + sender: str | None = None, + suggested_params: algosdk.transaction.SuggestedParams | None = None, + template_values: algokit_utils.TemplateValueMapping | None = None, + app_name: str | None = None, + ) -> None: + ... + + @typing.overload + def __init__( + self, + algod_client: algosdk.v2client.algod.AlgodClient, + *, + creator: str | algokit_utils.Account, + indexer_client: algosdk.v2client.indexer.IndexerClient | None = None, + existing_deployments: algokit_utils.AppLookup | None = None, + signer: TransactionSigner | algokit_utils.Account | None = None, + sender: str | None = None, + suggested_params: algosdk.transaction.SuggestedParams | None = None, + template_values: algokit_utils.TemplateValueMapping | None = None, + app_name: str | None = None, + ) -> None: + ... + + def __init__( + self, + algod_client: algosdk.v2client.algod.AlgodClient, + *, + creator: str | algokit_utils.Account | None = None, + indexer_client: algosdk.v2client.indexer.IndexerClient | None = None, + existing_deployments: algokit_utils.AppLookup | None = None, + app_id: int = 0, + signer: TransactionSigner | algokit_utils.Account | None = None, + sender: str | None = None, + suggested_params: algosdk.transaction.SuggestedParams | None = None, + template_values: algokit_utils.TemplateValueMapping | None = None, + app_name: str | None = None, + ) -> None: + """ + AlgoAidClient can be created with an app_id to interact with an existing application, alternatively + it can be created with a creator and indexer_client specified to find existing applications by name and creator. + + :param AlgodClient algod_client: AlgoSDK algod client + :param int app_id: The app_id of an existing application, to instead find the application by creator and name + use the creator and indexer_client parameters + :param str | Account creator: The address or Account of the app creator to resolve the app_id + :param IndexerClient indexer_client: AlgoSDK indexer client, only required if deploying or finding app_id by + creator and app name + :param AppLookup existing_deployments: + :param TransactionSigner | Account signer: Account or signer to use to sign transactions, if not specified and + creator was passed as an Account will use that. + :param str sender: Address to use as the sender for all transactions, will use the address associated with the + signer if not specified. + :param TemplateValueMapping template_values: Values to use for TMPL_* template variables, dictionary keys should + *NOT* include the TMPL_ prefix + :param str | None app_name: Name of application to use when deploying, defaults to name defined on the + Application Specification + """ + + self.app_spec = APP_SPEC + + # calling full __init__ signature, so ignoring mypy warning about overloads + self.app_client = algokit_utils.ApplicationClient( # type: ignore[call-overload, misc] + algod_client=algod_client, + app_spec=self.app_spec, + app_id=app_id, + creator=creator, + indexer_client=indexer_client, + existing_deployments=existing_deployments, + signer=signer, + sender=sender, + suggested_params=suggested_params, + template_values=template_values, + app_name=app_name, + ) + + @property + def algod_client(self) -> algosdk.v2client.algod.AlgodClient: + return self.app_client.algod_client + + @property + def app_id(self) -> int: + return self.app_client.app_id + + @app_id.setter + def app_id(self, value: int) -> None: + self.app_client.app_id = value + + @property + def app_address(self) -> str: + return self.app_client.app_address + + @property + def sender(self) -> str | None: + return self.app_client.sender + + @sender.setter + def sender(self, value: str) -> None: + self.app_client.sender = value + + @property + def signer(self) -> TransactionSigner | None: + return self.app_client.signer + + @signer.setter + def signer(self, value: TransactionSigner) -> None: + self.app_client.signer = value + + @property + def suggested_params(self) -> algosdk.transaction.SuggestedParams | None: + return self.app_client.suggested_params + + @suggested_params.setter + def suggested_params(self, value: algosdk.transaction.SuggestedParams | None) -> None: + self.app_client.suggested_params = value + + def init_aid_nft( + self, + *, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + ) -> algokit_utils.ABITransactionResponse[int]: + """Calls `initAidNft()uint64` ABI method + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns algokit_utils.ABITransactionResponse[int]: The result of the transaction""" + + args = InitAidNftArgs() + result = self.app_client.call( + call_abi_method=args.method(), + transaction_parameters=_convert_call_transaction_parameters(transaction_parameters), + **_as_dict(args, convert_all=True), + ) + return result + + def purchase_aid_nft( + self, + *, + asset_id: int, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + ) -> algokit_utils.ABITransactionResponse[int]: + """Calls `purchaseAidNft(uint64)uint64` ABI method + + :param int asset_id: The `asset_id` ABI parameter + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns algokit_utils.ABITransactionResponse[int]: The result of the transaction""" + + args = PurchaseAidNftArgs( + asset_id=asset_id, + ) + result = self.app_client.call( + call_abi_method=args.method(), + transaction_parameters=_convert_call_transaction_parameters(transaction_parameters), + **_as_dict(args, convert_all=True), + ) + return result + + def sell_aid_nft( + self, + *, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + ) -> algokit_utils.ABITransactionResponse[int]: + """Calls `sellAidNft()uint64` ABI method + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns algokit_utils.ABITransactionResponse[int]: The result of the transaction""" + + args = SellAidNftArgs() + result = self.app_client.call( + call_abi_method=args.method(), + transaction_parameters=_convert_call_transaction_parameters(transaction_parameters), + **_as_dict(args, convert_all=True), + ) + return result + + def unlock_asset( + self, + *, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + ) -> algokit_utils.ABITransactionResponse[None]: + """Calls `unlockAsset()void` ABI method + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns algokit_utils.ABITransactionResponse[None]: The result of the transaction""" + + args = UnlockAssetArgs() + result = self.app_client.call( + call_abi_method=args.method(), + transaction_parameters=_convert_call_transaction_parameters(transaction_parameters), + **_as_dict(args, convert_all=True), + ) + return result + + def create_bare( + self, + *, + on_complete: typing.Literal["no_op"] = "no_op", + transaction_parameters: algokit_utils.CreateTransactionParameters | None = None, + ) -> algokit_utils.TransactionResponse: + """Creates an application using the no_op bare method + + :param typing.Literal[no_op] on_complete: On completion type to use + :param algokit_utils.CreateTransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns algokit_utils.TransactionResponse: The result of the transaction""" + + result = self.app_client.create( + call_abi_method=False, + transaction_parameters=_convert_create_transaction_parameters(transaction_parameters, on_complete), + ) + return result + + def clear_state( + self, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + app_args: list[bytes] | None = None, + ) -> algokit_utils.TransactionResponse: + """Calls the application with on completion set to ClearState + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :param list[bytes] | None app_args: (optional) Application args to pass + :returns algokit_utils.TransactionResponse: The result of the transaction""" + + return self.app_client.clear_state(_convert_transaction_parameters(transaction_parameters), app_args) + + def deploy( + self, + version: str | None = None, + *, + signer: TransactionSigner | None = None, + sender: str | None = None, + allow_update: bool | None = None, + allow_delete: bool | None = None, + on_update: algokit_utils.OnUpdate = algokit_utils.OnUpdate.Fail, + on_schema_break: algokit_utils.OnSchemaBreak = algokit_utils.OnSchemaBreak.Fail, + template_values: algokit_utils.TemplateValueMapping | None = None, + create_args: algokit_utils.DeployCallArgs | None = None, + update_args: algokit_utils.DeployCallArgs | None = None, + delete_args: algokit_utils.DeployCallArgs | None = None, + ) -> algokit_utils.DeployResponse: + """Deploy an application and update client to reference it. + + Idempotently deploy (create, update/delete if changed) an app against the given name via the given creator + account, including deploy-time template placeholder substitutions. + To understand the architecture decisions behind this functionality please see + + + ```{note} + If there is a breaking state schema change to an existing app (and `on_schema_break` is set to + 'ReplaceApp' the existing app will be deleted and re-created. + ``` + + ```{note} + If there is an update (different TEAL code) to an existing app (and `on_update` is set to 'ReplaceApp') + the existing app will be deleted and re-created. + ``` + + :param str version: version to use when creating or updating app, if None version will be auto incremented + :param algosdk.atomic_transaction_composer.TransactionSigner signer: signer to use when deploying app + , if None uses self.signer + :param str sender: sender address to use when deploying app, if None uses self.sender + :param bool allow_delete: Used to set the `TMPL_DELETABLE` template variable to conditionally control if an app + can be deleted + :param bool allow_update: Used to set the `TMPL_UPDATABLE` template variable to conditionally control if an app + can be updated + :param OnUpdate on_update: Determines what action to take if an application update is required + :param OnSchemaBreak on_schema_break: Determines what action to take if an application schema requirements + has increased beyond the current allocation + :param dict[str, int|str|bytes] template_values: Values to use for `TMPL_*` template variables, dictionary keys + should *NOT* include the TMPL_ prefix + :param algokit_utils.DeployCallArgs | None create_args: Arguments used when creating an application + :param algokit_utils.DeployCallArgs | None update_args: Arguments used when updating an application + :param algokit_utils.DeployCallArgs | None delete_args: Arguments used when deleting an application + :return DeployResponse: details action taken and relevant transactions + :raises DeploymentError: If the deployment failed""" + + return self.app_client.deploy( + version, + signer=signer, + sender=sender, + allow_update=allow_update, + allow_delete=allow_delete, + on_update=on_update, + on_schema_break=on_schema_break, + template_values=template_values, + create_args=_convert_deploy_args(create_args), + update_args=_convert_deploy_args(update_args), + delete_args=_convert_deploy_args(delete_args), + ) + + def compose(self, atc: AtomicTransactionComposer | None = None) -> Composer: + return Composer(self.app_client, atc or AtomicTransactionComposer()) diff --git a/backend/smart_contracts/artifacts/algo-aid/contract.json b/backend/smart_contracts/artifacts/algo-aid/contract.json new file mode 100644 index 0000000..c1e52a8 --- /dev/null +++ b/backend/smart_contracts/artifacts/algo-aid/contract.json @@ -0,0 +1,39 @@ +{ + "name": "algo-aid", + "methods": [ + { + "name": "initAidNft", + "args": [], + "returns": { + "type": "uint64" + } + }, + { + "name": "purchaseAidNft", + "args": [ + { + "type": "uint64", + "name": "asset_id" + } + ], + "returns": { + "type": "uint64" + } + }, + { + "name": "sellAidNft", + "args": [], + "returns": { + "type": "uint64" + } + }, + { + "name": "unlockAsset", + "args": [], + "returns": { + "type": "void" + } + } + ], + "networks": {} +} \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 728559b..464909a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -34,21 +34,35 @@ class GroupProvider { { id: 1, title: 'World WildLife Fund', - description: 'Funding for Wildlife', + description: 'Funding for Wildlife and the Env', imageUrl: 'https://logowik.com/content/uploads/images/753_wwf.jpg', }, { id: 2, - title: 'YAYASAN ZURIATCARE', - description: 'Sebuah yayasan amal di bawah Awqaf Holdings Berhad.', - imageUrl: 'https://ngohub-production.s3.amazonaws.com/logos/organization/1564/Thumbnail_Logo.jpg', + title: 'MERCY MALAYSIA', + description: + 'MERCY Malaysia is an international NGO providing medical relief, sustainable health-related & risk reduction activities for vulnerable communities.', + imageUrl: 'https://www.mercy.org.my/wp-content/uploads/2019/02/logo-trace.png', }, { id: 3, - title: 'Youth Build Nation (YBN)', - description: 'A nonprofit organisation aim to empower young Malaysians.', - imageUrl: - 'https://ngohub-production.s3.amazonaws.com/header_images/organization/2773/header_94488791_255581725833536_6597782409104588800_o.png', + title: 'ACTION AGAINST HUNGER', + description: + 'Action Against Hunger is a global humanitarian organization that takes decisive action against the causes and effects of hunger.', + imageUrl: 'https://www.actionagainsthunger.org/app/themes/actionagainsthunger/assets/images/aah-og.jpg', + }, + { + id: 4, + title: 'Teach For Malaysia', + description: 'A nonprofit organisation aim to empower young Malaysians with Education.', + imageUrl: 'https://blog.theincitement.com/wp-content/uploads/2022/07/Teach-for-Malaysia-on-Incitement.jpg', + }, + { + id: 5, + title: 'Mental Ilness Awareness & Support Association (MIASA)', + description: + 'MIASA is To be the leading voice in mental health education in support of people with mental health disorders in Malaysia.', + imageUrl: 'https://www.miasa.org.my/images/MIASA-LOGO-2022-PNG-300x264.png', }, ] } @@ -108,16 +122,16 @@ export default function App() { Dashboard
  • - Category + Category
  • - FundRaise + FundRaise
  • - Donation History + Donation History
  • - Profile + Profile
  • @@ -151,8 +165,9 @@ export default function App() { style={{ minHeight: '150px', maxHeight: '150px', + maxWidth: '150px', borderRadius: '20px', - objectFit: 'cover', + objectFit: 'scale-down', }} src={group.imageUrl} alt="Movie" @@ -188,16 +203,16 @@ export default function App() { Dashboard
  • - Category + Category
  • - FundRaise + FundRaise
  • - Donation History + Donation History
  • - Profile + Profile
  • diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 53229c3..761b6fc 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -5,7 +5,10 @@ import FundRaisePage from './App' import ErrorBoundary from './components/ErrorBoundary' import './styles/main.css' +import CategoryPage from './pages/CategoryPage' import DashboardPage from './pages/DashboardPage' +import DonationHistoryPage from './pages/DonationHistoryPage' +import ProfilePage from './pages/ProfilePage' const router = createBrowserRouter([ { @@ -16,6 +19,18 @@ const router = createBrowserRouter([ path: '/fundraise', element: , }, + { + path: '/category', + element: , + }, + { + path: '/donation', + element: , + }, + { + path: '/profile', + element: , + }, ]) ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( diff --git a/frontend/src/pages/CategoryPage.tsx b/frontend/src/pages/CategoryPage.tsx index e69de29..f091972 100644 --- a/frontend/src/pages/CategoryPage.tsx +++ b/frontend/src/pages/CategoryPage.tsx @@ -0,0 +1,171 @@ +import { useWallet } from '@txnlab/use-wallet' +import { SnackbarProvider } from 'notistack' +import { useState } from 'react' +import { CategoryModel, CategoryProvider } from '../providers/CategoryProvider' +import { GroupModel, GroupProvider } from '../providers/GroupProvider' + +export default function App() { + const [openWalletModal, setOpenWalletModal] = useState(false) + const [openDemoModal, setOpenDemoModal] = useState(false) + const [appCallsDemoModal, setAppCallsDemoModal] = useState(false) + const { activeAddress } = useWallet() + const [activeTab, setActiveTab] = useState('Environment') + + const groupList: GroupModel[] = GroupProvider.groupList + const filteredGroups = groupList.filter((group) => group.category === activeTab) + + const categoryList: CategoryModel[] = CategoryProvider.categoryList + const filteredCategory = categoryList.filter((category) => category.name === activeTab) + + const toggleWalletModal = () => { + setOpenWalletModal(!openWalletModal) + } + + const toggleDemoModal = () => { + setOpenDemoModal(!openDemoModal) + } + + const toggleAppCallsModal = () => { + setAppCallsDemoModal(!appCallsDemoModal) + } + + return ( + +
    + +
    + {/* Navbar */} +
    +
    + +
    +
    AlgoAid
    +
    + +
    +
    + +
    +
    +
    +
    +

    +
    AlgoAid Categories ⛑️
    +

    +
    +
    + +
    +
    +
    +
    +
    +
    + + + {filteredCategory.map((category) => { + return ( +
    +

    {category.name}

    +

    {category.description}

    +
    + ) + })} + + {filteredGroups.map((group, index) => { + return ( +
    +
    + {`Image +
    +
    +

    {group.title}

    +

    {group.description}

    +
    + {activeAddress && ( + + )} +
    +
    +
    + ) + })} +
    +
    +
    +
    +
    +
    + + +
    +
    +
    + ) +} diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index 8bbf208..e13cd3d 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -75,16 +75,16 @@ export default function App() { Dashboard
  • - Category + Category
  • FundRaise
  • - Donation History + Donation History
  • - Profile + Profile
  • diff --git a/frontend/src/pages/DonationHistoryPage.tsx b/frontend/src/pages/DonationHistoryPage.tsx index e69de29..cb033dc 100644 --- a/frontend/src/pages/DonationHistoryPage.tsx +++ b/frontend/src/pages/DonationHistoryPage.tsx @@ -0,0 +1,178 @@ +import { SnackbarProvider } from 'notistack' + +export default function DonationHistory() { + return ( + +
    + +
    + {/* Navbar */} +
    +
    + +
    +
    AlgoAid
    +
    + +
    +
    + +
    +

    MyAlgorand Donation History

    +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + TxID + + Block + + Age + + From + + To + + Amount + + Fee + + Type +
    + + QQE5PFUBO... + + 335482875 mins + + 4MVOEIBT... + + + + S2RBDBW2... + + 10 Algos0.001 AlgosTransfer
    + + UZEGUADF... + + 335482403 mins + + DISPE5MN... + + + + 4MVOEIBT... + + 20 Algos0.001 AlgosTransfer
    +
    +
    +
    +
    + + +
    +
    +
    + ) +} diff --git a/frontend/src/pages/ProfilePage.tsx b/frontend/src/pages/ProfilePage.tsx index e69de29..4bd46b2 100644 --- a/frontend/src/pages/ProfilePage.tsx +++ b/frontend/src/pages/ProfilePage.tsx @@ -0,0 +1,190 @@ +import { useWallet } from '@txnlab/use-wallet' +import { SnackbarProvider } from 'notistack' +import { useState } from 'react' + +export interface PeopleModel { + id: string + imageURL: string + name: string + subtitle: string + bio: string + interests: string + fundedGroups: number[] // IDs of funded groups + // fundingHistory: number[] // nfts +} + +const userList: PeopleModel[] = [ + { + id: 'user1', + imageURL: 'https://img.freepik.com/premium-vector/avatar-icon002_750950-52.jpg', + name: 'John Doeing Doedy', + subtitle: 'Web3 Enthusiast', + bio: 'The world is unfair, and I want to save it a little more with what I have.', + interests: 'Web3, NFT, Crypto, Art', + fundedGroups: [1, 2], + // fundingHistory: [1, 2], + }, +] + +const user = userList[0] + +export default function App() { + const [openWalletModal, setOpenWalletModal] = useState(false) + const [openDemoModal, setOpenDemoModal] = useState(false) + const [appCallsDemoModal, setAppCallsDemoModal] = useState(false) + const { activeAddress } = useWallet() + const [activeTab, setActiveTab] = useState('Environment') + + const toggleWalletModal = () => { + setOpenWalletModal(!openWalletModal) + } + + const toggleDemoModal = () => { + setOpenDemoModal(!openDemoModal) + } + + const toggleAppCallsModal = () => { + setAppCallsDemoModal(!appCallsDemoModal) + } + + return ( + +
    + +
    + {/* Navbar */} +
    +
    + +
    +
    AlgoAid
    +
    + +
    +
    + +
    +
    +
    +
    +

    +
    Profile
    +

    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +

    {user.name}

    +

    {user.subtitle}

    +

    {user.bio}

    +
    +
    +

    NFT

    +
    +
    + Drink +
    +
    + Drink +
    +
    + Drink +
    +
    + Drink +
    +
    + Drink +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    + ) +} diff --git a/frontend/src/providers/CategoryProvider.tsx b/frontend/src/providers/CategoryProvider.tsx new file mode 100644 index 0000000..075f568 --- /dev/null +++ b/frontend/src/providers/CategoryProvider.tsx @@ -0,0 +1,40 @@ +export interface CategoryModel { + id: number + name: string + description: string +} + +export class CategoryProvider { + static categoryList: CategoryModel[] = [ + { + id: 1, + name: 'Environment', + description: + 'Our environment sustains all life on Earth. By contributing to this category, you are supporting initiatives dedicated to preserving and protecting our natural world. From reforestation efforts to marine conservation projects, your donations will help create a healthier planet for future generations.', + }, + { + id: 2, + name: 'Community', + description: + "Strong communities are built on shared values and support. When you donate to the Community category, you're investing in programs that empower and uplift local neighborhoods. Your contributions can fund community centers, youth mentorship programs, and initiatives that promote inclusivity, social cohesion, and a sense of belonging.", + }, + { + id: 3, + name: 'Non-Profit', + description: + 'Non-profit organizations are essential drivers of positive change. Your contributions to this category directly support the work of non-profits dedicated to various causes, from education and healthcare to poverty alleviation and disaster relief. Help make the world a better place by donating to non-profit initiatives that align with your values.', + }, + { + id: 4, + name: 'Animals', + description: + "Animals are an integral part of our planet, and they deserve our care and protection. When you give to the Animals category, you're aiding efforts to rescue, rehabilitate, and provide sanctuary for wildlife and domestic animals alike. Your support can make a difference in preserving biodiversity and ensuring the well-being of animals in need.", + }, + { + id: 5, + name: 'Medical', + description: + "Access to quality healthcare is a fundamental human right. By contributing to the Medical category, you're backing medical research, healthcare infrastructure, and initiatives that improve the health and well-being of individuals and communities. Your donations can help advance medical breakthroughs and provide essential care to those in need.", + }, + ] +} diff --git a/frontend/src/providers/GroupProvider.tsx b/frontend/src/providers/GroupProvider.tsx new file mode 100644 index 0000000..804cfa1 --- /dev/null +++ b/frontend/src/providers/GroupProvider.tsx @@ -0,0 +1,201 @@ +export interface GroupModel { + id: number + title: string + category: string + description: string + imageUrl: string +} + +export class GroupProvider { + static groupList: GroupModel[] = [ + { + id: 1, + title: 'Environmental Cause', + category: 'Environment', + description: "Donate through us to support the environment's preservation and conservation to preserve our Mother Nature.", + imageUrl: 'https://upload.wikimedia.org/wikipedia/commons/3/36/Hopetoun_falls.jpg', + }, + { + id: 2, + title: 'Natural Disaster Relief', + category: 'Environment', + description: + "Donate through us to take part in helping the disaster relief's efforts such as post Earthquake, Flood or even Landslide relief", + imageUrl: 'https://www.habitatforhumanity.org.uk/wp-content/uploads/2017/10/natural-disaster-relief-charity-response-emergency.jpg', + }, + { + id: 3, + title: 'World Hunger', + category: 'Community', + description: 'Donate through us to contribute towards reducing the amount of starving people in this', + imageUrl: 'https://www.compassion.com/multimedia/hunger-in-asia.jpg', + }, + { + id: 4, + title: 'Education', + category: 'Community', + description: 'Donate through us to give the less fortunate a chance in receiving formal education', + imageUrl: 'https://www.thenation.com/wp-content/uploads/2016/07/joseph_teachforindia_img.jpg', + }, + // { + // id: 5, + // title: 'Mental Health', + // category: 'Community', + // description: 'Donate through us to fund mental health causes that contribute their efforts towards spreading mental health awareness', + // imageUrl: 'https://www.biospectrumasia.com/uploads/articles/3-21968.png', + // }, + // { + // id: 6, + // title: 'Climate Change Advocates', + // category: 'Environment', + // description: 'Support our mission to combat climate change and promote sustainable living.', + // imageUrl: 'https://www.example.com/climate-change-advocates.jpg', + // }, + // { + // id: 7, + // title: 'Wildlife Conservation Society', + // category: 'Environment', + // description: 'Contribute to the protection of endangered species and their habitats.', + // imageUrl: 'https://www.example.com/wildlife-conservation.jpg', + // }, + // { + // id: 8, + // title: 'Clean Water Initiative', + // category: 'Environment', + // description: 'Help provide clean and safe drinking water to communities in need.', + // imageUrl: 'https://www.example.com/clean-water-initiative.jpg', + // }, + // { + // id: 9, + // title: 'Eco-Friendly Farming Project', + // category: 'Environment', + // description: 'Support sustainable farming practices and reduce environmental impact.', + // imageUrl: 'https://www.example.com/eco-friendly-farming.jpg', + // }, + // { + // id: 10, + // title: 'Local Youth Center', + // category: 'Community', + // description: 'Empower local youth with educational and recreational opportunities.', + // imageUrl: 'https://www.example.com/local-youth-center.jpg', + // }, + // { + // id: 11, + // title: 'Community Garden Project', + // category: 'Community', + // description: 'Help create green spaces for community members to grow fresh produce.', + // imageUrl: 'https://www.example.com/community-garden.jpg', + // }, + // { + // id: 12, + // title: 'Elderly Care Assistance', + // category: 'Community', + // description: 'Support programs that provide care and companionship to elderly individuals.', + // imageUrl: 'https://www.example.com/elderly-care.jpg', + // }, + // { + // id: 13, + // title: 'Youth Education Fund', + // category: 'Community', + // description: 'Give underprivileged youth access to quality education and brighter futures.', + // imageUrl: 'https://www.example.com/youth-education-fund.jpg', + // }, + // { + // id: 14, + // title: "Children's Hospital Foundation", + // category: 'Medical', + // description: 'Fund life-saving treatments and research at our pediatric hospital.', + // imageUrl: 'https://www.example.com/childrens-hospital.jpg', + // }, + // { + // id: 15, + // title: 'Medical Supplies for Underserved Communities', + // category: 'Medical', + // description: 'Provide essential medical supplies to communities in need around the world.', + // imageUrl: 'https://www.example.com/medical-supplies.jpg', + // }, + // { + // id: 16, + // title: 'Cancer Research Institute', + // category: 'Medical', + // description: 'Support groundbreaking research to find a cure for cancer and improve treatments.', + // imageUrl: 'https://www.example.com/cancer-research.jpg', + // }, + // { + // id: 17, + // title: 'Animal Rescue Shelter', + // category: 'Animals', + // description: 'Rescue and care for abandoned and abused animals in our community.', + // imageUrl: 'https://www.example.com/animal-rescue.jpg', + // }, + // { + // id: 18, + // title: 'Marine Conservation Society', + // category: 'Animals', + // description: 'Protect our oceans and marine life through conservation efforts.', + // imageUrl: 'https://www.example.com/marine-conservation.jpg', + // }, + // { + // id: 19, + // title: 'Wildlife Rehabilitation Center', + // category: 'Animals', + // description: 'Rehabilitate injured and orphaned wildlife and release them back into the wild.', + // imageUrl: 'https://www.example.com/wildlife-rehabilitation.jpg', + // }, + // { + // id: 20, + // title: 'Veterinary Care for Strays', + // category: 'Animals', + // description: 'Provide medical care and shelter to stray animals in need of assistance.', + // imageUrl: 'https://www.example.com/veterinary-care.jpg', + // }, + // { + // id: 21, + // title: 'Global Hunger Relief', + // category: 'Non-Profit', + // description: 'Join us in the fight against global hunger and food insecurity.', + // imageUrl: 'https://www.example.com/global-hunger-relief.jpg', + // }, + // { + // id: 22, + // title: 'Disaster Relief Fund', + // category: 'Non-Profit', + // description: 'Support disaster response efforts and aid communities affected by emergencies.', + // imageUrl: 'https://www.example.com/disaster-relief.jpg', + // }, + // { + // id: 23, + // title: 'Education Access Initiative', + // category: 'Non-Profit', + // description: 'Promote educational access and opportunities for marginalized populations.', + // imageUrl: 'https://www.example.com/education-access.jpg', + // }, + // { + // id: 24, + // title: 'Refugee Assistance Program', + // category: 'Non-Profit', + // description: 'Provide essential support to refugees seeking safety and stability.', + // imageUrl: 'https://www.example.com/refugee-assistance.jpg', + // }, + ] +} + +// { +// id: 1, +// title: 'Environmental Cause', +// description: "Donate through us to support the environment's preservation and conservation to preserve our Mother Nature.", +// imageUrl: 'https://upload.wikimedia.org/wikipedia/commons/3/36/Hopetoun_falls.jpg', +// }, +// { +// id: 2, +// title: 'Natural Disaster Relief', +// description: +// "Donate through us to take part in helping the disaster relief's efforts such as post Earthquake, Flood or even Landslide relief", +// imageUrl: 'https://www.habitatforhumanity.org.uk/wp-content/uploads/2017/10/natural-disaster-relief-charity-response-emergency.jpg', +// }, +// { +// id: 3, +// title: 'World Hunger', +// description: 'Donate through us to contribute towards reducing the amount of starving people in this', +// imageUrl:'https://www.compassion.com/multimedia/hunger-in-asia.jpg', +// },