Skip to content
Closed
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
1 change: 1 addition & 0 deletions cmd/soroban-cli/src/commands/skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ After `stellar keys use`, other commands sign and pay with that identity, so you
- Clear the default: `stellar keys unset`

Only pass `--source <name>` when a single command needs to override the default identity.
- Predictable auth signatures: pass `--signature-expiration-ledger <ledger>` (or env `STELLAR_SIGNATURE_EXPIRATION_LEDGER`) to explicitly set the authorization expiration sequence instead of querying RPC `getLatestLedger`.

## Contracts: use aliases, don't stash contract ids in env vars

Expand Down
10 changes: 7 additions & 3 deletions cmd/soroban-cli/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,13 @@ impl Args {
print: &Print,
) -> Result<Option<Transaction>, Error> {
let network = self.get_network()?;
let client = network.rpc_client()?;
let latest_ledger = client.get_latest_ledger().await?.sequence;
let seq_num = latest_ledger + 60; // ~ 5 min
let seq_num = if let Some(exp) = self.sign_with.signature_expiration_ledger {
exp
} else {
let client = network.rpc_client()?;
let latest_ledger = client.get_latest_ledger().await?.sequence;
latest_ledger + 60 // ~ 5 min
};
Ok(signer::sign_soroban_authorizations(
tx,
signers,
Expand Down
48 changes: 48 additions & 0 deletions cmd/soroban-cli/src/config/sign_with.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ pub struct Args {
/// Sign without prompting for approval. Only applies to signatures that require user approval, like non-root Soroban auth entries.
#[arg(long, help_heading = HEADING_SIGNING)]
pub auto_sign: bool,

/// Explicit signature expiration ledger sequence for Soroban authorization signatures. If not set, defaults to latest ledger + 60 (~5 minutes).
#[arg(
long,
env = "STELLAR_SIGNATURE_EXPIRATION_LEDGER",
help_heading = HEADING_SIGNING
)]
pub signature_expiration_ledger: Option<u32>,
}

impl Args {
Expand Down Expand Up @@ -115,3 +123,43 @@ impl Args {
Ok(signer.sign_tx_env(tx, network).await?)
}
}

#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;

#[derive(Parser, Debug)]
struct TestCli {
#[command(flatten)]
sign_with: Args,
}

#[test]
fn test_signature_expiration_ledger_arg_parsing() {
// 1. Defaults to None
let parsed = TestCli::try_parse_from(["test"]).unwrap();
assert_eq!(parsed.sign_with.signature_expiration_ledger, None);

// 2. Parsed from command-line argument
let parsed = TestCli::try_parse_from(["test", "--signature-expiration-ledger", "123456"]).unwrap();
assert_eq!(parsed.sign_with.signature_expiration_ledger, Some(123456));

// 3. Parsed from environment variable
let env_key = "STELLAR_SIGNATURE_EXPIRATION_LEDGER";
let prev = std::env::var(env_key).ok();
std::env::set_var(env_key, "654321");
let parsed = TestCli::try_parse_from(["test"]).unwrap();
assert_eq!(parsed.sign_with.signature_expiration_ledger, Some(654321));

// Command line argument overrides env var
let parsed = TestCli::try_parse_from(["test", "--signature-expiration-ledger", "789012"]).unwrap();
assert_eq!(parsed.sign_with.signature_expiration_ledger, Some(789012));

if let Some(p) = prev {
std::env::set_var(env_key, p);
} else {
std::env::remove_var(env_key);
}
}
}