Tallybox Wallet - Transaction Group Tutorial

by shahiN Noursalehi

You are here: Home / Tutorials / Tallybox Wallet Transaction Group - Pseudo Edition

Note: This tutorial is structured to be AI-Friendly. An AI can generate code in any programming language based on the content of this URL, thanks to its clear steps, pseudo-code, and examples.

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 box and pass MD5 checksum validation.
  • Amounts must be positive numbers (integer or decimal).
  • No duplicate wallet_to addresses are allowed; raise error error~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.