• 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/common_types/src/wallet_types.rs
1
//  Copyright 2023 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
    fmt,
25
    fmt::{Display, Formatter},
26
};
27

28
use serde::{Deserialize, Serialize};
29
use tari_common::configuration::Network;
30

31
use crate::types::{CompressedPublicKey, PrivateKey};
32

33
#[derive(Debug, Clone, Serialize, Deserialize, Default, Eq, PartialEq)]
34
pub enum WalletType {
35
    #[default]
36
    DerivedKeys,
37
    Ledger(LedgerWallet),
38
    ProvidedKeys(ProvidedKeysWallet),
39
}
40

41
impl Display for WalletType {
42
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
×
43
        match self {
×
44
            WalletType::DerivedKeys => write!(f, "Derived wallet"),
×
45
            WalletType::Ledger(ledger_wallet) => write!(f, "Ledger({ledger_wallet})"),
×
46
            WalletType::ProvidedKeys(provided_keys_wallet) => write!(f, "Provided Keys ({provided_keys_wallet})"),
×
47
        }
48
    }
×
49
}
50

51
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
52
pub struct ProvidedKeysWallet {
53
    pub public_spend_key: CompressedPublicKey,
54
    pub private_spend_key: Option<PrivateKey>,
55
    pub private_comms_key: Option<PrivateKey>,
56
    pub view_key: PrivateKey,
57
    pub birthday: Option<u16>,
58
}
59

60
impl Display for ProvidedKeysWallet {
61
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
×
62
        write!(f, "public spend key {}", self.public_spend_key)?;
×
63
        Ok(())
×
64
    }
×
65
}
66

67
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
68
pub struct LedgerWallet {
69
    pub account: u64,
70
    pub public_alpha: Option<CompressedPublicKey>,
71
    pub network: Network,
72
    pub view_key: Option<PrivateKey>,
73
}
74

75
impl Display for LedgerWallet {
76
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
×
77
        write!(f, "account '{}', ", self.account)?;
×
78
        write!(f, "network '{}', ", self.network)?;
×
79
        write!(f, "public_alpha '{}', ", self.public_alpha.is_some())?;
×
80
        write!(f, "view_key '{}'", self.view_key.is_some())?;
×
81
        Ok(())
×
82
    }
×
83
}
84

85
impl LedgerWallet {
86
    pub fn new(
×
87
        account: u64,
×
88
        network: Network,
×
89
        public_alpha: Option<CompressedPublicKey>,
×
90
        view_key: Option<PrivateKey>,
×
91
    ) -> Self {
×
92
        Self {
×
93
            account,
×
94
            public_alpha,
×
95
            network,
×
96
            view_key,
×
97
        }
×
98
    }
×
99
}
100

101
/// Specifies either a total fee for a transaction or a fee per gram
102
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
103
pub enum FeeType {
104
    /// A total fee for the transaction (in MicroMinotari units)
105
    TotalFee(u64),
106
    /// A fee per gram for the transaction (in MicroMinotari units)
107
    FeePerGram(u64),
108
}
109

110
impl Display for FeeType {
NEW
111
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
×
NEW
112
        match self {
×
NEW
113
            Self::TotalFee(fee) => write!(f, "TotalFee({fee})"),
×
NEW
114
            Self::FeePerGram(fee_per_gram) => write!(f, "FeePerGram({fee_per_gram})"),
×
115
        }
NEW
116
    }
×
117
}
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