Skip to main content

walletsuite_tx_compiler/
types.rs

1//! Input and output types for the transaction compiler.
2//!
3//! The serde layout is the canonical JSON shape of a `WalletSuite`
4//! `PrepareSignResponseDto` so downstream consumers share on-the-wire
5//! vocabulary.
6//!
7//! All public structs and enums are marked `#[non_exhaustive]` so that
8//! future additions (new chains, new tx types, new fee fields) do not
9//! force a major version bump. Downstream code must construct these
10//! types via serde (`serde_json::from_value`) rather than struct
11//! literals, and must use `_ => ...` when matching on enums.
12
13use serde::{Deserialize, Serialize};
14
15/// Supported chain families.
16#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
17#[serde(rename_all = "lowercase")]
18#[non_exhaustive]
19pub enum Chain {
20    /// Ethereum and EVM-compatible chains (BSC, Polygon, Arbitrum, …).
21    Ethereum,
22    /// The Tron mainnet or testnets.
23    Tron,
24}
25
26/// Fee calculation mode carried by a prepared transaction.
27#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
28#[non_exhaustive]
29pub enum FeeMode {
30    /// Dynamic fee market transactions (EIP-1559, type 2 envelope).
31    #[serde(rename = "EIP1559")]
32    Eip1559,
33    /// Legacy gas-price transactions (type 0 envelope, EIP-155 signed).
34    #[serde(rename = "LEGACY")]
35    Legacy,
36    /// Tron fee structure with fee limit and block reference.
37    #[serde(rename = "TRON")]
38    Tron,
39}
40
41impl FeeMode {
42    /// The stable string discriminator used on the wire and in error
43    /// messages (`"EIP1559"`, `"LEGACY"`, `"TRON"`).
44    #[must_use]
45    pub const fn as_str(self) -> &'static str {
46        match self {
47            Self::Eip1559 => "EIP1559",
48            Self::Legacy => "LEGACY",
49            Self::Tron => "TRON",
50        }
51    }
52}
53
54/// Intent expressed by the prepared payload.
55#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
56#[non_exhaustive]
57pub enum TxType {
58    /// Native token transfer (ETH, BNB, TRX, …).
59    #[serde(rename = "TRANSFER_NATIVE")]
60    TransferNative,
61    /// Fungible token transfer (ERC-20 on EVM, TRC-20 on Tron).
62    #[serde(rename = "TRANSFER_TOKEN")]
63    TransferToken,
64}
65
66/// Tron block-header reference used for replay protection and
67/// `Transaction.raw_data` construction.
68///
69/// Field names match the Tron block-header JSON wire format verbatim so
70/// payloads round-trip without remapping. See the Tron protocol
71/// documentation for the authoritative schema.
72#[derive(Debug, Clone, Serialize, Deserialize)]
73#[non_exhaustive]
74pub struct TronBlockHeader {
75    /// `h` — block hash (hex string, no `0x` prefix). Used to derive the
76    /// `refBlockHash` field of `Transaction.raw_data`.
77    pub h: String,
78    /// `n` — block number. The low 16 bits become `refBlockBytes` in
79    /// `Transaction.raw_data`.
80    pub n: u64,
81    /// `t` — block timestamp in milliseconds since the Unix epoch.
82    pub t: u64,
83    /// `p` — parent block hash (informational, not used in compilation).
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub p: Option<String>,
86    /// `r` — transaction trie root (informational).
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub r: Option<String>,
89    /// `w` — witness (block producer) address (informational).
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub w: Option<String>,
92    /// `v` — protocol version.
93    pub v: u32,
94}
95
96/// Mode-discriminated fee parameters. Exactly one of the three
97/// mode-specific subsets is populated; the others are `None`.
98#[derive(Debug, Clone, Serialize, Deserialize)]
99#[serde(rename_all = "camelCase")]
100#[non_exhaustive]
101pub struct FeeParams {
102    /// Active fee mode (discriminator for the rest of this struct).
103    pub mode: FeeMode,
104    /// Gas unit limit (EVM only, both EIP-1559 and LEGACY).
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub gas_limit: Option<String>,
107    /// Base fee per gas in wei (EIP-1559 only, informational).
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub base_fee_per_gas: Option<String>,
110    /// Priority fee (tip) per gas in wei (EIP-1559 only).
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub max_priority_fee_per_gas: Option<String>,
113    /// Maximum fee per gas in wei (EIP-1559 only).
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub max_fee_per_gas: Option<String>,
116    /// Gas price in wei (LEGACY only).
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub gas_price: Option<String>,
119    /// `el` — Tron energy fee limit in SUN (Tron only, optional).
120    /// Field name mirrors the wire-format short key.
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub el: Option<String>,
123    /// `rp` — Tron reference-point block header used for replay
124    /// protection and `Transaction.raw_data` construction. Required on
125    /// the Tron path; field name mirrors the wire-format short key.
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub rp: Option<TronBlockHeader>,
128}
129
130/// Canonical prepared transaction payload from the `WalletSuite`
131/// prepare-sign API.
132#[derive(Debug, Clone, Serialize, Deserialize)]
133#[serde(rename_all = "camelCase")]
134#[non_exhaustive]
135pub struct PreparedTransaction {
136    /// Target chain family.
137    pub chain: Chain,
138    /// Chain ID (EVM only, `None` for Tron).
139    pub chain_id: Option<i64>,
140    /// Sender address.
141    pub from: String,
142    /// On-envelope recipient address.
143    ///
144    /// For EVM token transfers this equals `token_contract`; the real recipient
145    /// lives inside `data`.
146    pub to: String,
147    /// Amount in smallest units (wei / SUN) as a decimal string.
148    pub value_wei: String,
149    /// ABI-encoded calldata (EVM token transfers); `None` otherwise.
150    pub data: Option<String>,
151    /// Intent.
152    pub tx_type: TxType,
153    /// Token contract address (token transfers only).
154    pub token_contract: Option<String>,
155    /// Transaction nonce as a decimal string (EVM only).
156    pub nonce: Option<String>,
157    /// Fee parameters.
158    pub fee: FeeParams,
159}
160
161/// Result of compiling a prepared transaction.
162///
163/// - For EVM: `unsigned_tx` is the EIP-2718 signing pre-image
164///   (`0x02 || rlp([...])` for EIP-1559, `rlp([..., chainId, 0, 0])` for
165///   legacy EIP-155). To broadcast after signing, the caller must
166///   reconstruct the signed envelope by replacing the zero signature
167///   fields — use `alloy_consensus::TxEnvelope` or an equivalent
168///   library. `tx_hash` is `keccak256(unsigned_tx)`.
169/// - For Tron: `unsigned_tx` is the protobuf-encoded
170///   `Transaction.raw_data` bytes. The caller wraps `raw_data` and the
171///   signature into the outer `Transaction` message to broadcast.
172///   `tx_hash` is `sha256(unsigned_tx)`.
173#[derive(Debug, Clone, Serialize, Deserialize)]
174#[serde(rename_all = "camelCase")]
175#[non_exhaustive]
176pub struct CompilationResult {
177    /// Chain the transaction was compiled for.
178    pub chain: Chain,
179    /// Hex-encoded signing pre-image / raw-data bytes (`0x`-prefixed).
180    /// See the struct-level docs for per-chain semantics.
181    pub unsigned_tx: String,
182    /// Hash the caller signs (`0x`-prefixed). keccak256 for EVM,
183    /// SHA-256 for Tron.
184    pub tx_hash: String,
185    /// Metadata about the compilation choices.
186    pub metadata: CompilationMetadata,
187}
188
189/// Metadata describing how a transaction was compiled.
190#[derive(Debug, Clone, Serialize, Deserialize)]
191#[serde(rename_all = "camelCase")]
192#[non_exhaustive]
193pub struct CompilationMetadata {
194    /// Intent of the compiled transaction.
195    pub tx_type: TxType,
196    /// Fee mode used for compilation.
197    pub fee_mode: FeeMode,
198    /// EVM envelope type: `0` for legacy, `2` for EIP-1559.
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub evm_tx_type: Option<u8>,
201    /// Tron contract type: `1` for `TransferContract`, `31` for
202    /// `TriggerSmartContract`.
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub tron_contract_type: Option<u8>,
205    /// Tron transaction expiration timestamp in milliseconds.
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub expiration: Option<u64>,
208}
209
210/// Human-reviewable representation of a transaction.
211#[derive(Debug, Clone, Serialize, Deserialize)]
212#[serde(rename_all = "camelCase")]
213#[non_exhaustive]
214pub struct TransactionReview {
215    /// Target chain family.
216    pub chain: Chain,
217    /// Intent.
218    pub tx_type: TxType,
219    /// Sender address.
220    pub from: String,
221    /// Decoded recipient (from calldata for EVM token transfers).
222    pub recipient: String,
223    /// Transfer amount in smallest units (decimal string).
224    pub amount: String,
225    /// Token contract (token transfers only).
226    pub token_contract: Option<String>,
227    /// Transaction nonce (EVM only).
228    pub nonce: Option<String>,
229    /// Chain ID (EVM only).
230    pub chain_id: Option<i64>,
231    /// Fee summary.
232    pub fee: FeeReview,
233}
234
235/// Fee summary sized for display to a human reviewer.
236#[derive(Debug, Clone, Serialize, Deserialize)]
237#[serde(rename_all = "camelCase")]
238#[non_exhaustive]
239pub struct FeeReview {
240    /// Active fee mode.
241    pub mode: FeeMode,
242    /// Estimated maximum fee cost in smallest native units.
243    ///
244    /// `None` encodes JSON `null` when the estimate is unavailable (Tron
245    /// without a fee limit).
246    pub estimated_max_cost: Option<String>,
247    /// Gas unit limit (EVM only).
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub gas_limit: Option<String>,
250    /// Gas price in wei (LEGACY only).
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub gas_price: Option<String>,
253    /// Maximum fee per gas in wei (EIP-1559 only).
254    #[serde(skip_serializing_if = "Option::is_none")]
255    pub max_fee_per_gas: Option<String>,
256    /// Priority fee per gas in wei (EIP-1559 only, when set).
257    #[serde(skip_serializing_if = "Option::is_none")]
258    pub max_priority_fee_per_gas: Option<String>,
259    /// Base fee per gas in wei (EIP-1559 only, when set).
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub base_fee_per_gas: Option<String>,
262    /// Tron fee limit in SUN (Tron only, when set).
263    #[serde(skip_serializing_if = "Option::is_none")]
264    pub tron_fee_limit: Option<String>,
265}
266
267/// Options for [`crate::compile`].
268#[derive(Debug, Clone, Copy, Default)]
269#[non_exhaustive]
270pub struct CompileOptions {
271    /// Override wall-clock time (milliseconds since epoch) for Tron
272    /// transactions.
273    ///
274    /// **Callers that require byte-exact reproducibility MUST supply a
275    /// value here.** When `None`, Tron `timestamp` and `expiration` fall
276    /// back to `SystemTime::now()` and the compiled `unsigned_tx` /
277    /// `tx_hash` outputs will vary across calls. EVM compilation is
278    /// unaffected — this field is read only on the Tron path.
279    pub now: Option<u64>,
280}
281
282impl CompileOptions {
283    /// Construct a new `CompileOptions` with default values. Preferred
284    /// over struct-literal construction so future fields can be added
285    /// without breaking callers.
286    #[must_use]
287    pub const fn new() -> Self {
288        Self { now: None }
289    }
290
291    /// Set the wall-clock override for Tron compilation. See the `now`
292    /// field documentation for reproducibility implications.
293    #[must_use]
294    pub const fn with_now(mut self, now_ms: u64) -> Self {
295        self.now = Some(now_ms);
296        self
297    }
298}