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 with- box
- 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 boxand pass MD5 checksum validation.
- Amounts must be positive numbers (integer or decimal).
- No duplicate wallet_toaddresses 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.