1+ import base64
12import json
23import logging
34from pathlib import Path
45from typing import TYPE_CHECKING , cast
56
67import click
7- from algosdk import encoding , error
8- from algosdk .transaction import SignedTransaction , retrieve_from_file
8+ import msgpack
9+ from algokit_utils .clients import UnexpectedStatusError
10+ from algokit_utils .transact import SignedTransaction , decode_signed_transaction , encode_signed_transaction
911
1012from algokit .cli .common .constants import AlgorandNetwork , ExplorerEntityType
1113from algokit .cli .common .utils import MutuallyExclusiveOption , get_explorer_url
@@ -34,6 +36,60 @@ def _is_sign_task_output_txn(item: dict) -> bool:
3436 return isinstance (item , dict ) and all (key in item for key in ["transaction_id" , "content" ])
3537
3638
39+ def _retrieve_transactions_from_file (file_path : Path ) -> list [SignedTransaction ]:
40+ """
41+ Load signed transactions from a file containing msgpack-encoded data.
42+
43+ Args:
44+ file_path: Path to the file containing signed transaction(s)
45+
46+ Returns:
47+ A list of SignedTransaction objects
48+
49+ Raises:
50+ click.ClickException: If the file cannot be read or decoded
51+ """
52+ try :
53+ file_content = file_path .read_bytes ()
54+
55+ # Try to decode as a single transaction first
56+ try :
57+ return [decode_signed_transaction (file_content )]
58+ except Exception :
59+ pass
60+
61+ # Try to parse multiple concatenated msgpack objects
62+ transactions : list [SignedTransaction ] = []
63+ offset = 0
64+
65+ while offset < len (file_content ):
66+ # Use msgpack Unpacker to find the boundaries of each msgpack object
67+ unpacker = msgpack .Unpacker (raw = True , strict_map_key = False )
68+ unpacker .feed (file_content [offset :])
69+ try :
70+ _ = unpacker .unpack () # Advance to get the position
71+ msgpack_bytes_consumed = unpacker .tell ()
72+
73+ # Extract just this msgpack object
74+ encoded_txn = file_content [offset : offset + msgpack_bytes_consumed ]
75+ offset += msgpack_bytes_consumed
76+
77+ # Decode the signed transaction
78+ stx = decode_signed_transaction (encoded_txn )
79+ transactions .append (stx )
80+ except msgpack .OutOfData :
81+ break
82+ except Exception :
83+ break
84+
85+ if transactions :
86+ return transactions
87+ raise ValueError ("No valid transactions found in file" ) from None
88+ except Exception as ex :
89+ logger .debug (ex , exc_info = True )
90+ raise click .ClickException (f"Failed to read transactions from file: { ex } " ) from ex
91+
92+
3793def _load_from_stdin () -> list [SignedTransaction ]:
3894 """
3995 Load transaction data from standard input and convert it into a list of SignedTransaction objects.
@@ -59,7 +115,7 @@ def _load_from_stdin() -> list[SignedTransaction]:
59115 raise click .ClickException ("Invalid piped transaction content!" )
60116
61117 # Convert the content into SignedTransaction objects
62- return [encoding . msgpack_decode (item ["content" ]) for item in file_content ] # type: ignore[no-untyped-call ]
118+ return [decode_signed_transaction ( base64 . b64decode (item ["content" ])) for item in file_content ]
63119
64120
65121def _get_signed_transactions (file : Path | None = None , transaction : str | None = None ) -> list [SignedTransaction ]:
@@ -80,17 +136,17 @@ def _get_signed_transactions(file: Path | None = None, transaction: str | None =
80136 """
81137 try :
82138 if file :
83- txns = retrieve_from_file ( str ( file )) # type: ignore[no-untyped-call]
139+ txns = _retrieve_transactions_from_file ( file )
84140 elif transaction :
85- txns = [encoding . msgpack_decode (transaction )] # type: ignore[no-untyped-call ]
141+ txns = [decode_signed_transaction ( base64 . b64decode (transaction )) ]
86142 else :
87143 txns = _load_from_stdin ()
88144
89145 for txn in txns :
90146 if not isinstance (txn , SignedTransaction ):
91147 raise click .ClickException ("Supplied transaction is not signed!" )
92148
93- return cast ( "list[SignedTransaction]" , txns )
149+ return txns
94150
95151 except Exception as ex :
96152 logger .debug (ex , exc_info = True )
@@ -112,16 +168,21 @@ def _send_transactions(network: AlgorandNetwork, txns: list[SignedTransaction])
112168 """
113169 algod_client = load_algod_client (network )
114170
115- if any (txn .transaction .group for txn in txns ):
116- txid = algod_client .send_transactions (txns )
171+ if any (txn .txn .group for txn in txns ):
172+ # Send all transactions as a group
173+ encoded_txns = [encode_signed_transaction (txn ) for txn in txns ]
174+ result = algod_client .send_raw_transaction (encoded_txns )
175+ txid = result .tx_id
117176 click .echo (f"Transaction group successfully sent with txid: { txid } " )
118177 click .echo (
119178 f"Check transaction group status at: { get_explorer_url (txid , network , ExplorerEntityType .TRANSACTION )} "
120179 )
121180 else :
122181 for index , txn in enumerate (txns , start = 1 ):
123182 click .echo (f"\n Sending transaction { index } /{ len (txns )} " )
124- txid = algod_client .send_transaction (txn )
183+ encoded_txn = encode_signed_transaction (txn )
184+ result = algod_client .send_raw_transaction (encoded_txn )
185+ txid = result .tx_id
125186 click .echo (f"Transaction successfully sent with txid: { txid } " )
126187 click .echo (
127188 f"Check transaction status at: { get_explorer_url (txid , network , ExplorerEntityType .TRANSACTION )} "
@@ -169,7 +230,7 @@ def send(*, file: Path | None, transaction: str | None, network: AlgorandNetwork
169230
170231 try :
171232 _send_transactions (network , txns )
172- except error . AlgodHTTPError as ex :
233+ except UnexpectedStatusError as ex :
173234 raise click .ClickException (str (ex )) from ex
174235 except Exception as ex :
175236 logger .debug (ex , exc_info = True )
0 commit comments