use alloc::collections::BTreeMap;
use js_export_macro::js_export;
use miden_client::account::{AccountComponentInterfaceExt, AccountId as NativeAccountId};
use miden_client::agglayer::B2AggNote;
use miden_client::asset::{AssetAmount, FungibleAsset};
use miden_client::crypto::FeltRng;
use miden_client::note::{
BlockNumber,
Note as NativeNote,
NoteAssets as NativeNoteAssets,
PswapNote,
};
#[cfg(feature = "testing")]
use miden_client::transaction::LocalTransactionProver;
use miden_client::transaction::{
AccountComponentInterface,
ChainAnchorError,
ForeignAccount as NativeForeignAccount,
PaymentNoteDescription,
ProvenTransaction as NativeProvenTransaction,
PswapTransactionData,
SwapTransactionData,
TransactionExecutorError,
TransactionRequest as NativeTransactionRequest,
TransactionRequestBuilder as NativeTransactionRequestBuilder,
};
use miden_client::{Client, ClientError, Word as NativeWord};
use crate::models::NoteType;
use crate::models::account_id::AccountId;
use crate::models::advice_inputs::AdviceInputs;
use crate::models::chain_anchor::ChainAnchor;
use crate::models::eth_address::EthAddress;
use crate::models::felt::Felt;
use crate::models::miden_arrays::{FeltArray, ForeignAccountArray};
use crate::models::note::Note;
use crate::models::proven_transaction::ProvenTransaction;
use crate::models::provers::TransactionProver;
use crate::models::transaction_id::TransactionId;
use crate::models::transaction_request::TransactionRequest;
use crate::models::transaction_request::transaction_request_builder::TransactionRequestBuilder;
use crate::models::transaction_result::TransactionResult;
use crate::models::transaction_script::TransactionScript;
use crate::models::transaction_store_update::TransactionStoreUpdate;
use crate::models::transaction_summary::TransactionSummary;
use crate::platform::{
JsBytes,
JsErr,
from_str_err,
from_str_err_with_code,
js_u64_to_u64,
maybe_wrap_send,
};
use crate::utils::deserialize_from_bytes;
use crate::{WebClient, js_error_with_context};
#[js_export]
impl WebClient {
#[js_export(js_name = "newMintTransactionRequest")]
pub async fn new_mint_transaction_request(
&self,
target_account_id: &AccountId,
faucet_id: &AccountId,
note_type: NoteType,
amount: JsU64,
) -> Result<TransactionRequest, JsErr> {
let amount = js_u64_to_u64(amount);
let fungible_asset = FungibleAsset::new(faucet_id.into(), amount)
.map_err(|err| js_error_with_context(err, "failed to create fungible asset"))?;
let mint_transaction_request = {
let mut guard = self.get_mut_inner().await;
let client = guard.as_mut().ok_or_else(|| {
from_str_err("Client not initialized while generating transaction request")
})?;
let builder = fee_aware_builder(client, faucet_id.into()).await?;
builder
.build_mint_fungible_asset(
fungible_asset,
target_account_id.into(),
note_type.into(),
client.rng(),
)
.map_err(|err| {
js_error_with_context(err, "failed to create mint transaction request")
})?
};
Ok(mint_transaction_request.into())
}
#[js_export(js_name = "newSendTransactionRequest")]
#[allow(clippy::too_many_arguments)]
pub async fn new_send_transaction_request(
&self,
sender_account_id: &AccountId,
target_account_id: &AccountId,
faucet_id: &AccountId,
note_type: NoteType,
amount: JsU64,
recall_height: Option<u32>,
timelock_height: Option<u32>,
) -> Result<TransactionRequest, JsErr> {
let mut guard = self.get_mut_inner().await;
let client = guard.as_mut().ok_or_else(|| {
from_str_err("Client not initialized while generating transaction request")
})?;
let amount = js_u64_to_u64(amount);
let fungible_asset = FungibleAsset::new(faucet_id.into(), amount)
.map_err(|err| js_error_with_context(err, "failed to create fungible asset"))?;
let mut payment_description = PaymentNoteDescription::new(
vec![fungible_asset.into()],
sender_account_id.into(),
target_account_id.into(),
);
if let Some(recall_height) = recall_height {
payment_description =
payment_description.with_reclaim_height(BlockNumber::from(recall_height));
}
if let Some(height) = timelock_height {
payment_description =
payment_description.with_timelock_height(BlockNumber::from(height));
}
let builder = fee_aware_builder(client, sender_account_id.into()).await?;
let send_transaction_request = builder
.build_pay_to_id(payment_description, note_type.into(), client.rng())
.map_err(|err| {
js_error_with_context(err, "failed to create send transaction request")
})?;
Ok(send_transaction_request.into())
}
#[js_export(js_name = "newB2AggTransactionRequest")]
#[allow(clippy::too_many_arguments)]
pub async fn new_b2agg_transaction_request(
&self,
sender_account_id: &AccountId,
bridge_account_id: &AccountId,
faucet_id: &AccountId,
amount: JsU64,
destination_network: u32,
destination_address: &EthAddress,
) -> Result<TransactionRequest, JsErr> {
let mut guard = self.get_mut_inner().await;
let client = guard.as_mut().ok_or_else(|| {
from_str_err("Client not initialized while generating transaction request")
})?;
let amount = js_u64_to_u64(amount);
let fungible_asset = FungibleAsset::new(faucet_id.into(), amount)
.map_err(|err| js_error_with_context(err, "failed to create fungible asset"))?;
let note_assets = NativeNoteAssets::new(vec![fungible_asset.into()])
.map_err(|err| js_error_with_context(err, "failed to create b2agg note assets"))?;
let b2agg_note = B2AggNote::create(
destination_network,
destination_address.into(),
note_assets,
bridge_account_id.into(),
sender_account_id.into(),
client.rng(),
)
.map_err(|err| js_error_with_context(err, "failed to create b2agg note"))?;
let builder = fee_aware_builder(client, sender_account_id.into()).await?;
let b2agg_transaction_request =
builder.own_output_notes(vec![b2agg_note]).build().map_err(|err| {
js_error_with_context(err, "failed to create b2agg transaction request")
})?;
Ok(b2agg_transaction_request.into())
}
#[js_export(js_name = "newSwapTransactionRequest")]
#[allow(clippy::too_many_arguments)]
pub async fn new_swap_transaction_request(
&self,
sender_account_id: &AccountId,
offered_asset_faucet_id: &AccountId,
offered_asset_amount: JsU64,
requested_asset_faucet_id: &AccountId,
requested_asset_amount: JsU64,
note_type: NoteType,
payback_note_type: NoteType,
) -> Result<TransactionRequest, JsErr> {
let offered_asset_amount = js_u64_to_u64(offered_asset_amount);
let offered_fungible_asset =
FungibleAsset::new(offered_asset_faucet_id.into(), offered_asset_amount)
.map_err(|err| {
js_error_with_context(err, "failed to create offered fungible asset")
})?
.into();
let requested_asset_amount = js_u64_to_u64(requested_asset_amount);
let requested_fungible_asset =
FungibleAsset::new(requested_asset_faucet_id.into(), requested_asset_amount)
.map_err(|err| {
js_error_with_context(err, "failed to create requested fungible asset")
})?
.into();
let swap_transaction_data = SwapTransactionData::new(
sender_account_id.into(),
offered_fungible_asset,
requested_fungible_asset,
);
let swap_transaction_request = {
let mut guard = self.get_mut_inner().await;
let client = guard.as_mut().ok_or_else(|| {
from_str_err("Client not initialized while generating transaction request")
})?;
let builder = fee_aware_builder(client, sender_account_id.into()).await?;
builder
.build_swap(
&swap_transaction_data,
note_type.into(),
payback_note_type.into(),
client.rng(),
)
.map_err(|err| {
js_error_with_context(err, "failed to create swap transaction request")
})?
};
Ok(swap_transaction_request.into())
}
#[js_export(js_name = "newPswapCreateTransactionRequest")]
#[allow(clippy::too_many_arguments)]
pub async fn new_pswap_create_transaction_request(
&self,
creator_account_id: &AccountId,
offered_asset_faucet_id: &AccountId,
offered_asset_amount: JsU64,
requested_asset_faucet_id: &AccountId,
requested_asset_amount: JsU64,
note_type: NoteType,
payback_note_type: NoteType,
) -> Result<TransactionRequest, JsErr> {
let offered_asset_amount = js_u64_to_u64(offered_asset_amount);
let offered_fungible_asset =
FungibleAsset::new(offered_asset_faucet_id.into(), offered_asset_amount).map_err(
|err| js_error_with_context(err, "failed to create offered fungible asset"),
)?;
let requested_asset_amount = js_u64_to_u64(requested_asset_amount);
let requested_fungible_asset =
FungibleAsset::new(requested_asset_faucet_id.into(), requested_asset_amount).map_err(
|err| js_error_with_context(err, "failed to create requested fungible asset"),
)?;
let pswap_transaction_data = PswapTransactionData::new(
creator_account_id.into(),
offered_fungible_asset,
requested_fungible_asset,
);
let pswap_transaction_request = {
let mut guard = self.get_mut_inner().await;
let client = guard.as_mut().ok_or_else(|| {
from_str_err("Client not initialized while generating transaction request")
})?;
let builder = fee_aware_builder(client, creator_account_id.into()).await?;
builder
.build_pswap_create(
&pswap_transaction_data,
note_type.into(),
payback_note_type.into(),
None,
client.rng(),
)
.map_err(|err| {
js_error_with_context(err, "failed to create PSWAP create transaction request")
})?
};
Ok(pswap_transaction_request.into())
}
#[js_export(js_name = "newPswapConsumeTransactionRequest")]
pub async fn new_pswap_consume_transaction_request(
&self,
pswap_note: &Note,
consumer_account_id: &AccountId,
account_fill_amount: JsU64,
note_fill_amount: JsU64,
) -> Result<TransactionRequest, JsErr> {
let native_pswap_note: NativeNote = pswap_note.into();
let pswap = PswapNote::try_from(&native_pswap_note)
.map_err(|err| js_error_with_context(err, "invalid PSWAP note"))?;
let account_fill_amount = AssetAmount::new(js_u64_to_u64(account_fill_amount))
.map_err(|err| js_error_with_context(err, "invalid account fill amount"))?;
let note_fill_amount = AssetAmount::new(js_u64_to_u64(note_fill_amount))
.map_err(|err| js_error_with_context(err, "invalid note fill amount"))?;
let total_fill_amount = (account_fill_amount + note_fill_amount)
.map_err(|err| js_error_with_context(err, "invalid total fill amount"))?;
if total_fill_amount == AssetAmount::ZERO {
return Err(from_str_err("Fill amount must be greater than 0"));
}
let requested_amount = pswap.storage().min_requested_asset().amount();
if total_fill_amount > requested_amount {
return Err(from_str_err(&format!(
"Fill amount {total_fill_amount} exceeds requested amount {requested_amount}"
)));
}
let pswap_transaction_request = {
let mut guard = self.get_mut_inner().await;
let client = guard.as_mut().ok_or_else(|| {
from_str_err("Client not initialized while generating transaction request")
})?;
let builder = fee_aware_builder(client, consumer_account_id.into()).await?;
builder
.build_pswap_consume(
&native_pswap_note,
consumer_account_id.into(),
account_fill_amount,
note_fill_amount,
)
.map_err(|err| {
js_error_with_context(err, "failed to create PSWAP consume transaction request")
})?
};
Ok(pswap_transaction_request.into())
}
#[js_export(js_name = "newPswapCancelTransactionRequest")]
pub async fn new_pswap_cancel_transaction_request(
&self,
pswap_note: &Note,
creator_account_id: &AccountId,
) -> Result<TransactionRequest, JsErr> {
let native_pswap_note: NativeNote = pswap_note.into();
let pswap_transaction_request = {
let mut guard = self.get_mut_inner().await;
let client = guard.as_mut().ok_or_else(|| {
from_str_err("Client not initialized while generating transaction request")
})?;
let builder = fee_aware_builder(client, creator_account_id.into()).await?;
builder
.build_pswap_cancel(native_pswap_note, creator_account_id.into())
.map_err(|err| {
js_error_with_context(err, "failed to create PSWAP cancel transaction request")
})?
};
Ok(pswap_transaction_request.into())
}
#[js_export(js_name = "submitNewTransaction")]
pub async fn submit_new_transaction(
&self,
account_id: &AccountId,
transaction_request: &TransactionRequest,
) -> Result<TransactionId, JsErr> {
let transaction_result = self.execute_transaction(account_id, transaction_request).await?;
let tx_id = transaction_result.id();
let proven_transaction = self.prove_transaction(&transaction_result, None).await?;
let submission_height =
self.submit_proven_transaction(&proven_transaction, &transaction_result).await?;
self.apply_transaction(&transaction_result, submission_height).await?;
Ok(tx_id)
}
#[js_export(js_name = "submitNewTransactionWithProver")]
pub async fn submit_new_transaction_with_prover(
&self,
account_id: &AccountId,
transaction_request: &TransactionRequest,
prover: &TransactionProver,
) -> Result<TransactionId, JsErr> {
let transaction_result = self.execute_transaction(account_id, transaction_request).await?;
let tx_id = transaction_result.id();
let proven_transaction =
self.prove_transaction(&transaction_result, Some(prover.clone())).await?;
let submission_height =
self.submit_proven_transaction(&proven_transaction, &transaction_result).await?;
self.apply_transaction(&transaction_result, submission_height).await?;
Ok(tx_id)
}
#[js_export(js_name = "submitNewTransactionBatch")]
pub async fn submit_new_transaction_batch(
&self,
account_id: &AccountId,
transaction_requests: Vec<JsBytes>,
) -> Result<u32, JsErr> {
let mut guard = self.get_mut_inner().await;
let client = guard.as_mut().ok_or_else(|| from_str_err("Client not initialized"))?;
let native_account_id: miden_client::account::AccountId = account_id.into();
let mut native_reqs: Vec<NativeTransactionRequest> =
Vec::with_capacity(transaction_requests.len());
for bytes in &transaction_requests {
let req = deserialize_from_bytes::<NativeTransactionRequest>(bytes).map_err(|err| {
from_str_err(&format!("failed to deserialize transaction request: {err:?}"))
})?;
native_reqs.push(req);
}
let mut builder = client.new_transaction_batch();
for native_req in native_reqs {
maybe_wrap_send(Box::pin(builder.push(native_account_id, native_req)))
.await
.map_err(|err| js_error_with_context(err, "failed to push transaction to batch"))?;
}
maybe_wrap_send(Box::pin(builder.submit()))
.await
.map(|block_number| block_number.as_u32())
.map_err(|err| js_error_with_context(err, "failed to submit transaction batch"))
}
#[js_export(js_name = "executeTransaction")]
pub async fn execute_transaction(
&self,
account_id: &AccountId,
transaction_request: &TransactionRequest,
) -> Result<TransactionResult, JsErr> {
let mut guard = self.get_mut_inner().await;
let client = guard.as_mut().ok_or_else(|| from_str_err("Client not initialized"))?;
let native_request: NativeTransactionRequest = transaction_request.into();
let fut = Box::pin(client.execute_transaction(account_id.into(), native_request));
maybe_wrap_send(fut)
.await
.map(TransactionResult::from)
.map_err(|err| js_error_with_context(err, "failed to execute transaction"))
}
#[js_export(js_name = "chainAnchorForRequest")]
pub async fn chain_anchor_for_request(
&self,
transaction_request: &TransactionRequest,
) -> Result<ChainAnchor, JsErr> {
let mut guard = self.get_mut_inner().await;
let client = guard.as_mut().ok_or_else(|| from_str_err("Client not initialized"))?;
let native_request: NativeTransactionRequest = transaction_request.into();
let fut = Box::pin(client.chain_anchor_for_request(&native_request));
maybe_wrap_send(fut)
.await
.map(ChainAnchor::from)
.map_err(|err| map_anchor_err(err, "failed to capture chain anchor"))
}
#[js_export(js_name = "executeTransactionAt")]
pub async fn execute_transaction_at(
&self,
account_id: &AccountId,
transaction_request: &TransactionRequest,
anchor: &ChainAnchor,
) -> Result<TransactionResult, JsErr> {
let mut guard = self.get_mut_inner().await;
let client = guard.as_mut().ok_or_else(|| from_str_err("Client not initialized"))?;
let native_request: NativeTransactionRequest = transaction_request.into();
let fut = Box::pin(client.execute_transaction_at(
account_id.into(),
native_request,
anchor.into(),
));
maybe_wrap_send(fut)
.await
.map(TransactionResult::from)
.map_err(|err| map_anchor_err(err, "failed to execute transaction at anchor"))
}
#[js_export(js_name = "executeForSummaryAt")]
pub async fn execute_for_summary_at(
&self,
account_id: &AccountId,
transaction_request: &TransactionRequest,
anchor: &ChainAnchor,
) -> Result<TransactionSummary, JsErr> {
let mut guard = self.get_mut_inner().await;
let client = guard.as_mut().ok_or_else(|| from_str_err("Client not initialized"))?;
let native_request: NativeTransactionRequest = transaction_request.into();
let fut = Box::pin(client.execute_transaction_at(
account_id.into(),
native_request,
anchor.into(),
));
match maybe_wrap_send(fut).await {
Ok(_) => Err(from_str_err_with_code(
"transaction is already fully authorized, so no transaction summary was \
produced during execution; submit it with executeTransactionAt against the \
same anchor instead",
"TRANSACTION_ALREADY_AUTHORIZED",
)),
Err(ClientError::TransactionExecutorError(TransactionExecutorError::Unauthorized(
summary,
))) => Ok(TransactionSummary::from(*summary)),
Err(err) => Err(map_anchor_err(err, "failed to execute transaction at anchor")),
}
}
#[js_export(js_name = "executeForSummary")]
pub async fn execute_for_summary(
&self,
account_id: &AccountId,
transaction_request: &TransactionRequest,
) -> Result<TransactionSummary, JsErr> {
let mut guard = self.get_mut_inner().await;
let client = guard.as_mut().ok_or_else(|| from_str_err("Client not initialized"))?;
let native_request: NativeTransactionRequest = transaction_request.into();
let fut = Box::pin(client.execute_transaction(account_id.into(), native_request));
match maybe_wrap_send(fut).await {
Ok(_) => Err(from_str_err_with_code(
"transaction is already fully authorized, so no transaction summary was \
produced during execution; submit it with execute instead",
"TRANSACTION_ALREADY_AUTHORIZED",
)),
Err(ClientError::TransactionExecutorError(TransactionExecutorError::Unauthorized(
summary,
))) => Ok(TransactionSummary::from(*summary)),
Err(err) => Err(js_error_with_context(err, "failed to execute transaction")),
}
}
#[js_export(js_name = "executeProgram")]
pub async fn execute_program(
&self,
account_id: &AccountId,
tx_script: &TransactionScript,
advice_inputs: &AdviceInputs,
foreign_accounts: ForeignAccountArray,
) -> Result<FeltArray, JsErr> {
let mut guard = self.get_mut_inner().await;
let client = guard.as_mut().ok_or_else(|| from_str_err("Client not initialized"))?;
let foreign_accounts_vec: Vec<crate::models::foreign_account::ForeignAccount> =
foreign_accounts.into();
let foreign_accounts_map: BTreeMap<NativeAccountId, NativeForeignAccount> =
foreign_accounts_vec
.into_iter()
.map(|a| {
let fa: NativeForeignAccount = a.into();
(fa.account_id(), fa)
})
.collect();
let result = client
.execute_program(
account_id.into(),
tx_script.into(),
advice_inputs.into(),
foreign_accounts_map,
)
.await
.map_err(|err| js_error_with_context(err, "failed to execute program"))?;
let felt_vec: Vec<Felt> = result.iter().map(|f| Felt::from(*f)).collect();
Ok(felt_vec.into())
}
#[js_export(js_name = "proveTransaction")]
pub async fn prove_transaction(
&self,
transaction_result: &TransactionResult,
prover: Option<TransactionProver>,
) -> Result<ProvenTransaction, JsErr> {
#[cfg(feature = "testing")]
if prover.is_none() && self.mock_rpc_api.lock().await.is_some() {
return LocalTransactionProver::default()
.prove_dummy(transaction_result.native().executed_transaction().clone())
.map(Into::into)
.map_err(|err| js_error_with_context(err, "failed to prove transaction"));
}
let prover_arc = if let Some(custom_prover) = prover {
custom_prover.get_prover()
} else {
let mut guard = self.get_mut_inner().await;
let client = guard.as_mut().ok_or_else(|| from_str_err("Client not initialized"))?;
client.prover()
};
let executed_transaction = transaction_result.native().executed_transaction().clone();
let fut = Box::pin(async move { prover_arc.prove(executed_transaction.into()).await });
maybe_wrap_send(fut)
.await
.map(Into::into)
.map_err(|err| js_error_with_context(err, "failed to prove transaction"))
}
#[js_export(js_name = "submitProvenTransaction")]
pub async fn submit_proven_transaction(
&self,
proven_transaction: &ProvenTransaction,
transaction_result: &TransactionResult,
) -> Result<u32, JsErr> {
let mut guard = self.get_mut_inner().await;
let client = guard.as_mut().ok_or_else(|| from_str_err("Client not initialized"))?;
let native_proven: NativeProvenTransaction = proven_transaction.clone().into();
client
.submit_proven_transaction(native_proven, transaction_result.native())
.await
.map(|block_number| block_number.as_u32())
.map_err(|err| js_error_with_context(err, "failed to submit proven transaction"))
}
#[js_export(js_name = "applyTransaction")]
pub async fn apply_transaction(
&self,
transaction_result: &TransactionResult,
submission_height: u32,
) -> Result<TransactionStoreUpdate, JsErr> {
let mut guard = self.get_mut_inner().await;
let client = guard.as_mut().ok_or_else(|| from_str_err("Client not initialized"))?;
let height = BlockNumber::from(submission_height);
let fut =
Box::pin(client.get_transaction_store_update(transaction_result.native(), height));
let update = maybe_wrap_send(fut)
.await
.map(TransactionStoreUpdate::from)
.map_err(|err| js_error_with_context(err, "failed to build transaction update"))?;
let fut = Box::pin(client.apply_transaction(transaction_result.native(), height));
maybe_wrap_send(fut)
.await
.map_err(|err| js_error_with_context(err, "failed to apply transaction result"))?;
Ok(update)
}
#[js_export(js_name = "newConsumeTransactionRequest")]
pub async fn new_consume_transaction_request(
&self,
list_of_notes: Vec<Note>,
consuming_account_id: &AccountId,
) -> Result<TransactionRequest, JsErr> {
let mut guard = self.get_mut_inner().await;
let client = guard.as_mut().ok_or_else(|| {
from_str_err("Client not initialized while generating consume transaction request")
})?;
let consume_transaction_request = {
let native_notes = list_of_notes
.into_iter()
.map(NativeNote::try_from)
.collect::<Result<Vec<_>, _>>()
.map_err(|err| {
from_str_err(&format!("Failed to convert note to native note: {err}"))
})?;
let builder = fee_aware_builder(client, consuming_account_id.into()).await?;
builder.build_consume_notes(native_notes).map_err(|err| {
from_str_err(&format!("Failed to create Consume Transaction Request: {err}"))
})?
};
Ok(consume_transaction_request.into())
}
#[js_export(js_name = "feeAwareTransactionRequestBuilder")]
pub async fn fee_aware_transaction_request_builder(
&self,
account_id: &AccountId,
) -> Result<TransactionRequestBuilder, JsErr> {
let mut guard = self.get_mut_inner().await;
let client = guard.as_mut().ok_or_else(|| {
from_str_err("Client not initialized while creating a transaction request builder")
})?;
let builder = fee_aware_builder(client, account_id.into()).await?;
Ok(TransactionRequestBuilder::from_native(builder))
}
}
fn map_anchor_err(err: ClientError, context: &'static str) -> JsErr {
match err {
ClientError::ChainAnchorError(anchor_err) => {
let lost_to_concurrent_sync = matches!(
anchor_err,
ChainAnchorError::ChainLengthMismatch { .. }
| ChainAnchorError::ChainCommitmentMismatch { .. }
);
let message = if lost_to_concurrent_sync {
format!(
"{context}: {anchor_err}; a sync may have landed during capture, so retrying \
is usually the fix"
)
} else {
format!("{context}: {anchor_err}")
};
from_str_err_with_code(&message, "INVALID_CHAIN_ANCHOR")
},
err => js_error_with_context(err, context),
}
}
async fn standard_auth_components(
client: &Client<crate::ClientAuth>,
account_id: NativeAccountId,
) -> Result<Option<Vec<AccountComponentInterface>>, JsErr> {
let Some(code) = client.get_account_code(account_id).await.map_err(|err| {
js_error_with_context(
err,
&format!(
"failed to read the code of account {account_id} to classify its auth component"
),
)
})?
else {
return Ok(None);
};
Ok(Some(
AccountComponentInterface::from_procedures(code.procedures())
.into_iter()
.filter(|component| {
matches!(
component,
AccountComponentInterface::AuthSingleSig
| AccountComponentInterface::AuthMultisig
| AccountComponentInterface::AuthMultisigSmart
| AccountComponentInterface::AuthGuardedMultisig
| AccountComponentInterface::AuthNoAuth
| AccountComponentInterface::AuthNetworkAccount
)
})
.collect(),
))
}
async fn requires_caller_chosen_salt(
client: &Client<crate::ClientAuth>,
account_id: NativeAccountId,
) -> Result<bool, JsErr> {
let Some(components) = standard_auth_components(client, account_id).await? else {
return Ok(false);
};
if components
.iter()
.any(|component| matches!(component, AccountComponentInterface::AuthSingleSig))
{
return Ok(false);
}
Ok(components.iter().any(|component| {
matches!(
component,
AccountComponentInterface::AuthMultisig
| AccountComponentInterface::AuthMultisigSmart
| AccountComponentInterface::AuthGuardedMultisig
)
}))
}
async fn caller_chosen_fee_conversion_salt(
client: &mut Client<crate::ClientAuth>,
executing_account_id: NativeAccountId,
) -> Result<Option<NativeWord>, JsErr> {
let header = client.get_latest_block_header().await.map_err(|err| {
js_error_with_context(
err,
&format!(
"failed to read fee parameters from the latest block header while preparing a \
request for account {executing_account_id}"
),
)
})?;
if header.fee_parameters().verification_base_fee() == 0 {
return Ok(None);
}
if !requires_caller_chosen_salt(client, executing_account_id).await? {
return Ok(None);
}
Ok(Some(client.rng().draw_word()))
}
async fn fee_aware_builder(
client: &mut Client<crate::ClientAuth>,
executing_account_id: NativeAccountId,
) -> Result<NativeTransactionRequestBuilder, JsErr> {
let mut builder = NativeTransactionRequestBuilder::new();
if let Some(salt) = caller_chosen_fee_conversion_salt(client, executing_account_id).await? {
builder = builder.fee_conversion_salt(salt);
}
Ok(builder)
}