xrpl_mithril/lib.rs
1#![doc(html_logo_url = "https://raw.githubusercontent.com/KyleWMiller/xrpl-mithril/main/assets/mithrilLogo.png")]
2//!
3//! <div align="center">
4//! <img src="https://raw.githubusercontent.com/KyleWMiller/xrpl-mithril/main/assets/mithrilLogo.png" width="200" alt="xrpl-mithril">
5//!
6//! # xrpl-mithril
7//!
8//! **A next-generation, pure Rust SDK for the XRP Ledger.**
9//! </div>
10//!
11//! xrpl-mithril targets the 2026 XRPL protocol surface (rippled v3.1.0+),
12//! covering 50+ transaction types including Multi-Purpose Tokens, Token Escrow
13//! (XLS-85), AMM, Credentials, DynamicNFT, and every mainnet feature through
14//! February 2026. The entire codebase enforces `#![forbid(unsafe_code)]`.
15//!
16//! # Quick Start
17//!
18//! Send 10 XRP on testnet:
19//!
20//! ```no_run
21//! use xrpl_mithril::client::JsonRpcClient;
22//! use xrpl_mithril::tx::builder::PaymentBuilder;
23//! use xrpl_mithril::tx::autofill::autofill;
24//! use xrpl_mithril::tx::{sign_transaction, submit_and_wait};
25//! use xrpl_mithril::types::{Amount, XrpAmount};
26//! use xrpl_mithril::wallet::{Algorithm, Wallet};
27//!
28//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
29//! let sender = Wallet::generate(Algorithm::Ed25519)?;
30//!
31//! let mut unsigned = PaymentBuilder::new()
32//! .account(*sender.account_id())
33//! .destination("rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe".parse()?)
34//! .amount(Amount::Xrp(XrpAmount::from_drops(10_000_000)?))
35//! .build()?;
36//!
37//! let client = JsonRpcClient::new("https://s.altnet.rippletest.net:51234")?;
38//! autofill(&client, &mut unsigned).await?;
39//!
40//! let signed = sign_transaction(&unsigned, &sender)?;
41//! let result = submit_and_wait(&client, &signed).await?;
42//! println!("Validated in ledger {}: {}", result.ledger_index, result.result_code);
43//! # Ok(())
44//! # }
45//! ```
46//!
47//! Or use the one-liner convenience function:
48//!
49//! ```no_run
50//! use xrpl_mithril::client::JsonRpcClient;
51//! use xrpl_mithril::tx::builder::PaymentBuilder;
52//! use xrpl_mithril::tx::submit_transaction;
53//! use xrpl_mithril::types::{Amount, XrpAmount};
54//! use xrpl_mithril::wallet::{Algorithm, Wallet};
55//!
56//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
57//! let client = JsonRpcClient::new("https://s.altnet.rippletest.net:51234")?;
58//! let wallet = Wallet::from_seed_encoded("sEdT7wHTCLzDG7Ue4312Kp4QA389Xmb")?;
59//!
60//! let tx = PaymentBuilder::new()
61//! .account(*wallet.account_id())
62//! .destination("rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe".parse()?)
63//! .amount(Amount::Xrp(XrpAmount::from_drops(1_000_000)?))
64//! .build()?;
65//!
66//! let result = submit_transaction(&client, tx, &wallet).await?;
67//! # Ok(())
68//! # }
69//! ```
70//!
71//! # Installation
72//!
73//! **Default (pure Rust, no C toolchain required):**
74//!
75//! ```toml
76//! [dependencies]
77//! xrpl-mithril = "0.5.3"
78//! ```
79//!
80//! **With native cryptography for maximum secp256k1 performance:**
81//!
82//! ```toml
83//! [dependencies]
84//! xrpl-mithril = { version = "0.5.3", features = ["native-crypto"] }
85//! ```
86//!
87//! # Feature Flags
88//!
89//! | Feature | Default | Description |
90//! |---------|:-------:|-------------|
91//! | `pure-rust-crypto` | Yes | `k256` + `ed25519-dalek` — builds anywhere, no C compiler |
92//! | `native-crypto` | No | `libsecp256k1` via `secp256k1` crate — ~2x faster ECDSA |
93//!
94//! Both backends expose the identical API. Switching is a `Cargo.toml` change,
95//! not a code change.
96//!
97//! # Wallet Operations
98//!
99//! ```
100//! use xrpl_mithril::wallet::{Algorithm, Wallet};
101//! use xrpl_mithril::wallet::address::{encode_x_address, decode_x_address};
102//!
103//! // Generate a random wallet
104//! let wallet = Wallet::generate(Algorithm::Ed25519).unwrap();
105//! println!("Address: {}", wallet.account_id().to_classic_address());
106//!
107//! // Restore from an encoded seed
108//! let wallet = Wallet::from_seed_encoded("snoPBrXtMeMyMHUVTgbuqAfg1SUTb").unwrap();
109//! assert_eq!(
110//! wallet.account_id().to_classic_address(),
111//! "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"
112//! );
113//!
114//! // X-address encoding (returns String directly, no Result)
115//! let x_addr = encode_x_address(wallet.account_id(), Some(12345), false);
116//! let (account, tag, is_test) = decode_x_address(&x_addr).unwrap();
117//! assert_eq!(tag, Some(12345));
118//! ```
119//!
120//! # Transaction Builders
121//!
122//! Fluent builders are provided for common transaction types. Every builder
123//! produces an [`models::transactions::wrapper::UnsignedTransaction`]
124//! ready for autofill and signing.
125//!
126//! ```
127//! use xrpl_mithril::tx::builder::{
128//! PaymentBuilder, TrustSetBuilder, OfferCreateBuilder, EscrowCreateBuilder,
129//! };
130//! use xrpl_mithril::types::*;
131//!
132//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
133//! // XRP payment
134//! let payment = PaymentBuilder::new()
135//! .account("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh".parse()?)
136//! .destination("rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe".parse()?)
137//! .amount(Amount::Xrp(XrpAmount::from_drops(5_000_000)?))
138//! .build()?;
139//!
140//! // Trust line
141//! let issuer: AccountId = "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe".parse()?;
142//! let trust_set = TrustSetBuilder::new()
143//! .account("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh".parse()?)
144//! .limit_amount(IssuedAmount {
145//! value: IssuedValue::from_decimal_string("1000000")?,
146//! currency: CurrencyCode::from_ascii("USD")?,
147//! issuer,
148//! })
149//! .build()?;
150//!
151//! // DEX offer
152//! let offer = OfferCreateBuilder::new()
153//! .account("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh".parse()?)
154//! .taker_pays(Amount::Xrp(XrpAmount::from_drops(50_000_000)?))
155//! .taker_gets(Amount::Issued(IssuedAmount {
156//! value: IssuedValue::from_decimal_string("100")?,
157//! currency: CurrencyCode::from_ascii("USD")?,
158//! issuer,
159//! }))
160//! .build()?;
161//!
162//! // Time-locked escrow
163//! let escrow = EscrowCreateBuilder::new()
164//! .account("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh".parse()?)
165//! .destination(issuer)
166//! .amount(Amount::Xrp(XrpAmount::from_drops(10_000_000)?))
167//! .finish_after(820_000_000)
168//! .cancel_after(830_000_000)
169//! .build()?;
170//! # Ok(())
171//! # }
172//! ```
173//!
174//! # WebSocket Subscriptions
175//!
176//! ```no_run
177//! use futures::StreamExt;
178//! use xrpl_mithril::client::{Client, WebSocketClient};
179//! use xrpl_mithril::models::requests::subscription::SubscribeRequest;
180//!
181//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
182//! let client = WebSocketClient::connect("wss://s.altnet.rippletest.net:51233").await?;
183//! let mut stream = client.subscribe_stream()?;
184//!
185//! client.request(SubscribeRequest {
186//! streams: Some(vec!["ledger".to_string()]),
187//! accounts: None,
188//! accounts_proposed: None,
189//! books: None,
190//! }).await?;
191//!
192//! while let Some(msg) = stream.next().await {
193//! if msg["type"].as_str() == Some("ledgerClosed") {
194//! println!("Ledger #{}: {} txns",
195//! msg["ledger_index"], msg["txn_count"]);
196//! }
197//! }
198//! # Ok(())
199//! # }
200//! ```
201//!
202//! # Binary Codec
203//!
204//! Serialize transactions to the XRPL binary wire format and back:
205//!
206//! ```
207//! use xrpl_mithril::codec::{serializer, deserializer};
208//!
209//! let tx = serde_json::json!({
210//! "TransactionType": "Payment",
211//! "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
212//! "Destination": "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe",
213//! "Amount": "1000000",
214//! "Fee": "12",
215//! "Sequence": 1
216//! });
217//!
218//! let map = tx.as_object().unwrap();
219//! let mut bytes = Vec::new();
220//! serializer::serialize_json_object(map, &mut bytes, false).unwrap();
221//! let decoded = deserializer::deserialize_object(&bytes).unwrap();
222//! assert_eq!(decoded["TransactionType"], "Payment");
223//! ```
224//!
225//! # Multi-Signature Transactions
226//!
227//! ```
228//! use xrpl_mithril::wallet::{Wallet, Algorithm};
229//! use xrpl_mithril::wallet::signer::{multi_sign, combine_signatures};
230//!
231//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
232//! let signer1 = Wallet::generate(Algorithm::Ed25519)?;
233//! let signer2 = Wallet::generate(Algorithm::Secp256k1)?;
234//!
235//! let tx_json: serde_json::Map<String, serde_json::Value> =
236//! serde_json::from_value(serde_json::json!({
237//! "TransactionType": "Payment",
238//! "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
239//! "Destination": "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe",
240//! "Amount": "1000000",
241//! "Fee": "12",
242//! "Sequence": 1
243//! }))?;
244//!
245//! let sig1 = multi_sign(&tx_json, &signer1)?;
246//! let sig2 = multi_sign(&tx_json, &signer2)?;
247//! let combined = combine_signatures(&tx_json, vec![sig1, sig2])?;
248//! assert!(combined.tx_json.contains_key("Signers"));
249//! # Ok(())
250//! # }
251//! ```
252//!
253//! # Signing and Verification
254//!
255//! ```
256//! use xrpl_mithril::wallet::{Wallet, Algorithm};
257//! use xrpl_mithril::wallet::signer::sign;
258//!
259//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
260//! let wallet = Wallet::from_seed_encoded("snoPBrXtMeMyMHUVTgbuqAfg1SUTb")?;
261//!
262//! let tx_json: serde_json::Map<String, serde_json::Value> =
263//! serde_json::from_value(serde_json::json!({
264//! "TransactionType": "Payment",
265//! "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
266//! "Destination": "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe",
267//! "Amount": "1000000",
268//! "Fee": "12",
269//! "Sequence": 1
270//! }))?;
271//!
272//! let signed = sign(&tx_json, &wallet)?;
273//! println!("Hash: {}", signed.hash);
274//! println!("Blob: {}", signed.tx_blob);
275//! assert!(signed.tx_json.contains_key("TxnSignature"));
276//! # Ok(())
277//! # }
278//! ```
279//!
280//! # Working with Types
281//!
282//! Core protocol types enforce validity at construction time:
283//!
284//! ```
285//! use xrpl_mithril::types::*;
286//!
287//! // Amounts
288//! let xrp = XrpAmount::from_drops(1_000_000).unwrap();
289//! assert_eq!(xrp, XrpAmount::ONE_XRP);
290//!
291//! let issued = IssuedValue::from_decimal_string("99.5").unwrap();
292//! assert_eq!(issued.to_decimal_string(), "99.5");
293//!
294//! // Account addresses
295//! let account: AccountId = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh".parse().unwrap();
296//! assert_eq!(account.to_classic_address(), "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh");
297//!
298//! // Currency codes
299//! let usd = CurrencyCode::from_ascii("USD").unwrap();
300//! assert_eq!(&usd.as_ascii().unwrap(), b"USD");
301//!
302//! // Hashes
303//! let hash = Hash256::from_hex(
304//! "4C1A1B1E1F1D1C1B1A191817161514131211100F0E0D0C0B0A09080706050403"
305//! ).unwrap();
306//! assert_eq!(hash.as_bytes().len(), 32);
307//! ```
308//!
309//! # Crate Organization
310//!
311//! | Crate | Re-export | Purpose |
312//! |-------|-----------|---------|
313//! | `xrpl-mithril-types` | [`types`] | Core protocol types (amounts, accounts, hashes, currencies) |
314//! | `xrpl-mithril-codec` | [`codec`] | Binary serialization/deserialization (XRPL wire format) |
315//! | `xrpl-mithril-models` | [`models`] | 50+ transaction types, 17 ledger entry types, request/response types |
316//! | `xrpl-mithril-wallet` | [`wallet`] | Key generation, signing, seed/address encoding |
317//! | `xrpl-mithril-client` | [`client`] | JSON-RPC and WebSocket clients (rustls TLS, no OpenSSL) |
318//! | `xrpl-mithril-tx` | [`tx`] | Transaction builders, autofill, reliable submission |
319//!
320//! Depend on `xrpl-mithril` to get everything, or pick individual crates
321//! for a smaller dependency footprint. All crates enforce
322//! `#![forbid(unsafe_code)]`.
323
324#![forbid(unsafe_code)]
325
326pub use xrpl_mithril_types as types;
327pub use xrpl_mithril_codec as codec;
328pub use xrpl_mithril_models as models;
329pub use xrpl_mithril_wallet as wallet;
330pub use xrpl_mithril_client as client;
331pub use xrpl_mithril_tx as tx;