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