• LIP: 10
  • Layer: Application
  • Title: Transaction Manifest (LTM)
  • Author: Allwin Ketnawang
  • Created: 2025-07-15
  • Requires: 7
  • Status: Proposed

Abstract

This LIP proposes the LEA Transaction Manifest (LTM), a standardized, declarative JSON format for defining LEA transactions. The LTM schema is designed to abstract away the complexities of binary serialization, allowing developers and users to define complex transactions in a human-readable and reusable manner. It provides a robust system for sourcing data dynamically from the local file system and other data structures. The primary goal of LTM is to make transaction creation simpler, safer, and more transparent, separating the definition of a transaction's data from the logic of its binary encoding.

Motivation

The process of creating a valid LEA transaction, as defined in LIP-7, requires precise binary encoding using the Simple Compact Transaction Protocol (SCTP). Constructing this binary stream programmatically is a low-level task that is verbose, error-prone, and requires custom scripting for each new type of transaction. This approach tightly couples the transaction's data with the application logic, making it difficult to manage, reuse, or audit transaction definitions.

The LEA Transaction Manifest (LTM) introduces a declarative layer that solves this problem. By defining a transaction in a structured JSON format, users can clearly specify all its components (such as signers, gas parameters, and contract invocations) without writing any encoding logic. The LTM format is designed to be processed by a build tool that handles all the underlying complexities of data resolution, SCTP encoding, hashing, and signing. This separation of concerns dramatically simplifies the user experience, reduces the risk of malformed transactions, and promotes the creation of reusable transaction templates.

Specification

A LEA Transaction Manifest is a JSON file that adheres to the following structure. The file must be parsed by a tool that can safely handle large integers to prevent precision loss.

1. Top-Level Structure

The LTM is a single JSON object with the following root fields. The version field, specified as the first field in the LIP-7 binary format, is intentionally excluded. The build tool that processes the manifest is responsible for automatically prepending the protocol version (1) during binary serialization.

Field Type Required Description
sequence Number or String Yes The sequence number (nonce) for the fee-paying signer.
feePayer String Yes The name of the signer who will pay the transaction fees. Must match a key in the signers object.
gasLimit Number or String Yes The maximum gas units the transaction can consume.
gasPrice Number or String Yes The price per gas unit.
outputFile String No The file path where the final binary transaction will be written. If omitted when using the CLI, the binary output is written to stdout. This field is ignored when used as a module.
constants Object No An object defining reusable values that can be referenced elsewhere in the manifest.
signers Object Yes An object defining the one or more signers for the transaction.
invocations Array Yes An array of one or more invocation objects.

2. signers Object

The signers object contains one or more key-value pairs, where each key is a unique, user-defined name for the signer (e.g., mainAccount, alice). This name is used to reference the signer in the feePayer field and in $signer() placeholders.

The value for each signer can be one of two types:

  • File Path (String): A string containing the relative path to a LEA Keyset file. The file MUST conform to the format defined in LIP-12: LEA Keyset File Format.
  • Keyset Object (Array): A direct JSON array conforming to the LIP-12 keyset structure. This is primarily for programmatic use when the LTM is used as a library/module.

A build tool resolves these values to load the cryptographic keys needed for signing.

Example:

"signers": {
  "alice": "./keys/alice.keyset.json",
  "bob": "./keys/bob.keyset.json",
  "ephemeral": [
    [ 1, 2, 3, ..., 64 ],
    [
      [ 101, 102, 103, ..., 164 ],
      [ 201, 202, 203, ..., 232 ]
    ]
  ]
}

3. constants Object

This optional top-level object allows you to define key-value pairs for reusable values. These constants can be referenced in other parts of the manifest using the $const() placeholder. This is useful for reducing repetition and improving maintainability.

Example:

"constants": {
  "tokenProgram": "a1b2c3d4e5f6...",
  "USER_REGISTRATION_ID": 5
}

4. invocations Array

The invocations array contains one or more objects, each representing a single call to a smart contract.

Field Type Required Description
targetAddress String Yes The 32-byte hex address of the target contract. Can be a literal or use a placeholder.
instructions Array Yes An array defining the instruction payload to be sent to the target.

5. Address Vector and Signature Assembly

A compliant LTM build tool MUST follow a strict, deterministic algorithm to assemble the final addresses vector and signatures list for the binary transaction defined in LIP-7.

The following algorithm MUST be used:

  1. Resolve Placeholders and Collect Addresses: a. A build tool MUST first perform a full resolution of all placeholders in the manifest, including chained placeholders (e.g., $addr($const(user))). It MUST detect and fail if a circular dependency is found. b. During or after resolution, create a set to store all unique 32-byte addresses required for the transaction. This automatically handles de-duplication as required by LIP-7. c. Add the addresses of all signers defined in the signers object to the set. d. Add the fully-resolved targetAddress from each object in the invocations array to the set. e. Add the fully-resolved address from any $addr() placeholder to the set.
  2. Identify Fee Payer: The signer name specified in the feePayer field is the fee payer.
  3. Build the Final addresses Vector: The final vector is assembled using a deterministic, three-tiered sorting algorithm to ensure stability and predictability. a. Group 1 (Fee Payer): The fee payer's address is the first address in the vector (index 0). b. Group 2 (Other Signers): The addresses of all other signers (excluding the fee payer) are sorted amongst themselves based on their 32-byte binary lexicographical value. These are appended to the vector after the fee payer. c. Group 3 (Non-Signer Addresses): All remaining unique addresses from the set (i.e., non-signers) are sorted amongst themselves based on their 32-byte binary lexicographical value. These are appended last.
  4. Resolve $addr() to Index: After the final addresses vector is constructed, replace every $addr() placeholder string with the final numeric index of its corresponding address in the vector. This final number is then encoded according to the instruction's integer type.

The resulting ordered list is the final addresses vector for the transaction. The signatures in the binary output MUST correspond to the signer addresses in the exact same order as they appear in the final vector (fee payer first, then the other signers sorted by their binary address).

6. instructions Array

This array defines the sequence of data fields that constitute the instruction payload. Each element in the array is an object with a single key, where the key is the SCTP type (e.g., vector, uleb) and the value is the data to be encoded.

7. Placeholder Syntax for Dynamic Data

To enable dynamic data sourcing, LTM values can use a function-like placeholder syntax: $<source>(<value>). Placeholders can be chained (nested), and a build tool MUST support this.

Placeholder Syntax Example Description
$const(<key>) "$const(tokenProgram)" Retrieves a value from the constants object.
$signer(<name>.<key>) "$signer(main.address)" Retrieves a derived value from a named signer. Valid keys are ed25519Pk, sphincsPk, and address.
$addr(<source>[#<fmt>]) "$addr($const(user))" A special placeholder for integer types (uleb, uint8, etc.) that resolves an address source to its final index in the transaction's address vector. The <source> can be a literal string or another placeholder. The optional fmt (hex or bech32m) specifies how to interpret the resolved source string. Defaults to bech32m.
$file(<path>) "$file(./payload.bin)" Reads the raw binary content of a file. The value is a byte array.
$hex(<hex_string>) "$hex(deadbeef0123)" Interprets a literal string as a hexadecimal byte sequence. The value is a byte array.
$json(<path>#<path>[#<fmt>]) "$json(./cfg.json#profile.id#hex)" Parses a JSON file and extracts a value using a dot-separated path. The path is a dot-separated sequence of keys used to extract a value from a nested JSON object (e.g., data.user.id). This simple pathing does not support array indexing or advanced query expressions. If the extracted value is a string and a fmt (hex or bech32m) is provided, it is decoded into a byte array. Otherwise, the raw JSON value (string, number) is used.

Text-to-Binary Decoding

For placeholders like $json that produce string outputs, the #hex and #bech32m format specifiers can be used to decode the final string into a raw byte array. This is required when the instruction field is a vector.

8. General Schema Rules

Field Naming and Case Sensitivity

All field names defined in this specification are case-sensitive and MUST be written in camelCase. An LTM parsing tool must reject any manifest that uses a different case or format (e.g., snake_case or kebab-case) for official field names.

Comments

JSON does not natively support comments. To allow for inline documentation, any JSON object within an LTM file may include a field with the key comment. An LTM tool MUST completely ignore this field and its value during processing.

Example:

{
  "uleb": 5,
  "comment": "Instruction ID 5 is for user registration."
}

9. Data Type Handling

  • Literals: Values without placeholders are treated as literals (e.g., 123, "hello").
  • Large Integers: The JSON specification allows for numbers of arbitrary precision. However, many standard parsers (like JavaScript's native JSON.parse()) will truncate large integer values, leading to silent data corruption. Therefore, it is a strict requirement that any compliant LTM implementation MUST use a parser that preserves the full precision of all numeric literals, converting them to a BigInt or an equivalent lossless type. While a compliant tool must handle unquoted large numbers safely, providing them as strings (e.g., "18446744073709551615") remains a recommended best practice to ensure maximum compatibility across different platforms and tools.
  • Programmable Use: When used as a library, an LTM build tool should provide maximum flexibility by accepting direct JavaScript types for instruction fields. This includes Uint8Array for vector types, BigInt for large integer types (e.g., uint64, sleb), and standard Number for smaller integer and float types (e.g., uint32, float64). The tool is responsible for validating that a provided Number is within the valid and safe range for its target SCTP type.

Full Example

{
  "comment": "This transaction registers a new user and transfers an initial credit.",
  "sequence": 2,
  "feePayer": "registrar",
  "gasLimit": 500000,
  "gasPrice": 10,
  "outputFile": "./register-user.tx.bin",
  "constants": {
    "contract": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
    "registrationId": 1,
    "transferId": 2,
    "recipient": "lea1qys33pduaxmjwsg329z2yotg5j222q8g2f53g6"
  },
  "signers": {
    "auditor": "./keys/auditor.keyset.json",
    "registrar": "./keys/registrar.keyset.json"
  },
  "invocations": [
    {
      "targetAddress": "$const(contract)",
      "instructions": [
        {
          "uleb": "$const(registrationId)",
          "comment": "Instruction ID 1 is for registering a new user."
        },
        { "vector": "$json(./user-data.json#profile.username)" },
        { "uint64": "9876543210" },
        { "vector": "$signer(registrar.address)" }
      ]
    },
    {
      "targetAddress": "$const(contract)",
      "instructions": [
        {
          "uleb": "$const(transferId)",
          "comment": "Instruction ID 2 is for transferring credits."
        },
        {
          "uint8": "$addr($const(recipient))",
          "comment": "The recipient is identified by their address, resolved to an index."
        },
        { "uint64": "1000", "comment": "Transfer 1000 credit units." }
      ]
    }
  ]
}

Rationale

The design of the LTM is guided by the principles of clarity, safety, and flexibility.

  • JSON as a Base: JSON is ubiquitous, human-readable, and supported by virtually all programming languages, making it an ideal foundation.
  • Declarative Approach: By defining what the transaction should contain rather than how to build it, the manifest separates concerns. This simplifies the user's task and allows the underlying build tool to be optimized independently.
  • Placeholder System: A simple, consistent placeholder syntax ($source(...)) provides powerful and explicit data sourcing capabilities. This avoids the need for complex templating languages while enabling composition from files and other data sources.
  • Single-Key Instruction Objects: Using { "type": "value" } for instructions is concise and less verbose than { "type": "type", "value": "value" }, making the manifest cleaner and easier to read.

Tooling Recommendations

To improve the developer experience and provide better debugging capabilities, this specification recommends that a compliant LTM build tool implement the following features.

  • Dry Run / Debug Mode: A tool SHOULD provide a "dry run" or "debug" flag (e.g., --resolve-only). When enabled, this mode would perform all placeholder resolution and validation steps and then output the fully resolved JSON manifest, with all placeholders replaced by their final values. It would stop before performing binary serialization or signing. This provides an invaluable mechanism for developers to inspect the final, resolved data and verify its correctness before creating the transaction.

Backwards Compatibility

The LEA Transaction Manifest is a new, additive standard for tooling and application-layer development. It does not propose any changes to the underlying transaction format defined in LIP-7 or any other core protocol rule. Therefore, it introduces no backwards compatibility issues.

Security Considerations

A compliant LTM build tool MUST be designed with a "secure by default" philosophy, protecting users from potentially malicious manifests unless they explicitly opt into dangerous functionality.

  • File System Access: The $file and $json placeholders can read from the file system. To prevent unauthorized access, a build tool MUST enforce the following rules by default:
    • All file paths MUST resolve to a location within the same directory as the manifest file, or a subdirectory thereof. Path traversal (../) is forbidden.
    • Symbolic links MUST NOT be followed.
    • Broader file system access should only be possible via an explicit flag (e.g., --enable-unsafe-filesystem-access).
  • Resource Limits (Anti-DoS): To prevent Denial-of-Service attacks, a build tool MUST enforce reasonable resource limits by default.
    • File Size: The maximum size for files read via $file or $json should be limited (e.g., 1 MB).
    • Nesting Depth: The maximum nesting depth for placeholders should be limited (e.g., 3 levels).
    • These limits should only be removable with an explicit flag (e.g., --enable-unsafe-limits).
  • Safe JSON Parsing: Implementations of LTM parsers MUST use a library that can safely handle large integers (e.g., json-bigint for Node.js) to prevent silent precision loss on 64-bit numbers. Parsers MUST also operate in a strict mode that rejects duplicate keys to prevent malicious or accidental parameter overrides.
  • Circular Dependencies: A compliant LTM build tool MUST implement a mechanism to detect and reject manifests containing circular placeholder references (e.g., "$const(a)" where a references "$const(b)" and b references "$const(a)"). Failure to do so can result in non-terminating loops and denial-of-service vulnerabilities.

This LIP is licensed under the MIT License.