diff --git a/Cargo.lock b/Cargo.lock index 6789ea504..772591e1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4067,9 +4067,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.9.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" dependencies = [ "base64 0.22.1", "chrono", @@ -4085,9 +4085,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.9.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" dependencies = [ "darling", "proc-macro2", @@ -4336,6 +4336,7 @@ dependencies = [ "serde", "serde-aux", "serde_json", + "serde_with", "sha2 0.10.8", "shell-escape", "shlex", diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 61686ab22..e453b53d8 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -958,6 +958,7 @@ Add a new identity (keypair, ledger, macOS keychain) * `--secret-key` — Add using `secret_key` Can provide with `SOROBAN_SECRET_KEY` * `--seed-phrase` — Add using 12 word seed phrase to generate `secret_key` +* `--public-key ` — Add a public key, ed25519, or muxed account, e.g. G1.., M2.. * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." @@ -1764,7 +1765,7 @@ https://developers.stellar.org/docs/learn/glossary#flags * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--trustor ` — Account to set trustline flags for +* `--trustor ` — Account to set trustline flags for, e.g. `GBX...`, or alias, or muxed account, `M123...`` * `--asset ` — Asset to set trustline flags for * `--set-authorize` — Signifies complete authorization allowing an account to transact freely with the asset to make and receive payments and place orders * `--set-authorize-to-maintain-liabilities` — Denotes limited authorization that allows an account to maintain current orders but not to otherwise transact with the asset @@ -1824,13 +1825,26 @@ https://developers.stellar.org/docs/learn/glossary#flags Transfers the XLM balance of an account to another account and removes the source account from the ledger -**Usage:** `stellar tx operation add account-merge [OPTIONS] --account ` +**Usage:** `stellar tx operation add account-merge [OPTIONS] --source-account --account ` ###### **Options:** +* `--operation-source-account ` — Source account used for the operation +* `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm + + Default value: `100` +* `--cost` — Output the cost execution to stderr +* `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout +* `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--operation-source-account ` — Source account used for the operation * `--account ` — Muxed Account to merge with, e.g. `GBX...`, 'MBX...' @@ -1839,13 +1853,26 @@ Transfers the XLM balance of an account to another account and removes the sourc Bumps forward the sequence number of the source account to the given sequence number, invalidating any transaction with a smaller sequence number -**Usage:** `stellar tx operation add bump-sequence [OPTIONS] --bump-to ` +**Usage:** `stellar tx operation add bump-sequence [OPTIONS] --source-account --bump-to ` ###### **Options:** +* `--operation-source-account ` — Source account used for the operation +* `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm + + Default value: `100` +* `--cost` — Output the cost execution to stderr +* `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout +* `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--operation-source-account ` — Source account used for the operation * `--bump-to ` — Sequence number to bump to @@ -1856,13 +1883,26 @@ Creates, updates, or deletes a trustline Learn more about trustlines https://developers.stellar.org/docs/learn/fundamentals/stellar-data-structures/accounts#trustlines -**Usage:** `stellar tx operation add change-trust [OPTIONS] --line ` +**Usage:** `stellar tx operation add change-trust [OPTIONS] --source-account --line ` ###### **Options:** +* `--operation-source-account ` — Source account used for the operation +* `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm + + Default value: `100` +* `--cost` — Output the cost execution to stderr +* `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout +* `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--operation-source-account ` — Source account used for the operation * `--line ` * `--limit ` — Limit for the trust line, 0 to remove the trust line @@ -1874,13 +1914,26 @@ https://developers.stellar.org/docs/learn/fundamentals/stellar-data-structures/a Creates and funds a new account with the specified starting balance -**Usage:** `stellar tx operation add create-account [OPTIONS] --destination ` +**Usage:** `stellar tx operation add create-account [OPTIONS] --source-account --destination ` ###### **Options:** +* `--operation-source-account ` — Source account used for the operation +* `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm + + Default value: `100` +* `--cost` — Output the cost execution to stderr +* `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout +* `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--operation-source-account ` — Source account used for the operation * `--destination ` — Account Id to create, e.g. `GBX...` * `--starting-balance ` — Initial balance in stroops of the account, default 1 XLM @@ -1894,13 +1947,26 @@ Sets, modifies, or deletes a data entry (name/value pair) that is attached to an Learn more about entries and subentries: https://developers.stellar.org/docs/learn/fundamentals/stellar-data-structures/accounts#subentries -**Usage:** `stellar tx operation add manage-data [OPTIONS] --data-name ` +**Usage:** `stellar tx operation add manage-data [OPTIONS] --source-account --data-name ` ###### **Options:** +* `--operation-source-account ` — Source account used for the operation +* `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm + + Default value: `100` +* `--cost` — Output the cost execution to stderr +* `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout +* `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--operation-source-account ` — Source account used for the operation * `--data-name ` — String up to 64 bytes long. If this is a new Name it will add the given name/value pair to the account. If this Name is already present then the associated value will be modified * `--data-value ` — Up to 64 bytes long hex string If not present then the existing Name will be deleted. If present then this value will be set in the `DataEntry` @@ -1910,13 +1976,26 @@ https://developers.stellar.org/docs/learn/fundamentals/stellar-data-structures/a Sends an amount in a specific asset to a destination account -**Usage:** `stellar tx operation add payment [OPTIONS] --destination --amount ` +**Usage:** `stellar tx operation add payment [OPTIONS] --source-account --destination --amount ` ###### **Options:** +* `--operation-source-account ` — Source account used for the operation +* `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm + + Default value: `100` +* `--cost` — Output the cost execution to stderr +* `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout +* `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--operation-source-account ` — Source account used for the operation * `--destination ` — Account to send to, e.g. `GBX...` * `--asset ` — Asset to send, default native, e.i. XLM @@ -1935,13 +2014,26 @@ https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0001.md Learn more about signers operations and key weight: https://developers.stellar.org/docs/learn/encyclopedia/security/signatures-multisig#multisig -**Usage:** `stellar tx operation add set-options [OPTIONS]` +**Usage:** `stellar tx operation add set-options [OPTIONS] --source-account ` ###### **Options:** +* `--operation-source-account ` — Source account used for the operation +* `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm + + Default value: `100` +* `--cost` — Output the cost execution to stderr +* `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout +* `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--operation-source-account ` — Source account used for the operation * `--inflation-dest ` — Account of the inflation destination * `--master-weight ` — A number from 0-255 (inclusive) representing the weight of the master key. If the weight of the master key is updated to 0, it is effectively disabled * `--low-threshold ` — A number from 0-255 (inclusive) representing the threshold this account sets on all operations it performs that have a low threshold. https://developers.stellar.org/docs/learn/encyclopedia/security/signatures-multisig#multisig @@ -1969,14 +2061,27 @@ If you are modifying a trustline to a pool share, however, this is composed of t Learn more about flags: https://developers.stellar.org/docs/learn/glossary#flags -**Usage:** `stellar tx operation add set-trustline-flags [OPTIONS] --trustor --asset ` +**Usage:** `stellar tx operation add set-trustline-flags [OPTIONS] --source-account --trustor --asset ` ###### **Options:** +* `--operation-source-account ` — Source account used for the operation +* `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm + + Default value: `100` +* `--cost` — Output the cost execution to stderr +* `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout +* `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--operation-source-account ` — Source account used for the operation -* `--trustor ` — Account to set trustline flags for +* `--trustor ` — Account to set trustline flags for, e.g. `GBX...`, or alias, or muxed account, `M123...`` * `--asset ` — Asset to set trustline flags for * `--set-authorize` — Signifies complete authorization allowing an account to transact freely with the asset to make and receive payments and place orders * `--set-authorize-to-maintain-liabilities` — Denotes limited authorization that allows an account to maintain current orders but not to otherwise transact with the asset diff --git a/cmd/crates/soroban-test/tests/it/integration/tx/operations.rs b/cmd/crates/soroban-test/tests/it/integration/tx/operations.rs index 1ce1f06c9..9135ae5d2 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx/operations.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx/operations.rs @@ -1,4 +1,5 @@ use soroban_cli::{ + config::locator, tx::{builder, ONE_XLM}, utils::contract_id_hash_from_asset, xdr::{self, ReadXdr, SequenceNumber}, @@ -89,6 +90,71 @@ async fn create_account() { invoke_hello_world(sandbox, &id); } +#[tokio::test] +async fn create_account_with_alias() { + let sandbox = &TestEnv::new(); + sandbox + .new_assert_cmd("keys") + .args(["generate", "--no-fund", "new"]) + .assert() + .success(); + let test = test_address(sandbox); + let client = soroban_rpc::Client::new(&sandbox.rpc_url).unwrap(); + let test_account = client.get_account(&test).await.unwrap(); + println!("test account has a balance of {}", test_account.balance); + let starting_balance = ONE_XLM * 100; + sandbox + .new_assert_cmd("tx") + .args([ + "new", + "create-account", + "--destination", + "new", + "--starting-balance", + starting_balance.to_string().as_str(), + ]) + .assert() + .success(); + let test_account_after = client.get_account(&test).await.unwrap(); + assert!(test_account_after.balance < test_account.balance); + let id = deploy_contract(sandbox, HELLO_WORLD, DeployKind::Normal, Some("new")).await; + println!("{id}"); + invoke_hello_world(sandbox, &id); +} + +#[tokio::test] +async fn payment_with_alias() { + let sandbox = &TestEnv::new(); + let client = soroban_rpc::Client::new(&sandbox.rpc_url).unwrap(); + let (test, test1) = setup_accounts(sandbox); + let test_account = client.get_account(&test).await.unwrap(); + println!("test account has a balance of {}", test_account.balance); + + let before = client.get_account(&test).await.unwrap(); + let test1_account_entry_before = client.get_account(&test1).await.unwrap(); + + sandbox + .new_assert_cmd("tx") + .args([ + "new", + "payment", + "--destination", + "test1", + "--amount", + ONE_XLM.to_string().as_str(), + ]) + .assert() + .success(); + let test1_account_entry = client.get_account(&test1).await.unwrap(); + assert_eq!( + ONE_XLM, + test1_account_entry.balance - test1_account_entry_before.balance, + "Should have One XLM more" + ); + let after = client.get_account(&test).await.unwrap(); + assert_eq!(before.balance - 10_000_100, after.balance); +} + #[tokio::test] async fn payment() { let sandbox = &TestEnv::new(); @@ -172,22 +238,53 @@ async fn account_merge() { assert_eq!(before.balance + before1.balance - fee, after.balance); } +#[tokio::test] +async fn account_merge_with_alias() { + let sandbox = &TestEnv::new(); + let client = soroban_rpc::Client::new(&sandbox.rpc_url).unwrap(); + let (test, test1) = setup_accounts(sandbox); + let before = client.get_account(&test).await.unwrap(); + let before1 = client.get_account(&test1).await.unwrap(); + let fee = 100; + sandbox + .new_assert_cmd("tx") + .args([ + "new", + "account-merge", + "--source", + "test1", + "--account", + "test", + "--fee", + fee.to_string().as_str(), + ]) + .assert() + .success(); + let after = client.get_account(&test).await.unwrap(); + assert!(client.get_account(&test1).await.is_err()); + assert_eq!(before.balance + before1.balance - fee, after.balance); +} + #[tokio::test] async fn set_trustline_flags() { let sandbox = &TestEnv::new(); - let (test, issuer) = setup_accounts(sandbox); - let asset = format!("usdc:{issuer}"); - issue_asset(sandbox, &test, &asset, 100_000, 100).await; + let (test, test1) = setup_accounts(sandbox); + let asset = "usdc:test1"; + issue_asset(sandbox, &test, asset, 100_000, 100).await; sandbox .new_assert_cmd("contract") .arg("asset") .arg("deploy") .arg("--asset") - .arg(&asset) + .arg(asset) .assert() .success(); let id = contract_id_hash_from_asset( - asset.parse::().unwrap(), + &format!("usdc:{test1}") + .parse::() + .unwrap() + .resolve(&locator::Args::default()) + .unwrap(), &sandbox.network_passphrase, ); // sandbox @@ -450,7 +547,11 @@ async fn change_trust() { // wrap_cmd(&asset).run().await.unwrap(); let id = contract_id_hash_from_asset( - asset.parse::().unwrap(), + &asset + .parse::() + .unwrap() + .resolve(&locator::Args::default()) + .unwrap(), &sandbox.network_passphrase, ); sandbox diff --git a/cmd/crates/soroban-test/tests/it/integration/wrap.rs b/cmd/crates/soroban-test/tests/it/integration/wrap.rs index 3fef56ef9..961cd6b4a 100644 --- a/cmd/crates/soroban-test/tests/it/integration/wrap.rs +++ b/cmd/crates/soroban-test/tests/it/integration/wrap.rs @@ -1,4 +1,4 @@ -use soroban_cli::{tx::builder, utils::contract_id_hash_from_asset}; +use soroban_cli::{config::locator, tx::builder, utils::contract_id_hash_from_asset}; use soroban_test::{AssertExt, TestEnv, LOCAL_NETWORK_PASSPHRASE}; #[tokio::test] @@ -12,21 +12,23 @@ async fn burn() { .arg("test") .assert() .stdout_as_str(); - let asset = format!("native:{address}"); + let asset = "native"; sandbox .new_assert_cmd("contract") .arg("asset") .arg("deploy") .arg("--source=test") .arg("--asset") - .arg(&asset) + .arg(asset) .assert() .success(); - // wrap_cmd(&asset).run().await.unwrap(); - let asset: builder::Asset = asset.parse().unwrap(); + let asset = asset + .parse::() + .unwrap() + .resolve(&locator::Args::default()) + .unwrap(); let hash = contract_id_hash_from_asset(&asset, &network_passphrase); let id = stellar_strkey::Contract(hash.0).to_string(); - println!("{id}, {address}"); sandbox .new_assert_cmd("contract") .args([ diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index 2f2a26255..e6e205248 100644 --- a/cmd/soroban-cli/Cargo.toml +++ b/cmd/soroban-cli/Cargo.toml @@ -124,6 +124,7 @@ fqdn = "0.3.12" open = "5.3.0" url = "2.5.2" wasm-gen = "0.1.4" +serde_with = "3.11.0" [build-dependencies] crate-git-revision = "0.0.6" diff --git a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs index 04a0380ed..9adf1e1be 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs @@ -39,6 +39,8 @@ pub enum Error { Network(#[from] network::Error), #[error(transparent)] Builder(#[from] builder::Error), + #[error(transparent)] + Asset(#[from] builder::asset::Error), } impl From for Error { @@ -85,7 +87,7 @@ impl NetworkRunnable for Cmd { ) -> Result { let config = config.unwrap_or(&self.config); // Parse asset - let asset = &self.asset; + let asset = self.asset.resolve(&config.locator)?; let network = config.get_network()?; let client = network.rpc_client()?; @@ -100,7 +102,7 @@ impl NetworkRunnable for Cmd { .await?; let sequence: i64 = account_details.seq_num.into(); let network_passphrase = &network.network_passphrase; - let contract_id = contract_id_hash_from_asset(asset, network_passphrase); + let contract_id = contract_id_hash_from_asset(&asset, network_passphrase); let tx = build_wrap_token_tx( asset, &contract_id, diff --git a/cmd/soroban-cli/src/commands/contract/id/asset.rs b/cmd/soroban-cli/src/commands/contract/id/asset.rs index 921592cf8..ddc03a715 100644 --- a/cmd/soroban-cli/src/commands/contract/id/asset.rs +++ b/cmd/soroban-cli/src/commands/contract/id/asset.rs @@ -21,6 +21,8 @@ pub enum Error { ConfigError(#[from] config::Error), #[error(transparent)] Xdr(#[from] crate::xdr::Error), + #[error(transparent)] + Asset(#[from] builder::asset::Error), } impl Cmd { pub fn run(&self) -> Result<(), Error> { @@ -30,7 +32,10 @@ impl Cmd { pub fn contract_address(&self) -> Result { let network = self.config.get_network()?; - let contract_id = contract_id_hash_from_asset(&self.asset, &network.network_passphrase); + let contract_id = contract_id_hash_from_asset( + &self.asset.resolve(&self.config.locator)?, + &network.network_passphrase, + ); Ok(stellar_strkey::Contract(contract_id.0)) } } diff --git a/cmd/soroban-cli/src/commands/keys/add.rs b/cmd/soroban-cli/src/commands/keys/add.rs index d8f528bae..5e32944e8 100644 --- a/cmd/soroban-cli/src/commands/keys/add.rs +++ b/cmd/soroban-cli/src/commands/keys/add.rs @@ -1,11 +1,11 @@ use clap::command; -use crate::config::{locator, secret}; +use crate::config::{key, locator, secret}; #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] - Secret(#[from] secret::Error), + Key(#[from] key::Error), #[error(transparent)] Config(#[from] locator::Error), @@ -28,6 +28,6 @@ impl Cmd { pub fn run(&self) -> Result<(), Error> { Ok(self .config_locator - .write_identity(&self.name, &self.secrets.read_secret()?)?) + .write_key(&self.name, &self.secrets.read_key()?)?) } } diff --git a/cmd/soroban-cli/src/commands/keys/address.rs b/cmd/soroban-cli/src/commands/keys/address.rs index d13381b49..8eda8dd83 100644 --- a/cmd/soroban-cli/src/commands/keys/address.rs +++ b/cmd/soroban-cli/src/commands/keys/address.rs @@ -1,15 +1,14 @@ -use crate::commands::config::secret; - -use super::super::config::locator; use clap::arg; +use crate::commands::config::{key, locator}; + #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] Config(#[from] locator::Error), #[error(transparent)] - Secret(#[from] secret::Error), + Key(#[from] key::Error), #[error(transparent)] StrKey(#[from] stellar_strkey::DecodeError), @@ -36,10 +35,13 @@ impl Cmd { } pub fn private_key(&self) -> Result { - Ok(self - .locator - .read_identity(&self.name)? - .key_pair(self.hd_path)?) + Ok(ed25519_dalek::SigningKey::from_bytes( + &self + .locator + .read_identity(&self.name)? + .private_key(self.hd_path)? + .0, + )) } pub fn public_key(&self) -> Result { diff --git a/cmd/soroban-cli/src/commands/keys/show.rs b/cmd/soroban-cli/src/commands/keys/show.rs index 58c47740c..ae0faab03 100644 --- a/cmd/soroban-cli/src/commands/keys/show.rs +++ b/cmd/soroban-cli/src/commands/keys/show.rs @@ -1,6 +1,6 @@ use clap::arg; -use crate::config::{locator, secret}; +use crate::config::{key, locator}; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -8,10 +8,7 @@ pub enum Error { Config(#[from] locator::Error), #[error(transparent)] - Secret(#[from] secret::Error), - - #[error(transparent)] - StrKey(#[from] stellar_strkey::DecodeError), + Key(#[from] key::Error), } #[derive(Debug, clap::Parser, Clone)] diff --git a/cmd/soroban-cli/src/commands/snapshot/create.rs b/cmd/soroban-cli/src/commands/snapshot/create.rs index 6ce48d3f2..9c1736914 100644 --- a/cmd/soroban-cli/src/commands/snapshot/create.rs +++ b/cmd/soroban-cli/src/commands/snapshot/create.rs @@ -322,7 +322,9 @@ impl Cmd { get_name_from_stellar_asset_contract_storage(storage) { let asset: builder::Asset = name.parse()?; - if let Some(issuer) = match asset.into() { + if let Some(issuer) = match asset + .resolve(&global_args.locator)? + { Asset::Native => None, Asset::CreditAlphanum4(a4) => Some(a4.issuer), Asset::CreditAlphanum12(a12) => Some(a12.issuer), diff --git a/cmd/soroban-cli/src/commands/tx/args.rs b/cmd/soroban-cli/src/commands/tx/args.rs index 1da1f230a..c312f36f4 100644 --- a/cmd/soroban-cli/src/commands/tx/args.rs +++ b/cmd/soroban-cli/src/commands/tx/args.rs @@ -1,9 +1,13 @@ use crate::{ commands::{global, txn_result::TxnEnvelopeResult}, - config::{self, data, network, secret}, + config::{ + self, + address::{self, Address}, + data, network, secret, + }, fee, rpc::{self, Client, GetTransactionResponse}, - tx::builder::{self, TxExt}, + tx::builder::{self, asset, TxExt}, xdr::{self, Limits, WriteXdr}, }; @@ -32,6 +36,12 @@ pub enum Error { Data(#[from] data::Error), #[error(transparent)] Xdr(#[from] xdr::Error), + #[error(transparent)] + Address(#[from] address::Error), + #[error(transparent)] + Asset(#[from] asset::Error), + #[error(transparent)] + TxXdr(#[from] super::xdr::Error), } impl Args { @@ -64,7 +74,7 @@ impl Args { op: impl Into, global_args: &global::Args, ) -> Result, Error> { - let tx = self.tx(op.into()).await?; + let tx = self.tx(op).await?; self.handle_tx(tx, global_args).await } pub async fn handle_and_print( @@ -104,4 +114,34 @@ impl Args { pub fn source_account(&self) -> Result { Ok(self.config.source_account()?) } + + pub fn reslove_muxed_address(&self, address: &Address) -> Result { + Ok(address.resolve_muxed_account(&self.config.locator, self.config.hd_path)?) + } + + pub fn reslove_account_id(&self, address: &Address) -> Result { + Ok(address + .resolve_muxed_account(&self.config.locator, self.config.hd_path)? + .account_id()) + } + + pub fn add_op( + &self, + op_body: impl Into, + tx_env: xdr::TransactionEnvelope, + op_source: Option<&address::Address>, + ) -> Result { + let source_account = op_source + .map(|a| self.reslove_muxed_address(a)) + .transpose()?; + let op = xdr::Operation { + source_account, + body: op_body.into(), + }; + Ok(super::xdr::add_op(tx_env, op)?) + } + + pub fn resolve_asset(&self, asset: &builder::Asset) -> Result { + Ok(asset.resolve(&self.config.locator)?) + } } diff --git a/cmd/soroban-cli/src/commands/tx/mod.rs b/cmd/soroban-cli/src/commands/tx/mod.rs index d9fd79faf..02be68bf0 100644 --- a/cmd/soroban-cli/src/commands/tx/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/mod.rs @@ -43,6 +43,8 @@ pub enum Error { #[error(transparent)] Sign(#[from] sign::Error), #[error(transparent)] + Args(#[from] args::Error), + #[error(transparent)] Simulate(#[from] simulate::Error), } diff --git a/cmd/soroban-cli/src/commands/tx/new/account_merge.rs b/cmd/soroban-cli/src/commands/tx/new/account_merge.rs index 0d07fce91..e52e1fc42 100644 --- a/cmd/soroban-cli/src/commands/tx/new/account_merge.rs +++ b/cmd/soroban-cli/src/commands/tx/new/account_merge.rs @@ -1,6 +1,6 @@ use clap::{command, Parser}; -use crate::{commands::tx, xdr}; +use crate::{commands::tx, config::address, xdr}; #[derive(Parser, Debug, Clone)] #[group(skip)] @@ -15,11 +15,14 @@ pub struct Cmd { pub struct Args { /// Muxed Account to merge with, e.g. `GBX...`, 'MBX...' #[arg(long)] - pub account: xdr::MuxedAccount, + pub account: address::Address, } -impl From<&Args> for xdr::OperationBody { - fn from(cmd: &Args) -> Self { - xdr::OperationBody::AccountMerge(cmd.account.clone()) +impl TryFrom<&Cmd> for xdr::OperationBody { + type Error = tx::args::Error; + fn try_from(cmd: &Cmd) -> Result { + Ok(xdr::OperationBody::AccountMerge( + cmd.tx.reslove_muxed_address(&cmd.op.account)?, + )) } } diff --git a/cmd/soroban-cli/src/commands/tx/new/bump_sequence.rs b/cmd/soroban-cli/src/commands/tx/new/bump_sequence.rs index ff04e96a0..96062bba2 100644 --- a/cmd/soroban-cli/src/commands/tx/new/bump_sequence.rs +++ b/cmd/soroban-cli/src/commands/tx/new/bump_sequence.rs @@ -18,10 +18,10 @@ pub struct Args { pub bump_to: i64, } -impl From<&Args> for xdr::OperationBody { - fn from(cmd: &Args) -> Self { +impl From<&Cmd> for xdr::OperationBody { + fn from(cmd: &Cmd) -> Self { xdr::OperationBody::BumpSequence(xdr::BumpSequenceOp { - bump_to: cmd.bump_to.into(), + bump_to: cmd.op.bump_to.into(), }) } } diff --git a/cmd/soroban-cli/src/commands/tx/new/change_trust.rs b/cmd/soroban-cli/src/commands/tx/new/change_trust.rs index 2013db75b..468291f73 100644 --- a/cmd/soroban-cli/src/commands/tx/new/change_trust.rs +++ b/cmd/soroban-cli/src/commands/tx/new/change_trust.rs @@ -20,16 +20,18 @@ pub struct Args { pub limit: i64, } -impl From<&Args> for xdr::OperationBody { - fn from(cmd: &Args) -> Self { - let line = match cmd.line.0.clone() { +impl TryFrom<&Cmd> for xdr::OperationBody { + type Error = tx::args::Error; + fn try_from(cmd: &Cmd) -> Result { + let asset = cmd.tx.resolve_asset(&cmd.op.line)?; + let line = match asset { xdr::Asset::CreditAlphanum4(asset) => xdr::ChangeTrustAsset::CreditAlphanum4(asset), xdr::Asset::CreditAlphanum12(asset) => xdr::ChangeTrustAsset::CreditAlphanum12(asset), xdr::Asset::Native => xdr::ChangeTrustAsset::Native, }; - xdr::OperationBody::ChangeTrust(xdr::ChangeTrustOp { + Ok(xdr::OperationBody::ChangeTrust(xdr::ChangeTrustOp { line, - limit: cmd.limit, - }) + limit: cmd.op.limit, + })) } } diff --git a/cmd/soroban-cli/src/commands/tx/new/create_account.rs b/cmd/soroban-cli/src/commands/tx/new/create_account.rs index acdfd6e2d..d062a07d5 100644 --- a/cmd/soroban-cli/src/commands/tx/new/create_account.rs +++ b/cmd/soroban-cli/src/commands/tx/new/create_account.rs @@ -1,6 +1,6 @@ use clap::{command, Parser}; -use crate::{commands::tx, tx::builder, xdr}; +use crate::{commands::tx, config::address, tx::builder, xdr}; #[derive(Parser, Debug, Clone)] #[group(skip)] @@ -14,18 +14,19 @@ pub struct Cmd { #[derive(Debug, clap::Args, Clone)] pub struct Args { /// Account Id to create, e.g. `GBX...` - #[arg(long, alias = "dest")] - pub destination: xdr::AccountId, + #[arg(long)] + pub destination: address::Address, /// Initial balance in stroops of the account, default 1 XLM #[arg(long, default_value = "10_000_000")] pub starting_balance: builder::Amount, } -impl From<&Args> for xdr::OperationBody { - fn from(cmd: &Args) -> Self { - xdr::OperationBody::CreateAccount(xdr::CreateAccountOp { - destination: cmd.destination.clone(), - starting_balance: cmd.starting_balance.into(), - }) +impl TryFrom<&Cmd> for xdr::OperationBody { + type Error = tx::args::Error; + fn try_from(cmd: &Cmd) -> Result { + Ok(xdr::OperationBody::CreateAccount(xdr::CreateAccountOp { + destination: cmd.tx.reslove_account_id(&cmd.op.destination)?, + starting_balance: cmd.op.starting_balance.into(), + })) } } diff --git a/cmd/soroban-cli/src/commands/tx/new/manage_data.rs b/cmd/soroban-cli/src/commands/tx/new/manage_data.rs index 30e9a36fd..672d8f66e 100644 --- a/cmd/soroban-cli/src/commands/tx/new/manage_data.rs +++ b/cmd/soroban-cli/src/commands/tx/new/manage_data.rs @@ -25,10 +25,10 @@ pub struct Args { pub data_value: Option>, } -impl From<&Args> for xdr::OperationBody { - fn from(cmd: &Args) -> Self { - let data_value = cmd.data_value.clone().map(Into::into); - let data_name = cmd.data_name.clone().into(); +impl From<&Cmd> for xdr::OperationBody { + fn from(cmd: &Cmd) -> Self { + let data_value = cmd.op.data_value.clone().map(Into::into); + let data_name = cmd.op.data_name.clone().into(); xdr::OperationBody::ManageData(xdr::ManageDataOp { data_name, data_value, diff --git a/cmd/soroban-cli/src/commands/tx/new/mod.rs b/cmd/soroban-cli/src/commands/tx/new/mod.rs index 24c25995f..761773418 100644 --- a/cmd/soroban-cli/src/commands/tx/new/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/new/mod.rs @@ -1,4 +1,5 @@ use clap::Parser; +use soroban_sdk::xdr::OperationBody; use super::global; @@ -38,17 +39,34 @@ pub enum Error { Tx(#[from] super::args::Error), } +impl TryFrom<&Cmd> for OperationBody { + type Error = super::args::Error; + fn try_from(cmd: &Cmd) -> Result { + Ok(match cmd { + Cmd::AccountMerge(cmd) => cmd.try_into()?, + Cmd::BumpSequence(cmd) => cmd.into(), + Cmd::ChangeTrust(cmd) => cmd.try_into()?, + Cmd::CreateAccount(cmd) => cmd.try_into()?, + Cmd::ManageData(cmd) => cmd.into(), + Cmd::Payment(cmd) => cmd.try_into()?, + Cmd::SetOptions(cmd) => cmd.try_into()?, + Cmd::SetTrustlineFlags(cmd) => cmd.try_into()?, + }) + } +} + impl Cmd { pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { + let op = OperationBody::try_from(self)?; match self { - Cmd::AccountMerge(cmd) => cmd.tx.handle_and_print(&cmd.op, global_args).await, - Cmd::BumpSequence(cmd) => cmd.tx.handle_and_print(&cmd.op, global_args).await, - Cmd::ChangeTrust(cmd) => cmd.tx.handle_and_print(&cmd.op, global_args).await, - Cmd::CreateAccount(cmd) => cmd.tx.handle_and_print(&cmd.op, global_args).await, - Cmd::ManageData(cmd) => cmd.tx.handle_and_print(&cmd.op, global_args).await, - Cmd::Payment(cmd) => cmd.tx.handle_and_print(&cmd.op, global_args).await, - Cmd::SetOptions(cmd) => cmd.tx.handle_and_print(&cmd.op, global_args).await, - Cmd::SetTrustlineFlags(cmd) => cmd.tx.handle_and_print(&cmd.op, global_args).await, + Cmd::AccountMerge(cmd) => cmd.tx.handle_and_print(op, global_args).await, + Cmd::BumpSequence(cmd) => cmd.tx.handle_and_print(op, global_args).await, + Cmd::ChangeTrust(cmd) => cmd.tx.handle_and_print(op, global_args).await, + Cmd::CreateAccount(cmd) => cmd.tx.handle_and_print(op, global_args).await, + Cmd::ManageData(cmd) => cmd.tx.handle_and_print(op, global_args).await, + Cmd::Payment(cmd) => cmd.tx.handle_and_print(op, global_args).await, + Cmd::SetOptions(cmd) => cmd.tx.handle_and_print(op, global_args).await, + Cmd::SetTrustlineFlags(cmd) => cmd.tx.handle_and_print(op, global_args).await, }?; Ok(()) } diff --git a/cmd/soroban-cli/src/commands/tx/new/payment.rs b/cmd/soroban-cli/src/commands/tx/new/payment.rs index 683b2731c..9544217d6 100644 --- a/cmd/soroban-cli/src/commands/tx/new/payment.rs +++ b/cmd/soroban-cli/src/commands/tx/new/payment.rs @@ -1,6 +1,6 @@ use clap::{command, Parser}; -use crate::{commands::tx, tx::builder, xdr}; +use crate::{commands::tx, config::address, tx::builder, xdr}; #[derive(Parser, Debug, Clone)] #[group(skip)] @@ -14,8 +14,8 @@ pub struct Cmd { #[derive(Debug, clap::Args, Clone)] pub struct Args { /// Account to send to, e.g. `GBX...` - #[arg(long, visible_alias = "dest")] - pub destination: xdr::MuxedAccount, + #[arg(long)] + pub destination: address::Address, /// Asset to send, default native, e.i. XLM #[arg(long, default_value = "native")] pub asset: builder::Asset, @@ -24,12 +24,23 @@ pub struct Args { pub amount: builder::Amount, } -impl From<&Args> for xdr::OperationBody { - fn from(cmd: &Args) -> Self { - xdr::OperationBody::Payment(xdr::PaymentOp { - destination: cmd.destination.clone(), - asset: cmd.asset.clone().into(), - amount: cmd.amount.into(), - }) +impl TryFrom<&Cmd> for xdr::OperationBody { + type Error = tx::args::Error; + fn try_from( + Cmd { + tx, + op: + Args { + destination, + asset, + amount, + }, + }: &Cmd, + ) -> Result { + Ok(xdr::OperationBody::Payment(xdr::PaymentOp { + destination: tx.reslove_muxed_address(destination)?, + asset: tx.resolve_asset(asset)?, + amount: amount.into(), + })) } } diff --git a/cmd/soroban-cli/src/commands/tx/new/set_options.rs b/cmd/soroban-cli/src/commands/tx/new/set_options.rs index 77c7c0895..42a0767b5 100644 --- a/cmd/soroban-cli/src/commands/tx/new/set_options.rs +++ b/cmd/soroban-cli/src/commands/tx/new/set_options.rs @@ -1,6 +1,6 @@ use clap::{command, Parser}; -use crate::{commands::tx, xdr}; +use crate::{commands::tx, config::address, xdr}; #[derive(Parser, Debug, Clone)] #[group(skip)] @@ -16,7 +16,7 @@ pub struct Cmd { pub struct Args { #[arg(long)] /// Account of the inflation destination. - pub inflation_dest: Option, + pub inflation_dest: Option, #[arg(long)] /// A number from 0-255 (inclusive) representing the weight of the master key. If the weight of the master key is updated to 0, it is effectively disabled. pub master_weight: Option, @@ -67,12 +67,15 @@ pub struct Args { pub clear_clawback_enabled: bool, } -impl From<&Args> for xdr::OperationBody { - fn from(cmd: &Args) -> Self { +impl TryFrom<&Cmd> for xdr::OperationBody { + type Error = tx::args::Error; + fn try_from(cmd: &Cmd) -> Result { + let tx = &cmd.tx; let mut set_flags = None; let mut set_flag = |flag: xdr::AccountFlags| { *set_flags.get_or_insert(0) |= flag as u32; }; + let cmd = &cmd.op; if cmd.set_required { set_flag(xdr::AccountFlags::RequiredFlag); @@ -114,8 +117,13 @@ impl From<&Args> for xdr::OperationBody { } else { None }; - xdr::OperationBody::SetOptions(xdr::SetOptionsOp { - inflation_dest: cmd.inflation_dest.clone().map(Into::into), + let inflation_dest: Option = cmd + .inflation_dest + .as_ref() + .map(|dest| tx.reslove_account_id(dest)) + .transpose()?; + Ok(xdr::OperationBody::SetOptions(xdr::SetOptionsOp { + inflation_dest, clear_flags, set_flags, master_weight: cmd.master_weight.map(Into::into), @@ -124,6 +132,6 @@ impl From<&Args> for xdr::OperationBody { high_threshold: cmd.high_threshold.map(Into::into), home_domain: cmd.home_domain.clone().map(Into::into), signer, - }) + })) } } diff --git a/cmd/soroban-cli/src/commands/tx/new/set_trustline_flags.rs b/cmd/soroban-cli/src/commands/tx/new/set_trustline_flags.rs index 482dd3a90..476fd1e7e 100644 --- a/cmd/soroban-cli/src/commands/tx/new/set_trustline_flags.rs +++ b/cmd/soroban-cli/src/commands/tx/new/set_trustline_flags.rs @@ -1,6 +1,6 @@ use clap::{command, Parser}; -use crate::{commands::tx, tx::builder, xdr}; +use crate::{commands::tx, config::address, tx::builder, xdr}; #[derive(Parser, Debug, Clone)] #[group(skip)] @@ -14,9 +14,9 @@ pub struct Cmd { #[derive(Debug, clap::Args, Clone)] #[allow(clippy::struct_excessive_bools, clippy::doc_markdown)] pub struct Args { - /// Account to set trustline flags for + /// Account to set trustline flags for, e.g. `GBX...`, or alias, or muxed account, `M123...`` #[arg(long)] - pub trustor: xdr::AccountId, + pub trustor: address::Address, /// Asset to set trustline flags for #[arg(long)] pub asset: builder::Asset, @@ -38,38 +38,41 @@ pub struct Args { pub clear_trustline_clawback_enabled: bool, } -impl From<&Args> for xdr::OperationBody { - fn from(cmd: &Args) -> Self { +impl TryFrom<&Cmd> for xdr::OperationBody { + type Error = tx::args::Error; + fn try_from(cmd: &Cmd) -> Result { let mut set_flags = 0; let mut set_flag = |flag: xdr::TrustLineFlags| set_flags |= flag as u32; - if cmd.set_authorize { + if cmd.op.set_authorize { set_flag(xdr::TrustLineFlags::AuthorizedFlag); }; - if cmd.set_authorize_to_maintain_liabilities { + if cmd.op.set_authorize_to_maintain_liabilities { set_flag(xdr::TrustLineFlags::AuthorizedToMaintainLiabilitiesFlag); }; - if cmd.set_trustline_clawback_enabled { + if cmd.op.set_trustline_clawback_enabled { set_flag(xdr::TrustLineFlags::TrustlineClawbackEnabledFlag); }; let mut clear_flags = 0; let mut clear_flag = |flag: xdr::TrustLineFlags| clear_flags |= flag as u32; - if cmd.clear_authorize { + if cmd.op.clear_authorize { clear_flag(xdr::TrustLineFlags::AuthorizedFlag); }; - if cmd.clear_authorize_to_maintain_liabilities { + if cmd.op.clear_authorize_to_maintain_liabilities { clear_flag(xdr::TrustLineFlags::AuthorizedToMaintainLiabilitiesFlag); }; - if cmd.clear_trustline_clawback_enabled { + if cmd.op.clear_trustline_clawback_enabled { clear_flag(xdr::TrustLineFlags::TrustlineClawbackEnabledFlag); }; - xdr::OperationBody::SetTrustLineFlags(xdr::SetTrustLineFlagsOp { - trustor: cmd.trustor.clone(), - asset: cmd.asset.clone().into(), - clear_flags, - set_flags, - }) + Ok(xdr::OperationBody::SetTrustLineFlags( + xdr::SetTrustLineFlagsOp { + trustor: cmd.tx.reslove_account_id(&cmd.op.trustor)?, + asset: cmd.tx.resolve_asset(&cmd.op.asset)?, + clear_flags, + set_flags, + }, + )) } } diff --git a/cmd/soroban-cli/src/commands/tx/op/add/account_merge.rs b/cmd/soroban-cli/src/commands/tx/op/add/account_merge.rs index bd643c199..cf274fd67 100644 --- a/cmd/soroban-cli/src/commands/tx/op/add/account_merge.rs +++ b/cmd/soroban-cli/src/commands/tx/op/add/account_merge.rs @@ -1,14 +1,8 @@ -use clap::{command, Parser}; - -use std::fmt::Debug; - -use super::new; - -#[derive(Parser, Debug, Clone)] +#[derive(clap::Parser, Debug, Clone)] #[group(skip)] pub struct Cmd { #[command(flatten)] pub args: super::args::Args, #[command(flatten)] - pub op: new::account_merge::Args, + pub op: super::new::account_merge::Cmd, } diff --git a/cmd/soroban-cli/src/commands/tx/op/add/args.rs b/cmd/soroban-cli/src/commands/tx/op/add/args.rs index f1858e0b0..9d9b4931b 100644 --- a/cmd/soroban-cli/src/commands/tx/op/add/args.rs +++ b/cmd/soroban-cli/src/commands/tx/op/add/args.rs @@ -1,22 +1,8 @@ -use super::xdr::add_op; -use crate::{ - config::{address, locator}, - xdr, -}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Address(#[from] address::Error), - #[error(transparent)] - TxXdr(#[from] super::xdr::Error), -} +use crate::{commands::tx, config::address, xdr}; #[derive(Debug, clap::Args, Clone)] #[group(skip)] pub struct Args { - #[clap(flatten)] - pub locator: locator::Args, /// Source account used for the operation #[arg( long, @@ -31,16 +17,8 @@ impl Args { &self, op_body: impl Into, tx_env: xdr::TransactionEnvelope, - ) -> Result { - let source_account = self - .operation_source_account - .as_ref() - .map(|a| a.resolve_muxed_account(&self.locator, None)) - .transpose()?; - let op = xdr::Operation { - source_account, - body: op_body.into(), - }; - Ok(add_op(tx_env, op)?) + tx: &tx::args::Args, + ) -> Result { + tx.add_op(op_body, tx_env, self.operation_source_account.as_ref()) } } diff --git a/cmd/soroban-cli/src/commands/tx/op/add/bump_sequence.rs b/cmd/soroban-cli/src/commands/tx/op/add/bump_sequence.rs index 907d8d2d6..640b748b3 100644 --- a/cmd/soroban-cli/src/commands/tx/op/add/bump_sequence.rs +++ b/cmd/soroban-cli/src/commands/tx/op/add/bump_sequence.rs @@ -1,14 +1,8 @@ -use clap::{command, Parser}; - -use std::fmt::Debug; - -use super::new; - -#[derive(Parser, Debug, Clone)] +#[derive(clap::Parser, Debug, Clone)] #[group(skip)] pub struct Cmd { #[command(flatten)] pub args: super::args::Args, #[command(flatten)] - pub op: new::bump_sequence::Args, + pub op: super::new::bump_sequence::Cmd, } diff --git a/cmd/soroban-cli/src/commands/tx/op/add/change_trust.rs b/cmd/soroban-cli/src/commands/tx/op/add/change_trust.rs index af9afae1b..5647e4cec 100644 --- a/cmd/soroban-cli/src/commands/tx/op/add/change_trust.rs +++ b/cmd/soroban-cli/src/commands/tx/op/add/change_trust.rs @@ -1,14 +1,8 @@ -use clap::{command, Parser}; - -use std::fmt::Debug; - -use super::new; - -#[derive(Parser, Debug, Clone)] +#[derive(clap::Parser, Debug, Clone)] #[group(skip)] pub struct Cmd { #[command(flatten)] pub args: super::args::Args, #[command(flatten)] - pub op: new::change_trust::Args, + pub op: super::new::change_trust::Cmd, } diff --git a/cmd/soroban-cli/src/commands/tx/op/add/create_account.rs b/cmd/soroban-cli/src/commands/tx/op/add/create_account.rs index e30ff20a1..1ca978e0f 100644 --- a/cmd/soroban-cli/src/commands/tx/op/add/create_account.rs +++ b/cmd/soroban-cli/src/commands/tx/op/add/create_account.rs @@ -1,14 +1,8 @@ -use clap::{command, Parser}; - -use std::fmt::Debug; - -use super::new; - -#[derive(Parser, Debug, Clone)] +#[derive(clap::Parser, Debug, Clone)] #[group(skip)] pub struct Cmd { #[command(flatten)] pub args: super::args::Args, #[command(flatten)] - pub op: new::create_account::Args, + pub op: super::new::create_account::Cmd, } diff --git a/cmd/soroban-cli/src/commands/tx/op/add/manage_data.rs b/cmd/soroban-cli/src/commands/tx/op/add/manage_data.rs index 962233a84..5758478c3 100644 --- a/cmd/soroban-cli/src/commands/tx/op/add/manage_data.rs +++ b/cmd/soroban-cli/src/commands/tx/op/add/manage_data.rs @@ -1,14 +1,8 @@ -use clap::{command, Parser}; - -use std::fmt::Debug; - -use super::new; - -#[derive(Parser, Debug, Clone)] +#[derive(clap::Parser, Debug, Clone)] #[group(skip)] pub struct Cmd { #[command(flatten)] pub args: super::args::Args, #[command(flatten)] - pub op: new::manage_data::Args, + pub op: super::new::manage_data::Cmd, } diff --git a/cmd/soroban-cli/src/commands/tx/op/add/mod.rs b/cmd/soroban-cli/src/commands/tx/op/add/mod.rs index b94fc74ce..1a02d01a2 100644 --- a/cmd/soroban-cli/src/commands/tx/op/add/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/op/add/mod.rs @@ -1,9 +1,7 @@ -use clap::Parser; - use super::super::{global, help, xdr::tx_envelope_from_stdin}; -use crate::xdr::WriteXdr; +use crate::xdr::{OperationBody, WriteXdr}; -pub(crate) use super::super::{new, xdr}; +pub(crate) use super::super::new; mod account_merge; mod args; @@ -15,7 +13,7 @@ mod payment; mod set_options; mod set_trustline_flags; -#[derive(Debug, Parser)] +#[derive(Debug, clap::Parser)] #[allow(clippy::doc_markdown)] pub enum Cmd { #[command(about = help::ACCOUNT_MERGE)] @@ -38,26 +36,45 @@ pub enum Cmd { #[derive(thiserror::Error, Debug)] pub enum Error { - #[error(transparent)] - Args(#[from] args::Error), #[error(transparent)] TxXdr(#[from] super::super::xdr::Error), #[error(transparent)] Xdr(#[from] crate::xdr::Error), + #[error(transparent)] + New(#[from] super::super::new::Error), + #[error(transparent)] + Tx(#[from] super::super::args::Error), +} + +impl TryFrom<&Cmd> for OperationBody { + type Error = super::super::new::Error; + fn try_from(cmd: &Cmd) -> Result { + Ok(match &cmd { + Cmd::AccountMerge(account_merge::Cmd { op, .. }) => op.try_into()?, + Cmd::BumpSequence(bump_sequence::Cmd { op, .. }) => op.into(), + Cmd::ChangeTrust(change_trust::Cmd { op, .. }) => op.try_into()?, + Cmd::CreateAccount(create_account::Cmd { op, .. }) => op.try_into()?, + Cmd::ManageData(manage_data::Cmd { op, .. }) => op.into(), + Cmd::Payment(payment::Cmd { op, .. }) => op.try_into()?, + Cmd::SetOptions(set_options::Cmd { op, .. }) => op.try_into()?, + Cmd::SetTrustlineFlags(set_trustline_flags::Cmd { op, .. }) => op.try_into()?, + }) + } } impl Cmd { pub fn run(&self, _: &global::Args) -> Result<(), Error> { let tx_env = tx_envelope_from_stdin()?; + let op = OperationBody::try_from(self)?; let res = match self { - Cmd::AccountMerge(cmd) => cmd.args.add_op(&cmd.op, tx_env), - Cmd::BumpSequence(cmd) => cmd.args.add_op(&cmd.op, tx_env), - Cmd::ChangeTrust(cmd) => cmd.args.add_op(&cmd.op, tx_env), - Cmd::CreateAccount(cmd) => cmd.args.add_op(&cmd.op, tx_env), - Cmd::ManageData(cmd) => cmd.args.add_op(&cmd.op, tx_env), - Cmd::Payment(cmd) => cmd.args.add_op(&cmd.op, tx_env), - Cmd::SetOptions(cmd) => cmd.args.add_op(&cmd.op, tx_env), - Cmd::SetTrustlineFlags(cmd) => cmd.args.add_op(&cmd.op, tx_env), + Cmd::AccountMerge(cmd) => cmd.args.add_op(op, tx_env, &cmd.op.tx), + Cmd::BumpSequence(cmd) => cmd.args.add_op(op, tx_env, &cmd.op.tx), + Cmd::ChangeTrust(cmd) => cmd.args.add_op(op, tx_env, &cmd.op.tx), + Cmd::CreateAccount(cmd) => cmd.args.add_op(op, tx_env, &cmd.op.tx), + Cmd::ManageData(cmd) => cmd.args.add_op(op, tx_env, &cmd.op.tx), + Cmd::Payment(cmd) => cmd.args.add_op(op, tx_env, &cmd.op.tx), + Cmd::SetOptions(cmd) => cmd.args.add_op(op, tx_env, &cmd.op.tx), + Cmd::SetTrustlineFlags(cmd) => cmd.args.add_op(op, tx_env, &cmd.op.tx), }?; println!("{}", res.to_xdr_base64(crate::xdr::Limits::none())?); Ok(()) diff --git a/cmd/soroban-cli/src/commands/tx/op/add/payment.rs b/cmd/soroban-cli/src/commands/tx/op/add/payment.rs index d8146c91a..f5f9c5fcc 100644 --- a/cmd/soroban-cli/src/commands/tx/op/add/payment.rs +++ b/cmd/soroban-cli/src/commands/tx/op/add/payment.rs @@ -1,14 +1,8 @@ -use clap::{command, Parser}; - -use std::fmt::Debug; - -use super::new; - -#[derive(Parser, Debug, Clone)] +#[derive(clap::Parser, Debug, Clone)] #[group(skip)] pub struct Cmd { #[command(flatten)] pub args: super::args::Args, #[command(flatten)] - pub op: new::payment::Args, + pub op: super::new::payment::Cmd, } diff --git a/cmd/soroban-cli/src/commands/tx/op/add/set_options.rs b/cmd/soroban-cli/src/commands/tx/op/add/set_options.rs index 75b43124a..88323f57c 100644 --- a/cmd/soroban-cli/src/commands/tx/op/add/set_options.rs +++ b/cmd/soroban-cli/src/commands/tx/op/add/set_options.rs @@ -1,14 +1,8 @@ -use clap::{command, Parser}; - -use std::fmt::Debug; - -use super::new; - -#[derive(Parser, Debug, Clone)] +#[derive(clap::Parser, Debug, Clone)] #[group(skip)] pub struct Cmd { #[command(flatten)] pub args: super::args::Args, #[command(flatten)] - pub op: new::set_options::Args, + pub op: super::new::set_options::Cmd, } diff --git a/cmd/soroban-cli/src/commands/tx/op/add/set_trustline_flags.rs b/cmd/soroban-cli/src/commands/tx/op/add/set_trustline_flags.rs index 8ffee7a7b..09ee11607 100644 --- a/cmd/soroban-cli/src/commands/tx/op/add/set_trustline_flags.rs +++ b/cmd/soroban-cli/src/commands/tx/op/add/set_trustline_flags.rs @@ -1,14 +1,8 @@ -use clap::{command, Parser}; - -use std::fmt::Debug; - -use super::new; - -#[derive(Parser, Debug, Clone)] +#[derive(clap::Parser, Debug, Clone)] #[group(skip)] pub struct Cmd { #[command(flatten)] pub args: super::args::Args, #[command(flatten)] - pub op: new::set_trustline_flags::Args, + pub op: super::new::set_trustline_flags::Cmd, } diff --git a/cmd/soroban-cli/src/config/address.rs b/cmd/soroban-cli/src/config/address.rs index 066bc8d91..ac2adc762 100644 --- a/cmd/soroban-cli/src/config/address.rs +++ b/cmd/soroban-cli/src/config/address.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use crate::xdr; -use super::{locator, secret}; +use super::{key, locator, secret}; /// Address can be either a public key or eventually an alias of a address. #[derive(Clone, Debug)] @@ -22,7 +22,7 @@ pub enum Error { #[error(transparent)] Locator(#[from] locator::Error), #[error(transparent)] - Secret(#[from] secret::Error), + Key(#[from] key::Error), #[error("Address cannot be used to sign {0}")] CannotSign(xdr::MuxedAccount), } @@ -46,18 +46,16 @@ impl Address { ) -> Result { match self { Address::MuxedAccount(muxed_account) => Ok(muxed_account.clone()), - Address::AliasOrSecret(alias) => alias.parse().or_else(|_| { - Ok(xdr::MuxedAccount::Ed25519( - locator.read_identity(alias)?.public_key(hd_path)?.0.into(), - )) - }), + Address::AliasOrSecret(alias) => alias + .parse() + .or_else(|_| Ok(locator.get_public_key(alias, hd_path)?)), } } pub fn resolve_secret(&self, locator: &locator::Args) -> Result { match &self { + Address::AliasOrSecret(alias) => Ok(locator.get_private_key(alias)?), Address::MuxedAccount(muxed_account) => Err(Error::CannotSign(muxed_account.clone())), - Address::AliasOrSecret(alias) => Ok(locator.read_identity(alias)?), } } } diff --git a/cmd/soroban-cli/src/config/key.rs b/cmd/soroban-cli/src/config/key.rs new file mode 100644 index 000000000..cecb83522 --- /dev/null +++ b/cmd/soroban-cli/src/config/key.rs @@ -0,0 +1,172 @@ +use std::{fmt::Display, str::FromStr}; + +use serde::{Deserialize, Serialize}; + +use super::secret::{self, Secret}; +use crate::xdr; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("failed to extract secret from public key ")] + SecretPublicKey, + #[error(transparent)] + Secret(#[from] secret::Error), + #[error(transparent)] + StrKey(#[from] stellar_strkey::DecodeError), + #[error("failed to parse key")] + Parse, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +pub enum Key { + #[serde(rename = "public_key")] + PublicKey(Public), + #[serde(rename = "muxed_account")] + MuxedAccount(MuxedAccount), + #[serde(untagged)] + Secret(Secret), +} + +impl Key { + pub fn public_key(&self, hd_path: Option) -> Result { + let bytes = match self { + Key::Secret(secret) => secret.public_key(hd_path)?.0, + Key::PublicKey(Public(key)) => key.0, + Key::MuxedAccount(MuxedAccount(stellar_strkey::ed25519::MuxedAccount { + ed25519, + id, + })) => { + return Ok(xdr::MuxedAccount::MuxedEd25519(xdr::MuxedAccountMed25519 { + ed25519: xdr::Uint256(*ed25519), + id: *id, + })) + } + }; + Ok(xdr::MuxedAccount::Ed25519(xdr::Uint256(bytes))) + } + + pub fn private_key( + &self, + hd_path: Option, + ) -> Result { + match self { + Key::Secret(secret) => Ok(secret.private_key(hd_path)?), + _ => Err(Error::SecretPublicKey), + } + } +} + +impl FromStr for Key { + type Err = Error; + + fn from_str(s: &str) -> Result { + if let Ok(secret) = s.parse() { + return Ok(Key::Secret(secret)); + } + if let Ok(public_key) = s.parse() { + return Ok(Key::PublicKey(public_key)); + } + if let Ok(muxed_account) = s.parse() { + return Ok(Key::MuxedAccount(muxed_account)); + } + Err(Error::Parse) + } +} + +impl From for Key { + fn from(value: stellar_strkey::ed25519::PublicKey) -> Self { + Key::PublicKey(Public(value)) + } +} + +impl From<&stellar_strkey::ed25519::PublicKey> for Key { + fn from(stellar_strkey::ed25519::PublicKey(key): &stellar_strkey::ed25519::PublicKey) -> Self { + stellar_strkey::ed25519::PublicKey(*key).into() + } +} + +#[derive(Debug, PartialEq, Eq, serde_with::SerializeDisplay, serde_with::DeserializeFromStr)] +pub struct Public(pub stellar_strkey::ed25519::PublicKey); + +impl FromStr for Public { + type Err = stellar_strkey::DecodeError; + + fn from_str(s: &str) -> Result { + Ok(Public(stellar_strkey::ed25519::PublicKey::from_str(s)?)) + } +} + +impl Display for Public { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From<&Public> for stellar_strkey::ed25519::MuxedAccount { + fn from(Public(stellar_strkey::ed25519::PublicKey(key)): &Public) -> Self { + stellar_strkey::ed25519::MuxedAccount { + id: 0, + ed25519: *key, + } + } +} + +#[derive(Debug, PartialEq, Eq, serde_with::SerializeDisplay, serde_with::DeserializeFromStr)] +pub struct MuxedAccount(pub stellar_strkey::ed25519::MuxedAccount); + +impl FromStr for MuxedAccount { + type Err = stellar_strkey::DecodeError; + + fn from_str(s: &str) -> Result { + Ok(MuxedAccount( + stellar_strkey::ed25519::MuxedAccount::from_str(s)?, + )) + } +} + +impl Display for MuxedAccount { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +#[cfg(test)] +mod test { + use super::*; + + fn round_trip(key: &Key) { + let serialized = toml::to_string(&key).unwrap(); + println!("{serialized}"); + let deserialized: Key = toml::from_str(&serialized).unwrap(); + assert_eq!(key, &deserialized); + } + + #[test] + fn public_key() { + let key = Key::PublicKey(Public(stellar_strkey::ed25519::PublicKey([0; 32]))); + round_trip(&key); + } + #[test] + fn muxed_key() { + let key: stellar_strkey::ed25519::MuxedAccount = + "MA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAAAAAAAAAPCICBKU" + .parse() + .unwrap(); + let key = Key::MuxedAccount(MuxedAccount(key)); + round_trip(&key); + } + #[test] + fn secret_key() { + let secret_key = stellar_strkey::ed25519::PrivateKey([0; 32]).to_string(); + let secret = Secret::SecretKey { secret_key }; + let key = Key::Secret(secret); + round_trip(&key); + } + #[test] + fn secret_seed_phrase() { + let seed_phrase = "singer swing mango apple singer swing mango apple singer swing mango apple singer swing mango apple".to_string(); + let secret = Secret::SeedPhrase { seed_phrase }; + let key = Key::Secret(secret); + round_trip(&key); + } +} diff --git a/cmd/soroban-cli/src/config/locator.rs b/cmd/soroban-cli/src/config/locator.rs index b6f5c75c1..68a95f628 100644 --- a/cmd/soroban-cli/src/config/locator.rs +++ b/cmd/soroban-cli/src/config/locator.rs @@ -12,10 +12,11 @@ use std::{ }; use stellar_strkey::{Contract, DecodeError}; -use crate::{commands::HEADING_GLOBAL, utils::find_config_dir, Pwd}; +use crate::{commands::HEADING_GLOBAL, utils::find_config_dir, xdr, Pwd}; use super::{ alias, + key::{self, Key}, network::{self, Network}, secret::Secret, Config, @@ -83,6 +84,10 @@ pub enum Error { UpgradeCheckReadFailed { path: PathBuf, error: io::Error }, #[error("Failed to write upgrade check file: {path}: {error}")] UpgradeCheckWriteFailed { path: PathBuf, error: io::Error }, + #[error("Only private keys and seed phrases are supported for getting private keys {0}")] + SecretKeyOnly(String), + #[error(transparent)] + Key(#[from] key::Error), } #[derive(Debug, clap::Args, Default, Clone)] @@ -163,7 +168,19 @@ impl Args { } pub fn write_identity(&self, name: &str, secret: &Secret) -> Result<(), Error> { - KeyType::Identity.write(name, secret, &self.config_dir()?) + self.write_key(name, &Key::Secret(secret.clone())) + } + + pub fn write_public_key( + &self, + name: &str, + public_key: &stellar_strkey::ed25519::PublicKey, + ) -> Result<(), Error> { + self.write_key(name, &public_key.into()) + } + + pub fn write_key(&self, name: &str, public_key: &Key) -> Result<(), Error> { + KeyType::Identity.write(name, public_key, &self.config_dir()?) } pub fn write_network(&self, name: &str, network: &Network) -> Result<(), Error> { @@ -228,20 +245,33 @@ impl Args { Ok(saved_networks.chain(default_networks).collect()) } - pub fn read_identity(&self, name: &str) -> Result { - Ok(KeyType::Identity - .read_with_global(name, &self.local_config()?) - .or_else(|_| name.parse())?) + pub fn read_identity(&self, name: &str) -> Result { + KeyType::Identity.read_with_global(name, &self.local_config()?) } - pub fn key(&self, key_or_name: &str) -> Result { - if let Ok(signer) = key_or_name.parse::() { - Ok(signer) + pub fn read_key(&self, key_or_name: &str) -> Result { + if let Ok(key) = self.read_identity(key_or_name) { + Ok(key) } else { - self.read_identity(key_or_name) + Ok(key_or_name.parse()?) + } + } + + pub fn get_private_key(&self, key_or_name: &str) -> Result { + match self.read_key(key_or_name)? { + Key::Secret(s) => Ok(s), + _ => Err(Error::SecretKeyOnly(key_or_name.to_string())), } } + pub fn get_public_key( + &self, + key_or_name: &str, + hd_path: Option, + ) -> Result { + Ok(self.read_key(key_or_name)?.public_key(hd_path)?) + } + pub fn read_network(&self, name: &str) -> Result { let res = KeyType::Network.read_with_global(name, &self.local_config()?); if let Err(Error::ConfigMissing(_, _)) = &res { diff --git a/cmd/soroban-cli/src/config/mod.rs b/cmd/soroban-cli/src/config/mod.rs index a429ff434..fb4c787fd 100644 --- a/cmd/soroban-cli/src/config/mod.rs +++ b/cmd/soroban-cli/src/config/mod.rs @@ -17,6 +17,7 @@ use network::Network; pub mod address; pub mod alias; pub mod data; +pub mod key; pub mod locator; pub mod network; pub mod secret; diff --git a/cmd/soroban-cli/src/config/secret.rs b/cmd/soroban-cli/src/config/secret.rs index a7fd86fda..82008795b 100644 --- a/cmd/soroban-cli/src/config/secret.rs +++ b/cmd/soroban-cli/src/config/secret.rs @@ -9,6 +9,8 @@ use crate::{ utils, }; +use super::key::{self, Key}; + #[derive(thiserror::Error, Debug)] pub enum Error { #[error("invalid secret key")] @@ -39,19 +41,26 @@ pub struct Args { /// Add using 12 word seed phrase to generate `secret_key` #[arg(long, conflicts_with = "secret_key")] pub seed_phrase: bool, + + /// Add a public key, ed25519, or muxed account, e.g. G1.., M2.. + #[arg(long, conflicts_with = "seed_phrase", conflicts_with = "secret_key")] + pub public_key: Option, } impl Args { - pub fn read_secret(&self) -> Result { + pub fn read_key(&self) -> Result { + if let Some(public_key) = self.public_key.as_ref() { + return public_key.parse(); + }; if let Ok(secret_key) = std::env::var("SOROBAN_SECRET_KEY") { - Ok(Secret::SecretKey { secret_key }) + Ok(Key::Secret(Secret::SecretKey { secret_key })) } else if self.secret_key { println!("Type a secret key: "); let secret_key = read_password()?; let secret_key = PrivateKey::from_string(&secret_key) .map_err(|_| Error::InvalidSecretKey)? .to_string(); - Ok(Secret::SecretKey { secret_key }) + Ok(Key::Secret(Secret::SecretKey { secret_key })) } else if self.seed_phrase { println!("Type a 12 word seed phrase: "); let seed_phrase = read_password()?; @@ -60,20 +69,20 @@ impl Args { // let len = seed_phrase.len(); // return Err(Error::InvalidSeedPhrase { len }); // } - Ok(Secret::SeedPhrase { + Ok(Key::Secret(Secret::SeedPhrase { seed_phrase: seed_phrase .into_iter() .map(ToString::to_string) .collect::>() .join(" "), - }) + })) } else { - Err(Error::PasswordRead {}) + Err(Error::PasswordRead {}.into()) } } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(untagged)] pub enum Secret { SecretKey { secret_key: String }, diff --git a/cmd/soroban-cli/src/config/sign_with.rs b/cmd/soroban-cli/src/config/sign_with.rs index 475013bc8..c9d164914 100644 --- a/cmd/soroban-cli/src/config/sign_with.rs +++ b/cmd/soroban-cli/src/config/sign_with.rs @@ -64,7 +64,7 @@ impl Args { } } else { let key_or_name = self.sign_with_key.as_deref().ok_or(Error::NoSignWithKey)?; - let secret = locator.key(key_or_name)?; + let secret = locator.get_private_key(key_or_name)?; secret.signer(self.hd_path, print)? }; Ok(signer.sign_tx_env(tx, network)?) diff --git a/cmd/soroban-cli/src/tx/builder/asset.rs b/cmd/soroban-cli/src/tx/builder/asset.rs index bba39804e..c67e4b42e 100644 --- a/cmd/soroban-cli/src/tx/builder/asset.rs +++ b/cmd/soroban-cli/src/tx/builder/asset.rs @@ -1,17 +1,24 @@ use std::str::FromStr; -use crate::xdr::{self, AlphaNum12, AlphaNum4, AssetCode}; +use crate::{ + config::{address, locator}, + xdr::{self, AlphaNum12, AlphaNum4, AssetCode}, +}; #[derive(Clone, Debug)] -pub struct Asset(pub xdr::Asset); +pub enum Asset { + Asset(AssetCode, address::Address), + Native, +} #[derive(thiserror::Error, Debug)] pub enum Error { #[error("cannot parse asset: {0}, expected format: 'native' or 'code:issuer'")] CannotParseAsset(String), - #[error(transparent)] Xdr(#[from] xdr::Error), + #[error(transparent)] + Address(#[from] address::Error), } impl FromStr for Asset { @@ -19,32 +26,31 @@ impl FromStr for Asset { fn from_str(value: &str) -> Result { if value == "native" { - return Ok(Asset(xdr::Asset::Native)); + return Ok(Asset::Native); } let mut iter = value.splitn(2, ':'); let (Some(code), Some(issuer), None) = (iter.next(), iter.next(), iter.next()) else { return Err(Error::CannotParseAsset(value.to_string())); }; - let issuer = issuer.parse()?; - Ok(Asset(match code.parse()? { - AssetCode::CreditAlphanum4(asset_code) => { - xdr::Asset::CreditAlphanum4(AlphaNum4 { asset_code, issuer }) - } - AssetCode::CreditAlphanum12(asset_code) => { - xdr::Asset::CreditAlphanum12(AlphaNum12 { asset_code, issuer }) - } - })) + Ok(Asset::Asset(code.parse()?, issuer.parse()?)) } } -impl From for xdr::Asset { - fn from(builder: Asset) -> Self { - builder.0 - } -} - -impl From<&Asset> for xdr::Asset { - fn from(builder: &Asset) -> Self { - builder.clone().into() +impl Asset { + pub fn resolve(&self, locator: &locator::Args) -> Result { + Ok(match self { + Asset::Asset(code, issuer) => { + let issuer = issuer.resolve_muxed_account(locator, None)?.account_id(); + match code.clone() { + AssetCode::CreditAlphanum4(asset_code) => { + xdr::Asset::CreditAlphanum4(AlphaNum4 { asset_code, issuer }) + } + AssetCode::CreditAlphanum12(asset_code) => { + xdr::Asset::CreditAlphanum12(AlphaNum12 { asset_code, issuer }) + } + } + } + Asset::Native => xdr::Asset::Native, + }) } } diff --git a/cmd/soroban-cli/src/utils.rs b/cmd/soroban-cli/src/utils.rs index 0c4207a4e..3efe619ef 100644 --- a/cmd/soroban-cli/src/utils.rs +++ b/cmd/soroban-cli/src/utils.rs @@ -125,13 +125,13 @@ pub fn is_hex_string(s: &str) -> bool { } pub fn contract_id_hash_from_asset( - asset: impl Into, + asset: &Asset, network_passphrase: &str, ) -> stellar_strkey::Contract { let network_id = Hash(Sha256::digest(network_passphrase.as_bytes()).into()); let preimage = HashIdPreimage::ContractId(HashIdPreimageContractId { network_id, - contract_id_preimage: ContractIdPreimage::Asset(asset.into()), + contract_id_preimage: ContractIdPreimage::Asset(asset.clone()), }); let preimage_xdr = preimage .to_xdr(Limits::none())