use std::{io::Write, str::FromStr, sync::OnceLock};
pub mod actions;
pub mod delegate_action;
pub mod result;
use base64::{prelude::BASE64_STANDARD, Engine};
use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};
use crate::{
errors::DataConversionError, AccountId, Action, CryptoHash, Nonce, PublicKey, Signature,
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub struct TransactionV0 {
pub signer_id: AccountId,
pub public_key: PublicKey,
pub nonce: Nonce,
pub receiver_id: AccountId,
pub block_hash: CryptoHash,
pub actions: Vec<Action>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub struct TransactionV1 {
pub signer_id: AccountId,
pub public_key: PublicKey,
pub nonce: Nonce,
pub receiver_id: AccountId,
pub block_hash: CryptoHash,
pub actions: Vec<Action>,
pub priority_fee: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub enum Transaction {
V0(TransactionV0),
V1(TransactionV1),
}
impl Transaction {
pub const fn signer_id(&self) -> &AccountId {
match self {
Self::V0(tx) => &tx.signer_id,
Self::V1(tx) => &tx.signer_id,
}
}
pub const fn receiver_id(&self) -> &AccountId {
match self {
Self::V0(tx) => &tx.receiver_id,
Self::V1(tx) => &tx.receiver_id,
}
}
pub const fn nonce(&self) -> Nonce {
match self {
Self::V0(tx) => tx.nonce,
Self::V1(tx) => tx.nonce,
}
}
pub const fn public_key(&self) -> PublicKey {
match self {
Self::V0(tx) => tx.public_key,
Self::V1(tx) => tx.public_key,
}
}
pub fn actions(&self) -> &[Action] {
match self {
Self::V0(tx) => &tx.actions,
Self::V1(tx) => &tx.actions,
}
}
pub const fn actions_mut(&mut self) -> &mut Vec<Action> {
match self {
Self::V0(tx) => &mut tx.actions,
Self::V1(tx) => &mut tx.actions,
}
}
pub fn take_actions(&mut self) -> Vec<Action> {
let actions = match self {
Self::V0(tx) => &mut tx.actions,
Self::V1(tx) => &mut tx.actions,
};
std::mem::take(actions)
}
pub fn get_hash(&self) -> CryptoHash {
#[allow(clippy::expect_used)]
let bytes = borsh::to_vec(&self).expect("Failed to serialize");
CryptoHash::hash(&bytes)
}
}
impl BorshSerialize for Transaction {
fn serialize<W: Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
match self {
Self::V0(tx) => BorshSerialize::serialize(tx, writer)?,
Self::V1(tx) => {
BorshSerialize::serialize(&1_u8, writer)?;
BorshSerialize::serialize(tx, writer)?;
}
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub struct SignedTransaction {
pub transaction: Transaction,
pub signature: Signature,
#[borsh(skip)]
#[serde(skip)]
hash: OnceLock<CryptoHash>,
}
impl TryFrom<near_openapi_types::SignedTransactionView> for SignedTransaction {
type Error = DataConversionError;
fn try_from(value: near_openapi_types::SignedTransactionView) -> Result<Self, Self::Error> {
let near_openapi_types::SignedTransactionView {
signer_id,
public_key,
nonce,
receiver_id,
actions,
priority_fee,
hash,
signature,
} = value;
let transaction = if priority_fee > 0 {
Transaction::V1(TransactionV1 {
signer_id,
public_key: public_key.try_into()?,
nonce,
receiver_id,
block_hash: hash.try_into()?,
actions: actions
.into_iter()
.map(Action::try_from)
.collect::<Result<Vec<_>, _>>()?,
priority_fee,
})
} else {
Transaction::V0(TransactionV0 {
signer_id,
public_key: public_key.try_into()?,
nonce,
receiver_id,
block_hash: hash.try_into()?,
actions: actions
.into_iter()
.map(Action::try_from)
.collect::<Result<Vec<_>, _>>()?,
})
};
Ok(Self::new(Signature::from_str(&signature)?, transaction))
}
}
impl From<SignedTransaction> for near_openapi_types::SignedTransaction {
fn from(transaction: SignedTransaction) -> Self {
#[allow(clippy::expect_used)]
let bytes = borsh::to_vec(&transaction).expect("Failed to serialize");
Self(BASE64_STANDARD.encode(bytes))
}
}
impl From<SignedTransaction> for PrepopulateTransaction {
fn from(mut transaction: SignedTransaction) -> Self {
Self {
signer_id: transaction.transaction.signer_id().clone(),
receiver_id: transaction.transaction.receiver_id().clone(),
actions: transaction.transaction.take_actions(),
}
}
}
impl SignedTransaction {
pub const fn new(signature: Signature, transaction: Transaction) -> Self {
Self {
signature,
transaction,
hash: OnceLock::new(),
}
}
pub fn get_hash(&self) -> CryptoHash {
*self.hash.get_or_init(|| self.transaction.get_hash())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PrepopulateTransaction {
pub signer_id: near_account_id::AccountId,
pub receiver_id: near_account_id::AccountId,
pub actions: Vec<Action>,
}