casper_client/cli/
deploy.rs

1//! Functions facilitating sending of [`Deploy`]s to the network
2
3use casper_types::{
4    account::AccountHash, ActivationPoint, AsymmetricType, CoreConfig, Deploy,
5    ExecutableDeployItem, HashAddr, HighwayConfig, Key, ProtocolVersion, PublicKey, RuntimeArgs,
6    StorageCosts, SystemConfig, TransactionConfig, TransferTarget, UIntParseError, URef,
7    VacancyConfig, WasmConfig, U512,
8};
9use serde::{Deserialize, Serialize};
10
11use super::{
12    get_block, parse, query_global_state, transaction::get_maybe_secret_key, CliError,
13    DeployStrParams, PaymentStrParams, SessionStrParams,
14};
15use crate::{
16    cli::DeployBuilder,
17    rpcs::results::{PutDeployResult, SpeculativeExecResult},
18    SuccessResponse, MAX_SERIALIZED_SIZE_OF_DEPLOY,
19};
20
21const DEFAULT_GAS_PRICE: u64 = 1;
22
23#[derive(PartialEq, Eq, Serialize, Deserialize, Debug)]
24// Disallow unknown fields to ensure config files and command-line overrides contain valid keys.
25#[serde(deny_unknown_fields)]
26struct TomlNetwork {
27    name: String,
28    maximum_net_message_size: u32,
29}
30
31#[derive(PartialEq, Eq, Serialize, Deserialize, Debug)]
32// Disallow unknown fields to ensure config files and command-line overrides contain valid keys.
33#[serde(deny_unknown_fields)]
34struct TomlProtocol {
35    version: ProtocolVersion,
36    hard_reset: bool,
37    activation_point: ActivationPoint,
38}
39
40/// A chainspec configuration as laid out in the TOML-encoded configuration file.
41#[derive(PartialEq, Eq, Serialize, Deserialize, Debug)]
42// Disallow unknown fields to ensure config files and command-line overrides contain valid keys.
43#[serde(deny_unknown_fields)]
44pub(super) struct TomlChainspec {
45    protocol: TomlProtocol,
46    network: TomlNetwork,
47    core: CoreConfig,
48    transactions: TransactionConfig,
49    highway: HighwayConfig,
50    wasm: WasmConfig,
51    system_costs: SystemConfig,
52    vacancy: VacancyConfig,
53    storage_costs: StorageCosts,
54}
55
56pub(crate) async fn do_withdraw_amount_checks(
57    node_address: &str,
58    verbosity_level: u64,
59    public_key: PublicKey,
60    amount: U512,
61    min_bid_override: bool,
62) -> Result<(), CliError> {
63    let chainspec_bytes = crate::cli::get_chainspec("", node_address, verbosity_level)
64        .await?
65        .result
66        .chainspec_bytes;
67
68    let chainspec_as_str = std::str::from_utf8(chainspec_bytes.chainspec_bytes())
69        .map_err(|_| CliError::FailedToParseChainspecBytes)?;
70    let toml_chainspec: TomlChainspec =
71        toml::from_str(chainspec_as_str).map_err(|_| CliError::FailedToParseChainspecBytes)?;
72
73    let minimum_validator_bid = toml_chainspec.core.minimum_bid_amount;
74
75    match crate::cli::get_auction_info("", node_address, verbosity_level, "")
76        .await?
77        .result
78        .auction_state
79        .bids()
80        .find(|(bid_key, _bid)| **bid_key == public_key)
81    {
82        Some((_, bid)) => {
83            let staked_amount = *bid.staked_amount();
84            let remainder = staked_amount.saturating_sub(amount);
85            if remainder < U512::from(minimum_validator_bid) {
86                if !min_bid_override {
87                    return Err(CliError::ReducedStakeBelowMinAmount);
88                } else {
89                    println!("[WARN] Execution of this withdraw bid will result in unbonding of all stake")
90                }
91            }
92        }
93        None => return Err(CliError::FailedToGetAuctionState),
94    };
95
96    Ok(())
97}
98
99async fn check_auction_state_for_withdraw(
100    node_address: &str,
101    verbosity_level: u64,
102    hash_addr: HashAddr,
103    entry_point_name: String,
104    runtime_args: &RuntimeArgs,
105    min_bid_override: bool,
106) -> Result<(), CliError> {
107    // Best guess on the entry point name
108    if entry_point_name == *"withdraw_bid" {
109        let state_root_hash = *get_block("", node_address, 0, "")
110            .await?
111            .result
112            .block_with_signatures
113            .ok_or_else(|| CliError::FailedToGetStateRootHash)?
114            .block
115            .state_root_hash();
116        let encoded_hash = base16::encode_lower(&state_root_hash);
117        let registry =
118            crate::cli::get_system_hash_registry(node_address, verbosity_level, &encoded_hash)
119                .await?;
120        let auction_hash_addr = *registry
121            .get("auction")
122            .ok_or_else(|| CliError::MissingAuctionHash)?;
123        // First check if we are calling the auction.
124        if auction_hash_addr == hash_addr {
125            // Now parse the args for the amount to do the value check.
126        } else {
127            // check if the hash addr matches the package hash addr on the contract itself.
128            let key = Key::Hash(auction_hash_addr);
129            let package_addr = query_global_state(
130                "",
131                node_address,
132                verbosity_level,
133                "",
134                &encoded_hash,
135                &key.to_formatted_string(),
136                "",
137            )
138            .await?
139            .result
140            .stored_value
141            .as_contract()
142            .ok_or_else(|| CliError::FailedToGetSystemHashRegistry)?
143            .contract_package_hash()
144            .value();
145            if package_addr != hash_addr {
146                return Ok(());
147            }
148        }
149        let amount = runtime_args
150            .get("amount")
151            .ok_or_else(|| CliError::InvalidCLValue("count not parse amount".to_string()))?
152            .to_t::<U512>()
153            .map_err(|err| CliError::InvalidCLValue(err.to_string()))?;
154        let public_key = runtime_args
155            .get("public_key")
156            .ok_or_else(|| CliError::InvalidCLValue("count not parse amount".to_string()))?
157            .to_t::<PublicKey>()
158            .map_err(|err| CliError::InvalidCLValue(err.to_string()))?;
159        return do_withdraw_amount_checks(node_address, 0, public_key, amount, min_bid_override)
160            .await;
161    }
162    Ok(())
163}
164
165async fn do_deploy_checks(
166    node_address: &str,
167    min_bid_override: bool,
168    deploy: &Deploy,
169) -> Result<(), CliError> {
170    let session = deploy.session();
171    let state_root_hash = *get_block("", node_address, 0, "")
172        .await?
173        .result
174        .block_with_signatures
175        .ok_or_else(|| CliError::FailedToGetStateRootHash)?
176        .block
177        .state_root_hash();
178    let encoded_hash = base16::encode_lower(&state_root_hash);
179    match session {
180        ExecutableDeployItem::ModuleBytes { .. } | ExecutableDeployItem::Transfer { .. } => Ok(()),
181        ExecutableDeployItem::StoredContractByHash {
182            entry_point,
183            hash,
184            args,
185        } => {
186            let hash_addr = hash.value();
187            check_auction_state_for_withdraw(
188                node_address,
189                0,
190                hash_addr,
191                entry_point.clone(),
192                args,
193                min_bid_override,
194            )
195            .await
196        }
197        ExecutableDeployItem::StoredContractByName {
198            name,
199            entry_point,
200            args,
201        } => {
202            let account = Key::Account(deploy.account().to_account_hash());
203            let cl_value = query_global_state(
204                "",
205                node_address,
206                0,
207                "",
208                &encoded_hash,
209                &account.to_formatted_string(),
210                "",
211            )
212            .await?
213            .result
214            .stored_value
215            .into_account()
216            .ok_or_else(|| CliError::InvalidCLValue("unable to parse as cl _value".to_string()))?;
217            let key = cl_value.named_keys().get(name);
218            match key {
219                Some(key) => {
220                    let hash_addr = match *key {
221                        Key::Hash(addr) => addr,
222                        Key::SmartContract(addr) => addr,
223                        _ => return Ok(()),
224                    };
225                    check_auction_state_for_withdraw(
226                        node_address,
227                        0,
228                        hash_addr,
229                        entry_point.clone(),
230                        args,
231                        min_bid_override,
232                    )
233                    .await
234                }
235                None => {
236                    println!("unable to get named key skipping withdrawal checks");
237                    Ok(())
238                }
239            }
240        }
241        ExecutableDeployItem::StoredVersionedContractByHash {
242            entry_point,
243            hash,
244            args,
245            ..
246        } => {
247            let hash_addr = hash.value();
248            check_auction_state_for_withdraw(
249                node_address,
250                0,
251                hash_addr,
252                entry_point.clone(),
253                args,
254                min_bid_override,
255            )
256            .await
257        }
258        ExecutableDeployItem::StoredVersionedContractByName {
259            name,
260            entry_point,
261            args,
262            ..
263        } => {
264            let account = Key::Account(deploy.account().to_account_hash());
265            let account = query_global_state(
266                "",
267                node_address,
268                0,
269                "",
270                &encoded_hash,
271                &account.to_formatted_string(),
272                "",
273            )
274            .await?
275            .result
276            .stored_value
277            .into_account()
278            .ok_or_else(|| CliError::InvalidCLValue("unable to parse as cl _value".to_string()))?;
279            let key = account.named_keys().get(name);
280            match key {
281                Some(key) => {
282                    let hash_addr = match *key {
283                        Key::Hash(addr) => addr,
284                        Key::SmartContract(addr) => addr,
285                        _ => return Ok(()),
286                    };
287                    check_auction_state_for_withdraw(
288                        node_address,
289                        0,
290                        hash_addr,
291                        entry_point.clone(),
292                        args,
293                        min_bid_override,
294                    )
295                    .await
296                }
297                None => {
298                    println!("unable to get named key skipping withdrawal checks");
299                    Ok(())
300                }
301            }
302        }
303    }
304}
305
306/// Creates a [`Deploy`] and sends it to the network for execution.
307///
308/// For details of the parameters, see [the module docs](crate::cli#common-parameters) or the docs
309/// of the individual parameter types.
310pub async fn put_deploy(
311    maybe_rpc_id: &str,
312    node_address: &str,
313    verbosity_level: u64,
314    deploy_params: DeployStrParams<'_>,
315    session_params: SessionStrParams<'_>,
316    payment_params: PaymentStrParams<'_>,
317) -> Result<SuccessResponse<PutDeployResult>, CliError> {
318    let rpc_id = parse::rpc_id(maybe_rpc_id);
319    let verbosity = parse::verbosity(verbosity_level);
320    let deploy = with_payment_and_session(deploy_params, payment_params, session_params, false)?;
321    do_deploy_checks(node_address, true, &deploy).await?;
322    #[allow(deprecated)]
323    crate::put_deploy(rpc_id, node_address, verbosity, deploy)
324        .await
325        .map_err(CliError::from)
326}
327
328/// Creates a [`Deploy`] and sends it to the network for execution.
329///
330/// For details of the parameters, see [the module docs](crate::cli#common-parameters) or the docs
331/// of the individual parameter types.
332pub async fn put_deploy_with_min_bid_override(
333    maybe_rpc_id: &str,
334    node_address: &str,
335    verbosity_level: u64,
336    min_bid_override: bool,
337    deploy_params: DeployStrParams<'_>,
338    session_params: SessionStrParams<'_>,
339    payment_params: PaymentStrParams<'_>,
340) -> Result<SuccessResponse<PutDeployResult>, CliError> {
341    let rpc_id = parse::rpc_id(maybe_rpc_id);
342    let verbosity = parse::verbosity(verbosity_level);
343    let deploy = with_payment_and_session(deploy_params, payment_params, session_params, false)?;
344    if let Err(err) = do_deploy_checks(node_address, min_bid_override, &deploy).await {
345        if !min_bid_override {
346            return Err(err);
347        } else {
348            println!("[WARN]: Skipping withdraw bid amount checks: {}", err)
349        }
350    };
351    #[allow(deprecated)]
352    crate::put_deploy(rpc_id, node_address, verbosity, deploy)
353        .await
354        .map_err(CliError::from)
355}
356
357/// Creates a [`Deploy`] and sends it to the specified node for speculative execution.
358///
359/// For details of the parameters, see [the module docs](crate::cli#common-parameters) or the docs
360/// of the individual parameter types.
361pub async fn speculative_put_deploy(
362    maybe_rpc_id: &str,
363    node_address: &str,
364    verbosity_level: u64,
365    deploy_params: DeployStrParams<'_>,
366    session_params: SessionStrParams<'_>,
367    payment_params: PaymentStrParams<'_>,
368) -> Result<SuccessResponse<SpeculativeExecResult>, CliError> {
369    let rpc_id = parse::rpc_id(maybe_rpc_id);
370    let verbosity = parse::verbosity(verbosity_level);
371    let deploy = with_payment_and_session(deploy_params, payment_params, session_params, false)?;
372    #[allow(deprecated)]
373    crate::speculative_exec(rpc_id, node_address, verbosity, deploy)
374        .await
375        .map_err(CliError::from)
376}
377
378/// Returns a [`Deploy`] and outputs it to a file or stdout if the `std-fs-io` feature is enabled.
379///
380/// As a file, the `Deploy` can subsequently be signed by other parties using [`sign_deploy_file`]
381/// and then sent to the network for execution using [`send_deploy_file`].
382///
383/// If the `std-fs-io` feature is NOT enabled, `maybe_output_path` and `force` are ignored.
384/// Otherwise, `maybe_output_path` specifies the output file path, or if empty, will print it to
385/// `stdout`.  If `force` is true, and a file exists at `maybe_output_path`, it will be
386/// overwritten.  If `force` is false and a file exists at `maybe_output_path`,
387/// [`crate::Error::FileAlreadyExists`] is returned and the file will not be written.
388pub fn make_deploy(
389    #[allow(unused_variables)] maybe_output_path: &str,
390    deploy_params: DeployStrParams<'_>,
391    session_params: SessionStrParams<'_>,
392    payment_params: PaymentStrParams<'_>,
393    #[allow(unused_variables)] force: bool,
394) -> Result<Deploy, CliError> {
395    let deploy = with_payment_and_session(deploy_params, payment_params, session_params, true)?;
396    #[cfg(feature = "std-fs-io")]
397    {
398        let output = parse::output_kind(maybe_output_path, force);
399        #[allow(deprecated)]
400        crate::output_deploy(output, &deploy).map_err(CliError::from)?;
401    }
402    Ok(deploy)
403}
404
405/// Reads a previously-saved [`Deploy`] from a file, cryptographically signs it, and outputs it to a
406/// file or stdout.
407///
408/// `maybe_output_path` specifies the output file path, or if empty, will print it to `stdout`.  If
409/// `force` is true, and a file exists at `maybe_output_path`, it will be overwritten.  If `force`
410/// is false and a file exists at `maybe_output_path`, [`crate::Error::FileAlreadyExists`] is returned
411/// and the file will not be written.
412#[cfg(feature = "std-fs-io")]
413pub fn sign_deploy_file(
414    input_path: &str,
415    secret_key_path: &str,
416    maybe_output_path: &str,
417    force: bool,
418) -> Result<(), CliError> {
419    let secret_key = parse::secret_key_from_file(secret_key_path)?;
420    let output = parse::output_kind(maybe_output_path, force);
421    #[allow(deprecated)]
422    crate::sign_deploy_file(input_path, &secret_key, output).map_err(CliError::from)
423}
424
425/// Reads a previously-saved [`Deploy`] from a file and sends it to the network for execution.
426///
427/// For details of the parameters, see [the module docs](crate::cli#common-parameters).
428#[cfg(feature = "std-fs-io")]
429pub async fn send_deploy_file(
430    maybe_rpc_id: &str,
431    node_address: &str,
432    verbosity_level: u64,
433    input_path: &str,
434) -> Result<SuccessResponse<PutDeployResult>, CliError> {
435    let rpc_id = parse::rpc_id(maybe_rpc_id);
436    let verbosity = parse::verbosity(verbosity_level);
437    #[allow(deprecated)]
438    let deploy = crate::read_deploy_file(input_path)?;
439    #[allow(deprecated)]
440    crate::put_deploy(rpc_id, node_address, verbosity, deploy)
441        .await
442        .map_err(CliError::from)
443}
444
445/// Reads a previously-saved [`Deploy`] from a file and sends it to the specified node for
446/// speculative execution.
447/// For details of the parameters, see [the module docs](crate::cli#common-parameters).
448#[cfg(feature = "std-fs-io")]
449pub async fn speculative_send_deploy_file(
450    maybe_rpc_id: &str,
451    node_address: &str,
452    verbosity_level: u64,
453    input_path: &str,
454) -> Result<SuccessResponse<SpeculativeExecResult>, CliError> {
455    let rpc_id = parse::rpc_id(maybe_rpc_id);
456    let verbosity = parse::verbosity(verbosity_level);
457    #[allow(deprecated)]
458    let deploy = crate::read_deploy_file(input_path)?;
459    #[allow(deprecated)]
460    crate::speculative_exec(rpc_id, node_address, verbosity, deploy)
461        .await
462        .map_err(CliError::from)
463}
464
465/// Transfers funds between purses.
466///
467/// * `amount` is a string to be parsed as a `U512` specifying the amount to be transferred.
468/// * `target_account` is the [`AccountHash`], [`URef`] or [`PublicKey`] of the account to which the
469///   funds will be transferred, formatted as a hex-encoded string.  The account's main purse will
470///   receive the funds.
471/// * `transfer_id` is a string to be parsed as a `u64` representing a user-defined identifier which
472///   will be permanently associated with the transfer.
473///
474/// For details of other parameters, see [the module docs](crate::cli#common-parameters).
475#[allow(clippy::too_many_arguments)]
476pub async fn transfer(
477    maybe_rpc_id: &str,
478    node_address: &str,
479    verbosity_level: u64,
480    amount: &str,
481    target_account: &str,
482    transfer_id: &str,
483    deploy_params: DeployStrParams<'_>,
484    payment_params: PaymentStrParams<'_>,
485) -> Result<SuccessResponse<PutDeployResult>, CliError> {
486    let rpc_id = parse::rpc_id(maybe_rpc_id);
487    let verbosity = parse::verbosity(verbosity_level);
488    let deploy = new_transfer(
489        amount,
490        None,
491        target_account,
492        transfer_id,
493        deploy_params,
494        payment_params,
495        false,
496    )?;
497    #[allow(deprecated)]
498    crate::put_deploy(rpc_id, node_address, verbosity, deploy)
499        .await
500        .map_err(CliError::from)
501}
502
503/// Creates a [`Deploy`] to transfer funds between purses, and sends it to the specified node for
504/// speculative execution.
505///
506/// * `amount` is a string to be parsed as a `U512` specifying the amount to be transferred.
507/// * `target_account` is the [`AccountHash`], [`URef`] or [`PublicKey`] of the account to which the
508///   funds will be transferred, formatted as a hex-encoded string.  The account's main purse will
509///   receive the funds.
510/// * `transfer_id` is a string to be parsed as a `u64` representing a user-defined identifier which
511///   will be permanently associated with the transfer.
512///
513/// For details of other parameters, see [the module docs](crate::cli#common-parameters).
514#[allow(clippy::too_many_arguments)]
515pub async fn speculative_transfer(
516    maybe_rpc_id: &str,
517    node_address: &str,
518    verbosity_level: u64,
519    amount: &str,
520    target_account: &str,
521    transfer_id: &str,
522    deploy_params: DeployStrParams<'_>,
523    payment_params: PaymentStrParams<'_>,
524) -> Result<SuccessResponse<SpeculativeExecResult>, CliError> {
525    let rpc_id = parse::rpc_id(maybe_rpc_id);
526    let verbosity = parse::verbosity(verbosity_level);
527    let deploy = new_transfer(
528        amount,
529        None,
530        target_account,
531        transfer_id,
532        deploy_params,
533        payment_params,
534        false,
535    )?;
536    #[allow(deprecated)]
537    crate::speculative_exec(rpc_id, node_address, verbosity, deploy)
538        .await
539        .map_err(CliError::from)
540}
541
542/// Returns a transfer [`Deploy`] and outputs it to a file or stdout if the `std-fs-io` feature is
543/// enabled.
544///
545/// As a file, the `Deploy` can subsequently be signed by other parties using [`sign_deploy_file`]
546/// and then sent to the network for execution using [`send_deploy_file`].
547///
548/// If the `std-fs-io` feature is NOT enabled, `maybe_output_path` and `force` are ignored.
549/// Otherwise, `maybe_output_path` specifies the output file path, or if empty, will print it to
550/// `stdout`.  If `force` is true, and a file exists at `maybe_output_path`, it will be
551/// overwritten.  If `force` is false and a file exists at `maybe_output_path`,
552/// [`crate::Error::FileAlreadyExists`] is returned and the file will not be written.
553pub fn make_transfer(
554    #[allow(unused_variables)] maybe_output_path: &str,
555    amount: &str,
556    target_account: &str,
557    transfer_id: &str,
558    deploy_params: DeployStrParams<'_>,
559    payment_params: PaymentStrParams<'_>,
560    #[allow(unused_variables)] force: bool,
561) -> Result<Deploy, CliError> {
562    let deploy = new_transfer(
563        amount,
564        None,
565        target_account,
566        transfer_id,
567        deploy_params,
568        payment_params,
569        true,
570    )?;
571    #[cfg(feature = "std-fs-io")]
572    {
573        let output = parse::output_kind(maybe_output_path, force);
574        #[allow(deprecated)]
575        crate::output_deploy(output, &deploy).map_err(CliError::from)?;
576    }
577    Ok(deploy)
578}
579
580/// Creates new Deploy with specified payment and session data.
581pub fn with_payment_and_session(
582    deploy_params: DeployStrParams,
583    payment_params: PaymentStrParams,
584    session_params: SessionStrParams,
585    allow_unsigned_deploy: bool,
586) -> Result<Deploy, CliError> {
587    let gas_price: u64 = deploy_params
588        .gas_price_tolerance
589        .parse::<u64>()
590        .unwrap_or(DEFAULT_GAS_PRICE);
591    let chain_name = deploy_params.chain_name.to_string();
592    let session = parse::session_executable_deploy_item(session_params)?;
593    let payment = parse::payment_executable_deploy_item(payment_params)?;
594    let timestamp = parse::timestamp(deploy_params.timestamp)?;
595    let ttl = parse::ttl(deploy_params.ttl)?;
596    let maybe_session_account = parse::session_account(deploy_params.session_account)?;
597
598    let mut deploy_builder = DeployBuilder::new(chain_name, session)
599        .with_payment(payment)
600        .with_timestamp(timestamp)
601        .with_gas_price(gas_price)
602        .with_ttl(ttl);
603    let maybe_secret_key = get_maybe_secret_key(
604        deploy_params.secret_key,
605        allow_unsigned_deploy,
606        "with_payment_and_session",
607    )?;
608    if let Some(secret_key) = &maybe_secret_key {
609        deploy_builder = deploy_builder.with_secret_key(secret_key);
610    }
611    if let Some(account) = maybe_session_account {
612        deploy_builder = deploy_builder.with_account(account);
613    }
614
615    let deploy = deploy_builder.build().map_err(crate::Error::from)?;
616    deploy
617        .is_valid_size(MAX_SERIALIZED_SIZE_OF_DEPLOY)
618        .map_err(crate::Error::from)?;
619    Ok(deploy)
620}
621
622/// Creates new Transfer with specified data.
623pub fn new_transfer(
624    amount: &str,
625    source_purse: Option<URef>,
626    target_account: &str,
627    transfer_id: &str,
628    deploy_params: DeployStrParams,
629    payment_params: PaymentStrParams,
630    allow_unsigned_deploy: bool,
631) -> Result<Deploy, CliError> {
632    let chain_name = deploy_params.chain_name.to_string();
633    let payment = parse::payment_executable_deploy_item(payment_params)?;
634    let amount = U512::from_dec_str(amount).map_err(|err| CliError::FailedToParseUint {
635        context: "new_transfer amount",
636        error: UIntParseError::FromDecStr(err),
637    })?;
638
639    let target = if let Ok(public_key) = PublicKey::from_hex(target_account) {
640        TransferTarget::PublicKey(public_key)
641    } else if let Ok(account_hash) = AccountHash::from_formatted_str(target_account) {
642        TransferTarget::AccountHash(account_hash)
643    } else if let Ok(uref) = URef::from_formatted_str(target_account) {
644        TransferTarget::URef(uref)
645    } else {
646        return Err(CliError::InvalidArgument {
647            context: "new_transfer target_account",
648            error: format!(
649                "allowed types: PublicKey, AccountHash or URef, got {}",
650                target_account
651            ),
652        });
653    };
654
655    let transfer_id = parse::transfer_id(transfer_id)?;
656    let maybe_transfer_id = Some(transfer_id);
657
658    let timestamp = parse::timestamp(deploy_params.timestamp)?;
659    let ttl = parse::ttl(deploy_params.ttl)?;
660    let maybe_session_account = parse::session_account(deploy_params.session_account)?;
661    let gas_price: u64 = deploy_params
662        .gas_price_tolerance
663        .parse::<u64>()
664        .unwrap_or(DEFAULT_GAS_PRICE);
665
666    let mut deploy_builder =
667        DeployBuilder::new_transfer(chain_name, amount, source_purse, target, maybe_transfer_id)
668            .with_payment(payment)
669            .with_timestamp(timestamp)
670            .with_gas_price(gas_price)
671            .with_ttl(ttl);
672
673    let maybe_secret_key = get_maybe_secret_key(
674        deploy_params.secret_key,
675        allow_unsigned_deploy,
676        "new_transfer",
677    )?;
678    if let Some(secret_key) = &maybe_secret_key {
679        deploy_builder = deploy_builder.with_secret_key(secret_key);
680    }
681    if let Some(account) = maybe_session_account {
682        deploy_builder = deploy_builder.with_account(account);
683    }
684    let deploy = deploy_builder.build().map_err(crate::Error::from)?;
685    deploy
686        .is_valid_size(MAX_SERIALIZED_SIZE_OF_DEPLOY)
687        .map_err(crate::Error::from)?;
688    Ok(deploy)
689}