From 9996b116a77aef85f09fba272d18acd10091139e Mon Sep 17 00:00:00 2001 From: Matt Hess Date: Fri, 27 Feb 2026 03:19:45 +0000 Subject: [PATCH] Fix get_outs HTTP 500 by using global index space for decoy selection The transfer pipeline was passing asset-type-specific asset_type to get_output_distribution and get_outs but using global output indices from the wallet DB, causing an index space mismatch that made the daemon return HTTP 500. Switched to empty asset_type (global index space) for both calls, matching the existing salvium-cli pattern. Removed unused is_global_out field from OutputRequest. Bumped version to r012. --- Cargo.lock | 26 +++++----- Cargo.toml | 2 +- crates/salvium-explorer/Cargo.toml | 2 +- crates/salvium-ffi/src/transfer.rs | 13 +++-- crates/salvium-rpc/src/daemon.rs | 9 ++-- docs/wallet-sync-spec.md | 83 +++++++++++++++++++++++++++++- 6 files changed, 110 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e9896b..7e3ac74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2047,7 +2047,7 @@ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "salvium-cli" -version = "1.0.7-r011" +version = "1.0.7-r012" dependencies = [ "clap", "dirs", @@ -2069,7 +2069,7 @@ dependencies = [ [[package]] name = "salvium-consensus" -version = "1.0.7-r011" +version = "1.0.7-r012" dependencies = [ "hex", "salvium-types", @@ -2081,7 +2081,7 @@ dependencies = [ [[package]] name = "salvium-crypto" -version = "1.0.7-r011" +version = "1.0.7-r012" dependencies = [ "aes-gcm", "argon2", @@ -2107,7 +2107,7 @@ dependencies = [ [[package]] name = "salvium-ffi" -version = "1.0.7-r011" +version = "1.0.7-r012" dependencies = [ "hex", "log", @@ -2125,7 +2125,7 @@ dependencies = [ [[package]] name = "salvium-miner" -version = "1.0.7-r011" +version = "1.0.7-r012" dependencies = [ "clap", "hex", @@ -2140,7 +2140,7 @@ dependencies = [ [[package]] name = "salvium-miner-gr" -version = "1.0.7-r011" +version = "1.0.7-r012" dependencies = [ "cc", "clap", @@ -2152,7 +2152,7 @@ dependencies = [ [[package]] name = "salvium-miner-v2" -version = "1.0.7-r011" +version = "1.0.7-r012" dependencies = [ "cc", "clap", @@ -2167,7 +2167,7 @@ dependencies = [ [[package]] name = "salvium-multisig" -version = "1.0.7-r011" +version = "1.0.7-r012" dependencies = [ "curve25519-dalek", "hex", @@ -2183,7 +2183,7 @@ dependencies = [ [[package]] name = "salvium-rpc" -version = "1.0.7-r011" +version = "1.0.7-r012" dependencies = [ "base64", "hex", @@ -2197,7 +2197,7 @@ dependencies = [ [[package]] name = "salvium-sync-bench" -version = "1.0.7-r011" +version = "1.0.7-r012" dependencies = [ "clap", "env_logger", @@ -2214,7 +2214,7 @@ dependencies = [ [[package]] name = "salvium-tx" -version = "1.0.7-r011" +version = "1.0.7-r012" dependencies = [ "curve25519-dalek", "getrandom 0.2.17", @@ -2232,7 +2232,7 @@ dependencies = [ [[package]] name = "salvium-types" -version = "1.0.7-r011" +version = "1.0.7-r012" dependencies = [ "hex", "serde", @@ -2242,7 +2242,7 @@ dependencies = [ [[package]] name = "salvium-wallet" -version = "1.0.7-r011" +version = "1.0.7-r012" dependencies = [ "aes-gcm", "chacha20", diff --git a/Cargo.toml b/Cargo.toml index f661b1d..6d27eaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ members = [ ] [workspace.package] -version = "1.0.7-r011" +version = "1.0.7-r012" edition = "2021" license = "LicenseRef-Salvium-RS" repository = "https://github.com/salvium/salvium-rs" diff --git a/crates/salvium-explorer/Cargo.toml b/crates/salvium-explorer/Cargo.toml index ee12ccd..dcb18a6 100644 --- a/crates/salvium-explorer/Cargo.toml +++ b/crates/salvium-explorer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "salvium-explorer" -version = "1.0.7-r011" +version = "1.0.7-r012" edition = "2021" description = "High-level WASM APIs for Salvium blockchain explorers" diff --git a/crates/salvium-ffi/src/transfer.rs b/crates/salvium-ffi/src/transfer.rs index 4df8d2a..c84591f 100644 --- a/crates/salvium-ffi/src/transfer.rs +++ b/crates/salvium-ffi/src/transfer.rs @@ -95,7 +95,7 @@ fn parse_priority(s: &str) -> FeePriority { /// ```json /// { /// "destinations": [{"address": "Svk1...", "amount": "1000000000"}], -/// "asset_type": "SAL", +/// "assetType": "SAL1", /// "priority": "normal", /// "ring_size": 16 /// } @@ -139,7 +139,7 @@ pub unsafe extern "C" fn salvium_wallet_transfer( /// ```json /// { /// "amount": "1000000000", -/// "asset_type": "SAL", +/// "assetType": "SAL1", /// "priority": "normal", /// "ring_size": 16 /// } @@ -212,7 +212,7 @@ pub unsafe extern "C" fn salvium_wallet_stake_dry_run( /// ```json /// { /// "address": "Svk1...", -/// "asset_type": "SAL", +/// "assetType": "SAL1", /// "priority": "normal", /// "ring_size": 16, /// "dry_run": false @@ -560,8 +560,11 @@ async fn build_sign_maybe_broadcast( let keys = wallet.keys(); // 1. Get output distribution for decoy selection. + // We use global index space (empty asset_type) because our wallet stores + // global output indices. Both decoys and the real output use global IDs, + // so the index spaces are consistent. This matches salvium-cli's approach. let dist = daemon - .get_output_distribution(&[0], 0, 0, true, asset_type) + .get_output_distribution(&[0], 0, 0, true, "") .await .map_err(|e| format!("get_output_distribution failed: {e}"))?; @@ -590,7 +593,7 @@ async fn build_sign_maybe_broadcast( .collect(); let outs = daemon - .get_outs(&requests, true, asset_type) + .get_outs(&requests, true, "") .await .map_err(|e| format!("get_outs failed: {e}"))?; diff --git a/crates/salvium-rpc/src/daemon.rs b/crates/salvium-rpc/src/daemon.rs index d0507ad..12c7efc 100644 --- a/crates/salvium-rpc/src/daemon.rs +++ b/crates/salvium-rpc/src/daemon.rs @@ -988,9 +988,9 @@ impl DaemonRpc { /// Get output details by amount and index. /// /// When `asset_type` is non-empty, indices in `OutputRequest` are treated as - /// asset-type-specific indices (unless `is_global_out` is set on individual - /// entries). The daemon converts them to global output IDs internally. - /// When `asset_type` is empty, indices are treated as global output IDs. + /// asset-type-specific indices. The daemon converts them to global output IDs + /// internally. When `asset_type` is empty, indices are treated as global + /// output IDs. pub async fn get_outs( &self, outputs: &[OutputRequest], @@ -1556,7 +1556,8 @@ impl DaemonRpc { /// Request for a specific output by amount and index. /// /// When `asset_type` is set on the parent `get_outs` request, `index` is treated -/// as an asset-type-specific index. Without `asset_type`, it's a global output ID. +/// as an asset-type-specific index. Without `asset_type`, `index` is a global +/// output ID. #[derive(Debug, Clone, Serialize)] pub struct OutputRequest { pub amount: u64, diff --git a/docs/wallet-sync-spec.md b/docs/wallet-sync-spec.md index c9c400d..f048930 100644 --- a/docs/wallet-sync-spec.md +++ b/docs/wallet-sync-spec.md @@ -343,7 +343,88 @@ salvium_string_free(stakes); | `returnHeight` | i64/null | Block height of return | | `returnAmount` | string | Amount returned (atomic) | -## 11. Cleanup +## 11. Sending Transactions + +All transaction functions take a JSON params string. **`assetType` is required** — there is +no default. Omitting it returns an error. + +All return a JSON string on success (caller must free with `salvium_string_free()`), or +NULL on error. + +### Transfer + +```c +char* result = salvium_wallet_transfer(wallet, daemon, "{\"destinations\":[{\"address\":\"Svk1...\",\"amount\":\"100000000\"}],\"assetType\":\"SAL1\"}"); +salvium_string_free(result); +``` + +**Params (camelCase):** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `destinations` | array | yes | `[{"address": "...", "amount": "..."}]` (atomic units) | +| `assetType` | string | yes | Asset to spend (e.g. `"SAL1"`) | +| `priority` | string | no | `"low"`, `"normal"` (default), `"elevated"`, `"priority"` | +| `ringSize` | number | no | Default 16 | +| `dryRun` | bool | no | If true, build + sign but don't broadcast | + +**Result:** `{"txHash": "...", "fee": "...", "amount": "..."}` +When `dryRun` is true, also includes `txHex` and `weight`. + +### Transfer Dry Run + +Convenience wrapper — forces `dryRun: true` regardless of params. + +```c +char* result = salvium_wallet_transfer_dry_run(wallet, daemon, params_json); +``` + +### Stake + +```c +char* result = salvium_wallet_stake(wallet, daemon, "{\"amount\":\"100000000\",\"assetType\":\"SAL1\"}"); +salvium_string_free(result); +``` + +**Params:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `amount` | string | yes | Amount to stake (atomic units) | +| `assetType` | string | yes | Asset to stake | +| `priority` | string | no | Fee priority (default `"normal"`) | +| `ringSize` | number | no | Default 16 | + +**Result:** `{"txHash": "...", "fee": "...", "amount": "...", "weight": ...}` + +### Stake Dry Run + +```c +char* result = salvium_wallet_stake_dry_run(wallet, daemon, params_json); +``` + +### Sweep + +Sends all unlocked outputs of the specified asset to a single address. + +```c +char* result = salvium_wallet_sweep(wallet, daemon, "{\"address\":\"Svk1...\",\"assetType\":\"SAL1\"}"); +salvium_string_free(result); +``` + +**Params:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `address` | string | yes | Destination address | +| `assetType` | string | yes | Asset to sweep | +| `priority` | string | no | Fee priority (default `"normal"`) | +| `ringSize` | number | no | Default 16 | +| `dryRun` | bool | no | If true, build + sign but don't broadcast | + +**Result:** `{"txHash": "...", "fee": "...", "amount": "..."}` + +## 12. Cleanup **Close in reverse order.** Always close handles when done.