1use std::fmt::Debug;
8
9use dusk_core::signatures::bls::PublicKey as BlsPublicKey;
10use dusk_core::stake::StakeFundOwner;
11use dusk_core::transfer::data::TransactionData;
12use dusk_core::transfer::phoenix::PublicKey as PhoenixPublicKey;
13use dusk_core::transfer::Transaction;
14use rand::rngs::StdRng;
15use rand::SeedableRng;
16use wallet_core::transaction::{
17 moonlight, moonlight_deployment, moonlight_stake, moonlight_stake_reward,
18 moonlight_to_phoenix, moonlight_unstake, phoenix, phoenix_deployment,
19 phoenix_stake, phoenix_stake_reward, phoenix_to_moonlight, phoenix_unstake,
20};
21use zeroize::Zeroize;
22
23use super::file::SecureWalletFile;
24use super::{Address, Wallet};
25use crate::clients::Prover;
26use crate::currency::Dusk;
27use crate::gas::Gas;
28use crate::Error;
29
30impl<F: SecureWalletFile + Debug> Wallet<F> {
31 pub async fn phoenix_transfer(
33 &self,
34 sender_idx: u8,
35 receiver_pk: &PhoenixPublicKey,
36 memo: Option<String>,
37 amt: Dusk,
38 gas: Gas,
39 ) -> Result<Transaction, Error> {
40 if amt == 0 && memo.is_none() {
42 return Err(Error::AmountIsZero);
43 }
44 if !gas.is_enough() {
46 return Err(Error::NotEnoughGas);
47 }
48
49 let state = self.state()?;
50
51 let mut rng = StdRng::from_entropy();
52 let amt = *amt;
53
54 let mut sender_sk = self.derive_phoenix_sk(sender_idx);
55 let refund_pk = self.shielded_key(sender_idx)?;
56
57 let tx_cost = amt + gas.limit * gas.price;
58 let inputs = state
59 .tx_input_notes(sender_idx, tx_cost)
60 .await?
61 .into_iter()
62 .map(|(note, opening, _nullifier)| (note, opening))
63 .collect();
64
65 let root = state.fetch_root().await?;
66 let chain_id = state.fetch_chain_id().await?;
67
68 let tx = phoenix(
69 &mut rng,
70 &sender_sk,
71 refund_pk,
72 receiver_pk,
73 inputs,
74 root,
75 amt,
76 true,
77 0,
78 gas.limit,
79 gas.price,
80 chain_id,
81 memo,
82 &Prover,
83 )?;
84
85 sender_sk.zeroize();
86
87 let tx = state.prove(tx).await?;
88 state.propagate(tx).await
89 }
90
91 pub async fn moonlight_transfer(
93 &self,
94 sender_idx: u8,
95 rcvr: &BlsPublicKey,
96 memo: Option<String>,
97 amt: Dusk,
98 gas: Gas,
99 ) -> Result<Transaction, Error> {
100 if amt == 0 && memo.is_none() {
102 return Err(Error::AmountIsZero);
103 }
104 if !gas.is_enough() {
106 return Err(Error::NotEnoughGas);
107 }
108
109 let mut sender_sk = self.derive_bls_sk(sender_idx);
110 let sender_pk = self.public_key(sender_idx)?;
111 let amt = *amt;
112
113 let state = self.state()?;
114 let nonce = state.fetch_account(sender_pk).await?.nonce + 1;
115 let chain_id = state.fetch_chain_id().await?;
116
117 let tx = moonlight(
118 &sender_sk,
119 Some(*rcvr),
120 amt,
121 0,
122 gas.limit,
123 gas.price,
124 nonce,
125 chain_id,
126 memo,
127 )?;
128
129 sender_sk.zeroize();
130
131 state.propagate(tx).await
132 }
133
134 pub async fn phoenix_execute(
136 &self,
137 sender_idx: u8,
138 deposit: Dusk,
139 gas: Gas,
140 data: TransactionData,
141 ) -> Result<Transaction, Error> {
142 if !gas.is_enough() {
144 return Err(Error::NotEnoughGas);
145 }
146
147 let state = self.state()?;
148 let deposit = *deposit;
149
150 let mut rng = StdRng::from_entropy();
151 let mut sender_sk = self.derive_phoenix_sk(sender_idx);
152 let receiver_pk = self.shielded_key(sender_idx)?;
155
156 let tx_cost = deposit + gas.limit * gas.price;
157 let inputs = state
158 .tx_input_notes(sender_idx, tx_cost)
159 .await?
160 .into_iter()
161 .map(|(a, b, _)| (a, b))
162 .collect();
163
164 let root = state.fetch_root().await?;
165 let chain_id = state.fetch_chain_id().await?;
166
167 let tx = phoenix(
168 &mut rng,
169 &sender_sk,
170 self.shielded_key(sender_idx)?,
171 receiver_pk,
172 inputs,
173 root,
174 0,
175 true,
176 deposit,
177 gas.limit,
178 gas.price,
179 chain_id,
180 Some(data),
181 &Prover,
182 )?;
183
184 sender_sk.zeroize();
185
186 let tx = state.prove(tx).await?;
187 state.propagate(tx).await
188 }
189
190 #[allow(clippy::too_many_arguments)]
192 pub async fn moonlight_execute(
193 &self,
194 sender_idx: u8,
195 transfer_value: Dusk,
196 deposit: Dusk,
197 gas: Gas,
198 exec: Option<impl Into<TransactionData>>,
199 ) -> Result<Transaction, Error> {
200 if !gas.is_enough() {
202 return Err(Error::NotEnoughGas);
203 }
204
205 let state = self.state()?;
206 let deposit = *deposit;
207
208 let mut sender_sk = self.derive_bls_sk(sender_idx);
209 let sender = self.public_key(sender_idx)?;
210
211 let account = state.fetch_account(sender).await?;
212
213 let nonce = account.nonce + 1;
216
217 let chain_id = state.fetch_chain_id().await?;
218
219 let tx = moonlight(
220 &sender_sk,
221 None,
222 *transfer_value,
223 deposit,
224 gas.limit,
225 gas.price,
226 nonce,
227 chain_id,
228 exec,
229 )?;
230
231 sender_sk.zeroize();
232
233 state.propagate(tx).await
234 }
235
236 pub async fn phoenix_stake(
238 &self,
239 profile_idx: u8,
240 owner_idx: Option<u8>,
241 amt: Dusk,
242 gas: Gas,
243 ) -> Result<Transaction, Error> {
244 if amt == 0 {
246 return Err(Error::AmountIsZero);
247 }
248 if !gas.is_enough() {
250 return Err(Error::NotEnoughGas);
251 }
252
253 let state = self.state()?;
254
255 let mut rng = StdRng::from_entropy();
256 let amt = *amt;
257 let mut sender_sk = self.derive_phoenix_sk(profile_idx);
258 let mut stake_sk = self.derive_bls_sk(profile_idx);
259
260 let stake_pk = self.public_key(profile_idx)?;
261 let stake_owner_idx = match self.find_stake_owner_idx(stake_pk).await {
262 Ok(state_idx) => {
263 if let Some(owner_idx) = owner_idx {
264 if state_idx != owner_idx {
265 return Err(Error::Unauthorized);
266 }
267 }
268 state_idx
269 }
270 Err(Error::NotStaked) => owner_idx.unwrap_or(profile_idx),
271 Err(e) => {
272 return Err(e);
273 }
274 };
275 let mut stake_owner_sk = self.derive_bls_sk(stake_owner_idx);
276
277 let tx_cost = amt + gas.limit * gas.price;
278 let inputs = state
279 .tx_input_notes(profile_idx, tx_cost)
280 .await?
281 .into_iter()
282 .map(|(a, b, _)| (a, b))
283 .collect();
284
285 let root = state.fetch_root().await?;
286 let chain_id = state.fetch_chain_id().await?;
287
288 let stake = phoenix_stake(
289 &mut rng,
290 &sender_sk,
291 &stake_sk,
292 &stake_owner_sk,
293 inputs,
294 root,
295 gas.limit,
296 gas.price,
297 chain_id,
298 amt,
299 &Prover,
300 )?;
301
302 sender_sk.zeroize();
303 stake_sk.zeroize();
304 stake_owner_sk.zeroize();
305
306 let stake = state.prove(stake).await?;
307 state.propagate(stake).await
308 }
309
310 pub async fn moonlight_stake(
312 &self,
313 profile_idx: u8,
314 owner_idx: Option<u8>,
315 amt: Dusk,
316 gas: Gas,
317 ) -> Result<Transaction, Error> {
318 if amt == 0 {
320 return Err(Error::AmountIsZero);
321 }
322 if !gas.is_enough() {
324 return Err(Error::NotEnoughGas);
325 }
326
327 let state = self.state()?;
328 let amt = *amt;
329 let mut stake_sk = self.derive_bls_sk(profile_idx);
330 let stake_pk = self.public_key(profile_idx)?;
331 let chain_id = state.fetch_chain_id().await?;
332 let moonlight_current_nonce =
333 state.fetch_account(stake_pk).await?.nonce + 1;
334
335 let stake_owner_idx = match self.find_stake_owner_idx(stake_pk).await {
336 Ok(state_idx) => {
337 if let Some(owner_idx) = owner_idx {
338 if state_idx != owner_idx {
339 return Err(Error::Unauthorized);
340 }
341 }
342 state_idx
343 }
344 Err(Error::NotStaked) => owner_idx.unwrap_or(profile_idx),
345 Err(e) => {
346 return Err(e);
347 }
348 };
349 let mut stake_owner_sk = self.derive_bls_sk(stake_owner_idx);
350
351 let stake = moonlight_stake(
352 &stake_sk,
353 &stake_sk,
354 &stake_owner_sk,
355 amt,
356 gas.limit,
357 gas.price,
358 moonlight_current_nonce,
359 chain_id,
360 )?;
361
362 stake_sk.zeroize();
363 stake_owner_sk.zeroize();
364
365 state.propagate(stake).await
366 }
367
368 pub async fn phoenix_unstake(
370 &self,
371 profile_idx: u8,
372 gas: Gas,
373 ) -> Result<Transaction, Error> {
374 let mut rng = StdRng::from_entropy();
375
376 let state = self.state()?;
377
378 let mut sender_sk = self.derive_phoenix_sk(profile_idx);
379 let mut stake_sk = self.derive_bls_sk(profile_idx);
380 let stake_pk = BlsPublicKey::from(&stake_sk);
381
382 let stake_owner_idx = self.find_stake_owner_idx(&stake_pk).await?;
383 let mut stake_owner_sk = self.derive_bls_sk(stake_owner_idx);
384
385 let unstake_value = state
386 .fetch_stake(&stake_pk)
387 .await?
388 .and_then(|s| s.amount)
389 .map(|s| s.total_funds())
390 .unwrap_or_default();
391
392 if unstake_value == 0 {
393 return Err(Error::NotStaked);
394 }
395
396 let tx_cost = gas.limit * gas.price;
397 let inputs = state.tx_input_notes(profile_idx, tx_cost).await?;
398
399 let root = state.fetch_root().await?;
400 let chain_id = state.fetch_chain_id().await?;
401
402 let unstake = phoenix_unstake(
403 &mut rng,
404 &sender_sk,
405 &stake_sk,
406 &stake_owner_sk,
407 inputs,
408 root,
409 unstake_value,
410 gas.limit,
411 gas.price,
412 chain_id,
413 &Prover,
414 )?;
415
416 sender_sk.zeroize();
417 stake_sk.zeroize();
418 stake_owner_sk.zeroize();
419
420 let unstake = state.prove(unstake).await?;
421 state.propagate(unstake).await
422 }
423
424 pub async fn moonlight_unstake(
426 &self,
427 profile_idx: u8,
428 gas: Gas,
429 ) -> Result<Transaction, Error> {
430 let mut rng = StdRng::from_entropy();
431 let state = self.state()?;
432 let mut stake_sk = self.derive_bls_sk(profile_idx);
433
434 let stake_pk = self.public_key(profile_idx)?;
435
436 let chain_id = state.fetch_chain_id().await?;
437 let account_nonce = state.fetch_account(stake_pk).await?.nonce + 1;
438
439 let unstake_value = state
440 .fetch_stake(stake_pk)
441 .await?
442 .and_then(|s| s.amount)
443 .map(|s| s.total_funds())
444 .unwrap_or_default();
445
446 if unstake_value == 0 {
447 return Err(Error::NotStaked);
448 }
449
450 let stake_owner_idx = self.find_stake_owner_idx(stake_pk).await?;
451 let mut stake_owner_sk = self.derive_bls_sk(stake_owner_idx);
452
453 let unstake = moonlight_unstake(
454 &mut rng,
455 &stake_sk,
456 &stake_sk,
457 &stake_owner_sk,
458 unstake_value,
459 gas.limit,
460 gas.price,
461 account_nonce,
462 chain_id,
463 )?;
464
465 stake_sk.zeroize();
466 stake_owner_sk.zeroize();
467
468 state.propagate(unstake).await
469 }
470
471 pub async fn phoenix_stake_withdraw(
473 &self,
474 sender_idx: u8,
475 gas: Gas,
476 ) -> Result<Transaction, Error> {
477 let state = self.state()?;
478 let mut rng = StdRng::from_entropy();
479
480 let mut sender_sk = self.derive_phoenix_sk(sender_idx);
481 let mut stake_sk = self.derive_bls_sk(sender_idx);
482
483 let tx_cost = gas.limit * gas.price;
484 let inputs = state.tx_input_notes(sender_idx, tx_cost).await?;
485
486 let root = state.fetch_root().await?;
487 let chain_id = state.fetch_chain_id().await?;
488
489 let stake_pk = BlsPublicKey::from(&stake_sk);
490
491 let reward_amount = state
492 .fetch_stake(&stake_pk)
493 .await?
494 .map(|s| s.reward)
495 .unwrap_or(0);
496
497 let stake_owner_idx = self.find_stake_owner_idx(&stake_pk).await?;
498 let mut stake_owner_sk = self.derive_bls_sk(stake_owner_idx);
499
500 let withdraw = phoenix_stake_reward(
501 &mut rng,
502 &sender_sk,
503 &stake_sk,
504 &stake_owner_sk,
505 inputs,
506 root,
507 reward_amount,
508 gas.limit,
509 gas.price,
510 chain_id,
511 &Prover,
512 )?;
513
514 sender_sk.zeroize();
515 stake_sk.zeroize();
516 stake_owner_sk.zeroize();
517
518 let withdraw = state.prove(withdraw).await?;
519 state.propagate(withdraw).await
520 }
521
522 pub async fn moonlight_stake_withdraw(
524 &self,
525 sender_idx: u8,
526 gas: Gas,
527 ) -> Result<Transaction, Error> {
528 let mut rng = StdRng::from_entropy();
529 let state = self.state()?;
530
531 let pk = self.public_key(sender_idx)?;
532 let nonce = state.fetch_account(pk).await?.nonce + 1;
533 let chain_id = state.fetch_chain_id().await?;
534 let stake_info = state.fetch_stake(pk).await?;
535 let reward = stake_info.map(|s| s.reward).ok_or(Error::NoReward)?;
536 let reward = Dusk::from(reward);
537
538 let mut sender_sk = self.derive_bls_sk(sender_idx);
539
540 let stake_pk = self.public_key(sender_idx)?;
541 let stake_owner_idx = self.find_stake_owner_idx(stake_pk).await?;
542 let mut stake_owner_sk = self.derive_bls_sk(stake_owner_idx);
543
544 let withdraw = moonlight_stake_reward(
545 &mut rng,
546 &sender_sk,
547 &sender_sk,
548 &stake_owner_sk,
549 *reward,
550 gas.limit,
551 gas.price,
552 nonce,
553 chain_id,
554 )?;
555
556 sender_sk.zeroize();
557 stake_owner_sk.zeroize();
558
559 state.propagate(withdraw).await
560 }
561
562 pub async fn phoenix_to_moonlight(
564 &self,
565 profile_idx: u8,
566 amt: Dusk,
567 gas: Gas,
568 ) -> Result<Transaction, Error> {
569 let mut rng = StdRng::from_entropy();
570 let state = self.state()?;
571 let tx_cost = *amt + gas.limit * gas.price;
572 let inputs = state.tx_input_notes(profile_idx, tx_cost).await?;
573
574 let root = state.fetch_root().await?;
575 let chain_id = state.fetch_chain_id().await?;
576
577 let mut phoenix_sk = self.derive_phoenix_sk(profile_idx);
578 let mut moonlight_sk = self.derive_bls_sk(profile_idx);
579
580 let convert = phoenix_to_moonlight(
581 &mut rng,
582 &phoenix_sk,
583 &moonlight_sk,
584 inputs,
585 root,
586 *amt,
587 gas.limit,
588 gas.price,
589 chain_id,
590 &Prover,
591 )?;
592
593 phoenix_sk.zeroize();
594 moonlight_sk.zeroize();
595
596 let convert = state.prove(convert).await?;
597 state.propagate(convert).await
598 }
599
600 pub async fn moonlight_to_phoenix(
602 &self,
603 profile_idx: u8,
604 amt: Dusk,
605 gas: Gas,
606 ) -> Result<Transaction, Error> {
607 let mut rng = StdRng::from_entropy();
608 let state = self.state()?;
609
610 let moonlight_pk = self.public_key(profile_idx)?;
611
612 let nonce = state.fetch_account(moonlight_pk).await?.nonce + 1;
613 let chain_id = state.fetch_chain_id().await?;
614
615 let mut phoenix_sk = self.derive_phoenix_sk(profile_idx);
616 let mut moonlight_sk = self.derive_bls_sk(profile_idx);
617
618 let convert = moonlight_to_phoenix(
619 &mut rng,
620 &moonlight_sk,
621 &phoenix_sk,
622 *amt,
623 gas.limit,
624 gas.price,
625 nonce,
626 chain_id,
627 )?;
628
629 phoenix_sk.zeroize();
630 moonlight_sk.zeroize();
631
632 state.propagate(convert).await
633 }
634
635 pub async fn phoenix_deploy(
637 &self,
638 sender_idx: u8,
639 bytes_code: Vec<u8>,
640 init_args: Vec<u8>,
641 deploy_nonce: u64,
642 gas: Gas,
643 ) -> Result<Transaction, Error> {
644 let mut rng = StdRng::from_entropy();
645 let state = self.state()?;
646
647 let chain_id = state.fetch_chain_id().await?;
648 let root = state.fetch_root().await?;
649
650 let tx_cost = gas.limit * gas.price;
651 let inputs = state.tx_input_notes(sender_idx, tx_cost).await?;
652
653 let mut sender_sk = self.derive_phoenix_sk(sender_idx);
654 let owner_pk = self.public_key(sender_idx)?;
655
656 let deploy = phoenix_deployment(
657 &mut rng,
658 &sender_sk,
659 inputs,
660 root,
661 bytes_code,
662 owner_pk,
663 init_args,
664 deploy_nonce,
665 gas.limit,
666 gas.price,
667 chain_id,
668 &Prover,
669 )?;
670
671 sender_sk.zeroize();
672
673 let deploy = state.prove(deploy).await?;
674 state.propagate(deploy).await
675 }
676
677 pub async fn moonlight_deploy(
679 &self,
680 sender_idx: u8,
681 bytes_code: Vec<u8>,
682 init_args: Vec<u8>,
683 deploy_nonce: u64,
684 gas: Gas,
685 ) -> Result<Transaction, Error> {
686 let state = self.state()?;
687
688 let pk = self.public_key(sender_idx)?;
689 let moonlight_nonce = state.fetch_account(pk).await?.nonce + 1;
690 let chain_id = state.fetch_chain_id().await?;
691
692 let mut sender_sk = self.derive_bls_sk(sender_idx);
693
694 let deploy = moonlight_deployment(
695 &sender_sk,
696 bytes_code,
697 pk,
698 init_args,
699 gas.limit,
700 gas.price,
701 moonlight_nonce,
702 deploy_nonce,
703 chain_id,
704 )?;
705
706 sender_sk.zeroize();
707
708 state.propagate(deploy).await
709 }
710
711 pub async fn find_stake_owner_idx(
713 &self,
714 stake_pk: &BlsPublicKey,
715 ) -> Result<u8, Error> {
716 self.find_index(&self.find_stake_owner_account(stake_pk).await?)
717 }
718
719 pub async fn find_stake_owner_account(
721 &self,
722 stake_pk: &BlsPublicKey,
723 ) -> Result<Address, Error> {
724 let stake_owner = self
725 .state()?
726 .fetch_stake_owner(stake_pk)
727 .await?
728 .ok_or(Error::NotStaked)?;
729
730 match stake_owner {
731 StakeFundOwner::Account(public_key) => {
732 Ok(Address::Public(public_key))
733 }
734 StakeFundOwner::Contract(_) => Err(Error::Unauthorized),
735 }
736 }
737}