xrpl_wasm_stdlib/core/current_tx/traits.rs
1//! # Transaction Field Access Traits
2//!
3//! This module defines traits for accessing fields from XRPL transactions in a type-safe manner.
4//! It provides a structured interface for retrieving both common transaction fields (shared across
5//! all transaction types) and transaction-specific fields (unique to particular transaction types).
6//!
7//! ## Overview
8//!
9//! XRPL transactions contain a variety of fields, some mandatory and others optional. This module
10//! organizes field access into logical groups:
11//!
12//! - **Common Fields**: Fields present in all XRPL transactions (Account, Fee, Sequence, etc.)
13//! - **Transaction-Specific Fields**: Fields unique to specific transaction types
14//!
15//! ## Design Philosophy
16//!
17//! The trait-based design provides several benefits:
18//!
19//! - **Type Safety**: Each field is accessed through methods with appropriate return types
20//! - **Composability**: Transaction types can implement multiple traits as needed
21//! - **Zero-Cost Abstraction**: Trait methods compile down to direct host function calls
22//! - **Extensibility**: New transaction types can easily implement the relevant traits
23//!
24//! ## Field Categories
25//!
26//! ### Mandatory vs. Optional Fields
27//!
28//! - **Mandatory fields** return `Result<T>` and will error if missing
29//! - **Optional fields** return `Result<Option<T>>` and return `None` if missing
30//!
31//! ### Field Types
32//!
33//! - **AccountID**: 20-byte account identifiers
34//! - **Hash256**: 256-bit cryptographic hashes
35//! - **Amount**: XRP amounts (with future support for tokens)
36//! - **u32**: 32-bit unsigned integers for sequence numbers, flags, etc.
37//! - **Blob**: Variable-length binary data
38//! - **PublicKey**: 33-byte compressed public keys
39//! - **TransactionType**: Enumerated transaction type identifiers
40
41use crate::core::current_tx::{get_field, get_field_optional};
42use crate::core::types::account_id::AccountID;
43use crate::core::types::amount::Amount;
44use crate::core::types::crypto_condition::{Condition, Fulfillment};
45use crate::core::types::public_key::PublicKey;
46use crate::core::types::signature::Signature;
47use crate::core::types::transaction_type::TransactionType;
48use crate::core::types::uint::Hash256;
49use crate::host::error_codes::{
50 match_result_code_optional, match_result_code_with_expected_bytes_optional,
51};
52use crate::host::{Result, get_tx_field};
53use crate::sfield;
54
55/// Trait providing access to common fields present in all XRPL transactions.
56///
57/// ## Implementation Requirements
58///
59/// Types implementing this trait should ensure they are used only in the context of a valid
60/// XRPL transaction. The trait methods assume the current transaction context is properly
61/// established by the XRPL Programmability environment.
62pub trait TransactionCommonFields {
63 /// Retrieves the account field from the current transaction.
64 ///
65 /// This field identifies (Required) The unique address of the account that initiated the
66 /// transaction.
67 ///
68 /// # Returns
69 ///
70 /// Returns a `Result<AccountID>` where:
71 /// * `Ok(AccountID)` - The 20-byte account identifier of the transaction sender
72 /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
73 fn get_account(&self) -> Result<AccountID> {
74 get_field(sfield::Account)
75 }
76
77 /// Retrieves the transaction type from the current transaction.
78 ///
79 /// This field specifies the type of transaction. Valid transaction types include:
80 /// Payment, OfferCreate, TrustSet, and many others.
81 ///
82 /// # Returns
83 ///
84 /// Returns a `Result<TransactionType>` where:
85 /// * `Ok(TransactionType)` - An enumerated value representing the transaction type
86 /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
87 ///
88 fn get_transaction_type(&self) -> Result<TransactionType> {
89 get_field(sfield::TransactionType)
90 }
91
92 /// Retrieves the computation allowance from the current transaction.
93 ///
94 /// This field specifies the maximum computational resources that the transaction is
95 /// allowed to consume during execution in the XRPL Programmability environment.
96 /// It helps prevent runaway computations and ensures network stability.
97 ///
98 /// # Returns
99 ///
100 /// Returns a `Result<u32>` where:
101 /// * `Ok(u32)` - The computation allowance value in platform-defined units
102 /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
103 fn get_computation_allowance(&self) -> Result<u32> {
104 get_field(sfield::ComputationAllowance)
105 }
106
107 /// Retrieves the fee amount from the current transaction.
108 ///
109 /// This field specifies the amount of XRP (in drops) that the sender is willing to pay
110 /// as a transaction fee. The fee is consumed regardless of whether the transaction
111 /// succeeds or fails, and higher fees can improve transaction priority during
112 /// network congestion.
113 ///
114 /// # Returns
115 ///
116 /// Returns a `Result<Amount>` where:
117 /// * `Ok(Amount)` - The fee amount as an XRP amount in drops
118 /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
119 ///
120 /// # Note
121 ///
122 /// Returns XRP amounts only (for now). Future versions may support other token types
123 /// when the underlying amount handling is enhanced.
124 fn get_fee(&self) -> Result<Amount> {
125 get_field(sfield::Fee)
126 }
127
128 /// Retrieves the sequence number from the current transaction.
129 ///
130 /// This field represents the sequence number of the account sending the transaction. A
131 /// transaction is only valid if the Sequence number is exactly 1 greater than the previous
132 /// transaction from the same account. The special case 0 means the transaction is using a
133 /// Ticket instead (Added by the TicketBatch amendment).
134 ///
135 /// # Returns
136 ///
137 /// Returns a `Result<u32>` where:
138 /// * `Ok(u32)` - The transaction sequence number
139 /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
140 ///
141 /// # Note
142 ///
143 /// If the transaction uses tickets instead of sequence numbers, this field may not
144 /// be present. In such cases, use `get_ticket_sequence()` instead.
145 fn get_sequence(&self) -> Result<u32> {
146 get_field(sfield::Sequence)
147 }
148
149 /// Retrieves the account transaction ID from the current transaction.
150 ///
151 /// This optional field contains the hash value identifying another transaction. If provided,
152 /// this transaction is only valid if the sending account's previously sent transaction matches
153 /// the provided hash.
154 ///
155 /// # Returns
156 ///
157 /// Returns a `Result<Option<Hash256>>` where:
158 /// * `Ok(Some(Hash256))` - The hash of the required previous transaction
159 /// * `Ok(None)` - If no previous transaction requirement is specified
160 /// * `Err(Error)` - If an error occurred during field retrieval
161 fn get_account_txn_id(&self) -> Result<Option<Hash256>> {
162 get_field_optional(sfield::AccountTxnID)
163 }
164
165 /// Retrieves the `flags` field from the current transaction.
166 ///
167 /// This optional field contains a bitfield of transaction-specific flags that modify
168 /// the transaction's behavior.
169 ///
170 /// # Returns
171 ///
172 /// Returns a `Result<Option<u32>>` where:
173 /// * `Ok(Some(u32))` - The flags bitfield if present
174 /// * `Ok(None)` - If no flags are specified (equivalent to flags = 0)
175 /// * `Err(Error)` - If an error occurred during field retrieval
176 fn get_flags(&self) -> Result<Option<u32>> {
177 get_field_optional(sfield::Flags)
178 }
179
180 /// Retrieves the last ledger sequence from the current transaction.
181 ///
182 /// This optional field specifies the highest ledger index this transaction can appear in.
183 /// Specifying this field places a strict upper limit on how long the transaction can wait to
184 /// be validated or rejected. See Reliable Transaction Submission for more details.
185 ///
186 /// # Returns
187 ///
188 /// Returns a `Result<Option<u32>>` where:
189 /// * `Ok(Some(u32))` - The maximum ledger index for transaction inclusion
190 /// * `Ok(None)` - If no expiration is specified (transaction never expires)
191 /// * `Err(Error)` - If an error occurred during field retrieval
192 fn get_last_ledger_sequence(&self) -> Result<Option<u32>> {
193 get_field_optional(sfield::LastLedgerSequence)
194 }
195
196 /// Retrieves the network ID from the current transaction.
197 ///
198 /// This optional field identifies the network ID of the chain this transaction is intended for.
199 /// MUST BE OMITTED for Mainnet and some test networks. REQUIRED on chains whose network ID is
200 /// 1025 or higher.
201 ///
202 /// # Returns
203 ///
204 /// Returns a `Result<Option<u32>>` where:
205 /// * `Ok(Some(u32))` - The network identifier
206 /// * `Ok(None)` - If no specific network is specified (uses default network)
207 /// * `Err(Error)` - If an error occurred during field retrieval
208 fn get_network_id(&self) -> Result<Option<u32>> {
209 get_field_optional(sfield::NetworkID)
210 }
211
212 /// Retrieves the source tag from the current transaction.
213 ///
214 /// This optional field is an arbitrary integer used to identify the reason for this payment, or
215 /// a sender on whose behalf this transaction is made. Conventionally, a refund should specify
216 /// the initial payment's SourceTag as the refund payment's DestinationTag.
217 ///
218 /// # Returns
219 ///
220 /// Returns a `Result<Option<u32>>` where:
221 /// * `Ok(Some(u32))` - The source tag identifier
222 /// * `Ok(None)` - If no source tag is specified
223 /// * `Err(Error)` - If an error occurred during field retrieval
224 fn get_source_tag(&self) -> Result<Option<u32>> {
225 get_field_optional(sfield::SourceTag)
226 }
227
228 /// Retrieves the signing public key from the current transaction.
229 ///
230 /// This field contains the hex representation of the public key that corresponds to the
231 /// private key used to sign this transaction. If an empty string, this field indicates that a
232 /// multi-signature is present in the Signers field instead.
233 ///
234 /// # Returns
235 ///
236 /// Returns a `Result<PublicKey>` where:
237 /// * `Ok(PublicKey)` - The 33-byte compressed public key used for signing
238 /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
239 ///
240 /// # Security Note
241 ///
242 /// The presence of this field doesn't guarantee the signature is valid. Instead, this field
243 /// only provides the key claimed to be used for signing. The XRPL network performs signature
244 /// validation before transaction execution.
245 fn get_signing_pub_key(&self) -> Result<PublicKey> {
246 get_field(sfield::SigningPubKey)
247 }
248
249 /// Retrieves the ticket sequence from the current transaction.
250 ///
251 /// This optional field provides the sequence number of the ticket to use in place of a
252 /// Sequence number. If this is provided, Sequence must be 0. Cannot be used with AccountTxnID.
253 ///
254 /// # Returns
255 ///
256 /// Returns a `Result<Option<u32>>` where:
257 /// * `Ok(Some(u32))` - The ticket sequence number if the transaction uses tickets
258 /// * `Ok(None)` - If the transaction uses traditional sequence numbering
259 /// * `Err(Error)` - If an error occurred during field retrieval
260 ///
261 /// # Note
262 ///
263 /// Transactions use either `Sequence` or `TicketSequence`, but not both. Check this
264 /// field when `get_sequence()` fails or when implementing ticket-aware logic.
265 fn get_ticket_sequence(&self) -> Result<Option<u32>> {
266 get_field_optional(sfield::TicketSequence)
267 }
268
269 /// Retrieves the transaction signature from the current transaction.
270 ///
271 /// This mandatory field contains the signature that verifies this transaction as originating
272 /// from the account it says it is from.
273 ///
274 /// Signatures can be either:
275 /// - 64 bytes for EdDSA (Ed25519) signatures
276 /// - 70-72 bytes for ECDSA (secp256k1) signatures
277 ///
278 /// # Returns
279 ///
280 /// Returns a `Result<Signature>` where:
281 /// * `Ok(Signature)` - The transaction signature (up to 72 bytes)
282 /// * `Err(Error)` - If the field cannot be retrieved
283 ///
284 /// # Security Note
285 ///
286 /// The signature is validated by the XRPL network before transaction execution.
287 /// In the programmability context, you can access the signature for logging or
288 /// analysis purposes, but signature validation has already been performed.
289 fn get_txn_signature(&self) -> Result<Signature> {
290 get_field(sfield::TxnSignature)
291 }
292}
293
294/// Trait providing access to fields specific to EscrowFinish transactions.
295///
296/// This trait extends `TransactionCommonFields` with methods for retrieving fields that are
297/// unique to EscrowFinish transactions. EscrowFinish transactions are used to complete
298/// time-based or condition-based escrows that were previously created with EscrowCreate
299/// transactions.
300///
301/// ## Implementation Requirements
302///
303/// Types implementing this trait should:
304/// - Also implement `TransactionCommonFields` for access to common transaction fields
305/// - Only be used in the context of processing EscrowFinish transactions
306/// - Ensure proper error handling when accessing conditional fields
307pub trait EscrowFinishFields: TransactionCommonFields {
308 /// Retrieves the owner account from the current EscrowFinish transaction.
309 ///
310 /// This mandatory field identifies the XRPL account that originally created the escrow
311 /// with an EscrowCreate transaction. The owner is the account that deposited the XRP
312 /// into the escrow and specified the conditions for its release.
313 ///
314 /// # Returns
315 ///
316 /// Returns a `Result<AccountID>` where:
317 /// * `Ok(AccountID)` - The 20-byte account identifier of the escrow owner
318 /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
319 fn get_owner(&self) -> Result<AccountID> {
320 get_field(sfield::Owner)
321 }
322
323 /// Retrieves the offer sequence from the current EscrowFinish transaction.
324 ///
325 /// This mandatory field specifies the sequence number of the original EscrowCreate
326 /// transaction that created the escrow being finished. This creates a unique reference
327 /// to the specific escrow object, as escrows are identified by the combination of
328 /// the owner account and the sequence number of the creating transaction.
329 ///
330 /// # Returns
331 ///
332 /// Returns a `Result<u32>` where:
333 /// * `Ok(u32)` - The sequence number of the EscrowCreate transaction
334 /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
335 fn get_offer_sequence(&self) -> Result<u32> {
336 get_field(sfield::OfferSequence)
337 }
338
339 /// Retrieves the cryptographic condition from the current EscrowFinish transaction.
340 ///
341 /// This optional field contains the cryptographic condition specified in the
342 /// original EscrowCreate transaction. If present, a valid `Fulfillment` must be provided
343 /// in the `Fulfillment` field for the escrow to be successfully finished. Conditions
344 /// enable complex release criteria beyond simple time-based locks.
345 ///
346 /// # Returns
347 ///
348 /// Returns a `Result<Option<Condition>>` where:
349 /// * `Ok(Some(Condition))` - The 32-byte condition hash if the escrow is conditional
350 /// * `Ok(None)` - If the escrow has no cryptographic condition (time-based only)
351 /// * `Err(Error)` - If an error occurred during field retrieval
352 fn get_condition(&self) -> Result<Option<Condition>> {
353 let mut buffer = [0u8; 32];
354
355 let result_code =
356 unsafe { get_tx_field(sfield::Condition, buffer.as_mut_ptr(), buffer.len()) };
357
358 match_result_code_with_expected_bytes_optional(result_code, 32, || Some(buffer.into()))
359 }
360
361 /// Retrieves the cryptographic fulfillment from the current EscrowFinish transaction.
362 ///
363 /// This optional field contains the cryptographic fulfillment that satisfies the condition
364 /// specified in the original EscrowCreate transaction. The fulfillment must cryptographically
365 /// prove that the condition's requirements have been met. This field is only required
366 /// when the escrow has an associated condition.
367 ///
368 /// # Returns
369 ///
370 /// Returns a `Result<Option<Fulfillment>>` where:
371 /// * `Ok(Some(Fulfillment))` - The fulfillment data if provided
372 /// * `Ok(None)` - If no fulfillment is provided (valid for unconditional escrows)
373 /// * `Err(Error)` - If an error occurred during field retrieval
374 ///
375 /// # Fulfillment Validation
376 ///
377 /// The XRPL network automatically validates that:
378 /// - The fulfillment satisfies the escrow's condition
379 /// - The fulfillment is properly formatted according to RFC 3814
380 /// - The cryptographic proof is mathematically valid
381 ///
382 /// # Size Limits
383 ///
384 /// Fulfillments are limited to 256 bytes in the current XRPL implementation.
385 /// This limit ensures network performance while supporting the most practical
386 /// cryptographic proof scenarios.
387 fn get_fulfillment(&self) -> Result<Option<Fulfillment>> {
388 // Fulfillment fields are limited in rippled to 256 bytes, so we don't use `get_blob_field`
389 // but instead just use a smaller buffer directly.
390
391 let mut buffer = [0u8; 256]; // <-- 256 is the current rippled cap.
392
393 let result_code = unsafe { get_tx_field(sfield::Fulfillment, buffer.as_mut_ptr(), 256) };
394 match_result_code_optional(result_code, || {
395 Some(Fulfillment {
396 data: buffer,
397 len: result_code as usize,
398 })
399 })
400 }
401
402 // TODO: credential IDS
403 // TODO: Signers
404}