• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tari-project / tari / 19468834672

18 Nov 2025 02:01PM UTC coverage: 51.294% (-0.3%) from 51.544%
19468834672

push

github

web-flow
feat: add coin selection and spending via bins or buckets (#7584)

Description
---

Add range limit coin-join:
- Added an unspent output coin distribution gRPC method
('CoinHistogramRequest'), whereby the wallet will return the amount and
value of coins in a pre-set range of buckets.
- Added a range limit coin-join gRPC method ('RangeLimitCoinJoin') to
the wallet, whereby the user can specify the minimum target amount,
maximum number of inputs, dust lower bound (inclusive), dust upper bound
(exclusive) and fee. Transaction size will be limited to the specified
maximum number of inputs, and multiple outputs will be created according
to the minimum target amount. All the inputs in the range will be spent,
unless the total available amount does not meet the minimum target
amount.

Closes #7582.

Motivation and Context
---
See #7582.

How Has This Been Tested?
---
System-level testing

gRPC **CoinHistogram** method
```
{
  "buckets": [
    {
      "count": "0",
      "total_amount": "0",
      "lower_bound": "0",
      "upper_bound": "1000"
    },
    {
      "count": "0",
      "total_amount": "0",
      "lower_bound": "1000",
      "upper_bound": "100000"
    },
    {
      "count": "2",
      "total_amount": "1165548",
      "lower_bound": "100000",
      "upper_bound": "1000000"
    },
    {
      "count": "158",
      "total_amount": "1455989209",
      "lower_bound": "1000000",
      "upper_bound": "1000000000"
    },
    {
      "count": "0",
      "total_amount": "0",
      "lower_bound": "1000000000",
      "upper_bound": "100000000000"
    },
    {
      "count": "0",
      "total_amount": "0",
      "lower_bound": "100000000000",
      "upper_bound": "21000000000000000"
    }
  ]
}
```

gRPC **RangeLimitCoinJoin** method

In this example, two transactions were created, bounded by the 350 input
size limit. gRPC client view:

<img width="897" height="396" alt="image"
src="https://github.com/user-attachments/assets/6c5ae857-8a01-4c90-9c55-1eee2fbd... (continued)

0 of 636 new or added lines in 11 files covered. (0.0%)

17 existing lines in 8 files now uncovered.

59180 of 115373 relevant lines covered (51.29%)

7948.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/base_layer/wallet/src/config.rs
1
// Copyright 2021. The Tari Project
2
//
3
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
4
// following conditions are met:
5
//
6
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
7
// disclaimer.
8
//
9
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
10
// following disclaimer in the documentation and/or other materials provided with the distribution.
11
//
12
// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
13
// products derived from this software without specific prior written permission.
14
//
15
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
16
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
20
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
21
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22

23
use std::{
24
    path::{Path, PathBuf},
25
    str::FromStr,
26
    time::Duration,
27
};
28

29
use serde::{Deserialize, Serialize};
30
use strum::EnumString;
31
use tari_common::{
32
    configuration::{
33
        bootstrap::{wallet_get_default_seed_https_address, wallet_http_service_default_port},
34
        serializers,
35
        Network,
36
        StringList,
37
    },
38
    SubConfigPath,
39
};
40
use tari_common_types::grpc_authentication::GrpcAuthentication;
41
use tari_comms::multiaddr::Multiaddr;
42
use tari_p2p::P2pConfig;
43
use tari_utilities::SafePassword;
44
use url::Url;
45

46
use crate::{
47
    base_node_service::config::BaseNodeServiceConfig,
48
    output_manager_service::config::OutputManagerServiceConfig,
49
    transaction_service::config::TransactionServiceConfig,
50
};
51

52
fn deserialize_safe_password_option<'de, D>(deserializer: D) -> Result<Option<SafePassword>, D::Error>
×
53
where D: serde::Deserializer<'de> {
×
54
    let password: Option<String> = Deserialize::deserialize(deserializer)?;
×
55
    Ok(password.map(SafePassword::from))
×
56
}
×
57

58
#[derive(Clone, Serialize, Deserialize, Debug)]
59
#[serde(deny_unknown_fields)]
60
pub struct WalletConfig {
61
    pub override_from: Option<String>,
62
    /// DEPRECATED: The p2p config settings
63
    #[serde(default)]
64
    pub p2p: P2pConfig,
65
    /// The transaction_service_config config settings
66
    #[serde(rename = "transactions")]
67
    pub transaction_service_config: TransactionServiceConfig,
68
    /// The output_manager_service_config config settings
69
    #[serde(rename = "outputs")]
70
    pub output_manager_service_config: OutputManagerServiceConfig,
71
    /// DEPRECATED: The buffer size for the publish/subscribe connector channel, connecting comms messages to the
72
    /// domain layer
73
    pub buffer_size: usize,
74
    /// Selected network
75
    pub network: Network,
76
    /// DEPRECATED: The base_node_service_config config settings
77
    #[serde(rename = "base_node")]
78
    pub base_node_service_config: BaseNodeServiceConfig,
79
    /// The relative path to store persistent data
80
    pub data_dir: PathBuf,
81
    /// The relative path to the config directory
82
    pub config_dir: PathBuf,
83
    /// The main wallet db file
84
    pub db_file: PathBuf,
85
    /// The main wallet db sqlite database backend connection pool size for concurrent reads
86
    pub db_connection_pool_size: usize,
87
    /// The main wallet password
88
    #[serde(deserialize_with = "deserialize_safe_password_option")]
89
    pub password: Option<SafePassword>,
90
    /// DEPRECATED: The auto ping interval to use for contacts liveness data
91
    #[serde(with = "serializers::seconds")]
92
    pub contacts_auto_ping_interval: Duration,
93
    /// DEPRECATED: How long a contact may be not seen before being determined to be offline
94
    pub contacts_online_ping_window: usize,
95
    /// When running the console wallet in command mode, how long to wait for sent transactions.
96
    #[serde(with = "serializers::seconds")]
97
    pub command_send_wait_timeout: Duration,
98
    /// When running the console wallet in command mode, at what "stage" to wait for sent transactions.
99
    pub command_send_wait_stage: TransactionStage,
100
    /// Notification script file for a notifier service - allows a wallet to execute a script or program when certain
101
    /// transaction events are received by the console wallet .
102
    /// (see example at 'applications/minotari_console_wallet/src/notifier/notify_example.sh')
103
    pub notify_file: Option<PathBuf>,
104
    /// If true, a GRPC server will bind to the configured address and listen for incoming GRPC requests.
105
    pub grpc_enabled: bool,
106
    /// GRPC bind address of the wallet
107
    pub grpc_address: Option<Multiaddr>,
108
    /// GRPC authentication mode
109
    pub grpc_authentication: GrpcAuthentication,
110
    /// GRPC tls enabled
111
    pub grpc_tls_enabled: bool,
112
    /// A list of base node peers that the wallet should use for service requests and tracking chain state
113
    pub base_node_service_peers: StringList,
114
    /// The amount of times wallet recovery will be retried before being abandoned
115
    pub recovery_retry_limit: usize,
116
    /// The default uT fee per gram to use for transaction fees
117
    pub fee_per_gram: u64,
118
    /// Number of required transaction confirmations used for UI purposes
119
    pub num_required_confirmations: u64,
120
    /// DEPRECATED: Spin up and use a built-in Tor instance. This only works on macos/linux - requires that the wallet
121
    /// DEPRECATED: was built with the optional "libtor" feature flag.
122
    pub use_libtor: bool,
123
    /// DEPRECATED: A path to the file that stores the wallet identity and secret key
124
    pub identity_file: Option<PathBuf>,
125
    /// The cool down period between balance enquiry checks in seconds; requests faster than this will be ignored.
126
    /// For specialized wallets processing many batch transactions this setting could be increased to 60 s to retain
127
    /// responsiveness of the wallet with slightly delayed balance updates
128
    #[serde(with = "serializers::seconds")]
129
    pub balance_enquiry_cooldown_period: Duration,
130
    /// How many days do we need to start scanning before our actual birthday
131
    pub birthday_offset: u16,
132
    /// The URL of the HTTP client to use for base node requests
133
    pub http_server_url: String,
134
    /// The fallback url address to use if the base node at http_server_url does not respond
135
    pub fallback_http_server_url: String,
136
    /// the scanning interval for the utxo scanner service
137
    pub scanning_interval: u64,
138
    /// grpc database write timeout in ms. This is how long the grpc server will wait for a database write to complete
139
    /// before returning an error to the client. This should be long enough to cover the majority of database writes
140
    /// but short enough to avoid blocking the grpc server for too long. Default = 100ms
141
    pub grpc_db_write_timeout: u64,
142
    /// gRPC broadcast confirmation timeout in ms. This is how long the grpc server will wait for the mempool to
143
    /// respond once the transaction has been submitted, either accepted or rejected. Default = 5000ms
144
    pub grpc_broadcast_confirmation: u64,
145
}
146

147
impl Default for WalletConfig {
148
    fn default() -> Self {
×
149
        let port = wallet_http_service_default_port(Network::get_current());
×
150
        let http_server_url = Url::parse(format!("http://127.0.0.1:{port}").as_str())
×
151
            .expect("This should be a valid URL")
×
152
            .to_string();
×
153
        let fallback_http_server_url = Url::parse(wallet_get_default_seed_https_address(Network::get_current()))
×
154
            .expect("This should be a valid URL")
×
155
            .to_string();
×
156
        Self {
×
157
            override_from: None,
×
158
            p2p: P2pConfig { ..Default::default() }, // DEPRECATED
×
159
            transaction_service_config: Default::default(),
×
160
            output_manager_service_config: Default::default(),
×
161
            buffer_size: 50_000, // DEPRECATED
×
162
            network: Default::default(),
×
163
            base_node_service_config: Default::default(), // DEPRECATED
×
164
            data_dir: PathBuf::from_str("data/wallet").unwrap(),
×
165
            config_dir: PathBuf::from_str("config/wallet").unwrap(),
×
166
            db_file: PathBuf::from_str("db/console_wallet.db").unwrap(),
×
167
            db_connection_pool_size: 16, // Note: Do not reduce this default number
×
168
            password: None,
×
169
            contacts_auto_ping_interval: Duration::from_secs(30), // DEPRECATED
×
170
            contacts_online_ping_window: 30,                      // DEPRECATED
×
171
            command_send_wait_stage: TransactionStage::Broadcast,
×
172
            command_send_wait_timeout: Duration::from_secs(300),
×
173
            notify_file: None,
×
174
            grpc_enabled: false,
×
175
            grpc_address: None,
×
176
            grpc_authentication: GrpcAuthentication::default(),
×
177
            grpc_tls_enabled: false,
×
178
            base_node_service_peers: StringList::default(),
×
179
            recovery_retry_limit: 3,
×
180
            fee_per_gram: 5,
×
181
            num_required_confirmations: 3,
×
182
            use_libtor: true,    // DEPRECATED
×
183
            identity_file: None, // DEPRECATED
×
184
            balance_enquiry_cooldown_period: Duration::from_secs(5),
×
185
            birthday_offset: 2,
×
186
            http_server_url,
×
187
            fallback_http_server_url,
×
188
            scanning_interval: 60,
×
189
            grpc_db_write_timeout: 100,
×
NEW
190
            grpc_broadcast_confirmation: 5000,
×
191
        }
×
192
    }
×
193
}
194

195
impl SubConfigPath for WalletConfig {
196
    fn main_key_prefix() -> &'static str {
×
197
        "wallet"
×
198
    }
×
199
}
200

201
impl WalletConfig {
202
    pub fn set_base_path<P: AsRef<Path>>(&mut self, base_path: P) {
×
203
        if !self.data_dir.is_absolute() {
×
204
            self.data_dir = base_path.as_ref().join(self.data_dir.as_path());
×
205
        }
×
206
        if !self.config_dir.is_absolute() {
×
207
            self.config_dir = base_path.as_ref().join(self.config_dir.as_path());
×
208
        }
×
209
        if !self.db_file.is_absolute() {
×
210
            self.db_file = self.data_dir.join(self.db_file.as_path());
×
211
        }
×
212
    }
×
213
}
214

215
#[derive(Debug, EnumString, PartialEq, Clone, Copy, Serialize, Deserialize)]
216
pub enum TransactionStage {
217
    Initiated,
218
    DirectSendOrSaf,
219
    Negotiated,
220
    Broadcast,
221
    MinedUnconfirmed,
222
    Mined,
223
    TimedOut,
224
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc