Preparing Transaction Groups with Tallybox Wallet
This tutorial guides you through loading a Tallybox Wallet, reading and validating a Transaction Group file, preparing a consolidated Transaction Group, and broadcasting it on a Directed Acyclic Graph (DAG) network. The script loads wallets from an XML file, decrypts the private key, processes multiple transactions from a text file, checks for duplicate target wallet addresses, signs the group using ECDSA (secp256r1) with RFC 6979-compliant signatures, and saves it offline. To ensure consistency and prevent sorting issues, it hashes the concatenated wallet_to~order_amount~
strings for the transaction group and uses a unique order ID formatted as #
followed by the Unix timestamp. The transaction file format excludes order IDs, aligning with consistent data flow across implementations. Follow the steps below to implement your own Transaction Group preparation code.
References:
Directed Acyclic Graph (Wikipedia)
Cryptocurrency Wallet (Wikipedia)
Step 1: Load Wallet and Decrypt Private Key
Load the wallet's XML file, which contains the wallet name, compressed public key (Base58-encoded), encrypted private key (Base64-encoded AES-256-CBC), and wallet address. Prompt the user to select an XML file and enter a password for decryption. Validate the XML structure and decrypt the private key using a special AES-256-CBC algorithm with custom padding. Reconstruct the ECDSA key pair using the secp256r1 curve.
Special AES-256-CBC with Custom Padding
Tallybox uses a unique AES-256-CBC decryption algorithm with custom padding to retrieve the private key. The algorithm derives the key, IV, and salt from a 64-character hex secret (SHA-256 of wallet_name~password~wallet_address
). The plaintext is padded with random strings. The configuration is:
- Secret Key: A 64-character hex string (32 bytes), split into:
- Password: First 32 hex chars (16 bytes, ASCII-encoded).
- IV: Next 16 hex chars (8 bytes, ASCII-encoded, used as 16-byte IV).
- Salt: Last 16 hex chars (8 bytes, ASCII-encoded).
- Key Derivation: PBKDF2 with SHA-256, 3 iterations, 32-byte output, using password and salt.
- Custom Padding: Format is
random_string(0–99 chars, a-zA-Z)|plaintext(64-char hex private key)|random_string(0–99 chars, a-zA-Z)
, followed by PKCS#7 padding. - Input: Base64-encoded ciphertext (no IV prepended, may include newlines).
Pseudo-Code: AES-256-CBC Decrypt with Custom Padding
FUNCTION aes256_cbc_decrypt_custom(base64_ciphertext, secret): IF length(secret) < 64 OR NOT is_hex(secret): THROW "Secret must be a 64-char hex string" END IF ciphertext = base64_decode(base64_ciphertext.replace('\n', '')) // Strip newlines password = secret[0:32].encode('ascii') // First 32 hex chars iv = secret[32:48].encode('ascii') // Next 16 hex chars salt = secret[48:64].encode('ascii') // Last 16 hex chars key = PBKDF2_HMAC_SHA256(password, salt, iterations=3, output_bytes=32) cipher = initialize_aes256_cbc_decrypt(key, iv) padded_data = cipher.decrypt(ciphertext) padding_length = padded_data[-1] IF padding_length > 16 OR padding_length == 0: THROW "Invalid PKCS#7 padding" END IF padded_data = padded_data[:-padding_length] // Remove PKCS#7 padding TRY: padded_text = padded_data.decode('utf-8') parts = padded_text.split('|') IF length(parts) != 3: THROW "Invalid padding format: expected 'left|data|right'" END IF plaintext = parts[1] CATCH decode_error: parts = padded_data.split(b'|') IF length(parts) != 3: THROW "Invalid padding format in bytes" END IF plaintext = parts[1].decode('ascii') END TRY IF NOT is_hex(plaintext) OR length(plaintext) != 64: THROW "Decrypted private key must be a 64-char hex string" END IF RETURN plaintext END FUNCTION
Pseudo-Code: Load Wallet
FUNCTION load_wallet(file, password, protocol="https", graph="tallybox.mixoftix.net"): TRY: xml_content = READ_FILE(file) xml_doc = PARSE_XML(xml_content) wallet_name = xml_doc.GET_TAG("wallet_name") public_key_b58 = xml_doc.GET_TAG("public_key_b58_compressed") private_key_aes_b64 = xml_doc.GET_TAG("private_key_aes_b64") wallet_address = xml_doc.GET_TAG("wallet_address") IF NOT (wallet_name AND public_key_b58 AND private_key_aes_b64 AND wallet_address): THROW "Invalid XML format" key_components = wallet_name + "~" + password + "~" + wallet_address secret = SHA256(key_components.encode('utf-8')).hex() private_key_hex = aes256_cbc_decrypt_custom(private_key_aes_b64, secret) private_key_int = PARSE_HEX_TO_INT(private_key_hex) SECP256R1_ORDER = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 IF NOT (1 <= private_key_int < SECP256R1_ORDER): THROW "Private key out of range for secp256r1" ec = NEW_ELLIPTIC_CURVE("secp256r1") key_pair = ec.KEY_FROM_PRIVATE(private_key_int) public_key = key_pair.GET_PUBLIC() public_key_bytes = public_key.ENCODE("x962", "uncompressed") x_bytes = public_key_bytes[1:33] y_bytes = public_key_bytes[33:] y_parity = PARSE_INT(y_bytes) MOD 2 suffix = "1" IF y_parity == 1 ELSE "2" compressed_key = x_bytes.hex() + "*" + suffix result = CREATE_RECORD() result.SET_FIELD("key_pair", key_pair) result.SET_FIELD("compressed_key", compressed_key) result.SET_FIELD("public_key_b58", public_key_b58) result.SET_FIELD("wallet_address", wallet_address) result.SET_FIELD("wallet_name", wallet_name) result.SET_FIELD("protocol", protocol) result.SET_FIELD("graph", graph) RETURN result CATCH error: THROW "Decryption failed or invalid file: " + error END FUNCTION
Notice: XML File Structure
The XML file must include <wallet_name>
, <public_key_b58_compressed>
,
<private_key_aes_b64>
, and <wallet_address>
. The password must match
the one used to encrypt the private key, or decryption will fail.
Example Process:
Step 1 - Loaded Wallet Name:
shahin
Step 2 - Extracted Public Key (Base58 Compressed):
8mDnzMdZUKu2GQVLVfBLgVvw9VroBG8GoYHR9JjY7kzg*2
Step 3 - Extracted Wallet Address:
boxBd0e08436d01CSjqsUPgP9uamYUYsPFv6NyzvLfHGMBXmuubEp8MdZdz
Step 4 - Key Components (pre-SHA-256):
shahin~mypassword~boxBd0e08436d01CSjqsUPgP9uamYUYsPFv6NyzvLfHGMBXmuubEp8MdZdz
Step 5 - Secret Key (SHA-256):
7e9b4699f7d3eea878f51a9d4e238922116cccc558b15f2db257d92a9779becc
Step 6 - Encrypted Private Key (Base64):
Lu8xEpnACOtVrmZxRt7I2m7Zc5pZI8sxIGhXV6FqwN6N2VG8yt4jof5c0Xo5slF+VyOLORhls2auG27Xmjd20Z/9YiBZ2E5ePFsQV7ZkeqKFZa+k5IrOHGjDcyJtiJt22zAzOaSvyTFFGRHwDGI1FCJ5BppBvYkoGhro9MxHp3MVS7eW2N3z2NEQV2Gxm+IErv0ExkxfesyXqw/kTwk2qHBuTUtKRgkOY8RCAPPkNglOjfo4ct+YCdkzoIS/Ww6xatWmAUA6bhtxK2q3vZly1iteNAa+Uz0Pyn166f7Y90cCGJPINc5x7WGcolqPeWf5
Step 7 - Decrypted Private Key (hex):
7a8d26a42f45ecc68bc9d9ad2de9e4868429de2b185ab7e023f93845c58c4fa1
Step 8 - Status:
Wallet loaded successfully!
References:
XML Specification (W3C)
AES Encryption (Wikipedia)
SHA-256 Algorithm (Wikipedia)
SEC 2: Recommended Elliptic Curve Domain Parameters (secp256r1)
Step 2: Read and Validate Transaction Group File
Prompt the user to provide a text file containing multiple transactions. The file must follow a specific format with a header and transaction details (wallet address and amount, no order ID). Validate the file structure, wallet addresses, amounts, and check for duplicate wallet addresses. Compute the total wallet_to string and total order amount.
Transaction File Format
The transaction file is a tilde-separated text file with:
- Header:
tallybox~parcel_of_transactions
- Transactions: Each transaction has 4 fields:
wallet_to_N
: Label (e.g.,wallet_to_1
)wallet_address
: Valid Tallybox address starting withbox
order_amount_N
: Label (e.g.,order_amount_1
)amount
: Positive number (e.g.,3500.00000000
)
Example Transaction File:
Single-Line Format (Optional):
Validation Rules:
- File must start with
tallybox~parcel_of_transactions
. - Total fields = 2 (header) + 4N (N transactions).
- Labels must be
wallet_to_N
,order_amount_N
(N starts at 1). - Wallet addresses must start with
box
and pass MD5 checksum validation. - Amounts must be positive numbers (integer or decimal).
- No duplicate
wallet_to
addresses are allowed; raise errorerror~304~duplicate wallet address found~<address>
.
Error Example (Duplicate Wallet Addresses):
Error Output:
Pseudo-Code: Read and Validate Transaction File
FUNCTION read_transaction_file(file_path): TRY: content = READ_FILE(file_path).TRIM() parts = content.SPLIT("~") IF length(parts) < 2 OR parts[0] != "tallybox" OR parts[1] != "parcel_of_transactions": THROW "Invalid file format: must start with tallybox~parcel_of_transactions" IF (length(parts) - 2) MOD 4 != 0: THROW "Invalid number of fields: expected 2 + 4N" transactions = CREATE_LIST() total_wallet_to = "" total_amount = 0.0 wallet_to_set = CREATE_SET() // For duplicate checking FOR i = 2 TO length(parts) - 1 STEP 4: wallet_to_label = parts[i] wallet_to = parts[i + 1] order_amount_label = parts[i + 2] order_amount = parts[i + 3] transaction_num = (i - 2) / 4 + 1 IF wallet_to_label != "wallet_to_" + transaction_num OR order_amount_label != "order_amount_" + transaction_num: THROW "Invalid field labels for transaction " + transaction_num IF wallet_to IN wallet_to_set: THROW "error~304~duplicate wallet address found~" + wallet_to END IF wallet_to_set.ADD(wallet_to) IF NOT VALIDATE_WALLET_ADDRESS(wallet_to): THROW "error~301~invalid wallet format~" + wallet_to IF NOT IS_NUMBER(order_amount) OR PARSE_NUMBER(order_amount) <= 0: THROW "error~303~invalid numeric data~order_amount_" + order_amount transaction = CREATE_RECORD() transaction.SET_FIELD("wallet_to", wallet_to) transaction.SET_FIELD("order_amount", order_amount) transactions.APPEND(transaction) total_wallet_to = total_wallet_to + wallet_to + "~" + order_amount + "~" total_amount = total_amount + PARSE_NUMBER(order_amount) IF length(transactions) == 0: THROW "No valid transactions found" RETURN transactions, total_wallet_to, total_amount CATCH error: THROW "Failed to read transaction file: " + error END FUNCTION
Pseudo-Code: Validate Wallet Address
FUNCTION VALIDATE_WALLET_ADDRESS(address): // Check for null/empty or insufficient length IF address IS NULL OR address.LENGTH < 40: RETURN FALSE END IF // Check prefix IF NOT address.STARTS_WITH("box"): RETURN FALSE END IF // Check curve character (4th character) curve_char = address[3] IF curve_char NOT IN ["A", "B", "C"]: RETURN FALSE END IF // Checksum validation checksum_md5 = address.SUBSTRING(4, 15) // Characters 4 to 14 (11 characters) base58_part = address.SUBSTRING(15) // From character 15 to end computed_md5 = MD5(base58_part.encode()).SUBSTRING(0, 11) IF checksum_md5 == computed_md5: RETURN TRUE END IF RETURN FALSE END FUNCTION
Warning: File Validation
Ensure the transaction file adheres to the specified format. Incorrect labels, invalid wallet addresses, non-positive amounts, or duplicate wallet addresses will cause validation errors.
Example Process:
Step 1 - File Content:
tallybox~parcel_of_transactions~wallet_to_1~boxB2bbc15c8c135W8PzPEZf98cEu2h2muhkeJQS3MwTYUaTHVkFTgihcS7~order_amount_1~3500.00000000~wallet_to_2~boxBff9b94f6471HQTnUgStoT5gwUkJLiVUWQbXkTcjqshqkD94vSTymkhF~order_amount_2~3700.00000000
Step 2 - Parsed Fields (10 total):
['tallybox', 'parcel_of_transactions', 'wallet_to_1', 'boxB2bbc15c8c135W8PzPEZf98cEu2h2muhkeJQS3MwTYUaTHVkFTgihcS7', 'order_amount_1', '3500.00000000', 'wallet_to_2', 'boxBff9b94f6471HQTnUgStoT5gwUkJLiVUWQbXkTcjqshqkD94vSTymkhF', 'order_amount_2', '3700.00000000']
Step 3 - Transaction 1:
Wallet To: boxB2bbc15c8c135W8PzPEZf98cEu2h2muhkeJQS3MwTYUaTHVkFTgihcS7
Amount: 3500.00000000
Step 4 - Transaction 2:
Wallet To: boxBff9b94f6471HQTnUgStoT5gwUkJLiVUWQbXkTcjqshqkD94vSTymkhF
Amount: 3700.00000000
Step 5 - Totals:
Total Wallet To: boxB2bbc15c8c135W8PzPEZf98cEu2h2muhkeJQS3MwTYUaTHVkFTgihcS7~3500.00000000~boxBff9b94f6471HQTnUgStoT5gwUkJLiVUWQbXkTcjqshqkD94vSTymkhF~3700.00000000~
Total Amount: 7200.00000000
Step 6 - Status:
2 transactions loaded successfully!
References:
File Formats (Wikipedia)
MD5 Algorithm (Wikipedia)
Data Validation (Wikipedia)
Step 3: Prepare Transaction Group
Prompt the user for the token name (e.g., 2PN, 2ZR, TLH). Use the transaction file data to compute the total wallet_to string (concatenated wallet_to~order_amount~
) and total order amount. Generate a SHA-256 hash of the total wallet_to string. Set the order ID to the Unix timestamp prefixed with '#'. Sign the consolidated transaction using ECDSA (secp256r1) with RFC 6979 deterministic signatures. Save the signed transaction to an offline file.
Pseudo-Code: Prepare Transaction Group
FUNCTION prepare_group_transaction(wallet_state, transactions, total_wallet_to, total_amount): token = PROMPT_USER("Select token: [2PN, 2ZR, TLH]") graph = wallet_state.graph utc_unix = CURRENT_TIMESTAMP_SECONDS() IF NOT (token IN ["2PN", "2ZR", "TLH"]): THROW "Invalid token" wallet_to_hash = SHA256(total_wallet_to.encode('utf-8')).hex() order_id = "#" + utc_unix transaction_data = graph + "~" + graph + "~" + wallet_state.wallet_address + "~" + wallet_to_hash + "~" + token + "~" + FORMAT_DECIMAL(total_amount, 8) + "~" + order_id + "~" + utc_unix msg_hash = SHA256(transaction_data.encode('utf-8')).bytes() signature = wallet_state.key_pair.SIGN_ECDSA(msg_hash, curve="secp256r1", deterministic=RFC6979) sig_der = signature.TO_DER() sig_base64 = BASE64_ENCODE(sig_der) broadcast_data = CONCATENATE_WITH_SEPARATOR( "tallybox", "parcel_of_transaction", "number_of_transactions", length(transactions), "graph_from", graph, "graph_to", graph, "wallet_from", wallet_state.wallet_address, "wallet_to", wallet_to_hash, "order_currency", token, "order_amount", FORMAT_DECIMAL(total_amount, 8), "order_id", order_id, "order_utc_unix", utc_unix, "the_sign", sig_base64, "publicKey_xy_compressed", wallet_state.public_key_b58, separator="~" ) timestamp = FORMAT_TIMESTAMP(utc_unix, "YYYY_MM_DD_HH_MM_SS") filename = CONCATENATE("sample_tx", "_offline_", token, "_", timestamp, ".txt") WRITE_FILE(filename, broadcast_data) RETURN broadcast_data, token END FUNCTION
Success: Transaction Group Signing
The signed transaction is a tilde-separated string including the number of transactions, hashed wallet_to, signature, and public key, saved to an offline file for later broadcasting. The signature is not URI-encoded in the offline file but will be encoded during broadcasting.
Example Process:
Step 1 - Selected Token:
TLH
Step 2 - Total Wallet To:
boxB2bbc15c8c135W8PzPEZf98cEu2h2muhkeJQS3MwTYUaTHVkFTgihcS7~3500.00000000~boxBff9b94f6471HQTnUgStoT5gwUkJLiVUWQbXkTcjqshqkD94vSTymkhF~3700.00000000~
Step 3 - Wallet To Hash (SHA-256):
7b9e4f2e8a3e9c1d6b2f4e8c7a5d3b9f1c2e4a6b7d8e9f0a1b2c3d4e5f6a7b8
Step 4 - Order ID:
#1745562261
Step 5 - Transaction Data:
tallybox.mixoftix.net~tallybox.mixoftix.net~boxBd0e08436d01CSjqsUPgP9uamYUYsPFv6NyzvLfHGMBXmuubEp8MdZdz~7b9e4f2e8a3e9c1d6b2f4e8c7a5d3b9f1c2e4a6b7d8e9f0a1b2c3d4e5f6a7b8~TLH~7200.00000000~#1745562261~1745562261
Step 6 - Message Hash (SHA-256):
3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4
Step 7 - Signature (Base64):
MEQCIBhk5tzsQJFNnBBrus8BZqJBqDeRsAa2HNj151wFhCxJAiBqTefQGjboPcSKLIQUlvHDdjzzgM4MrrA9rOa7KFDDsA==
Step 8 - Broadcast Data:
tallybox~parcel_of_transaction~number_of_transactions~2~graph_from~tallybox.mixoftix.net~graph_to~tallybox.mixoftix.net~wallet_from~boxBd0e08436d01CSjqsUPgP9uamYUYsPFv6NyzvLfHGMBXmuubEp8MdZdz~wallet_to~7b9e4f2e8a3e9c1d6b2f4e8c7a5d3b9f1c2e4a6b7d8e9f0a1b2c3d4e5f6a7b8~order_currency~TLH~order_amount~7200.00000000~order_id~#1745562261~order_utc_unix~1745562261~the_sign~MEQCIBhk5tzsQJFNnBBrus8BZqJBqDeRsAa2HNj151wFhCxJAiBqTefQGjboPcSKLIQUlvHDdjzzgM4MrrA9rOa7KFDDsA==~publicKey_xy_compressed~8mDnzMdZUKu2GQVLVfBLgVvw9VroBG8GoYHR9JjY7kzg*2
Step 9 - Saved File:
sample_tx_offline_TLH_2025_04_26_06_25_18.txt
Example Output:
References:
ECDSA Algorithm (Wikipedia)
RFC 6979: Deterministic Usage of DSA and ECDSA
SHA-256 Algorithm (Wikipedia)
Base64 Encoding (Wikipedia)
Step 4: Broadcast Transaction Group
Broadcast the signed Transaction Group to the DAG node's API endpoint (/broadcast.asmx/order_accept_multiple
) using a POST request. Include both the consolidated transaction and the original transaction details (without order IDs). The signature in the broadcast data must be URI-encoded. Handle the response to confirm whether the transaction was accepted or rejected.
Pseudo-Code: Broadcast Transaction Group
FUNCTION broadcast_group_transaction(wallet_state, broadcast_data, transactions): url = wallet_state.protocol + "://" + wallet_state.graph + "/broadcast.asmx/order_accept_multiple" order_csv_multiple = CONCATENATE_WITH_SEPARATOR( "tallybox", "parcel_of_transactions", FOR EACH transaction IN transactions WITH index: "wallet_to_" + index, transaction.wallet_to, "order_amount_" + index, transaction.order_amount, separator="~" ) broadcast_parts = broadcast_data.SPLIT("~") tallybox, parcel_type, num_label, num_txs, graph_from_label, graph_from, graph_to_label, graph_to, \ wallet_from_label, wallet_from, wallet_to_label, wallet_to, order_currency_label, order_currency, \ order_amount_label, order_amount, order_id_label, order_id, order_utc_unix_label, order_utc_unix, \ the_sign_label, sig_base64, public_key_label, public_key_b58 = broadcast_parts IF NOT (tallybox == "tallybox" AND parcel_type == "parcel_of_transaction" AND num_label == "number_of_transactions" AND graph_from_label == "graph_from" AND graph_to_label == "graph_to" AND wallet_from_label == "wallet_from" AND wallet_to_label == "wallet_to" AND order_currency_label == "order_currency" AND order_amount_label == "order_amount" AND order_id_label == "order_id" AND order_utc_unix_label == "order_utc_unix" AND the_sign_label == "the_sign" AND public_key_label == "publicKey_xy_compressed"): THROW "Invalid broadcast data structure" END IF broadcast_data_quoted = CONCATENATE_WITH_SEPARATOR( tallybox, parcel_type, num_label, num_txs, graph_from_label, graph_from, graph_to_label, graph_to, wallet_from_label, wallet_from, wallet_to_label, wallet_to, order_currency_label, order_currency, order_amount_label, order_amount, order_id_label, ENCODE_URI_COMPONENT(order_id), order_utc_unix_label, order_utc_unix, the_sign_label, ENCODE_URI_COMPONENT(sig_base64), public_key_label, public_key_b58, separator="~" ) post_data = "app_name=tallybox&app_version=2.0&order_csv=" + broadcast_data_quoted + "&order_csv_multiple=" + order_csv_multiple) TRY: response = POST_REQUEST(url, post_data, headers={"Content-Type": "application/x-www-form-urlencoded"}) IF response.STARTS_WITH("submitted~200~"): RETURN "Group transaction broadcast successfully: " + response ELSE: parts = response.SPLIT("~") error_code = parts[1] OR "unknown" error_message = parts[2] OR "no details provided" THROW "Broadcast failed with error " + error_code + ": " + error_message CATCH error: THROW "Failed to broadcast Transaction Group: " + error END FUNCTION
Success: Transaction Group Broadcasting
A successful broadcast returns a response starting with submitted~200~
, indicating the Transaction Group was accepted by the DAG node.
Example Process:
Step 1 - Constructed POST Data:
app_name=tallybox&app_version=2.0&order_csv=tallybox~parcel_of_transaction~number_of_transactions~2~graph_from~tallybox.mixoftix.net~graph_to~tallybox.mixoftix.net~wallet_from~boxBd0e08436d01CSjqsUPgP9uamYUYsPFv6NyzvLfHGMBXmuubEp8MdZdz~wallet_to~7b9e4f2e8a3e9c1d6b2f4e8c7a5d3b9f1c2e4a6b7d8e9f0a1b2c3d4e5f6a7b8~order_currency~TLH~order_amount~7200.00000000~order_id~%231745562261~order_utc_unix~1745562261~the_sign~MEQCIBhk5tzsQJFNnBBrus8BZqJBqDeRsAa2HNj151wFhCxJAiBqTefQGjboPcSKLIQUlvHDdjzzgM4MrrA9rOa7KFDDsA%3D%3D~publicKey_xy_compressed~8mDnzMdZUKu2GQVLVfBLgVvw9VroBG8GoYHR9JjY7kzg*2&order_csv_multiple=tallybox~parcel_of_transactions~wallet_to_1~boxB2bbc15c8c135W8PzPEZf98cEu2h2muhkeJQS3MwTYUaTHVkFTgihcS7~order_amount_1~3500.00000000~wallet_to_2~boxBff9b94f6471HQTnUgStoT5gwUkJLiVUWQbXkTcjqshqkD94vSTymkhF~order_amount_2~3700.00000000
Step 2 - Server Endpoint URL:
https://tallybox.mixoftix.net/broadcast.asmx/order_accept_multiple
Step 3 - Server Response:
submitted~200~1745562265
Step 4 - Status:
Group transaction broadcast successfully!
References:
HTTP POST Request (Wikipedia)
URI Encoding (Wikipedia)
API Concepts (Wikipedia)
Main Workflow
Combine the above steps into a main function that orchestrates the entire process: loading the wallet, reading the transaction file, preparing the transaction group, saving it offline, and optionally broadcasting it. Prompt the user for inputs and provide options to enable debug logging or quit without broadcasting.
Pseudo-Code: Main Workflow
FUNCTION main(): TRY: file_path = PROMPT_USER("Enter wallet XML file path: ") password = PROMPT_USER_SECURE("Enter wallet password: ") log_choice = PROMPT_USER("Show decryption logs? [1] Yes [2] No") show_logs = (log_choice == "1") wallet_state = load_wallet(file_path, password, show_logs=show_logs) OUTPUT("Wallet loaded successfully!") txn_file_path = PROMPT_USER("Enter transaction file path: ") transactions, total_wallet_to, total_amount = read_transaction_file(txn_file_path, show_logs=show_logs) OUTPUT("Loaded " + length(transactions) + " transactions from " + txn_file_path) broadcast_data, token = prepare_group_transaction(wallet_state, transactions, total_wallet_to, total_amount, show_logs=show_logs) OUTPUT("Group transaction signed: " + broadcast_data) OUTPUT("Group transaction saved to " + filename) action = PROMPT_USER("Select action: [1] Broadcast [2] Quit") IF action == "1": result = broadcast_group_transaction(wallet_state, broadcast_data, transactions, show_logs=show_logs) OUTPUT(result) ELSE IF action == "2": OUTPUT("Exiting") RETURN ELSE: THROW "Invalid action selected" CATCH error: OUTPUT("Error: " + error) END FUNCTION
Acknowledgments
Special thanks to Grok 3, for its invaluable assistance in creating and updating this TallyBox wallet Transaction Group tutorial.