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.
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.
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.
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. |
signers
ObjectThe 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:
A build tool resolves these values to load the cryptographic keys needed for signing.
Example:
constants
ObjectThis 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:
invocations
ArrayThe 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. |
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:
$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.feePayer
field is the fee payer.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.$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).
instructions
ArrayThis 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.
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. |
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
.
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.
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:
123
, "hello"
).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.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.The design of the LTM is guided by the principles of clarity, safety, and flexibility.
$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.{ "type": "value" }
for instructions is concise and less verbose than { "type": "type", "value": "value" }
, making the manifest cleaner and easier to read.To improve the developer experience and provide better debugging capabilities, this specification recommends that a compliant LTM build tool implement the following features.
--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.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.
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
and $json
placeholders can read from the file system. To prevent unauthorized access, a build tool MUST enforce the following rules by default:
../
) is forbidden.--enable-unsafe-filesystem-access
).$file
or $json
should be limited (e.g., 1 MB).--enable-unsafe-limits
).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."$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.