From ad0dc57a7632c20feee4a6d88c0fc4b8dce66cb0 Mon Sep 17 00:00:00 2001 From: Goksel Coban Date: Wed, 28 Dec 2022 17:58:22 +0300 Subject: [PATCH 1/6] add notes to app calls to track clients --- tests/v2/__init__.py | 4 +++ tests/v2/test_add_liquidity.py | 8 +++-- tests/v2/test_bootstrap.py | 10 ++++-- tests/v2/test_flash_loan.py | 10 ++++-- tests/v2/test_flash_swap.py | 11 +++++-- tests/v2/test_remove_liquidity.py | 9 ++++-- tests/v2/test_swap.py | 5 ++- tinyman/client.py | 14 +++++++++ tinyman/optin.py | 13 +++++--- tinyman/utils.py | 52 ++++++++++++++++++++++++++++++- tinyman/v1/bootstrap.py | 4 +++ tinyman/v1/burn.py | 4 +++ tinyman/v1/client.py | 1 + tinyman/v1/fees.py | 4 +++ tinyman/v1/mint.py | 4 +++ tinyman/v1/optout.py | 10 +++++- tinyman/v1/pools.py | 6 ++++ tinyman/v1/redeem.py | 4 +++ tinyman/v1/swap.py | 4 +++ tinyman/v2/add_liquidity.py | 6 ++++ tinyman/v2/bootstrap.py | 4 +++ tinyman/v2/fees.py | 8 +++++ tinyman/v2/flash_loan.py | 5 +++ tinyman/v2/flash_swap.py | 5 +++ tinyman/v2/management.py | 8 +++++ tinyman/v2/pools.py | 8 +++++ tinyman/v2/remove_liquidity.py | 6 ++++ tinyman/v2/swap.py | 4 +++ 28 files changed, 213 insertions(+), 18 deletions(-) diff --git a/tests/v2/__init__.py b/tests/v2/__init__.py index 538a59f..958f929 100644 --- a/tests/v2/__init__.py +++ b/tests/v2/__init__.py @@ -44,3 +44,7 @@ def get_pool_state( } state.update(**kwargs) return state + + @classmethod + def app_call_note(cls): + return b'tinyman/v2:j{"origin":"tinyman-py-sdk"}' diff --git a/tests/v2/test_add_liquidity.py b/tests/v2/test_add_liquidity.py index 7b87df5..2c40204 100644 --- a/tests/v2/test_add_liquidity.py +++ b/tests/v2/test_add_liquidity.py @@ -15,6 +15,7 @@ ADD_LIQUIDITY_APP_ARGUMENT, ADD_LIQUIDITY_FLEXIBLE_MODE_APP_ARGUMENT, ADD_LIQUIDITY_SINGLE_MODE_APP_ARGUMENT, + TESTNET_VALIDATOR_APP_ID_V2, ) from tinyman.v2.contracts import get_pool_logicsig from tinyman.v2.pools import Pool @@ -28,7 +29,7 @@ class InitialAddLiquidityTestCase(BaseTestCase): @classmethod def setUpClass(cls): - cls.VALIDATOR_APP_ID = 12345 + cls.VALIDATOR_APP_ID = TESTNET_VALIDATOR_APP_ID_V2 cls.sender_private_key, cls.user_address = generate_account() cls.asset_1_id = 10 cls.asset_2_id = 8 @@ -121,6 +122,7 @@ def test_add_liquidity(self): "lv": ANY, "snd": decode_address(self.user_address), "type": APPCALL_TXN, + "note": self.app_call_note(), }, ) @@ -128,7 +130,7 @@ def test_add_liquidity(self): class AddLiquidityTestCase(BaseTestCase): @classmethod def setUpClass(cls): - cls.VALIDATOR_APP_ID = 12345 + cls.VALIDATOR_APP_ID = TESTNET_VALIDATOR_APP_ID_V2 cls.sender_private_key, cls.user_address = generate_account() cls.asset_1_id = 10 cls.asset_2_id = 8 @@ -240,6 +242,7 @@ def test_flexible_add_liquidity(self): "lv": ANY, "snd": decode_address(self.user_address), "type": APPCALL_TXN, + "note": self.app_call_note(), }, ) @@ -311,5 +314,6 @@ def test_single_asset_add_liquidity(self): "lv": ANY, "snd": decode_address(self.user_address), "type": APPCALL_TXN, + "note": self.app_call_note(), }, ) diff --git a/tests/v2/test_bootstrap.py b/tests/v2/test_bootstrap.py index 8536fbc..89a60fb 100644 --- a/tests/v2/test_bootstrap.py +++ b/tests/v2/test_bootstrap.py @@ -7,7 +7,7 @@ from algosdk.logic import get_application_address from tests.v2 import BaseTestCase -from tinyman.v2.constants import BOOTSTRAP_APP_ARGUMENT +from tinyman.v2.constants import BOOTSTRAP_APP_ARGUMENT, TESTNET_VALIDATOR_APP_ID_V2 from tinyman.v2.contracts import get_pool_logicsig from tinyman.v2.pools import Pool @@ -15,7 +15,7 @@ class BootstrapTestCase(BaseTestCase): @classmethod def setUpClass(cls): - cls.VALIDATOR_APP_ID = 12345 + cls.VALIDATOR_APP_ID = TESTNET_VALIDATOR_APP_ID_V2 cls.application_address = get_application_address(cls.VALIDATOR_APP_ID) cls.sender_private_key, cls.user_address = generate_account() @@ -74,6 +74,7 @@ def test_bootstrap(self): "rekey": decode_address(self.application_address), "snd": decode_address(self.pool_address), "type": APPCALL_TXN, + "note": self.app_call_note(), }, ) @@ -101,6 +102,7 @@ def test_pool_is_already_funded(self): "rekey": decode_address(self.application_address), "snd": decode_address(self.pool_address), "type": APPCALL_TXN, + "note": self.app_call_note(), }, ) @@ -108,7 +110,7 @@ def test_pool_is_already_funded(self): class BootstrapAlgoPoolTestCase(BaseTestCase): @classmethod def setUpClass(cls): - cls.VALIDATOR_APP_ID = 12345 + cls.VALIDATOR_APP_ID = TESTNET_VALIDATOR_APP_ID_V2 cls.application_address = get_application_address(cls.VALIDATOR_APP_ID) cls.sender_private_key, cls.user_address = generate_account() @@ -167,6 +169,7 @@ def test_bootstrap(self): "rekey": decode_address(self.application_address), "snd": decode_address(self.pool_address), "type": APPCALL_TXN, + "note": self.app_call_note(), }, ) @@ -194,5 +197,6 @@ def test_pool_is_already_funded(self): "rekey": decode_address(self.application_address), "snd": decode_address(self.pool_address), "type": APPCALL_TXN, + "note": self.app_call_note(), }, ) diff --git a/tests/v2/test_flash_loan.py b/tests/v2/test_flash_loan.py index 4416032..70ed099 100644 --- a/tests/v2/test_flash_loan.py +++ b/tests/v2/test_flash_loan.py @@ -9,7 +9,11 @@ from tests.v2 import BaseTestCase from tinyman.assets import AssetAmount from tinyman.utils import int_to_bytes -from tinyman.v2.constants import FLASH_LOAN_APP_ARGUMENT, VERIFY_FLASH_LOAN_APP_ARGUMENT +from tinyman.v2.constants import ( + FLASH_LOAN_APP_ARGUMENT, + VERIFY_FLASH_LOAN_APP_ARGUMENT, + TESTNET_VALIDATOR_APP_ID_V2, +) from tinyman.v2.contracts import get_pool_logicsig from tinyman.v2.pools import Pool from tinyman.v2.quotes import FlashLoanQuote @@ -18,7 +22,7 @@ class FlashLoanTestCase(BaseTestCase): @classmethod def setUpClass(cls): - cls.VALIDATOR_APP_ID = 12345 + cls.VALIDATOR_APP_ID = TESTNET_VALIDATOR_APP_ID_V2 cls.sender_private_key, cls.user_address = generate_account() cls.asset_1_id = 10 cls.asset_2_id = 8 @@ -98,6 +102,7 @@ def test_flash_loan(self): "lv": ANY, "snd": decode_address(self.user_address), "type": APPCALL_TXN, + "note": self.app_call_note(), }, ) @@ -134,5 +139,6 @@ def test_flash_loan(self): "lv": ANY, "snd": decode_address(self.user_address), "type": APPCALL_TXN, + "note": self.app_call_note(), }, ) diff --git a/tests/v2/test_flash_swap.py b/tests/v2/test_flash_swap.py index ed7b6d1..3c0c0fa 100644 --- a/tests/v2/test_flash_swap.py +++ b/tests/v2/test_flash_swap.py @@ -8,7 +8,11 @@ from tests.v2 import BaseTestCase from tinyman.utils import int_to_bytes -from tinyman.v2.constants import FLASH_SWAP_APP_ARGUMENT, VERIFY_FLASH_SWAP_APP_ARGUMENT +from tinyman.v2.constants import ( + FLASH_SWAP_APP_ARGUMENT, + VERIFY_FLASH_SWAP_APP_ARGUMENT, + TESTNET_VALIDATOR_APP_ID_V2, +) from tinyman.v2.contracts import get_pool_logicsig from tinyman.v2.flash_swap import prepare_flash_swap_transactions from tinyman.v2.pools import Pool @@ -17,7 +21,7 @@ class FlashSwapTestCase(BaseTestCase): @classmethod def setUpClass(cls): - cls.VALIDATOR_APP_ID = 12345 + cls.VALIDATOR_APP_ID = TESTNET_VALIDATOR_APP_ID_V2 cls.sender_private_key, cls.user_address = generate_account() cls.asset_1_id = 10 cls.asset_2_id = 8 @@ -49,6 +53,7 @@ def test_flash_swap(self): transactions=[], sender=self.user_address, suggested_params=self.get_suggested_params(), + app_call_note=self.app_call_note().decode(), ) transactions = txn_group.transactions @@ -73,6 +78,7 @@ def test_flash_swap(self): "lv": ANY, "snd": decode_address(self.user_address), "type": APPCALL_TXN, + "note": self.app_call_note(), }, ) @@ -95,5 +101,6 @@ def test_flash_swap(self): "lv": ANY, "snd": decode_address(self.user_address), "type": APPCALL_TXN, + "note": self.app_call_note(), }, ) diff --git a/tests/v2/test_remove_liquidity.py b/tests/v2/test_remove_liquidity.py index 05881bc..8d81f4e 100644 --- a/tests/v2/test_remove_liquidity.py +++ b/tests/v2/test_remove_liquidity.py @@ -9,7 +9,10 @@ from tests.v2 import BaseTestCase from tinyman.assets import AssetAmount from tinyman.utils import int_to_bytes -from tinyman.v2.constants import REMOVE_LIQUIDITY_APP_ARGUMENT +from tinyman.v2.constants import ( + REMOVE_LIQUIDITY_APP_ARGUMENT, + TESTNET_VALIDATOR_APP_ID_V2, +) from tinyman.v2.contracts import get_pool_logicsig from tinyman.v2.pools import Pool from tinyman.v2.quotes import RemoveLiquidityQuote, SingleAssetRemoveLiquidityQuote @@ -18,7 +21,7 @@ class RemoveLiquidityTestCase(BaseTestCase): @classmethod def setUpClass(cls): - cls.VALIDATOR_APP_ID = 12345 + cls.VALIDATOR_APP_ID = TESTNET_VALIDATOR_APP_ID_V2 cls.sender_private_key, cls.user_address = generate_account() cls.asset_1_id = 10 cls.asset_2_id = 8 @@ -112,6 +115,7 @@ def test_remove_liquidity(self): "lv": ANY, "snd": decode_address(self.user_address), "type": APPCALL_TXN, + "note": self.app_call_note(), }, ) @@ -184,5 +188,6 @@ def test_single_asset_remove_liquidity(self): "lv": ANY, "snd": decode_address(self.user_address), "type": APPCALL_TXN, + "note": self.app_call_note(), }, ) diff --git a/tests/v2/test_swap.py b/tests/v2/test_swap.py index 96decfd..7570d77 100644 --- a/tests/v2/test_swap.py +++ b/tests/v2/test_swap.py @@ -13,6 +13,7 @@ SWAP_APP_ARGUMENT, FIXED_INPUT_APP_ARGUMENT, FIXED_OUTPUT_APP_ARGUMENT, + TESTNET_VALIDATOR_APP_ID_V2, ) from tinyman.v2.contracts import get_pool_logicsig from tinyman.v2.pools import Pool @@ -22,7 +23,7 @@ class SwapTestCase(BaseTestCase): @classmethod def setUpClass(cls): - cls.VALIDATOR_APP_ID = 12345 + cls.VALIDATOR_APP_ID = TESTNET_VALIDATOR_APP_ID_V2 cls.sender_private_key, cls.user_address = generate_account() cls.asset_1_id = 10 cls.asset_2_id = 8 @@ -103,6 +104,7 @@ def test_fixed_input_swap(self): "lv": ANY, "snd": decode_address(self.user_address), "type": APPCALL_TXN, + "note": self.app_call_note(), }, ) @@ -166,5 +168,6 @@ def test_fixed_output_swap(self): "lv": ANY, "snd": decode_address(self.user_address), "type": APPCALL_TXN, + "note": self.app_call_note(), }, ) diff --git a/tinyman/client.py b/tinyman/client.py index 296f51d..273698d 100644 --- a/tinyman/client.py +++ b/tinyman/client.py @@ -6,6 +6,7 @@ from tinyman.assets import Asset from tinyman.optin import prepare_asset_optin_transactions +from tinyman.utils import get_version, generate_app_call_note class BaseTinymanClient: @@ -15,12 +16,14 @@ def __init__( validator_app_id: int, user_address=None, staking_app_id: Optional[int] = None, + client_name: Optional[str] = None, ): self.algod = algod_client self.validator_app_id = validator_app_id self.staking_app_id = staking_app_id self.assets_cache = {} self.user_address = user_address + self.client_name = client_name def fetch_pool(self, *args, **kwargs): raise NotImplementedError() @@ -57,6 +60,10 @@ def prepare_asset_optin_transactions( ) return txn_group + @property + def version(self) -> str: + return get_version(self.validator_app_id) + def is_opted_in(self, user_address=None): user_address = user_address or self.user_address account_info = self.algod.account_info(user_address) @@ -72,3 +79,10 @@ def asset_is_opted_in(self, asset_id, user_address=None): if a["asset-id"] == asset_id: return True return False + + def generate_app_call_note(self, client_name: Optional[str] = None): + note = generate_app_call_note( + version=self.version, + client_name=client_name or self.client_name, + ) + return note diff --git a/tinyman/optin.py b/tinyman/optin.py index 30c409b..8c41bcd 100644 --- a/tinyman/optin.py +++ b/tinyman/optin.py @@ -1,13 +1,18 @@ +from typing import Optional + from algosdk.future.transaction import ApplicationOptInTxn, AssetOptInTxn from tinyman.utils import TransactionGroup -def prepare_app_optin_transactions(validator_app_id, sender, suggested_params): +def prepare_app_optin_transactions( + validator_app_id, + sender, + suggested_params, + app_call_note: Optional[str] = None, +): txn = ApplicationOptInTxn( - sender=sender, - sp=suggested_params, - index=validator_app_id, + sender=sender, sp=suggested_params, index=validator_app_id, note=app_call_note ) txn_group = TransactionGroup([txn]) return txn_group diff --git a/tinyman/utils.py b/tinyman/utils.py index 9c868fc..483bfaa 100644 --- a/tinyman/utils.py +++ b/tinyman/utils.py @@ -1,12 +1,24 @@ +import json import warnings from base64 import b64decode, b64encode from datetime import datetime +from typing import Optional + +from algosdk.error import AlgodHTTPError from algosdk.future.transaction import ( LogicSigTransaction, assign_group_id, wait_for_confirmation, ) -from algosdk.error import AlgodHTTPError + +from tinyman.v1.constants import ( + MAINNET_VALIDATOR_APP_ID_V1_1, + TESTNET_VALIDATOR_APP_ID_V1_1, +) +from tinyman.v2.constants import ( + MAINNET_VALIDATOR_APP_ID_V2, + TESTNET_VALIDATOR_APP_ID_V2, +) warnings.simplefilter("always", DeprecationWarning) @@ -103,6 +115,44 @@ def calculate_price_impact( return price_impact +def get_version(tinyman_app_id: int) -> str: + if tinyman_app_id in [MAINNET_VALIDATOR_APP_ID_V2, TESTNET_VALIDATOR_APP_ID_V2]: + return "v2" + elif tinyman_app_id in [ + MAINNET_VALIDATOR_APP_ID_V1_1, + TESTNET_VALIDATOR_APP_ID_V1_1, + ]: + return "v1" + + raise NotImplementedError() + + +def generate_app_call_note( + version: str, client_name: Optional[str] = None, extra_data: Optional[dict] = None +): + # https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0002.md + # : + note_template = "{dapp_name}/{dapp_version}:{data_format}{data}" + + client_name = client_name or "tinyman-py-sdk" + assert version in ["v1", "v2"] + + data = extra_data or dict() + data.update( + { + "origin": client_name, + } + ) + + # spaces are removed from separators, default is (', ', ': ') + serialized_data = json.dumps(data, separators=(",", ":")) + + note = note_template.format( + dapp_name="tinyman", dapp_version=version, data_format="j", data=serialized_data + ) + return note + + class TransactionGroup: def __init__(self, transactions): # Clear previously assigned group ids diff --git a/tinyman/v1/bootstrap.py b/tinyman/v1/bootstrap.py index 8de163f..4358ed2 100644 --- a/tinyman/v1/bootstrap.py +++ b/tinyman/v1/bootstrap.py @@ -1,3 +1,5 @@ +from typing import Optional + from algosdk.future.transaction import ( ApplicationOptInTxn, PaymentTxn, @@ -17,6 +19,7 @@ def prepare_bootstrap_transactions( asset2_unit_name, sender, suggested_params, + app_call_note: Optional[str] = None, ): pool_logicsig = get_pool_logicsig(validator_app_id, asset1_id, asset2_id) pool_address = pool_logicsig.address() @@ -40,6 +43,7 @@ def prepare_bootstrap_transactions( index=validator_app_id, app_args=["bootstrap", int_to_bytes(asset1_id), int_to_bytes(asset2_id)], foreign_assets=[asset1_id] if asset2_id == 0 else [asset1_id, asset2_id], + note=app_call_note, ), AssetCreateTxn( sender=pool_address, diff --git a/tinyman/v1/burn.py b/tinyman/v1/burn.py index 4247abe..28e623b 100644 --- a/tinyman/v1/burn.py +++ b/tinyman/v1/burn.py @@ -1,3 +1,5 @@ +from typing import Optional + from algosdk.future.transaction import ApplicationNoOpTxn, PaymentTxn, AssetTransferTxn from tinyman.utils import TransactionGroup @@ -14,6 +16,7 @@ def prepare_burn_transactions( liquidity_asset_amount, sender, suggested_params, + app_call_note: Optional[str] = None, ): pool_logicsig = get_pool_logicsig(validator_app_id, asset1_id, asset2_id) pool_address = pool_logicsig.address() @@ -35,6 +38,7 @@ def prepare_burn_transactions( foreign_assets=[asset1_id, liquidity_asset_id] if asset2_id == 0 else [asset1_id, asset2_id, liquidity_asset_id], + note=app_call_note, ), AssetTransferTxn( sender=pool_address, diff --git a/tinyman/v1/client.py b/tinyman/v1/client.py index 88a4908..85f12cc 100644 --- a/tinyman/v1/client.py +++ b/tinyman/v1/client.py @@ -28,6 +28,7 @@ def prepare_app_optin_transactions(self, user_address=None): validator_app_id=self.validator_app_id, sender=user_address, suggested_params=suggested_params, + app_call_note=self.generate_app_call_note(), ) return txn_group diff --git a/tinyman/v1/fees.py b/tinyman/v1/fees.py index 334f967..36b6964 100644 --- a/tinyman/v1/fees.py +++ b/tinyman/v1/fees.py @@ -1,3 +1,5 @@ +from typing import Optional + from algosdk.future.transaction import ApplicationNoOpTxn, PaymentTxn, AssetTransferTxn from tinyman.utils import TransactionGroup @@ -13,6 +15,7 @@ def prepare_redeem_fees_transactions( creator, sender, suggested_params, + app_call_note: Optional[str] = None, ): pool_logicsig = get_pool_logicsig(validator_app_id, asset1_id, asset2_id) pool_address = pool_logicsig.address() @@ -33,6 +36,7 @@ def prepare_redeem_fees_transactions( foreign_assets=[asset1_id, liquidity_asset_id] if asset2_id == 0 else [asset1_id, asset2_id, liquidity_asset_id], + note=app_call_note, ), AssetTransferTxn( sender=pool_address, diff --git a/tinyman/v1/mint.py b/tinyman/v1/mint.py index 550efe0..cd6f751 100644 --- a/tinyman/v1/mint.py +++ b/tinyman/v1/mint.py @@ -1,3 +1,5 @@ +from typing import Optional + from algosdk.future.transaction import ApplicationNoOpTxn, PaymentTxn, AssetTransferTxn from tinyman.utils import TransactionGroup @@ -14,6 +16,7 @@ def prepare_mint_transactions( liquidity_asset_amount, sender, suggested_params, + app_call_note: Optional[str] = None, ): pool_logicsig = get_pool_logicsig(validator_app_id, asset1_id, asset2_id) pool_address = pool_logicsig.address() @@ -35,6 +38,7 @@ def prepare_mint_transactions( foreign_assets=[asset1_id, liquidity_asset_id] if asset2_id == 0 else [asset1_id, asset2_id, liquidity_asset_id], + note=app_call_note, ), AssetTransferTxn( sender=sender, diff --git a/tinyman/v1/optout.py b/tinyman/v1/optout.py index ceef150..ff2b4fc 100644 --- a/tinyman/v1/optout.py +++ b/tinyman/v1/optout.py @@ -1,14 +1,22 @@ +from typing import Optional + from algosdk.future.transaction import ApplicationClearStateTxn from algosdk.v2client.algod import AlgodClient -def get_optout_transactions(client: AlgodClient, sender, validator_app_id): +def get_optout_transactions( + client: AlgodClient, + sender, + validator_app_id, + app_call_note: Optional[str] = None, +): suggested_params = client.suggested_params() txn = ApplicationClearStateTxn( sender=sender, sp=suggested_params, index=validator_app_id, + note=app_call_note, ) return [txn], [None] diff --git a/tinyman/v1/pools.py b/tinyman/v1/pools.py index ec99375..91bbde4 100644 --- a/tinyman/v1/pools.py +++ b/tinyman/v1/pools.py @@ -454,6 +454,7 @@ def prepare_swap_transactions( swap_type=swap_type, sender=swapper_address, suggested_params=suggested_params, + app_call_note=self.client.generate_app_call_note(), ) return txn_group @@ -478,6 +479,7 @@ def prepare_bootstrap_transactions(self, pooler_address=None): asset2_unit_name=self.asset2.unit_name, sender=pooler_address, suggested_params=suggested_params, + app_call_note=self.client.generate_app_call_note(), ) return txn_group @@ -501,6 +503,7 @@ def prepare_mint_transactions( liquidity_asset_amount=liquidity_asset_amount.amount, sender=pooler_address, suggested_params=suggested_params, + app_call_note=self.client.generate_app_call_note(), ) return txn_group @@ -534,6 +537,7 @@ def prepare_burn_transactions( liquidity_asset_amount=liquidity_asset_amount.amount, sender=pooler_address, suggested_params=suggested_params, + app_call_note=self.client.generate_app_call_note(), ) return txn_group @@ -558,6 +562,7 @@ def prepare_redeem_transactions(self, amount_out: AssetAmount, user_address=None asset_amount=amount_out.amount, sender=user_address, suggested_params=suggested_params, + app_call_note=self.client.generate_app_call_note(), ) return txn_group @@ -583,6 +588,7 @@ def prepare_redeem_fees_transactions(self, amount, creator, user_address=None): creator=creator, sender=user_address, suggested_params=suggested_params, + app_call_note=self.client.generate_app_call_note(), ) return txn_group diff --git a/tinyman/v1/redeem.py b/tinyman/v1/redeem.py index af19c38..7dad3b5 100644 --- a/tinyman/v1/redeem.py +++ b/tinyman/v1/redeem.py @@ -1,3 +1,5 @@ +from typing import Optional + from algosdk.future.transaction import ApplicationNoOpTxn, PaymentTxn, AssetTransferTxn from tinyman.utils import TransactionGroup @@ -13,6 +15,7 @@ def prepare_redeem_transactions( asset_amount, sender, suggested_params, + app_call_note: Optional[str] = None, ): pool_logicsig = get_pool_logicsig(validator_app_id, asset1_id, asset2_id) pool_address = pool_logicsig.address() @@ -34,6 +37,7 @@ def prepare_redeem_transactions( foreign_assets=[asset1_id, liquidity_asset_id] if asset2_id == 0 else [asset1_id, asset2_id, liquidity_asset_id], + note=app_call_note, ), AssetTransferTxn( sender=pool_address, diff --git a/tinyman/v1/swap.py b/tinyman/v1/swap.py index 33bcd21..219a04d 100644 --- a/tinyman/v1/swap.py +++ b/tinyman/v1/swap.py @@ -1,3 +1,5 @@ +from typing import Optional + from algosdk.future.transaction import ApplicationNoOpTxn, PaymentTxn, AssetTransferTxn from tinyman.utils import TransactionGroup @@ -15,6 +17,7 @@ def prepare_swap_transactions( swap_type, sender, suggested_params, + app_call_note: Optional[str] = None, ): pool_logicsig = get_pool_logicsig(validator_app_id, asset1_id, asset2_id) pool_address = pool_logicsig.address() @@ -43,6 +46,7 @@ def prepare_swap_transactions( foreign_assets=[asset1_id, liquidity_asset_id] if asset2_id == 0 else [asset1_id, asset2_id, liquidity_asset_id], + note=app_call_note, ), AssetTransferTxn( sender=sender, diff --git a/tinyman/v2/add_liquidity.py b/tinyman/v2/add_liquidity.py index 823395f..631717f 100644 --- a/tinyman/v2/add_liquidity.py +++ b/tinyman/v2/add_liquidity.py @@ -27,6 +27,7 @@ def prepare_flexible_add_liquidity_transactions( min_pool_token_asset_amount: int, sender: str, suggested_params: SuggestedParams, + app_call_note: Optional[str] = None, ) -> TransactionGroup: pool_logicsig = get_pool_logicsig(validator_app_id, asset_1_id, asset_2_id) pool_address = pool_logicsig.address() @@ -64,6 +65,7 @@ def prepare_flexible_add_liquidity_transactions( ], foreign_assets=[pool_token_asset_id], accounts=[pool_address], + note=app_call_note, ), ] @@ -85,6 +87,7 @@ def prepare_single_asset_add_liquidity_transactions( suggested_params: SuggestedParams, asset_1_amount: Optional[int] = None, asset_2_amount: Optional[int] = None, + app_call_note: Optional[str] = None, ) -> TransactionGroup: pool_logicsig = get_pool_logicsig(validator_app_id, asset_1_id, asset_2_id) pool_address = pool_logicsig.address() @@ -130,6 +133,7 @@ def prepare_single_asset_add_liquidity_transactions( ], foreign_assets=[pool_token_asset_id], accounts=[pool_address], + note=app_call_note, ), ] @@ -150,6 +154,7 @@ def prepare_initial_add_liquidity_transactions( asset_2_amount: int, sender: str, suggested_params: SuggestedParams, + app_call_note: Optional[str] = None, ) -> TransactionGroup: pool_logicsig = get_pool_logicsig(validator_app_id, asset_1_id, asset_2_id) pool_address = pool_logicsig.address() @@ -183,6 +188,7 @@ def prepare_initial_add_liquidity_transactions( app_args=[ADD_INITIAL_LIQUIDITY_APP_ARGUMENT], foreign_assets=[pool_token_asset_id], accounts=[pool_address], + note=app_call_note, ), ] diff --git a/tinyman/v2/bootstrap.py b/tinyman/v2/bootstrap.py index e8aa153..c83ef89 100644 --- a/tinyman/v2/bootstrap.py +++ b/tinyman/v2/bootstrap.py @@ -1,3 +1,5 @@ +from typing import Optional + from algosdk.future.transaction import ( ApplicationOptInTxn, PaymentTxn, @@ -18,6 +20,7 @@ def prepare_bootstrap_transactions( app_call_fee: int, required_algo: int, suggested_params: SuggestedParams, + app_call_note: Optional[str] = None, ) -> TransactionGroup: pool_logicsig = get_pool_logicsig(validator_app_id, asset_1_id, asset_2_id) pool_address = pool_logicsig.address() @@ -44,6 +47,7 @@ def prepare_bootstrap_transactions( app_args=[BOOTSTRAP_APP_ARGUMENT], foreign_assets=[asset_1_id, asset_2_id], rekey_to=get_application_address(validator_app_id), + note=app_call_note, ) bootstrap_app_call.fee = app_call_fee txns.append(bootstrap_app_call) diff --git a/tinyman/v2/fees.py b/tinyman/v2/fees.py index 99e4d30..03af641 100644 --- a/tinyman/v2/fees.py +++ b/tinyman/v2/fees.py @@ -1,3 +1,5 @@ +from typing import Optional + from algosdk.future.transaction import ( ApplicationNoOpTxn, SuggestedParams, @@ -19,6 +21,7 @@ def prepare_claim_fees_transactions( fee_collector: str, sender: str, suggested_params: SuggestedParams, + app_call_note: Optional[str] = None, ) -> TransactionGroup: txns = [ ApplicationNoOpTxn( @@ -28,6 +31,7 @@ def prepare_claim_fees_transactions( app_args=[CLAIM_FEES_APP_ARGUMENT], foreign_assets=[asset_1_id, asset_2_id], accounts=[pool_address, fee_collector], + note=app_call_note, ), ] @@ -46,6 +50,7 @@ def prepare_claim_extra_transactions( fee_collector: str, sender: str, suggested_params: SuggestedParams, + app_call_note: Optional[str] = None, ) -> TransactionGroup: txns = [ ApplicationNoOpTxn( @@ -55,6 +60,7 @@ def prepare_claim_extra_transactions( app_args=[CLAIM_EXTRA_APP_ARGUMENT], foreign_assets=[asset_id], accounts=[address, fee_collector], + note=app_call_note, ), ] @@ -73,6 +79,7 @@ def prepare_set_fee_transactions( protocol_fee_ratio: int, fee_manager: str, suggested_params: SuggestedParams, + app_call_note: Optional[str] = None, ) -> TransactionGroup: txns = [ ApplicationNoOpTxn( @@ -81,6 +88,7 @@ def prepare_set_fee_transactions( index=validator_app_id, app_args=[SET_FEE_APP_ARGUMENT, total_fee_share, protocol_fee_ratio], accounts=[pool_address], + note=app_call_note, ), ] txn_group = TransactionGroup(txns) diff --git a/tinyman/v2/flash_loan.py b/tinyman/v2/flash_loan.py index b4df949..f887fd4 100644 --- a/tinyman/v2/flash_loan.py +++ b/tinyman/v2/flash_loan.py @@ -1,3 +1,5 @@ +from typing import Optional + from algosdk.future.transaction import ( Transaction, ApplicationNoOpTxn, @@ -25,6 +27,7 @@ def prepare_flash_loan_transactions( transactions: "list[Transaction]", sender: str, suggested_params: SuggestedParams, + app_call_note: Optional[str] = None, ) -> TransactionGroup: assert asset_1_loan_amount or asset_2_loan_amount @@ -52,6 +55,7 @@ def prepare_flash_loan_transactions( ], foreign_assets=[asset_1_id, asset_2_id], accounts=[pool_address], + note=app_call_note, ) ] # This app call contains inner transactions @@ -101,6 +105,7 @@ def prepare_flash_loan_transactions( app_args=[VERIFY_FLASH_LOAN_APP_ARGUMENT, index_diff], foreign_assets=[], accounts=[pool_address], + note=app_call_note, ) ) diff --git a/tinyman/v2/flash_swap.py b/tinyman/v2/flash_swap.py index b89dbbc..d87d283 100644 --- a/tinyman/v2/flash_swap.py +++ b/tinyman/v2/flash_swap.py @@ -1,3 +1,5 @@ +from typing import Optional + from algosdk.future.transaction import ( Transaction, ApplicationNoOpTxn, @@ -21,6 +23,7 @@ def prepare_flash_swap_transactions( transactions: "list[Transaction]", sender: str, suggested_params: SuggestedParams, + app_call_note: Optional[str] = None, ) -> TransactionGroup: assert asset_1_loan_amount or asset_2_loan_amount @@ -48,6 +51,7 @@ def prepare_flash_swap_transactions( ], foreign_assets=[asset_1_id, asset_2_id], accounts=[pool_address], + note=app_call_note, ) ] # This app call contains inner transactions @@ -65,6 +69,7 @@ def prepare_flash_swap_transactions( app_args=[VERIFY_FLASH_SWAP_APP_ARGUMENT, index_diff], foreign_assets=[asset_1_id, asset_2_id], accounts=[pool_address], + note=app_call_note, ) ) diff --git a/tinyman/v2/management.py b/tinyman/v2/management.py index 5892af7..a96f3c1 100644 --- a/tinyman/v2/management.py +++ b/tinyman/v2/management.py @@ -1,3 +1,5 @@ +from typing import Optional + from algosdk.future.transaction import ( ApplicationNoOpTxn, SuggestedParams, @@ -16,6 +18,7 @@ def prepare_set_fee_collector_transactions( fee_manager: str, new_fee_collector: str, suggested_params: SuggestedParams, + app_call_note: Optional[str] = None, ) -> TransactionGroup: txns = [ ApplicationNoOpTxn( @@ -24,6 +27,7 @@ def prepare_set_fee_collector_transactions( index=validator_app_id, app_args=[SET_FEE_COLLECTOR_APP_ARGUMENT], accounts=[new_fee_collector], + note=app_call_note, ), ] txn_group = TransactionGroup(txns) @@ -35,6 +39,7 @@ def prepare_set_fee_setter_transactions( fee_manager: str, new_fee_setter: str, suggested_params: SuggestedParams, + app_call_note: Optional[str] = None, ) -> TransactionGroup: txns = [ ApplicationNoOpTxn( @@ -43,6 +48,7 @@ def prepare_set_fee_setter_transactions( index=validator_app_id, app_args=[SET_FEE_SETTER_APP_ARGUMENT], accounts=[new_fee_setter], + note=app_call_note, ), ] txn_group = TransactionGroup(txns) @@ -54,6 +60,7 @@ def prepare_set_fee_manager_transactions( fee_manager: str, new_fee_manager: str, suggested_params: SuggestedParams, + app_call_note: Optional[str] = None, ) -> TransactionGroup: txns = [ ApplicationNoOpTxn( @@ -62,6 +69,7 @@ def prepare_set_fee_manager_transactions( index=validator_app_id, app_args=[SET_FEE_MANAGER_APP_ARGUMENT], accounts=[new_fee_manager], + note=app_call_note, ), ] txn_group = TransactionGroup(txns) diff --git a/tinyman/v2/pools.py b/tinyman/v2/pools.py index 11eec60..ac5d646 100644 --- a/tinyman/v2/pools.py +++ b/tinyman/v2/pools.py @@ -368,6 +368,7 @@ def prepare_bootstrap_transactions( suggested_params=suggested_params, app_call_fee=app_call_fee, required_algo=required_algo, + app_call_note=self.client.generate_app_call_note(), ) return txn_group @@ -577,6 +578,7 @@ def prepare_flexible_add_liquidity_transactions( min_pool_token_asset_amount=min_pool_token_asset_amount, sender=user_address, suggested_params=suggested_params, + app_call_note=self.client.generate_app_call_note(), ) return txn_group @@ -606,6 +608,7 @@ def prepare_single_asset_add_liquidity_transactions( min_pool_token_asset_amount=min_pool_token_asset_amount, sender=user_address, suggested_params=suggested_params, + app_call_note=self.client.generate_app_call_note(), ) return txn_group @@ -631,6 +634,7 @@ def prepare_initial_add_liquidity_transactions( asset_2_amount=asset_2_amount.amount, sender=user_address, suggested_params=suggested_params, + app_call_note=self.client.generate_app_call_note(), ) return txn_group @@ -815,6 +819,7 @@ def prepare_remove_liquidity_transactions( pool_token_asset_amount=pool_token_asset_amount.amount, sender=user_address, suggested_params=suggested_params, + app_call_note=self.client.generate_app_call_note(), ) return txn_group @@ -845,6 +850,7 @@ def prepare_single_asset_remove_liquidity_transactions( pool_token_asset_amount=pool_token_asset_amount.amount, sender=user_address, suggested_params=suggested_params, + app_call_note=self.client.generate_app_call_note(), ) return txn_group @@ -981,6 +987,7 @@ def prepare_swap_transactions( swap_type=swap_type, sender=user_address, suggested_params=suggested_params, + app_call_note=self.client.generate_app_call_note(), ) return txn_group @@ -1102,6 +1109,7 @@ def prepare_flash_loan_transactions( transactions=transactions, sender=user_address, suggested_params=suggested_params, + app_call_note=self.client.generate_app_call_note(), ) return txn_group diff --git a/tinyman/v2/remove_liquidity.py b/tinyman/v2/remove_liquidity.py index 0149fe3..b6488f9 100644 --- a/tinyman/v2/remove_liquidity.py +++ b/tinyman/v2/remove_liquidity.py @@ -1,3 +1,5 @@ +from typing import Optional + from algosdk.future.transaction import ( ApplicationNoOpTxn, AssetTransferTxn, @@ -19,6 +21,7 @@ def prepare_remove_liquidity_transactions( pool_token_asset_amount: int, sender: str, suggested_params: SuggestedParams, + app_call_note: Optional[str] = None, ) -> TransactionGroup: pool_logicsig = get_pool_logicsig(validator_app_id, asset_1_id, asset_2_id) pool_address = pool_logicsig.address() @@ -42,6 +45,7 @@ def prepare_remove_liquidity_transactions( ], foreign_assets=[asset_1_id, asset_2_id], accounts=[pool_address], + note=app_call_note, ), ] @@ -64,6 +68,7 @@ def prepare_single_asset_remove_liquidity_transactions( pool_token_asset_amount: int, sender: str, suggested_params: SuggestedParams, + app_call_note: Optional[str] = None, ) -> TransactionGroup: pool_logicsig = get_pool_logicsig(validator_app_id, asset_1_id, asset_2_id) pool_address = pool_logicsig.address() @@ -96,6 +101,7 @@ def prepare_single_asset_remove_liquidity_transactions( ], foreign_assets=[output_asset_id], accounts=[pool_address], + note=app_call_note, ), ] diff --git a/tinyman/v2/swap.py b/tinyman/v2/swap.py index dabc6c8..c8b2c7b 100644 --- a/tinyman/v2/swap.py +++ b/tinyman/v2/swap.py @@ -1,3 +1,5 @@ +from typing import Optional + from algosdk.future.transaction import ( ApplicationNoOpTxn, PaymentTxn, @@ -24,6 +26,7 @@ def prepare_swap_transactions( swap_type: [str, bytes], sender: str, suggested_params: SuggestedParams, + app_call_note: Optional[str] = None, ) -> TransactionGroup: pool_logicsig = get_pool_logicsig(validator_app_id, asset_1_id, asset_2_id) pool_address = pool_logicsig.address() @@ -50,6 +53,7 @@ def prepare_swap_transactions( app_args=[SWAP_APP_ARGUMENT, swap_type, asset_out_amount], foreign_assets=[asset_1_id, asset_2_id], accounts=[pool_address], + note=app_call_note, ), ] From 188a997128474b9087b9107dfbb72a8635c8bd75 Mon Sep 17 00:00:00 2001 From: Goksel Coban Date: Wed, 28 Dec 2022 18:42:36 +0300 Subject: [PATCH 2/6] update changelog --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0f4c01..4878dab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Change Log +## Unreleased + +### Added + +* Added `client_name` attribute to `TinymanClient` classes. [#51](https://github.com/tinymanorg/tinyman-py-sdk/pull/51) +* Added note to application call transactions. The note (`tinyman/:j{"origin":""}`) follows [Algorand Transaction Note Field Conventions ARC-2](https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0002.md). [#51](https://github.com/tinymanorg/tinyman-py-sdk/pull/51) +* Added `version` property and `generate_app_call_note` method to `TinymanClient` classes. [#51](https://github.com/tinymanorg/tinyman-py-sdk/pull/51) +* Added `get_version` and `generate_app_call_note` to `tinyman.utils`. [#51](https://github.com/tinymanorg/tinyman-py-sdk/pull/51) + +### Changed + +* ... + +### Removed +* ... + + ## 2.0.0 ### Added From 1f4e369d07b34194f58e9ab8f7c78fe55b6671b8 Mon Sep 17 00:00:00 2001 From: Goksel Coban Date: Wed, 28 Dec 2022 18:57:55 +0300 Subject: [PATCH 3/6] add integration section to readme --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 309012b..c5baeb5 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,16 @@ tinyman-py-sdk is not released on PYPI. It can be installed directly from this r `pip install git+https://github.com/tinymanorg/tinyman-py-sdk.git` + +## Integration + +If you are integrating your project into Tinyman, you can provide `client_name` while setting up Tinyman Client classes. +The client name will be added to the application call transaction's note field. It is recommended and completely optional. + +```python +client = TinymanV2MainnetClient(..., client_name="project name", ...) +``` + ## V2 ## Sneak Preview From e8254d6b40604ea29e1a888d3f12d707341633b1 Mon Sep 17 00:00:00 2001 From: Goksel Coban Date: Thu, 29 Dec 2022 17:27:22 +0300 Subject: [PATCH 4/6] add app call note parser --- tests/test.py | 40 ++++++++++++++++++++++++++++++++++++++++ tinyman/utils.py | 37 +++++++++++++++++++++++++++++++++++-- 2 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 tests/test.py diff --git a/tests/test.py b/tests/test.py new file mode 100644 index 0000000..b7f457c --- /dev/null +++ b/tests/test.py @@ -0,0 +1,40 @@ +from base64 import b64encode +from unittest import TestCase + +from tinyman.utils import generate_app_call_note, parse_app_call_note + + +class BaseTestCase(TestCase): + maxDiff = None + + def test_app_call_note(self): + note = generate_app_call_note( + version="v2", client_name="unit-test", extra_data={"extra": "some text"} + ) + expected_result = { + "version": "v2", + "data": {"extra": "some text", "origin": "unit-test"}, + } + + # test possible versions + string_note = note + bytes_note = string_note.encode() + base64_note = b64encode(bytes_note).decode() + + result = parse_app_call_note(string_note) + self.assertDictEqual( + result, + expected_result, + ) + + result = parse_app_call_note(base64_note) + self.assertDictEqual( + result, + expected_result, + ) + + result = parse_app_call_note(base64_note) + self.assertDictEqual( + result, + expected_result, + ) diff --git a/tinyman/utils.py b/tinyman/utils.py index 483bfaa..d8aa80d 100644 --- a/tinyman/utils.py +++ b/tinyman/utils.py @@ -1,7 +1,9 @@ import json +import re import warnings from base64 import b64decode, b64encode from datetime import datetime +from json import JSONDecodeError from typing import Optional from algosdk.error import AlgodHTTPError @@ -129,7 +131,7 @@ def get_version(tinyman_app_id: int) -> str: def generate_app_call_note( version: str, client_name: Optional[str] = None, extra_data: Optional[dict] = None -): +) -> str: # https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0002.md # : note_template = "{dapp_name}/{dapp_version}:{data_format}{data}" @@ -145,7 +147,7 @@ def generate_app_call_note( ) # spaces are removed from separators, default is (', ', ': ') - serialized_data = json.dumps(data, separators=(",", ":")) + serialized_data = json.dumps(data, separators=(",", ":"), sort_keys=True) note = note_template.format( dapp_name="tinyman", dapp_version=version, data_format="j", data=serialized_data @@ -153,6 +155,37 @@ def generate_app_call_note( return note +def parse_app_call_note( + note: [str, bytes], raise_exception: bool = False +) -> Optional[dict]: + if isinstance(note, str): + try: + note = b64decode(note) + except Exception: + pass + else: + note = note.decode() + elif isinstance(note, bytes): + note = note.decode() + + pattern = r"tinyman/(?Pv[1-2]):j(?P.*)$" + match = re.match(pattern, note) + + if not match: + return None + + try: + data = json.loads(match.group("raw_data")) + except JSONDecodeError as e: + if raise_exception: + raise e + return None + + # Result + result = {"version": match.group("version"), "data": data} + return result + + class TransactionGroup: def __init__(self, transactions): # Clear previously assigned group ids From a5231362e4408b08cbc9eaacfc4b61f7eda122b9 Mon Sep 17 00:00:00 2001 From: Goksel Coban Date: Thu, 29 Dec 2022 17:45:01 +0300 Subject: [PATCH 5/6] minor test improvement --- tests/test.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test.py b/tests/test.py index b7f457c..d8364f6 100644 --- a/tests/test.py +++ b/tests/test.py @@ -38,3 +38,8 @@ def test_app_call_note(self): result, expected_result, ) + + result = parse_app_call_note("invalid format") + self.assertEqual(result, None) + result = parse_app_call_note(b"invalid format") + self.assertEqual(result, None) From 17e0579d5724fe958c3c0a1fc915ca6570bbfabd Mon Sep 17 00:00:00 2001 From: Goksel Coban Date: Thu, 29 Dec 2022 18:06:11 +0300 Subject: [PATCH 6/6] fix parse_app_call_note --- tests/test.py | 8 ++++++++ tinyman/utils.py | 10 +++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/test.py b/tests/test.py index d8364f6..b4788ac 100644 --- a/tests/test.py +++ b/tests/test.py @@ -41,5 +41,13 @@ def test_app_call_note(self): result = parse_app_call_note("invalid format") self.assertEqual(result, None) + result = parse_app_call_note( + "INVALID+dGlueW1hbi92MjpqeyJvcmlnaW4iOiJ0aW55bWFuLXB5dGhvbi1zZGsifQ==" + ) + self.assertEqual(result, None) result = parse_app_call_note(b"invalid format") self.assertEqual(result, None) + result = parse_app_call_note( + b'INVALID+tinyman/v2:j{"origin":"tinyman-python-sdk"}' + ) + self.assertEqual(result, None) diff --git a/tinyman/utils.py b/tinyman/utils.py index d8aa80d..a5ca13b 100644 --- a/tinyman/utils.py +++ b/tinyman/utils.py @@ -163,10 +163,14 @@ def parse_app_call_note( note = b64decode(note) except Exception: pass - else: + + if isinstance(note, bytes): + try: note = note.decode() - elif isinstance(note, bytes): - note = note.decode() + except Exception as e: + if raise_exception: + raise e + return None pattern = r"tinyman/(?Pv[1-2]):j(?P.*)$" match = re.match(pattern, note)