1pub mod derivation_indices;
2pub mod mnemonic;
3pub mod root_key;
4
5use bip39::{Language, Mnemonic};
6use derivation_indices::DerivationIndices;
7pub use mnemonic::MnemonicWallet;
8pub use root_key::RootKeyWallet;
9use whisky_common::{Fetcher, Submitter, UTxO, WError};
10use whisky_csl::{
11 csl::{
12 BaseAddress, Bip32PrivateKey, Credential, EnterpriseAddress, FixedTransaction, PrivateKey,
13 PublicKey,
14 },
15 sign_transaction,
16};
17
18#[derive(Copy, Clone)]
19pub enum NetworkId {
20 Preprod = 0, Mainnet = 1,
22}
23
24pub enum AddressType {
25 Enterprise,
26 Payment,
27}
28
29pub enum WalletType {
30 MnemonicWallet(MnemonicWallet),
31 RootKeyWallet(RootKeyWallet),
32 Cli(String),
33}
34
35pub struct Wallet {
41 pub wallet_type: WalletType,
42 pub network_id: NetworkId,
43 pub addresses: Addresses,
44 pub fetcher: Option<Box<dyn Fetcher>>,
45 pub submitter: Option<Box<dyn Submitter>>,
46 pub account: Option<Account>,
47}
48pub struct Addresses {
49 pub base_address: Option<BaseAddress>,
50 pub enterprise_address: Option<EnterpriseAddress>,
51}
52
53pub struct Account {
54 pub private_key: PrivateKey,
55 pub public_key: PublicKey,
56}
57
58impl Account {
59 pub fn sign_transaction(&self, tx_hex: &str) -> Result<String, WError> {
69 let mut tx = FixedTransaction::from_hex(tx_hex)
70 .map_err(WError::from_err("Account - failed to deserialize tx hex"))?;
71 tx.sign_and_add_vkey_signature(&self.private_key)
72 .map_err(WError::from_err("Account - failed to sign transaction"))?;
73 Ok(tx.to_hex())
74 }
75}
76
77impl Wallet {
78 fn empty() -> Self {
80 Self {
81 wallet_type: WalletType::Cli("".to_string()),
82 network_id: NetworkId::Preprod,
83 addresses: Addresses {
84 base_address: None,
85 enterprise_address: None,
86 },
87 fetcher: None,
88 submitter: None,
89 account: None,
90 }
91 }
92
93 pub fn new(wallet_type: WalletType) -> Result<Self, WError> {
105 let mut wallet = Self::default();
106 wallet.wallet_type = wallet_type;
107 wallet.account = Some(
108 Self::get_account(&wallet.wallet_type)
109 .map_err(WError::from_err("Wallet - new - failed to get account"))?,
110 );
111 wallet.init_addresses();
112 Ok(wallet)
113 }
114
115 pub fn new_cli(cli_skey: &str) -> Result<Self, WError> {
125 let mut wallet = Self::default();
126 wallet.wallet_type = WalletType::Cli(cli_skey.to_string());
127 wallet.account = Some(
128 Self::get_account(&wallet.wallet_type)
129 .map_err(WError::from_err("Wallet - new_cli - failed to get account"))?,
130 );
131 wallet.init_addresses();
132 Ok(wallet)
133 }
134
135 pub fn new_mnemonic(mnemonic_phrase: &str) -> Result<Self, WError> {
145 let wallet_type = WalletType::MnemonicWallet(MnemonicWallet {
146 mnemonic_phrase: mnemonic_phrase.to_string(),
147 derivation_indices: DerivationIndices::default(),
148 });
149 let mut wallet = Self::empty();
150 wallet.wallet_type = wallet_type;
151 wallet.account = Some(
152 Self::get_account(&wallet.wallet_type)
153 .map_err(WError::from_err("Wallet - new_mnemonic"))?,
154 );
155 wallet.init_addresses();
156 Ok(wallet)
157 }
158
159 pub fn new_root_key(root_key: &str) -> Result<Self, WError> {
169 let mut wallet = Self::default();
170 let wallet_type = WalletType::RootKeyWallet(RootKeyWallet {
171 root_key: root_key.to_string(),
172 derivation_indices: DerivationIndices::default(),
173 });
174 wallet.wallet_type = wallet_type;
175 wallet.account = Some(
176 Self::get_account(&wallet.wallet_type).map_err(WError::from_err(
177 "Wallet - new_root_key - failed to get account",
178 ))?,
179 );
180 wallet.init_addresses();
181 Ok(wallet)
182 }
183
184 pub fn with_network_id(mut self, network_id: NetworkId) -> Self {
194 self.network_id = network_id;
195 self.init_addresses();
196 self
197 }
198
199 pub fn with_fetcher<F: Fetcher + 'static>(mut self, fetcher: F) -> Self {
211 self.fetcher = Some(Box::new(fetcher));
212 self
213 }
214
215 pub fn with_submitter<S: Submitter + 'static>(mut self, submitter: S) -> Self {
227 self.submitter = Some(Box::new(submitter));
228 self
229 }
230
231 pub fn payment_account(
244 &mut self,
245 account_index: u32,
246 key_index: u32,
247 ) -> Result<&mut Self, WError> {
248 match &mut self.wallet_type {
249 WalletType::MnemonicWallet(mnemonic_wallet) => {
250 mnemonic_wallet.payment_account(account_index, key_index);
251 }
252 WalletType::RootKeyWallet(root_key_wallet) => {
253 root_key_wallet.payment_account(account_index, key_index);
254 }
255 _ => {}
256 }
257 self.account = Some(
258 Self::get_account(&self.wallet_type).map_err(WError::from_err(
259 "Wallet - payment_account - failed to get account",
260 ))?,
261 );
262 self.init_addresses();
263 Ok(self)
264 }
265
266 pub fn stake_account(
279 &mut self,
280 account_index: u32,
281 key_index: u32,
282 ) -> Result<&mut Self, WError> {
283 match &mut self.wallet_type {
284 WalletType::MnemonicWallet(mnemonic_wallet) => {
285 mnemonic_wallet.stake_account(account_index, key_index);
286 }
287 WalletType::RootKeyWallet(root_key_wallet) => {
288 root_key_wallet.stake_account(account_index, key_index);
289 }
290 _ => {}
291 }
292 self.account = Some(
293 Self::get_account(&self.wallet_type).map_err(WError::from_err(
294 "Wallet - stake_account - failed to get account",
295 ))?,
296 );
297 self.init_addresses();
298 Ok(self)
299 }
300
301 pub fn drep_account(
314 &mut self,
315 account_index: u32,
316 key_index: u32,
317 ) -> Result<&mut Self, WError> {
318 match &mut self.wallet_type {
319 WalletType::MnemonicWallet(mnemonic_wallet) => {
320 mnemonic_wallet.drep_account(account_index, key_index);
321 }
322 WalletType::RootKeyWallet(root_key_wallet) => {
323 root_key_wallet.drep_account(account_index, key_index);
324 }
325 _ => {}
326 }
327 self.account = Some(
328 Self::get_account(&self.wallet_type).map_err(WError::from_err(
329 "Wallet - drep_account - failed to get account",
330 ))?,
331 );
332 self.init_addresses();
333 Ok(self)
334 }
335
336 pub fn init_addresses(&mut self) -> &mut Self {
351 self.addresses = match &self.wallet_type {
352 WalletType::MnemonicWallet(mnemonic_wallet) => {
353 let mnemonic =
354 Mnemonic::from_phrase(&mnemonic_wallet.mnemonic_phrase, Language::English)
355 .map_err(WError::from_err(
356 "Wallet - init_addresses - failed to create mnemonic",
357 ))
358 .unwrap();
359 let entropy = mnemonic.entropy();
360 let mut root_key = Bip32PrivateKey::from_bip39_entropy(entropy, &[]);
361 for index in mnemonic_wallet.derivation_indices.0.iter().take(3) {
362 root_key = root_key.derive(index.clone());
363 }
364
365 let payment_credential = Credential::from_keyhash(
366 &root_key
367 .derive(mnemonic_wallet.derivation_indices.0[3].clone())
368 .derive(mnemonic_wallet.derivation_indices.0[4].clone())
369 .to_public()
370 .to_raw_key()
371 .hash(),
372 );
373
374 let stake_credential = Credential::from_keyhash(
375 &root_key.derive(2).derive(0).to_public().to_raw_key().hash(),
376 );
377
378 self.create_addresses(payment_credential, stake_credential)
379 }
380 WalletType::RootKeyWallet(root_key_wallet) => {
381 let mut root_key = Bip32PrivateKey::from_bech32(&root_key_wallet.root_key)
382 .map_err(WError::from_err(
383 "Wallet - init_addresses - invalid root key hex",
384 ))
385 .unwrap();
386 for index in root_key_wallet.derivation_indices.0.iter().take(3) {
387 root_key = root_key.derive(index.clone());
388 }
389
390 let payment_credential = Credential::from_keyhash(
391 &root_key
392 .derive(root_key_wallet.derivation_indices.0[3].clone())
393 .derive(root_key_wallet.derivation_indices.0[4].clone())
394 .to_public()
395 .to_raw_key()
396 .hash(),
397 );
398
399 let stake_credential = Credential::from_keyhash(
400 &root_key.derive(2).derive(0).to_public().to_raw_key().hash(),
401 );
402
403 self.create_addresses(payment_credential, stake_credential)
404 }
405 WalletType::Cli(_private_key) => Addresses {
406 base_address: None,
407 enterprise_address: None,
408 },
409 };
410 self
411 }
412
413 fn create_addresses(
416 &self,
417 payment_credential: Credential,
418 stake_credential: Credential,
419 ) -> Addresses {
420 Addresses {
421 base_address: Some(BaseAddress::new(
422 self.network_id as u8,
423 &payment_credential,
424 &stake_credential,
425 )),
426 enterprise_address: Some(EnterpriseAddress::new(
427 self.network_id as u8,
428 &payment_credential,
429 )),
430 }
431 }
432
433 pub fn sign_tx(&self, tx_hex: &str) -> Result<String, WError> {
434 match &self.wallet_type {
435 WalletType::Cli(cli_skey) => {
436 let signed_tx = sign_transaction(tx_hex, &[cli_skey])
437 .map_err(WError::from_err("Wallet - sign_tx"))?;
438 Ok(signed_tx)
439 }
440 _ => {
441 let account = self.account.as_ref().ok_or_else(WError::from_opt(
442 "Wallet - sign_tx",
443 "get account from wallet",
444 ))?;
445 let signed_tx = account
446 .sign_transaction(tx_hex)
447 .map_err(WError::from_err("Wallet - sign_tx"))?;
448 Ok(signed_tx.to_string())
449 }
450 }
451 }
452
453 pub fn get_account(wallet_type: &WalletType) -> Result<Account, WError> {
454 let private_key: PrivateKey = match wallet_type {
455 WalletType::MnemonicWallet(mnemonic_wallet) => {
456 let mnemonic =
457 Mnemonic::from_phrase(&mnemonic_wallet.mnemonic_phrase, Language::English)
458 .map_err(WError::from_err(
459 "Wallet - get_account - failed to create mnemonic",
460 ))?;
461 let entropy = mnemonic.entropy();
462 let mut root_key = Bip32PrivateKey::from_bip39_entropy(entropy, &[]);
463 for index in &mnemonic_wallet.derivation_indices.0 {
464 root_key = root_key.derive(index.clone());
465 }
466 root_key.to_raw_key()
467 }
468 WalletType::RootKeyWallet(root_key_wallet) => {
469 let mut root_key = Bip32PrivateKey::from_bech32(&root_key_wallet.root_key)
470 .map_err(WError::from_err(
471 "Wallet - get_account - invalid root key hex",
472 ))?;
473 for index in &root_key_wallet.derivation_indices.0 {
474 root_key = root_key.derive(index.clone());
475 }
476 root_key.to_raw_key()
477 }
478 WalletType::Cli(private_key) => PrivateKey::from_hex(&private_key).map_err(
479 WError::from_err("Wallet - get_account - invalid private key hex"),
480 )?,
481 };
482 let public_key = private_key.to_public();
483 Ok(Account {
484 private_key,
485 public_key,
486 })
487 }
488
489 pub fn get_change_address(&self, address_type: AddressType) -> Result<String, WError> {
499 match address_type {
500 AddressType::Payment => {
501 if let Some(base_address) = &self.addresses.base_address {
502 let address = base_address.to_address();
503 address.to_bech32(None).map_err(WError::from_err(
504 "Failed to convert payment address to bech32",
505 ))
506 } else {
507 Err(WError::from_err(
508 "Base address not available for this wallet type",
509 )("Base address not initialized"))
510 }
511 }
512 AddressType::Enterprise => {
513 if let Some(enterprise_address) = &self.addresses.enterprise_address {
514 let address = enterprise_address.to_address();
515 address.to_bech32(None).map_err(WError::from_err(
516 "Failed to convert enterprise address to bech32",
517 ))
518 } else {
519 Err(WError::from_err(
520 "Enterprise address not available for this wallet type",
521 )("Enterprise address not initialized"))
522 }
523 }
524 }
525 }
526
527 pub async fn get_utxos(
542 &self,
543 address_type: Option<AddressType>,
544 asset: Option<&str>,
545 ) -> Result<Vec<UTxO>, WError> {
546 let fetcher = self.fetcher.as_ref().ok_or_else(|| {
547 WError::from_err("Fetcher is required to fetch UTxOs. Please provide a fetcher.")(
548 "No fetcher provided",
549 )
550 })?;
551
552 let address_type = address_type.unwrap_or(AddressType::Payment);
553 let address = self.get_change_address(address_type)?;
554
555 fetcher
556 .fetch_address_utxos(&address, asset)
557 .await
558 .map_err(WError::from_err("Failed to fetch UTxOs"))
559 }
560
561 pub async fn get_collateral(
578 &self,
579 address_type: Option<AddressType>,
580 ) -> Result<Vec<UTxO>, WError> {
581 let address_type = address_type.unwrap_or(AddressType::Payment);
582 let utxos = self.get_utxos(Some(address_type), None).await?;
583
584 let mut collateral_candidates: Vec<UTxO> = utxos
585 .into_iter()
586 .filter(|utxo| {
587 utxo.output.amount.len() == 1
588 && utxo.output.amount[0].unit() == "lovelace"
589 && utxo.output.amount[0].quantity_i128() >= 5_000_000
590 })
591 .collect();
592
593 collateral_candidates.sort_by(|a, b| {
594 let a_quantity = a.output.amount[0].quantity_i128();
595 let b_quantity = b.output.amount[0].quantity_i128();
596 a_quantity.cmp(&b_quantity)
597 });
598
599 if let Some(smallest_utxo) = collateral_candidates.first() {
600 Ok(vec![smallest_utxo.clone()])
601 } else {
602 Ok(vec![])
603 }
604 }
605
606 pub async fn submit_tx(&self, tx_hex: &str) -> Result<String, WError> {
627 let submitter = self.submitter.as_ref().ok_or_else(|| {
628 WError::from_err(
629 "Submitter is required to submit transactions. Please provide a submitter.",
630 )("No submitter provided")
631 })?;
632
633 submitter.submit_tx(tx_hex).await
634 }
635}
636
637impl Default for Wallet {
638 fn default() -> Self {
639 Self::empty()
640 }
641}