1use alloc::string::String;
11use alloc::vec::Vec;
12use core::cmp::max;
13use core::fmt::Debug;
14
15use bytecheck::CheckBytes;
16use dusk_bytes::{DeserializableSlice, Error as BytesError};
17use poseidon_merkle::Opening;
18use rand::{CryptoRng, RngCore};
19use rkyv::{Archive, Deserialize, Serialize};
20#[cfg(feature = "serde")]
21use serde_with::hex::Hex;
22#[cfg(feature = "serde")]
23use serde_with::{As, DisplayFromStr, Same};
24
25use self::data::{
26 BlobData, BlobSidecar, ContractCall, ContractDeploy, TransactionData,
27};
28use self::moonlight::Transaction as MoonlightTransaction;
29use self::phoenix::{
30 NOTES_TREE_DEPTH, Note, Prove, PublicKey as PhoenixPublicKey,
31 SecretKey as PhoenixSecretKey, Sender, StealthAddress,
32 Transaction as PhoenixTransaction,
33};
34use self::withdraw::{Withdraw, WithdrawReceiver};
35use crate::abi::ContractId;
36use crate::error::TxPreconditionError;
37use crate::signatures::bls::{
38 PublicKey as AccountPublicKey, SecretKey as AccountSecretKey,
39};
40use crate::{BlsScalar, Error};
41
42pub mod data;
43pub mod moonlight;
44pub mod phoenix;
45pub mod withdraw;
46
47pub const TRANSFER_CONTRACT: ContractId = crate::reserved(0x1);
49
50pub const PANIC_NONCE_NOT_READY: &str = "Nonce not ready to be used yet";
52
53pub const MOONLIGHT_TOPIC: &str = "moonlight";
55pub const PHOENIX_TOPIC: &str = "phoenix";
57pub const CONTRACT_TO_CONTRACT_TOPIC: &str = "contract_to_contract";
59pub const CONTRACT_TO_ACCOUNT_TOPIC: &str = "contract_to_account";
61pub const WITHDRAW_TOPIC: &str = "withdraw";
63pub const DEPOSIT_TOPIC: &str = "deposit";
65pub const CONVERT_TOPIC: &str = "convert";
67pub const MINT_TOPIC: &str = "mint";
69pub const MINT_CONTRACT_TOPIC: &str = "mint_c";
71
72const BOREAS_FORMAT_VERSION: u8 = 2;
73
74#[derive(Debug, Clone, Copy, Eq, PartialEq)]
75#[repr(u8)]
76enum TransactionTag {
77 LegacyPhoenix = 0,
78 LegacyMoonlight = 1,
79 VersionedPhoenix = 2,
80 VersionedMoonlight = 3,
81}
82
83impl TryFrom<u8> for TransactionTag {
84 type Error = BytesError;
85
86 fn try_from(value: u8) -> Result<Self, Self::Error> {
87 match value {
88 0 => Ok(Self::LegacyPhoenix),
89 1 => Ok(Self::LegacyMoonlight),
90 2 => Ok(Self::VersionedPhoenix),
91 3 => Ok(Self::VersionedMoonlight),
92 _ => Err(BytesError::InvalidData),
93 }
94 }
95}
96
97impl TransactionTag {
98 fn from_transaction(
99 transaction: &Transaction,
100 format: TransactionFormat,
101 ) -> Self {
102 match (transaction, format) {
103 (Transaction::Phoenix(_), TransactionFormat::Boreas) => {
104 Self::VersionedPhoenix
105 }
106 (Transaction::Moonlight(_), TransactionFormat::Boreas) => {
107 Self::VersionedMoonlight
108 }
109 (Transaction::Phoenix(_), _) => Self::LegacyPhoenix,
110 (Transaction::Moonlight(_), _) => Self::LegacyMoonlight,
111 }
112 }
113
114 fn is_versioned(self) -> bool {
115 matches!(self, Self::VersionedPhoenix | Self::VersionedMoonlight)
116 }
117}
118
119#[derive(Debug, Clone, Copy, Eq, PartialEq)]
121pub enum TransactionFormat {
122 PreAegis,
124 Aegis,
126 Boreas,
128}
129
130#[derive(Debug, Clone, Eq, PartialEq)]
132pub struct DecodedTransaction {
133 pub transaction: Transaction,
135 pub format: TransactionFormat,
137}
138
139#[derive(Debug, Clone, Archive, PartialEq, Eq, Serialize, Deserialize)]
141#[archive_attr(derive(CheckBytes))]
142#[allow(clippy::large_enum_variant)]
143pub enum Transaction {
144 Phoenix(PhoenixTransaction),
146 Moonlight(MoonlightTransaction),
148}
149
150impl Transaction {
151 fn decode_tagged(
152 tag: TransactionTag,
153 buf: &[u8],
154 phoenix_tag: TransactionTag,
155 moonlight_tag: TransactionTag,
156 parse_phoenix: fn(&[u8]) -> Result<PhoenixTransaction, BytesError>,
157 ) -> Result<Transaction, BytesError> {
158 match tag {
159 tag if tag == phoenix_tag => Ok(Self::Phoenix(parse_phoenix(buf)?)),
160 tag if tag == moonlight_tag => {
161 Ok(Self::Moonlight(MoonlightTransaction::from_slice(buf)?))
162 }
163 _ => Err(BytesError::InvalidData),
164 }
165 }
166
167 fn decode_legacy(
168 format: TransactionFormat,
169 buf: &[u8],
170 parse_phoenix: fn(&[u8]) -> Result<PhoenixTransaction, BytesError>,
171 ) -> Result<DecodedTransaction, BytesError> {
172 let mut buf = buf;
173 let family_tag = TransactionTag::try_from(u8::from_reader(&mut buf)?)?;
174
175 let transaction = Self::decode_tagged(
176 family_tag,
177 buf,
178 TransactionTag::LegacyPhoenix,
179 TransactionTag::LegacyMoonlight,
180 parse_phoenix,
181 )?;
182
183 Ok(DecodedTransaction {
184 transaction,
185 format,
186 })
187 }
188
189 fn decode_boreas(buf: &[u8]) -> Result<DecodedTransaction, BytesError> {
190 let mut buf = buf;
191 let family_tag = TransactionTag::try_from(u8::from_reader(&mut buf)?)?;
192 let format_version = u8::from_reader(&mut buf)?;
193
194 if format_version != BOREAS_FORMAT_VERSION {
195 return Err(BytesError::InvalidData);
196 }
197
198 let transaction = Self::decode_tagged(
199 family_tag,
200 buf,
201 TransactionTag::VersionedPhoenix,
202 TransactionTag::VersionedMoonlight,
203 PhoenixTransaction::from_slice,
204 )?;
205
206 Ok(DecodedTransaction {
207 transaction,
208 format: TransactionFormat::Boreas,
209 })
210 }
211
212 pub fn decode_with_format(
218 format: TransactionFormat,
219 buf: &[u8],
220 ) -> Result<DecodedTransaction, BytesError> {
221 match format {
222 TransactionFormat::PreAegis => Self::decode_legacy(
223 format,
224 buf,
225 PhoenixTransaction::from_slice_ledger_compat,
226 ),
227 TransactionFormat::Aegis => {
228 Self::decode_legacy(format, buf, PhoenixTransaction::from_slice)
229 }
230 TransactionFormat::Boreas => Self::decode_boreas(buf),
231 }
232 }
233
234 pub fn decode_for_ingress(
244 format: TransactionFormat,
245 buf: &[u8],
246 ) -> Result<DecodedTransaction, BytesError> {
247 Self::decode_with_format(format, buf)
248 }
249
250 pub fn decode_any(buf: &[u8]) -> Result<DecodedTransaction, BytesError> {
260 let tag = TransactionTag::try_from(
261 buf.first().copied().ok_or(BytesError::InvalidData)?,
262 )?;
263
264 if tag.is_versioned() {
265 Self::decode_boreas(buf)
266 } else {
267 Self::decode_legacy(
268 TransactionFormat::Aegis,
269 buf,
270 PhoenixTransaction::from_slice,
271 )
272 .or_else(|_| {
273 Self::decode_legacy(
274 TransactionFormat::PreAegis,
275 buf,
276 PhoenixTransaction::from_slice_ledger_compat,
277 )
278 })
279 }
280 }
281
282 #[allow(clippy::too_many_arguments)]
292 pub fn phoenix<R: RngCore + CryptoRng, P: Prove>(
293 rng: &mut R,
294 sender_sk: &PhoenixSecretKey,
295 refund_pk: &PhoenixPublicKey,
296 receiver_pk: &PhoenixPublicKey,
297 inputs: Vec<(Note, Opening<(), NOTES_TREE_DEPTH>)>,
298 root: BlsScalar,
299 transfer_value: u64,
300 obfuscated_transaction: bool,
301 deposit: u64,
302 gas_limit: u64,
303 gas_price: u64,
304 chain_id: u8,
305 data: Option<impl Into<TransactionData>>,
306 prover: &P,
307 ) -> Result<Self, Error> {
308 Ok(Self::Phoenix(PhoenixTransaction::new::<R, P>(
309 rng,
310 sender_sk,
311 refund_pk,
312 receiver_pk,
313 inputs,
314 root,
315 transfer_value,
316 obfuscated_transaction,
317 deposit,
318 gas_limit,
319 gas_price,
320 chain_id,
321 data,
322 prover,
323 )?))
324 }
325
326 #[allow(clippy::too_many_arguments)]
332 pub fn moonlight(
333 sender_sk: &AccountSecretKey,
334 receiver: Option<AccountPublicKey>,
335 value: u64,
336 deposit: u64,
337 gas_limit: u64,
338 gas_price: u64,
339 nonce: u64,
340 chain_id: u8,
341 data: Option<impl Into<TransactionData>>,
342 ) -> Result<Self, Error> {
343 Ok(Self::Moonlight(MoonlightTransaction::new(
344 sender_sk, receiver, value, deposit, gas_limit, gas_price, nonce,
345 chain_id, data,
346 )?))
347 }
348
349 #[must_use]
351 pub fn moonlight_sender(&self) -> Option<&AccountPublicKey> {
352 match self {
353 Self::Phoenix(_) => None,
354 Self::Moonlight(tx) => Some(tx.sender()),
355 }
356 }
357
358 #[must_use]
369 pub fn moonlight_receiver(&self) -> Option<&AccountPublicKey> {
370 match self {
371 Self::Phoenix(_) => None,
372 Self::Moonlight(tx) => tx.receiver(),
373 }
374 }
375
376 #[must_use]
378 pub fn value(&self) -> Option<u64> {
379 match self {
380 Self::Phoenix(_) => None,
381 Self::Moonlight(tx) => Some(tx.value()),
382 }
383 }
384
385 #[must_use]
388 pub fn nullifiers(&self) -> &[BlsScalar] {
389 match self {
390 Self::Phoenix(tx) => tx.nullifiers(),
391 Self::Moonlight(_) => &[],
392 }
393 }
394
395 #[must_use]
397 pub fn root(&self) -> Option<&BlsScalar> {
398 match self {
399 Self::Phoenix(tx) => Some(tx.root()),
400 Self::Moonlight(_) => None,
401 }
402 }
403
404 #[must_use]
406 pub fn outputs(&self) -> &[Note] {
407 match self {
408 Self::Phoenix(tx) => &tx.outputs()[..],
409 Self::Moonlight(_) => &[],
410 }
411 }
412
413 #[must_use]
415 pub fn phoenix_sender(&self) -> Option<&Sender> {
416 match self {
417 Self::Phoenix(tx) => Some(tx.sender()),
418 Self::Moonlight(_) => None,
419 }
420 }
421
422 #[must_use]
424 pub fn deposit(&self) -> u64 {
425 match self {
426 Self::Phoenix(tx) => tx.deposit(),
427 Self::Moonlight(tx) => tx.deposit(),
428 }
429 }
430
431 #[must_use]
433 pub fn gas_limit(&self) -> u64 {
434 match self {
435 Self::Phoenix(tx) => tx.gas_limit(),
436 Self::Moonlight(tx) => tx.gas_limit(),
437 }
438 }
439
440 #[must_use]
442 pub fn gas_price(&self) -> u64 {
443 match self {
444 Self::Phoenix(tx) => tx.gas_price(),
445 Self::Moonlight(tx) => tx.gas_price(),
446 }
447 }
448
449 #[must_use]
451 pub fn refund_address(&self) -> RefundAddress<'_> {
452 match self {
453 Self::Phoenix(tx) => RefundAddress::Phoenix(tx.stealth_address()),
454 Self::Moonlight(tx) => {
455 RefundAddress::Moonlight(tx.refund_address())
456 }
457 }
458 }
459
460 #[must_use]
471 pub fn call(&self) -> Option<&ContractCall> {
472 match self {
473 Self::Phoenix(tx) => tx.call(),
474 Self::Moonlight(tx) => tx.call(),
475 }
476 }
477
478 #[must_use]
480 pub fn deploy(&self) -> Option<&ContractDeploy> {
481 match self {
482 Self::Phoenix(tx) => tx.deploy(),
483 Self::Moonlight(tx) => tx.deploy(),
484 }
485 }
486
487 #[must_use]
489 pub fn memo(&self) -> Option<&[u8]> {
490 match self {
491 Self::Phoenix(tx) => tx.memo(),
492 Self::Moonlight(tx) => tx.memo(),
493 }
494 }
495
496 #[must_use]
498 pub fn blob(&self) -> Option<&Vec<BlobData>> {
499 match self {
500 Self::Phoenix(tx) => tx.blob(),
501 Self::Moonlight(tx) => tx.blob(),
502 }
503 }
504
505 #[must_use]
507 pub fn blob_mut(&mut self) -> Option<&mut Vec<BlobData>> {
508 match self {
509 Self::Phoenix(tx) => tx.blob_mut(),
510 Self::Moonlight(tx) => tx.blob_mut(),
511 }
512 }
513
514 #[must_use]
524 pub fn strip_blobs(&mut self) -> Option<Vec<([u8; 32], BlobSidecar)>> {
525 let blob = match self {
526 Self::Phoenix(tx) => tx.blob_mut(),
527 Self::Moonlight(tx) => tx.blob_mut(),
528 }?;
529
530 let ret = blob
531 .iter_mut()
532 .filter_map(|b| b.take_sidecar().map(|d| (b.hash, d)))
533 .collect::<Vec<_>>();
534
535 Some(ret)
536 }
537
538 #[must_use]
544 pub fn blob_to_memo(&self) -> Option<Self> {
545 Some(match self {
546 Transaction::Phoenix(tx) => {
547 Transaction::Phoenix(tx.blob_to_memo()?)
548 }
549 Transaction::Moonlight(tx) => {
550 Transaction::Moonlight(tx.blob_to_memo()?)
551 }
552 })
553 }
554
555 #[must_use]
559 pub fn strip_off_bytecode(&self) -> Option<Self> {
560 Some(match self {
561 Transaction::Phoenix(tx) => {
562 Transaction::Phoenix(tx.strip_off_bytecode()?)
563 }
564 Transaction::Moonlight(tx) => {
565 Transaction::Moonlight(tx.strip_off_bytecode()?)
566 }
567 })
568 }
569
570 #[must_use]
572 pub fn encode_for_format(&self, format: TransactionFormat) -> Vec<u8> {
573 let mut bytes = Vec::new();
574 let tag = TransactionTag::from_transaction(self, format);
575
576 bytes.push(tag as u8);
577 if tag.is_versioned() {
578 bytes.push(BOREAS_FORMAT_VERSION);
579 }
580
581 match self {
582 Self::Phoenix(tx) => bytes.extend(tx.to_var_bytes()),
583 Self::Moonlight(tx) => bytes.extend(tx.to_var_bytes()),
584 }
585
586 bytes
587 }
588
589 #[must_use]
595 pub fn to_network_bytes(&self) -> Vec<u8> {
596 self.encode_for_format(TransactionFormat::Aegis)
597 }
598
599 #[must_use]
601 pub fn to_var_bytes(&self) -> Vec<u8> {
602 self.to_network_bytes()
603 }
604
605 pub fn from_slice(buf: &[u8]) -> Result<Self, BytesError> {
610 Self::decode_any(buf).map(|decoded| decoded.transaction)
611 }
612
613 #[must_use]
618 pub fn to_hash_input_bytes(&self) -> Vec<u8> {
619 match self {
620 Self::Phoenix(tx) => tx.to_hash_input_bytes(),
621 Self::Moonlight(tx) => tx.to_hash_input_bytes(),
622 }
623 }
624
625 #[must_use]
627 pub fn hash(&self) -> BlsScalar {
628 match self {
629 Self::Phoenix(tx) => tx.hash(),
630 Self::Moonlight(tx) => tx.hash(),
631 }
632 }
633
634 pub fn deploy_charge(
643 &self,
644 gas_per_deploy_byte: u64,
645 min_deploy_points: u64,
646 ) -> Result<u64, TxPreconditionError> {
647 if let Some(deploy) = self.deploy() {
648 let bytecode_len = deploy.bytecode.bytes.len() as u64;
649 let deploy_charge =
650 bytecode_len
651 .checked_mul(gas_per_deploy_byte)
652 .ok_or(TxPreconditionError::DeployChargeOverflow)?;
653
654 Ok(max(deploy_charge, min_deploy_points))
655 } else {
656 Ok(0)
657 }
658 }
659
660 pub fn blob_charge(
667 &self,
668 gas_per_blob: u64,
669 ) -> Result<Option<u64>, TxPreconditionError> {
670 self.blob()
671 .map(|blobs| {
672 (blobs.len() as u64)
673 .checked_mul(gas_per_blob)
674 .ok_or(TxPreconditionError::BlobChargeOverflow)
675 })
676 .transpose()
677 }
678
679 pub fn phoenix_fee_check(&self) -> Result<(), TxPreconditionError> {
687 if let Transaction::Phoenix(tx) = self {
688 let max_fee = tx
689 .fee()
690 .gas_limit
691 .checked_mul(tx.fee().gas_price)
692 .ok_or(TxPreconditionError::PhoenixFeeOverflow)?;
693
694 if max_fee != tx.max_fee() {
695 return Err(TxPreconditionError::PhoenixFeeTampered);
696 }
697 }
698 Ok(())
699 }
700
701 pub fn phoenix_refund_check(&self) -> Result<(), TxPreconditionError> {
711 if let Transaction::Phoenix(tx) = self
712 && tx.fee().stealth_address != *tx.outputs()[1].stealth_address()
713 {
714 return Err(TxPreconditionError::PhoenixFeeRefundMismatch);
715 }
716 Ok(())
717 }
718
719 pub fn deploy_check(
727 &self,
728 gas_per_deploy_byte: u64,
729 min_deploy_gas_price: u64,
730 min_deploy_points: u64,
731 ) -> Result<(), TxPreconditionError> {
732 if self.deploy().is_some() {
733 let deploy_charge =
734 self.deploy_charge(gas_per_deploy_byte, min_deploy_points)?;
735
736 if self.gas_price() < min_deploy_gas_price {
737 return Err(TxPreconditionError::DeployLowPrice(
738 min_deploy_gas_price,
739 ));
740 }
741 if self.gas_limit() < deploy_charge {
742 return Err(TxPreconditionError::DeployLowLimit(deploy_charge));
743 }
744 }
745
746 Ok(())
747 }
748
749 pub fn blob_check(
763 &self,
764 gas_per_blob: u64,
765 ) -> Result<Option<u64>, TxPreconditionError> {
766 if let Some(blobs) = self.blob() {
767 match blobs.len() {
768 0 => Err(TxPreconditionError::BlobEmpty),
769 n if n > 6 => Err(TxPreconditionError::BlobTooMany(n)),
770 _ => Ok(()),
771 }?;
772 } else {
773 return Ok(None);
774 }
775
776 let min_charge = self.blob_charge(gas_per_blob)?;
777 if let Some(min_charge) = min_charge
778 && self.gas_limit() < min_charge
779 {
780 return Err(TxPreconditionError::BlobLowLimit(min_charge));
781 }
782 Ok(min_charge)
783 }
784}
785
786impl From<PhoenixTransaction> for Transaction {
787 fn from(tx: PhoenixTransaction) -> Self {
788 Self::Phoenix(tx)
789 }
790}
791
792impl From<MoonlightTransaction> for Transaction {
793 fn from(tx: MoonlightTransaction) -> Self {
794 Self::Moonlight(tx)
795 }
796}
797
798pub enum RefundAddress<'a> {
801 Phoenix(&'a StealthAddress),
803 Moonlight(&'a AccountPublicKey),
805}
806
807#[derive(Debug, Clone, Archive, PartialEq, Eq, Serialize, Deserialize)]
810#[archive_attr(derive(CheckBytes))]
811#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
812pub struct ContractToContract {
813 pub contract: ContractId,
815 #[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
817 pub value: u64,
818 pub fn_name: String,
820 #[cfg_attr(feature = "serde", serde(with = "As::<Hex>"))]
822 pub data: Vec<u8>,
823}
824
825#[derive(Debug, Clone, Archive, PartialEq, Eq, Serialize, Deserialize)]
828#[archive_attr(derive(CheckBytes))]
829#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
830pub struct ReceiveFromContract {
831 pub contract: ContractId,
833 #[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
835 pub value: u64,
836 #[cfg_attr(feature = "serde", serde(with = "As::<Hex>"))]
838 pub data: Vec<u8>,
839}
840
841#[derive(Debug, Clone, Archive, PartialEq, Eq, Serialize, Deserialize)]
844#[archive_attr(derive(CheckBytes))]
845#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
846pub struct ContractToAccount {
847 pub account: AccountPublicKey,
849 #[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
851 pub value: u64,
852}
853
854#[derive(Debug, Clone, Archive, PartialEq, Serialize, Deserialize)]
856#[archive_attr(derive(CheckBytes))]
857#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
858pub struct WithdrawEvent {
859 pub sender: ContractId,
861 pub receiver: WithdrawReceiver,
863 #[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
865 pub value: u64,
866}
867
868impl From<Withdraw> for WithdrawEvent {
869 fn from(w: Withdraw) -> Self {
870 Self {
871 sender: w.contract,
872 receiver: w.receiver,
873 value: w.value,
874 }
875 }
876}
877
878#[derive(Debug, Clone, Archive, PartialEq, Serialize, Deserialize)]
881#[archive_attr(derive(CheckBytes))]
882#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
883pub struct ConvertEvent {
884 pub sender: Option<AccountPublicKey>,
888 pub receiver: WithdrawReceiver,
890 #[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
892 pub value: u64,
893}
894
895impl ConvertEvent {
896 #[must_use]
898 pub fn from_withdraw_and_sender(
899 sender: Option<AccountPublicKey>,
900 withdraw: &Withdraw,
901 ) -> Self {
902 Self {
903 sender,
904 receiver: withdraw.receiver,
905 value: withdraw.value,
906 }
907 }
908}
909
910#[derive(Debug, Clone, Archive, PartialEq, Serialize, Deserialize)]
912#[archive_attr(derive(CheckBytes))]
913#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
914pub struct DepositEvent {
915 pub sender: Option<AccountPublicKey>,
919 pub receiver: ContractId,
921 #[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
923 pub value: u64,
924}
925
926#[derive(Debug, Clone, Archive, PartialEq, Serialize, Deserialize)]
928#[archive_attr(derive(CheckBytes))]
929#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
930pub struct ContractToContractEvent {
931 pub sender: ContractId,
933 pub receiver: ContractId,
935 #[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
937 pub value: u64,
938}
939
940#[derive(Debug, Clone, Archive, PartialEq, Serialize, Deserialize)]
942#[archive_attr(derive(CheckBytes))]
943#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
944pub struct ContractToAccountEvent {
945 pub sender: ContractId,
947 pub receiver: AccountPublicKey,
949 #[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
951 pub value: u64,
952}
953
954#[derive(Debug, Clone, Archive, PartialEq, Serialize, Deserialize)]
956#[archive_attr(derive(CheckBytes))]
957#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
958pub struct PhoenixTransactionEvent {
959 pub nullifiers: Vec<BlsScalar>,
961 pub notes: Vec<Note>,
963 #[cfg_attr(feature = "serde", serde(with = "As::<Hex>"))]
965 pub memo: Vec<u8>,
966 #[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
968 pub gas_spent: u64,
969
970 pub refund_note: Option<Note>,
972}
973
974#[derive(Debug, Clone, Archive, PartialEq, Serialize, Deserialize)]
976#[archive_attr(derive(CheckBytes))]
977#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
978pub struct MoonlightTransactionEvent {
979 pub sender: AccountPublicKey,
981 pub receiver: Option<AccountPublicKey>,
983 #[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
985 pub value: u64,
986 #[cfg_attr(feature = "serde", serde(with = "As::<Hex>"))]
988 pub memo: Vec<u8>,
989 #[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
991 pub gas_spent: u64,
992 #[cfg_attr(
995 feature = "serde",
996 serde(with = "As::<Option<(Same, DisplayFromStr)>>")
997 )]
998 pub refund_info: Option<(AccountPublicKey, u64)>,
999}