Deterministic EVM and Tron unsigned-transaction compilation for Rust.
Crate name: walletsuite-tx-compiler
Deterministic transaction compilation for Ethereum (EVM) and Tron. Converts a
canonical PreparedTransaction payload into the signing pre-image bytes
and the corresponding pre-image digest a hardware wallet / HSM / signing
service can sign. Output is byte-exact and stable across versions; every
fixture case in tests/fixtures/canonical.json
is verified on every CI run.
- Byte-exact determinism. Compiled output (unsigned tx bytes, pre-image
hash, metadata, review) is stable for every payload in
tests/fixtures/canonical.json. Any change that alters compiled bytes must update that fixture in the same PR and is treated as a hard regression by CI. - No surprises at the boundary. Payloads are validated against the full canonical invariant set (address shape, safe integer ranges, mode-specific fee fields, EIP-155 chain-id, Tron block header, ERC-20 calldata selector with the 20-byte embedded recipient, 0x41 Tron address prefix) before any irreversible work runs.
- Pure library. Does not sign, broadcast, fetch nonces, or read wallets. Output is the signing pre-image + its hash; the caller drives the signer.
- Semver-stable. Every public struct and enum is
#[non_exhaustive]; adding fields or variants is non-breaking. Construct via serde or the provided builder (CompileOptions::new().with_now(...)), never via struct literals. - No-unsafe.
unsafe_code = "forbid". Clippypedantic,nursery, andcargolint groups enabled. - Small dependency surface.
alloy-consensusfor EVM RLP + signature hashing,bs58for Tron base58check,sha2for Tron hashing, and a hand-rolled protobuf encoder forTransaction.raw_data.
| Chain | Intent | Envelope |
|---|---|---|
| EVM | TRANSFER_NATIVE |
EIP-1559 (type 2) or legacy EIP-155 |
| EVM | TRANSFER_TOKEN |
ERC-20 transfer(address,uint256) |
| Tron | TRANSFER_NATIVE |
TransferContract (type 1) |
| Tron | TRANSFER_TOKEN |
TriggerSmartContract (type 31), TRC-20 transfer |
[dependencies]
walletsuite-tx-compiler = "0.1"use walletsuite_tx_compiler::{compile, review, validate, CompileOptions};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let raw: serde_json::Value = serde_json::from_str(r#"{
"chain": "ethereum",
"chainId": 1,
"txType": "TRANSFER_NATIVE",
"from": "0x1111111111111111111111111111111111111111",
"to": "0x2222222222222222222222222222222222222222",
"valueWei": "1000000000000000000",
"nonce": "0",
"fee": {
"mode": "EIP1559",
"gasLimit": "21000",
"maxFeePerGas": "30000000000",
"maxPriorityFeePerGas": "1500000000"
}
}"#)?;
let prepared = validate(&raw)?;
let human_review = review(&prepared)?;
let result = compile(&prepared, CompileOptions::default())?;
// EIP-1559 signing pre-image starts with the 0x02 envelope byte.
assert!(result.unsigned_tx.starts_with("0x02"));
println!("{human_review:#?}");
println!("digest to sign: {}", result.tx_hash);
// Sign `result.tx_hash` with your HSM / hardware wallet / signer, then
// reconstruct the signed wire transaction via
// `alloy_consensus::TxEnvelope` (EVM) or by wrapping the Tron
// `raw_data` + signature in the outer `Transaction` protobuf.
Ok(())
}Tron timestamp and expiration default to SystemTime::now(), so
compiled bytes vary per call. For byte-exact reproducibility, pin the
wall clock (milliseconds since epoch):
let options = CompileOptions::new().with_now(1_710_000_000_000);
let result = compile(&prepared, options)?;Per-chain semantics of CompilationResult::unsigned_tx:
- EVM: the EIP-2718 signing pre-image
(
0x02 || rlp([...])for EIP-1559;rlp([..., chainId, 0, 0])for legacy EIP-155). The caller hashes with keccak256 (already provided astx_hash), signs the hash, and reconstructs the signed envelope viaalloy_consensus::TxEnvelopeor an equivalent library. - Tron: the protobuf-encoded
Transaction.raw_databytes. The caller hashes with SHA-256 (already provided astx_hash), signs the hash, and wrapsraw_data+ signature into the outer TronTransactionmessage to broadcast.
tests/canonical.rs iterates every case in tests/fixtures/canonical.json
and asserts four invariants per case:
unsigned_txmatches the pinned hex byte-for-byte.tx_hashmatches the expected keccak256 (EVM) or SHA-256 (Tron) pre-image digest.metadataserializes to identical JSON (same field set, same key order).reviewserializes to identical JSON.
If you change any code path that alters compiled bytes, update the fixture in the same PR — otherwise CI will block the merge.
- This crate does not manage keys, sign transactions, fetch nonces, or broadcast. Pair it with a signer (hardware wallet, HSM, signing service) and a node client.
- Chains other than Ethereum (EVM-compatible) and Tron are out of scope for this release.
cargo fmt --all --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features
cargo doc --all-features --no-depsCI runs all of the above on Linux, macOS, and Windows, plus
cargo audit and a compile-only MSRV check (cargo check) against
Rust 1.91.
Licensed under the Apache License, Version 2.0.
See SECURITY.md. Report vulnerabilities privately to
security@walletsuite.io.