Skip to content

Commit

Permalink
fix: Fixed information about successful transfer of "send all" ft tok…
Browse files Browse the repository at this point in the history
…ens (#447)

These changes correct information about the successful transfer of ft
tokens

https://github.com/user-attachments/assets/d0732ff0-06d1-41bb-9b2d-c05372ffb1d2
  • Loading branch information
FroVolod authored Mar 1, 2025
1 parent 76e0517 commit 93085b1
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 59 deletions.
52 changes: 35 additions & 17 deletions src/commands/tokens/send_ft/amount_ft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,16 @@ impl FtTransferParamsContext {
let deposit = scope.deposit.unwrap_or(crate::types::near_token::NearToken::from_yoctonear(1));

move |network_config| {
let amount_ft = super::get_amount_ft(
&ft_transfer_amount,
network_config,
&signer_account_id,
&ft_contract_account_id
)?;
let amount_ft = if let crate::types::ft_properties::FungibleTokenTransferAmount::ExactAmount(ft) = &ft_transfer_amount {
ft.clone()
} else {
super::get_ft_balance_for_account(
network_config,
&signer_account_id,
&ft_contract_account_id,
near_primitives::types::Finality::Final.into()
)?
};

super::get_prepopulated_transaction(
network_config,
Expand All @@ -164,21 +168,35 @@ impl FtTransferParamsContext {
let signer_account_id = previous_context.signer_account_id.clone();
let ft_contract_account_id = previous_context.ft_contract_account_id.clone();
let receiver_account_id = previous_context.receiver_account_id.clone();
let ft_transfer_amount = previous_context.ft_transfer_amount.clone();

move |outcome_view, network_config| {
let amount_ft = super::get_amount_ft(
&ft_transfer_amount,
network_config,
&signer_account_id,
&ft_contract_account_id
)?;

if let near_primitives::views::FinalExecutionStatus::SuccessValue(_) = outcome_view.status {
eprintln!(
"<{signer_account_id}> has successfully transferred {amount_ft} (FT-contract: {ft_contract_account_id}) to <{receiver_account_id}>.",
);
for action in outcome_view.transaction.actions.clone() {
if let near_primitives::views::ActionView::FunctionCall { method_name: _, args, gas: _, deposit: _ } = action {
if let Ok(ft_transfer) = serde_json::from_slice::<crate::types::ft_properties::FtTransfer>(&args) {
if let Ok(ft_balance) = super::get_ft_balance_for_account(
network_config,
&signer_account_id,
&ft_contract_account_id,
near_primitives::types::BlockId::Hash(outcome_view.receipts_outcome.last().expect("FT transfer should have at least one receipt outcome, but none was received").block_hash).into()
) {
let ft_transfer_amount = crate::types::ft_properties::FungibleToken::from_params_ft(
ft_transfer.amount,
ft_balance.decimals(),
ft_balance.symbol().to_string()
);
eprintln!(
"<{signer_account_id}> has successfully transferred {ft_transfer_amount} (FT-contract: {ft_contract_account_id}) to <{receiver_account_id}>.\nRemaining balance: {ft_balance}",
);
return Ok(());
}
}
}
}
}
eprintln!(
"<{signer_account_id}> has successfully transferred fungible tokens (FT-contract: {ft_contract_account_id}) to <{receiver_account_id}>.",
);
Ok(())
}
});
Expand Down
72 changes: 30 additions & 42 deletions src/commands/tokens/send_ft/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,29 +80,22 @@ pub fn get_prepopulated_transaction(
deposit: &crate::types::near_token::NearToken,
gas: &crate::common::NearGas,
) -> color_eyre::eyre::Result<crate::commands::PrepopulatedTransaction> {
let args = serde_json::to_vec(&json!({
"receiver_id": amount_ft.amount().to_string(),
"amount": amount_ft.amount().to_string(),
"memo": memo.as_ref().and_then(|s| {
let trimmed = s.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
})
}))?;
let args_ft_transfer = serde_json::to_vec(&crate::types::ft_properties::FtTransfer {
receiver_id: receiver_account_id.clone(),
amount: amount_ft.amount(),
memo: memo.clone(),
})?;

let action_ft_transfer = near_primitives::transaction::Action::FunctionCall(Box::new(
near_primitives::transaction::FunctionCallAction {
method_name: "ft_transfer".to_string(),
args,
args: args_ft_transfer,
gas: gas.as_gas(),
deposit: deposit.as_yoctonear(),
},
));

let args = serde_json::to_vec(&json!({"account_id": receiver_account_id.to_string()}))?;
let args = serde_json::to_vec(&json!({"account_id": receiver_account_id}))?;

let call_result = network_config
.json_rpc_client()
Expand Down Expand Up @@ -131,45 +124,40 @@ pub fn get_prepopulated_transaction(
return Ok(crate::commands::PrepopulatedTransaction {
signer_id: signer_id.clone(),
receiver_id: ft_contract_account_id.clone(),
actions: vec![action_storage_deposit, action_ft_transfer],
actions: vec![action_storage_deposit, action_ft_transfer.clone()],
});
}

Ok(crate::commands::PrepopulatedTransaction {
signer_id: signer_id.clone(),
receiver_id: ft_contract_account_id.clone(),
actions: vec![action_ft_transfer],
actions: vec![action_ft_transfer.clone()],
})
}

pub fn get_amount_ft(
ft_transfer_amount: &crate::types::ft_properties::FungibleTokenTransferAmount,
fn get_ft_balance_for_account(
network_config: &crate::config::NetworkConfig,
signer_account_id: &near_primitives::types::AccountId,
ft_contract_account_id: &near_primitives::types::AccountId,
block_reference: near_primitives::types::BlockReference,
) -> color_eyre::eyre::Result<crate::types::ft_properties::FungibleToken> {
match ft_transfer_amount {
crate::types::ft_properties::FungibleTokenTransferAmount::ExactAmount(ft) => Ok(ft.clone()),
crate::types::ft_properties::FungibleTokenTransferAmount::MaxAmount => {
let function_args = serde_json::to_vec(&json!({"account_id": signer_account_id}))?;
let amount = get_ft_balance(
network_config,
ft_contract_account_id,
function_args,
near_primitives::types::Finality::Final.into(),
)?
.parse_result_from_json::<String>()?;
let crate::types::ft_properties::FtMetadata { decimals, symbol } =
crate::types::ft_properties::params_ft_metadata(
ft_contract_account_id.clone(),
network_config,
near_primitives::types::Finality::Final.into(),
)?;
Ok(crate::types::ft_properties::FungibleToken::from_params_ft(
amount.parse::<u128>()?,
decimals,
symbol,
))
}
}
let function_args = serde_json::to_vec(&json!({"account_id": signer_account_id}))?;
let amount = get_ft_balance(
network_config,
ft_contract_account_id,
function_args,
block_reference,
)?
.parse_result_from_json::<String>()?;
let crate::types::ft_properties::FtMetadata { decimals, symbol } =
crate::types::ft_properties::params_ft_metadata(
ft_contract_account_id.clone(),
network_config,
near_primitives::types::Finality::Final.into(),
)?;
Ok(crate::types::ft_properties::FungibleToken::from_params_ft(
amount.parse::<u128>()?,
decimals,
symbol,
))
}
58 changes: 58 additions & 0 deletions src/types/ft_properties.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use color_eyre::eyre::{Context, ContextCompat};
use serde::de::{Deserialize, Deserializer};
use serde::ser::{Serialize, Serializer};

use crate::common::CallResultExt;
use crate::common::JsonRpcClientExt;
Expand Down Expand Up @@ -218,6 +220,32 @@ pub fn params_ft_metadata(
Ok(ft_metadata)
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct FtTransfer {
pub receiver_id: near_primitives::types::AccountId,
#[serde(deserialize_with = "parse_u128_string", serialize_with = "to_string")]
pub amount: u128,
#[serde(skip_serializing_if = "Option::is_none")]
pub memo: Option<String>,
}

fn parse_u128_string<'de, D>(deserializer: D) -> color_eyre::eyre::Result<u128, D::Error>
where
D: Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse::<u128>()
.map_err(serde::de::Error::custom)
}

fn to_string<S, T: ToString>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = value.to_string();
String::serialize(&s, serializer)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -269,4 +297,34 @@ mod tests {
assert_eq!(ft_transfer_amount.to_string(), "all".to_string());
assert_eq!(ft_transfer_amount, FungibleTokenTransferAmount::MaxAmount);
}
#[test]
fn ft_transfer_with_memo_to_vec_u8() {
let ft_transfer = serde_json::to_vec(&crate::types::ft_properties::FtTransfer {
receiver_id: "fro_volod.testnet".parse().unwrap(),
amount: FungibleToken::from_str("0.123456 USDC").unwrap().amount(),
memo: Some("Memo".to_string()),
})
.unwrap();
assert_eq!(
serde_json::from_slice::<serde_json::Value>(&ft_transfer)
.unwrap()
.to_string(),
"{\"amount\":\"123456\",\"memo\":\"Memo\",\"receiver_id\":\"fro_volod.testnet\"}"
);
}
#[test]
fn ft_transfer_without_memo_to_vec_u8() {
let ft_transfer = serde_json::to_vec(&crate::types::ft_properties::FtTransfer {
receiver_id: "fro_volod.testnet".parse().unwrap(),
amount: FungibleToken::from_str("0.123456 USDC").unwrap().amount(),
memo: None,
})
.unwrap();
assert_eq!(
serde_json::from_slice::<serde_json::Value>(&ft_transfer)
.unwrap()
.to_string(),
"{\"amount\":\"123456\",\"receiver_id\":\"fro_volod.testnet\"}"
);
}
}

0 comments on commit 93085b1

Please sign in to comment.