#[cfg(feature = "sign")]
use crate::account::keys::{ApiKey, Wallet};
use crate::client::Network;
#[cfg(feature = "sign")]
use crate::client::{Client, Http, SendError, Sent};
use crate::utils::hex;
use crate::utils::json::Json;
#[cfg(feature = "sign")]
use crate::utils::sign::SignTxError;
use crate::utils::sign::goldilocks::Fp;
use crate::utils::sign::tx::{BadTx, Envelope, Tx, TxType, byte_list, element, hex16, number};
use crate::utils::units::{BadUnits, exact_units};
use crate::utils::{AccountIndex, Address, AssetId, Dec, Route};
pub const MAX_AMOUNT: u64 = (1 << 60) - 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Memo([u8; 32]);
impl Memo {
pub const NONE: Memo = Memo([0; 32]);
#[must_use]
pub const fn from_bytes(bytes: [u8; 32]) -> Memo {
Memo(bytes)
}
#[must_use]
pub fn address(address: Address) -> Memo {
let mut bytes = [0u8; 32];
bytes[..20].copy_from_slice(address.bytes());
Memo(bytes)
}
#[must_use]
pub fn text(text: &str) -> Option<Memo> {
let raw = text.as_bytes();
let mut bytes = [0u8; 32];
bytes.get_mut(..raw.len())?.copy_from_slice(raw);
Some(Memo(bytes))
}
#[must_use]
pub const fn bytes(&self) -> &[u8; 32] {
&self.0
}
#[must_use]
pub fn to_hex(&self) -> String {
hex::encode(&self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Transfer {
pub to: AccountIndex,
pub asset: AssetId,
pub from_route: Route,
pub to_route: Route,
pub amount: u64,
pub fee: u64,
pub memo: Memo,
}
impl Transfer {
pub fn new(to: AccountIndex, asset: AssetId, decimals: u32, amount: Dec) -> Result<Transfer, BadUnits> {
Ok(Transfer {
to,
asset,
from_route: Route::Perps,
to_route: Route::Perps,
amount: exact_units(amount, decimals, MAX_AMOUNT)?,
fee: 0,
memo: Memo::NONE,
})
}
pub fn collateral(network: Network, to: AccountIndex, amount: Dec) -> Result<Transfer, BadUnits> {
let collateral = network.collateral();
Transfer::new(to, collateral.asset, collateral.decimals, amount)
}
#[must_use]
pub const fn routes(mut self, from: Route, to: Route) -> Transfer {
self.from_route = from;
self.to_route = to;
self
}
#[must_use]
pub const fn with_fee(mut self, fee: u64) -> Transfer {
self.fee = fee;
self
}
#[must_use]
pub const fn with_memo(mut self, memo: Memo) -> Transfer {
self.memo = memo;
self
}
}
pub(crate) fn halves(amount: u64) -> [Fp; 2] {
[element(amount & 0xFFFF_FFFF), element(amount >> 32)]
}
impl Tx for Transfer {
fn tx_type(&self) -> TxType {
TxType::Transfer
}
fn hash_fields(&self) -> Vec<Fp> {
let mut fields = vec![
element(self.to.get()),
element(self.asset.get()),
element(self.from_route.code()),
element(self.to_route.code()),
];
fields.extend(halves(self.amount));
fields.extend(halves(self.fee));
fields
}
fn info_fields(&self) -> Vec<(&'static str, Json)> {
vec![
("ToAccountIndex", number(self.to.get())),
("AssetIndex", number(self.asset.get())),
("FromRouteType", number(self.from_route.code())),
("ToRouteType", number(self.to_route.code())),
("Amount", number(self.amount)),
("USDCFee", number(self.fee)),
("Memo", byte_list(self.memo.bytes())),
]
}
fn account_key(&self) -> &'static str {
"FromAccountIndex"
}
fn trailing_l1_sig(&self) -> bool {
true
}
fn l1_message(&self, envelope: &Envelope, chain_id: u32) -> Option<String> {
Some(format!(
"Transfer\n\nnonce: {}\nfrom: {} (route {})\napi key: {}\nto: {} (route {})\nasset: {}\namount: {}\nfee: {}\nchainId: {}\nmemo: {}\nOnly sign this message for a trusted client!",
hex16(envelope.nonce),
hex16(envelope.account.get()),
hex16(u64::from(self.from_route.code())),
hex16(u64::from(envelope.api_key.get())),
hex16(self.to.get()),
hex16(u64::from(self.to_route.code())),
hex16(u64::from(self.asset.get())),
hex16(self.amount),
hex16(self.fee),
hex16(u64::from(chain_id)),
self.memo.to_hex(),
))
}
fn check(&self) -> Result<(), BadTx> {
if self.amount == 0 {
return Err(BadTx::new("Amount", "a transfer moves at least 1 unit (TransferAmountTooLow)"));
}
if self.amount > MAX_AMOUNT {
return Err(BadTx::new("Amount", "a transfer moves at most 2^60 - 1 units"));
}
if self.fee > MAX_AMOUNT {
return Err(BadTx::new("USDCFee", "a fee is at most 2^60 - 1 units"));
}
Ok(())
}
}
#[cfg(feature = "sign")]
pub async fn transfer<H: Http>(client: &Client<H, ApiKey>, transfer: Transfer) -> Result<Sent, SendError> {
let transfer = priced(client, transfer).await?;
client.send(&transfer).await
}
#[cfg(feature = "sign")]
pub async fn transfer_to_owner<H: Http>(
client: &Client<H, ApiKey>,
transfer: Transfer,
owner: &impl Wallet,
) -> Result<Sent, SendError> {
let transfer = priced(client, transfer).await?;
client.send_with_owner(&transfer, owner).await
}
#[cfg(feature = "sign")]
async fn priced<H: Http>(client: &Client<H, ApiKey>, transfer: Transfer) -> Result<Transfer, SendError> {
let fee = crate::transfer::fees::transfer_fee(client, client.key().account(), Some(transfer.to)).await?;
Ok(transfer.with_fee(fee))
}
#[cfg(feature = "sign")]
pub(crate) fn refused_units(field: &'static str, error: &BadUnits) -> SendError {
SendError::Sign(SignTxError::Bad(BadTx::new(field, error.to_string())))
}
#[cfg(test)]
#[path = "tests/send.rs"]
mod tests;