Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions FULL_HELP_DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1957,6 +1957,8 @@ Interact with SEP-41 tokens and Stellar Asset Contracts
- `allowance` — Read the allowance a spender has on an owner's behalf
- `mint` — Mint new tokens to an account or contract (SAC admin)
- `clawback` — Claw back tokens from an account or contract (SAC admin)
- `set-admin` — Transfer administration of the token to a new admin (SAC admin)
- `set-authorized` — Authorize or deauthorize an account to hold the token (SAC admin)

## `stellar token transfer`

Expand Down Expand Up @@ -2403,6 +2405,102 @@ Calls the token's Stellar Asset Contract `clawback` function. A non-SAC contract
- `--fee <FEE>` — ⚠️ Deprecated, use `--inclusion-fee`. Fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm
- `--inclusion-fee <INCLUSION_FEE>` — Maximum fee amount for transaction inclusion, in stroops. 1 stroop = 0.0000001 xlm. Defaults to 100 if no arg, env, or config value is provided

## `stellar token set-admin`

Transfer administration of the token to a new admin (SAC admin)

Calls the token's Stellar Asset Contract `set_admin` function. A non-SAC contract with a same-named function that takes different arguments will fail or misbehave — use `stellar contract invoke` for those.

**Usage:** `stellar token set-admin [OPTIONS] --id <ID> --new-admin <NEW_ADMIN> --source-account <SOURCE_ACCOUNT>`

###### **Global Options:**

- `--config-dir <CONFIG_DIR>` — Location of config directory. By default, it uses `$XDG_CONFIG_HOME/stellar` if set, falling back to `~/.config/stellar` otherwise. Contains configuration files, aliases, and other persistent settings

###### **Options:**

- `--id <ID>` — The token to re-administer: a contract id or alias, or a classic asset as `CODE:ISSUER`
- `--new-admin <NEW_ADMIN>` — The new administrator to hand control to. Accepts a `G…`/`M…` account, a `C…` contract address, or an alias
- `--output <OUTPUT>` — Format of the output

Default value: `text`

Possible values:
- `text`: Human-readable text
- `json`: Compact, single-line JSON output
- `json-formatted`: Formatted (multiline) JSON output

###### **RPC Options:**

- `--rpc-url <RPC_URL>` — RPC server endpoint
- `--rpc-header <RPC_HEADERS>` — RPC Header(s) to include in requests to the RPC provider, example: "X-API-Key: abc123". Multiple headers can be added by passing the option multiple times
- `--network-passphrase <NETWORK_PASSPHRASE>` — Network passphrase to sign the transaction sent to the rpc server
- `-n`, `--network <NETWORK>` — Name of network to use from config

###### **Signing Options:**

- `--sign-with-key <SIGN_WITH_KEY>` — Sign with a local key or key saved in OS secure storage. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path
- `--hd-path <HD_PATH>` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0`
- `--sign-with-lab` — Sign with https://lab.stellar.org
- `--sign-with-ledger` — Sign with a ledger wallet
- `--auto-sign` — Sign without prompting for approval. Only applies to signatures that require user approval, like non-root Soroban auth entries

###### **Transaction Options:**

- `-s`, `--source-account <SOURCE_ACCOUNT>` [alias: `source`] — 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` was NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail
- `--fee <FEE>` — ⚠️ Deprecated, use `--inclusion-fee`. Fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm
- `--inclusion-fee <INCLUSION_FEE>` — Maximum fee amount for transaction inclusion, in stroops. 1 stroop = 0.0000001 xlm. Defaults to 100 if no arg, env, or config value is provided

## `stellar token set-authorized`

Authorize or deauthorize an account to hold the token (SAC admin)

Calls the token's Stellar Asset Contract `set_authorized` function. A non-SAC contract with a same-named function that takes different arguments will fail or misbehave — use `stellar contract invoke` for those.

**Usage:** `stellar token set-authorized [OPTIONS] --id <ID> --account <ACCOUNT> --authorize <AUTHORIZE> --source-account <SOURCE_ACCOUNT>`

###### **Global Options:**

- `--config-dir <CONFIG_DIR>` — Location of config directory. By default, it uses `$XDG_CONFIG_HOME/stellar` if set, falling back to `~/.config/stellar` otherwise. Contains configuration files, aliases, and other persistent settings

###### **Options:**

- `--id <ID>` — The token whose authorization to set: a contract id or alias, or a classic asset as `CODE:ISSUER`
- `--account <ACCOUNT>` — Account or contract whose authorization to set. Accepts a `G…`/`M…` account, a `C…` contract address, or an alias
- `--authorize <AUTHORIZE>` — Whether the account is authorized (`true`) to hold and transact the token, or deauthorized/frozen (`false`)

Possible values: `true`, `false`

- `--output <OUTPUT>` — Format of the output

Default value: `text`

Possible values:
- `text`: Human-readable text
- `json`: Compact, single-line JSON output
- `json-formatted`: Formatted (multiline) JSON output

###### **RPC Options:**

- `--rpc-url <RPC_URL>` — RPC server endpoint
- `--rpc-header <RPC_HEADERS>` — RPC Header(s) to include in requests to the RPC provider, example: "X-API-Key: abc123". Multiple headers can be added by passing the option multiple times
- `--network-passphrase <NETWORK_PASSPHRASE>` — Network passphrase to sign the transaction sent to the rpc server
- `-n`, `--network <NETWORK>` — Name of network to use from config

###### **Signing Options:**

- `--sign-with-key <SIGN_WITH_KEY>` — Sign with a local key or key saved in OS secure storage. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path
- `--hd-path <HD_PATH>` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0`
- `--sign-with-lab` — Sign with https://lab.stellar.org
- `--sign-with-ledger` — Sign with a ledger wallet
- `--auto-sign` — Sign without prompting for approval. Only applies to signatures that require user approval, like non-root Soroban auth entries

###### **Transaction Options:**

- `-s`, `--source-account <SOURCE_ACCOUNT>` [alias: `source`] — 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` was NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail
- `--fee <FEE>` — ⚠️ Deprecated, use `--inclusion-fee`. Fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm
- `--inclusion-fee <INCLUSION_FEE>` — Maximum fee amount for transaction inclusion, in stroops. 1 stroop = 0.0000001 xlm. Defaults to 100 if no arg, env, or config value is provided

## `stellar tx`

Sign, Simulate, and Send transactions
Expand Down
2 changes: 2 additions & 0 deletions cmd/crates/soroban-test/tests/it/integration/token/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ pub mod decimals;
pub mod mint;
pub mod name;
pub mod renamed;
pub mod set_admin;
pub mod set_authorized;
pub mod symbol;
pub mod transfer;
pub mod transfer_from;
Expand Down
175 changes: 175 additions & 0 deletions cmd/crates/soroban-test/tests/it/integration/token/set_admin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
use serde_json::Value;
use soroban_test::{AssertExt, TestEnv};

use crate::integration::{
token::{add_trustline, deploy_sac, sac_balance, sac_id},
util::{deploy_hello, new_account, test_address},
};

#[tokio::test]
async fn set_admin_transfers_control_and_returns_receipt() {
let sandbox = &TestEnv::new();
let test = test_address(sandbox);
let issuer = new_account(sandbox, "issuer");
let new_admin = new_account(sandbox, "newadmin");
let asset = format!("USDC:{issuer}");

add_trustline(sandbox, "test", &asset);
deploy_sac(sandbox, &asset, "issuer");

let stdout = sandbox
.new_assert_cmd("token")
.args([
"set-admin",
"--id",
&asset,
"--source",
"issuer",
"--new-admin",
&new_admin,
"--output",
"json",
])
.assert()
.success()
.stdout_as_str();
let receipt: Value = serde_json::from_str(&stdout).unwrap();
assert!(
receipt["tx_hash"].as_str().is_some(),
"expected a tx hash, got: {receipt}"
);

// Control has transferred: the new admin can now mint, proving the change
// took effect.
sandbox
.new_assert_cmd("token")
.args([
"mint", "--id", &asset, "--source", "newadmin", "--to", &test, "--amount", "9000000",
])
.assert()
.success();
let sac = sac_id(sandbox, &asset);
assert_eq!(
sac_balance(sandbox, &sac, &test),
9_000_000,
"the new admin should be able to mint"
);
}

#[tokio::test]
async fn set_admin_fails_when_sac_not_deployed() {
let sandbox = &TestEnv::new();
let issuer = new_account(sandbox, "issuer");
let new_admin = new_account(sandbox, "newadmin");
let asset = format!("USDC:{issuer}");

// No SAC deployed → structured deploy-pointer error with a typed discriminator.
let stdout = sandbox
.new_assert_cmd("token")
.args([
"set-admin",
"--id",
&asset,
"--source",
"issuer",
"--new-admin",
&new_admin,
"--output",
"json",
])
.assert()
.failure()
.stdout_as_str();
let value: Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(
value["error"]["type"], "sac_not_deployed",
"expected a typed error, got: {stdout}"
);
}

#[tokio::test]
async fn set_admin_rejects_muxed_source_with_clear_error() {
let sandbox = &TestEnv::new();
let new_admin = new_account(sandbox, "newadmin");

// Muxed (M…) source accounts aren't supported by the invoke pipeline yet
// (see #2645). Until then the command must reject them up front with a clear
// message rather than a raw strkey decode error deep in the pipeline.
let muxed = "MA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAAAAAAAAAPCICBKU";
sandbox
.new_assert_cmd("token")
.args([
"set-admin",
"--id",
"native",
"--source",
muxed,
"--new-admin",
&new_admin,
])
.assert()
.failure()
.stderr(predicates::str::contains(
"muxed (M…) source accounts are not yet supported",
));
}

#[tokio::test]
async fn set_admin_warns_when_target_is_not_a_sac() {
let sandbox = &TestEnv::new();
let new_admin = new_account(sandbox, "newadmin");
let contract_id = deploy_hello(sandbox).await;

// Pointing a SAC-admin command at a plain wasm contract warns. The call then
// fails (hello_world has no `set_admin`), but the heads-up is the point.
let stderr = sandbox
.new_assert_cmd("token")
.args([
"set-admin",
"--id",
&contract_id,
"--source",
"test",
"--new-admin",
&new_admin,
])
.assert()
.failure()
.stderr_as_str();
assert!(
stderr.contains("is not a Stellar Asset Contract"),
"expected a non-SAC warning, got: {stderr}"
);
}

#[tokio::test]
async fn set_admin_does_not_warn_when_target_is_a_sac() {
let sandbox = &TestEnv::new();
let issuer = new_account(sandbox, "issuer");
let new_admin = new_account(sandbox, "newadmin");
let asset = format!("USDC:{issuer}");

deploy_sac(sandbox, &asset, "issuer");
// Reference the SAC by its contract id, not the asset, so the check can only
// clear it by inspecting the on-chain executable — not the id's text form.
let sac = sac_id(sandbox, &asset);

let stderr = sandbox
.new_assert_cmd("token")
.args([
"set-admin",
"--id",
&sac,
"--source",
"issuer",
"--new-admin",
&new_admin,
])
.assert()
.success()
.stderr_as_str();
assert!(
!stderr.contains("is not a Stellar Asset Contract"),
"a genuine SAC should not warn, got: {stderr}"
);
}
Loading
Loading