From e044f15e76118dc15ab58822cb8e4e39ab51fce6 Mon Sep 17 00:00:00 2001 From: viraladmin <00purple@gmail.com> Date: Tue, 23 Jun 2026 11:24:35 -0600 Subject: [PATCH] fixed token name validation --- src/bin/create_burn_tx.rs | 13 +++---- src/bin/create_issue_token_tx.rs | 13 +++---- src/bin/create_loan_tx.rs | 26 ++++++++----- src/bin/create_nft_tx.rs | 6 ++- src/bin/create_swap_tx.rs | 26 ++++++++----- src/bin/create_tokens_tx.rs | 13 +++---- src/bin/create_transfer_tx.rs | 21 ++++++---- src/bin/verify_sign_swap_tx.rs | 22 +++++++---- src/common/asset_names.rs | 38 +++++++++++++++++++ src/common/mod.rs | 1 + src/verifications/async_funcs/verify_burn.rs | 4 ++ .../async_funcs/verify_create_nft.rs | 6 +++ .../async_funcs/verify_create_token.rs | 6 +++ .../async_funcs/verify_issue_token.rs | 6 +++ .../async_funcs/verify_lender.rs | 11 ++++++ src/verifications/async_funcs/verify_swap.rs | 15 +++++++- .../async_funcs/verify_transfer.rs | 7 ++++ 17 files changed, 175 insertions(+), 59 deletions(-) create mode 100644 src/common/asset_names.rs diff --git a/src/bin/create_burn_tx.rs b/src/bin/create_burn_tx.rs index f4f162d..39d4b34 100644 --- a/src/bin/create_burn_tx.rs +++ b/src/bin/create_burn_tx.rs @@ -1,4 +1,5 @@ use blockchain::blocks::burn::{BurnTransaction, UnsignedBurnTransaction}; +use blockchain::common::asset_names::padded_normalized_asset_input; use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path}; use blockchain::common::network_paths_and_settings::block_extension_and_paths; @@ -9,13 +10,6 @@ use blockchain::File; use blockchain::Utc; use blockchain::{create_dir_all, AsyncWriteExt}; -// Pad the asset name so it matches the 15-byte on-chain asset format. -fn pad_to_width(input: &str, width: usize) -> String { - let mut result = String::with_capacity(width); - let _ = std::fmt::write(&mut result, format_args!("{input: f64 { // Fees are stored as atomic units and displayed as whole-coin decimals for the CLI. value as f64 / 100_000_000.0 @@ -29,7 +23,10 @@ async fn main() { // Burnable assets are tokens or NFTs, never the base coin for this transaction type. let coin_name = prompt_visible("Please enter the token or NFT name you wish to burn: ").await; - let coin = pad_to_width(&coin_name.trim().to_lowercase(), 15); + let Some(coin) = padded_normalized_asset_input(coin_name.trim()) else { + println!("Burn asset must normalize to 3-15 alphanumeric characters."); + return; + }; if coin.trim().to_lowercase() == block_extension_and_paths().1.trim().to_lowercase() { println!("Base coin cannot be burned with this transaction type."); diff --git a/src/bin/create_issue_token_tx.rs b/src/bin/create_issue_token_tx.rs index 553c455..f7de16f 100644 --- a/src/bin/create_issue_token_tx.rs +++ b/src/bin/create_issue_token_tx.rs @@ -1,4 +1,5 @@ use blockchain::blocks::issue_token::{IssueTokenTransaction, UnsignedIssueTokenTransaction}; +use blockchain::common::asset_names::padded_normalized_asset_input; use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path}; use blockchain::common::types::ISSUE_TOKEN_FEE; @@ -8,13 +9,6 @@ use blockchain::File; use blockchain::Utc; use blockchain::{create_dir_all, AsyncWriteExt}; -// Pad the ticker so it matches the fixed-width 15-byte on-chain format. -fn pad_to_width(input: &str, width: usize) -> String { - let mut result = String::with_capacity(width); - let _ = std::fmt::write(&mut result, format_args!("{input: f64 { // Fees are stored as atomic units and displayed as whole-coin decimals for the CLI. value as f64 / 100_000_000.0 @@ -29,7 +23,10 @@ async fn main() { // The ticker is stored as a fixed-width 15-byte asset field. let ticker_name = prompt_visible("Please enter the ticker / token name you wish to issue more of: ").await; - let ticker = pad_to_width(&ticker_name.trim().to_lowercase(), 15); + let Some(ticker) = padded_normalized_asset_input(ticker_name.trim()) else { + println!("Ticker must normalize to 3-15 alphanumeric characters."); + return; + }; // Token amounts are entered as whole tokens and stored as atomic units. let number_string = diff --git a/src/bin/create_loan_tx.rs b/src/bin/create_loan_tx.rs index 2525f5e..bf7de5c 100644 --- a/src/bin/create_loan_tx.rs +++ b/src/bin/create_loan_tx.rs @@ -1,5 +1,7 @@ use blockchain::blocks::loans::UnsignedLoanContractTransaction; +use blockchain::common::asset_names::padded_normalized_asset_input; use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path}; +use blockchain::common::network_paths_and_settings::block_extension_and_paths; use blockchain::common::types::LENDER_FEE; use blockchain::json; @@ -9,11 +11,13 @@ use blockchain::File; use blockchain::{create_dir_all, AsyncWriteExt}; use blockchain::{Local, LocalResult, NaiveDate, NaiveTime, TimeZone}; -fn pad_to_width(input: &str, width: usize) -> String { - // Asset names are fixed-width fields in the loan transaction bytes. - let mut result = String::with_capacity(width); - let _ = std::fmt::write(&mut result, format_args!("{input: Option { + let base_coin = block_extension_and_paths().1; + if input.trim().eq_ignore_ascii_case(base_coin.trim()) { + Some(base_coin) + } else { + padded_normalized_asset_input(input.trim()) + } } fn display_fee(value: u64) -> f64 { @@ -63,8 +67,10 @@ async fn main() { // Coin/token and collateral names are padded to fixed-width transaction fields. let loan_coin_name = prompt_visible("What coin or token will you be lending out? ").await; - let loan_coin_name = loan_coin_name.trim().to_lowercase(); - let loan_coin = pad_to_width(&loan_coin_name, 15); + let Some(loan_coin) = normalize_asset_or_base(&loan_coin_name) else { + println!("Loan asset must be the base coin or normalize to 3-15 alphanumeric characters."); + return; + }; let loan_amount_string = prompt_visible("How many coins or tokens will you be lending? ").await; let loan_amount_f64: f64 = loan_amount_string @@ -123,8 +129,10 @@ async fn main() { let collateral_name = prompt_visible("What collateral coin, token, or NFT will you be accepting for this loan? ") .await; - let collateral_name = collateral_name.trim().to_lowercase(); - let collateral = pad_to_width(&collateral_name, 15); + let Some(collateral) = padded_normalized_asset_input(collateral_name.trim()) else { + println!("Collateral asset must normalize to 3-15 alphanumeric characters."); + return; + }; let collateral_amount_string = prompt_visible("How many collateral coins or tokens will you be accepting for this loan (enter 1 for NFT collateral)? ").await; let collateral_amount_f64: f64 = collateral_amount_string diff --git a/src/bin/create_nft_tx.rs b/src/bin/create_nft_tx.rs index 9abc966..63cc8b5 100644 --- a/src/bin/create_nft_tx.rs +++ b/src/bin/create_nft_tx.rs @@ -1,4 +1,5 @@ use blockchain::blocks::nft::{CreateNftTransaction, UnsignedCreateNftTransaction}; +use blockchain::common::asset_names::padded_normalized_asset_input; use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path}; use blockchain::common::types::CREATE_NFT_FEE; @@ -31,7 +32,10 @@ async fn main() { // get user input ticker let nft_ticker_name = prompt_visible("Please enter the name for your NFT. This can be 3 to 15 characters and must be 100% unique. First come first serve: ").await; - let nft_name = pad_to_width(nft_ticker_name.trim(), 15); + let Some(nft_name) = padded_normalized_asset_input(nft_ticker_name.trim()) else { + println!("NFT name must normalize to 3-15 alphanumeric characters."); + return; + }; // get the padded CID string let ipfs_hash = prompt_visible("Enter your IPFS CID for your NFT. This will be stored in a fixed 100 character field, so shorter CIDs are padded with spaces: ").await; diff --git a/src/bin/create_swap_tx.rs b/src/bin/create_swap_tx.rs index f36d9a0..beaf3a4 100644 --- a/src/bin/create_swap_tx.rs +++ b/src/bin/create_swap_tx.rs @@ -1,5 +1,7 @@ use blockchain::blocks::swap::UnsignedSwapTransaction; +use blockchain::common::asset_names::padded_normalized_asset_input; use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path}; +use blockchain::common::network_paths_and_settings::block_extension_and_paths; use blockchain::common::types::SWAP_FEE; use blockchain::json; @@ -10,11 +12,13 @@ use blockchain::File; use blockchain::Utc; use blockchain::{create_dir_all, AsyncWriteExt}; -// pad the coin to ensure 15 characters -fn pad_to_width(input: &str, width: usize) -> String { - let mut result = String::with_capacity(width); // Pre-allocate string with capacity - let _ = std::fmt::write(&mut result, format_args!("{input: Option { + let base_coin = block_extension_and_paths().1; + if input.trim().eq_ignore_ascii_case(base_coin.trim()) { + Some(base_coin) + } else { + padded_normalized_asset_input(input.trim()) + } } fn display_fee(value: u64) -> f64 { @@ -30,8 +34,10 @@ async fn main() { // get user input tocken1 let ticker1_name = prompt_visible("Please enter the coin or token you wish to send during the swap: ").await; - let ticker1_name = ticker1_name.trim().to_lowercase(); - let ticker1 = pad_to_width(&ticker1_name, 15); + let Some(ticker1) = normalize_asset_or_base(&ticker1_name) else { + println!("Swap asset must be the base coin or normalize to 3-15 alphanumeric characters."); + return; + }; let nft_series1_string = prompt_visible("Please enter the NFT series number for the asset you are sending, or 0 for coins/tokens/1of1 NFTs: ").await; let nft_series1: u32 = nft_series1_string @@ -52,8 +58,10 @@ async fn main() { let ticker2_name = prompt_visible("Please enter the coin or token you wish to receive during the swap: ") .await; - let ticker2_name = ticker2_name.trim().to_lowercase(); - let ticker2 = pad_to_width(&ticker2_name, 15); + let Some(ticker2) = normalize_asset_or_base(&ticker2_name) else { + println!("Swap asset must be the base coin or normalize to 3-15 alphanumeric characters."); + return; + }; let nft_series2_string = prompt_visible("Please enter the NFT series number for the asset you expect to receive, or 0 for coins/tokens/1of1 NFTs: ").await; let nft_series2: u32 = nft_series2_string diff --git a/src/bin/create_tokens_tx.rs b/src/bin/create_tokens_tx.rs index 8bcc177..fc5f059 100644 --- a/src/bin/create_tokens_tx.rs +++ b/src/bin/create_tokens_tx.rs @@ -1,4 +1,5 @@ use blockchain::blocks::token::{CreateTokenTransaction, UnsignedCreateTokenTransaction}; +use blockchain::common::asset_names::padded_normalized_asset_input; use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path}; use blockchain::common::types::CREATE_TOKEN_FEE; @@ -8,13 +9,6 @@ use blockchain::File; use blockchain::Utc; use blockchain::{create_dir_all, AsyncWriteExt}; -// pad the coin to ensure 15 characters -fn pad_to_width(input: &str, width: usize) -> String { - let mut result = String::with_capacity(width); // Pre-allocate string with capacity - let _ = std::fmt::write(&mut result, format_args!("{input: f64 { value as f64 / 100_000_000.0 } @@ -27,7 +21,10 @@ async fn main() { // get user input ticker let ticker_name = prompt_visible("Please enter the ticker / token name you wish to create. This can be 3 to 15 characters and must be 100% unique. First come first serve: ").await; - let ticker = pad_to_width(ticker_name.trim(), 15); + let Some(ticker) = padded_normalized_asset_input(ticker_name.trim()) else { + println!("Ticker must normalize to 3-15 alphanumeric characters."); + return; + }; // get user input value let number_string = prompt_visible( diff --git a/src/bin/create_transfer_tx.rs b/src/bin/create_transfer_tx.rs index 73b3f0b..efb6cd4 100644 --- a/src/bin/create_transfer_tx.rs +++ b/src/bin/create_transfer_tx.rs @@ -1,4 +1,5 @@ use blockchain::blocks::transfer::{TransferTransaction, UnsignedTransferTransaction}; +use blockchain::common::asset_names::padded_normalized_asset_input; use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path}; use blockchain::common::network_paths_and_settings::block_extension_and_paths; @@ -11,11 +12,13 @@ use blockchain::File; use blockchain::Utc; use blockchain::{create_dir_all, AsyncWriteExt}; -// pad the coin to ensure 15 characters -fn pad_to_width(input: &str, width: usize) -> String { - let mut result = String::with_capacity(width); // Pre-allocate string with capacity - let _ = std::fmt::write(&mut result, format_args!("{input: Option { + let base_coin = block_extension_and_paths().1; + if input.trim().eq_ignore_ascii_case(base_coin.trim()) { + Some(base_coin) + } else { + padded_normalized_asset_input(input.trim()) + } } fn display_fee(value: u64) -> f64 { @@ -42,9 +45,11 @@ async fn main() { } else { prompt_visible("Please enter the coin or token you wish to transfer: ").await }; - let coin = pad_to_width(&coin_name, 15); - let is_base_coin = - coin.trim().to_lowercase() == block_extension_and_paths().1.trim().to_lowercase(); + let Some(coin) = normalize_asset_or_base(&coin_name) else { + println!("Transfer asset must be the base coin or normalize to 3-15 alphanumeric characters."); + return; + }; + let is_base_coin = coin.trim().eq_ignore_ascii_case(block_extension_and_paths().1.trim()); let value_string = if args.len() > 2 { args[2].clone() diff --git a/src/bin/verify_sign_swap_tx.rs b/src/bin/verify_sign_swap_tx.rs index f15d254..67f5e1d 100644 --- a/src/bin/verify_sign_swap_tx.rs +++ b/src/bin/verify_sign_swap_tx.rs @@ -1,4 +1,5 @@ use blockchain::blocks::swap::UnsignedSwapTransaction; +use blockchain::common::asset_names::padded_normalized_asset_input; use blockchain::common::cli_prompts::{ ask_yes_no_question, prompt_hidden_nonempty, prompt_wallet_path, }; @@ -12,11 +13,12 @@ use blockchain::standalone_tools::vanity_resolver::resolve_wallet_address_input; use blockchain::wallets::structures::Wallet; use blockchain::Value; -// padd the coin to ensure 15 characters -fn pad_to_width(input: &str, width: usize) -> String { - let mut result = String::with_capacity(width); // Pre-allocate string with capacity - let _ = std::fmt::write(&mut result, format_args!("{input: Option { + if input.trim().eq_ignore_ascii_case(base_coin.trim()) { + Some(base_coin.to_string()) + } else { + padded_normalized_asset_input(input.trim()) + } } #[tokio::main] @@ -162,8 +164,14 @@ async fn main() { return; } - let padded_token_name1 = pad_to_width(&token_name1, 15); - let padded_token_name2 = pad_to_width(&token_name2, 15); + let Some(padded_token_name1) = normalize_asset_or_base(&token_name1, &network_coin) else { + println!("Ticker1 must be the base coin or normalize to 3-15 alphanumeric characters."); + return; + }; + let Some(padded_token_name2) = normalize_asset_or_base(&token_name2, &network_coin) else { + println!("Ticker2 must be the base coin or normalize to 3-15 alphanumeric characters."); + return; + }; let unsigned_swap = UnsignedSwapTransaction::new( txtype, diff --git a/src/common/asset_names.rs b/src/common/asset_names.rs new file mode 100644 index 0000000..d37af08 --- /dev/null +++ b/src/common/asset_names.rs @@ -0,0 +1,38 @@ +use crate::common::types::COIN_LENGTH; + +pub fn normalize_asset_identifier(input: &str) -> String { + input + .chars() + .filter(|character| character.is_ascii_alphanumeric()) + .flat_map(char::to_lowercase) + .collect() +} + +pub fn padded_normalized_asset_identifier(input: &str) -> Option { + let normalized = normalize_asset_identifier(input); + if normalized.len() < 3 || normalized.len() > COIN_LENGTH { + return None; + } + + Some(format!("{normalized: Option { + if !input + .chars() + .all(|character| character.is_ascii_alphanumeric() || character.is_ascii_whitespace()) + { + return None; + } + + padded_normalized_asset_identifier(input) +} + +pub fn is_canonical_padded_asset_identifier(input: &str) -> bool { + padded_normalized_asset_identifier(input).is_some_and(|canonical| canonical == input) +} + +pub fn is_canonical_padded_asset_or_base(input: &str, base_coin: &str) -> bool { + input.trim().eq_ignore_ascii_case(base_coin.trim()) + || is_canonical_padded_asset_identifier(input) +} diff --git a/src/common/mod.rs b/src/common/mod.rs index 57fe225..65b22b6 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,4 +1,5 @@ pub mod binary_conversions; +pub mod asset_names; pub mod check_genesis; pub mod cli_prompts; pub mod network_paths_and_settings; diff --git a/src/verifications/async_funcs/verify_burn.rs b/src/verifications/async_funcs/verify_burn.rs index a39d603..62a46e9 100644 --- a/src/verifications/async_funcs/verify_burn.rs +++ b/src/verifications/async_funcs/verify_burn.rs @@ -1,4 +1,5 @@ use crate::blocks::burn::BurnTransaction; +use crate::common::asset_names::is_canonical_padded_asset_identifier; use crate::common::nft_assets::nft_asset_name; use crate::common::types::{BURN_FEE, COIN_LENGTH}; use crate::encode; @@ -51,6 +52,9 @@ impl BurnTransaction { if self.unsigned_burn.coin.len() != COIN_LENGTH { return Err("Coin length is invalid.".to_string()); } + if !is_canonical_padded_asset_identifier(&self.unsigned_burn.coin) { + return Err("Burn asset must be a canonical alphanumeric asset id.".to_string()); + } // The base coin can never be destroyed through the burn path. if self.unsigned_burn.coin.trim().to_lowercase() == BASECOIN.trim().to_lowercase() { diff --git a/src/verifications/async_funcs/verify_create_nft.rs b/src/verifications/async_funcs/verify_create_nft.rs index 9b2d0da..81ac209 100644 --- a/src/verifications/async_funcs/verify_create_nft.rs +++ b/src/verifications/async_funcs/verify_create_nft.rs @@ -1,4 +1,5 @@ use crate::blocks::nft::CreateNftTransaction; +use crate::common::asset_names::is_canonical_padded_asset_identifier; use crate::common::nft_assets::nft_asset_name; use crate::common::types::{COIN_LENGTH, CREATE_NFT_FEE}; use crate::encode; @@ -27,6 +28,11 @@ impl CreateNftTransaction { ) .map_err(|s| s.to_string())?; } + if !is_canonical_padded_asset_identifier(&self.unsigned_create_nft.nft_name) { + return Err( + "NFT name must normalize to 3-15 lowercase alphanumeric characters.".to_string(), + ); + } if is_reserved_base_coin_ticker(&self.unsigned_create_nft.nft_name) { return Err("NFT name is reserved for the base coin.".to_string()); diff --git a/src/verifications/async_funcs/verify_create_token.rs b/src/verifications/async_funcs/verify_create_token.rs index 9be9210..f3e359b 100644 --- a/src/verifications/async_funcs/verify_create_token.rs +++ b/src/verifications/async_funcs/verify_create_token.rs @@ -1,4 +1,5 @@ use crate::blocks::token::CreateTokenTransaction; +use crate::common::asset_names::is_canonical_padded_asset_identifier; use crate::common::types::{COIN_LENGTH, CREATE_TOKEN_FEE}; use crate::encode; use crate::records::memory::mempool::BASECOIN; @@ -27,6 +28,11 @@ impl CreateTokenTransaction { return Err("Ticker length must be 15 characters. Consider padding with empty spaces") .map_err(|s| s.to_string())?; } + if !is_canonical_padded_asset_identifier(&self.unsigned_create_token.ticker) { + return Err( + "Ticker must normalize to 3-15 lowercase alphanumeric characters.".to_string(), + ); + } if is_reserved_base_coin_ticker(&self.unsigned_create_token.ticker) { return Err("Ticker is reserved for the base coin.".to_string()); diff --git a/src/verifications/async_funcs/verify_issue_token.rs b/src/verifications/async_funcs/verify_issue_token.rs index 2a11e21..163c3f3 100644 --- a/src/verifications/async_funcs/verify_issue_token.rs +++ b/src/verifications/async_funcs/verify_issue_token.rs @@ -1,5 +1,6 @@ use crate::blocks::issue_token::IssueTokenTransaction; use crate::blocks::token::CreateTokenTransaction; +use crate::common::asset_names::is_canonical_padded_asset_identifier; use crate::common::network_paths_and_settings::block_extension_and_paths; use crate::common::types::{COIN_LENGTH, ISSUE_TOKEN_FEE}; use crate::records::memory::mempool::BASECOIN; @@ -88,6 +89,11 @@ impl IssueTokenTransaction { .to_string(), ); } + if !is_canonical_padded_asset_identifier(ticker) { + return Err( + "Ticker must normalize to 3-15 lowercase alphanumeric characters.".to_string(), + ); + } // Check reserved keywords not used if reserved_base_coins() diff --git a/src/verifications/async_funcs/verify_lender.rs b/src/verifications/async_funcs/verify_lender.rs index 5375499..6e0e1b7 100644 --- a/src/verifications/async_funcs/verify_lender.rs +++ b/src/verifications/async_funcs/verify_lender.rs @@ -1,4 +1,7 @@ use crate::blocks::loans::LoanContractTransaction; +use crate::common::asset_names::{ + is_canonical_padded_asset_identifier, is_canonical_padded_asset_or_base, +}; use crate::common::types::{COIN_LENGTH, LENDER_FEE}; use crate::encode; use crate::records::memory::mempool::BASECOIN; @@ -91,6 +94,14 @@ impl LoanContractTransaction { ) .map_err(|s| s.to_string())?; } + if !is_canonical_padded_asset_or_base(&loan_coin, &BASECOIN) + || !is_canonical_padded_asset_identifier(&collateral) + { + return Err( + "Loan assets must use the base coin or canonical alphanumeric asset ids." + .to_string(), + ); + } // Loaned assets must already exist as either the network base coin // or a previously created token. diff --git a/src/verifications/async_funcs/verify_swap.rs b/src/verifications/async_funcs/verify_swap.rs index 50a2d37..78e623e 100644 --- a/src/verifications/async_funcs/verify_swap.rs +++ b/src/verifications/async_funcs/verify_swap.rs @@ -1,4 +1,5 @@ use crate::blocks::swap::SwapTransaction; +use crate::common::asset_names::is_canonical_padded_asset_or_base; use crate::common::network_paths_and_settings::block_extension_and_paths; use crate::common::nft_assets::nft_asset_name; use crate::common::types::{COIN_LENGTH, SWAP_FEE}; @@ -75,6 +76,18 @@ impl SwapTransaction { "Ticker lengths are invalid. Consider padding with empty spaces".to_string(), ); } + let network_base_coin_padded = block_extension_and_paths().1; + if !is_canonical_padded_asset_or_base(&self.unsigned_swap.ticker1, &network_base_coin_padded) + || !is_canonical_padded_asset_or_base( + &self.unsigned_swap.ticker2, + &network_base_coin_padded, + ) + { + return Err( + "Swap assets must be the base coin or canonical alphanumeric asset ids." + .to_string(), + ); + } // Build concrete asset names for NFT series swaps before checking // balances and registry existence. @@ -83,7 +96,7 @@ impl SwapTransaction { // The base coin name comes from the active network settings so // mainnet and testnet validate against their own ticker. - let network_base_coin = block_extension_and_paths().1.trim().to_lowercase(); + let network_base_coin = network_base_coin_padded.trim().to_lowercase(); // Numbered NFT swaps are single-item transfers, while non-series // assets must already exist as a token, NFT, or base coin. diff --git a/src/verifications/async_funcs/verify_transfer.rs b/src/verifications/async_funcs/verify_transfer.rs index c5c4ad5..8985beb 100644 --- a/src/verifications/async_funcs/verify_transfer.rs +++ b/src/verifications/async_funcs/verify_transfer.rs @@ -1,4 +1,5 @@ use crate::blocks::transfer::TransferTransaction; +use crate::common::asset_names::is_canonical_padded_asset_or_base; use crate::common::nft_assets::nft_asset_name; use crate::common::types::{minimum_transfer_fee, COIN_LENGTH}; use crate::encode; @@ -138,6 +139,12 @@ impl TransferTransaction { if self.unsigned_transfer.coin.len() != COIN_LENGTH { return Err("Coin length is invalid.".to_string()); } + if !is_canonical_padded_asset_or_base(&self.unsigned_transfer.coin, &BASECOIN) { + return Err( + "Transfer asset must be the base coin or a canonical alphanumeric asset id." + .to_string(), + ); + } // Saved-chain duplicates are rejected by txid even if the mempool // did not already contain the transaction.