rgbp/
coinselect.rs

1// Wallet Library for RGB smart contracts
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Designed in 2019-2025 by Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
6// Written in 2024-2025 by Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland.
9// Copyright (C) 2024-2025 LNP/BP Laboratories,
10//                         Institute for Distributed and Cognitive Systems (InDCS), Switzerland.
11// Copyright (C) 2025 RGB Consortium, Switzerland.
12// Copyright (C) 2019-2025 Dr Maxim Orlovsky.
13// All rights under the above copyrights are reserved.
14//
15// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
16// in compliance with the License. You may obtain a copy of the License at
17//
18//        http://www.apache.org/licenses/LICENSE-2.0
19//
20// Unless required by applicable law or agreed to in writing, software distributed under the License
21// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
22// or implied. See the License for the specific language governing permissions and limitations under
23// the License.
24
25use core::str::FromStr;
26
27use rgb::popls::bp::Coinselect;
28use rgb::{CellAddr, Outpoint, OwnedState, StateCalc};
29use strict_types::StrictVal;
30
31#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Display, Default)]
32#[display(lowercase)]
33pub enum CoinselectStrategy {
34    /// Collect them most small outputs unless the invoiced value if reached
35    #[default]
36    Aggregate,
37
38    /// Collect the minimum number of outputs (with the large value) to reduce the resulting input
39    /// count
40    SmallSize,
41}
42
43impl FromStr for CoinselectStrategy {
44    type Err = String;
45
46    fn from_str(s: &str) -> Result<Self, Self::Err> {
47        match s.to_lowercase().as_str() {
48            "aggregate" => Ok(CoinselectStrategy::Aggregate),
49            "smallsize" => Ok(CoinselectStrategy::SmallSize),
50            s => Err(s.to_string()),
51        }
52    }
53}
54
55impl Coinselect for CoinselectStrategy {
56    fn coinselect<'a>(
57        &mut self,
58        invoiced_state: &StrictVal,
59        calc: &mut StateCalc,
60        owned_state: impl IntoIterator<
61            Item = &'a OwnedState<Outpoint>,
62            IntoIter: DoubleEndedIterator<Item = &'a OwnedState<Outpoint>>,
63        >,
64    ) -> Option<Vec<(CellAddr, Outpoint)>> {
65        let res = match self {
66            CoinselectStrategy::Aggregate => owned_state
67                .into_iter()
68                .take_while(|owned| {
69                    if calc.is_satisfied(invoiced_state) {
70                        return false;
71                    }
72                    calc.accumulate(&owned.assignment.data).is_ok()
73                })
74                .map(|owned| (owned.addr, owned.assignment.seal))
75                .collect(),
76            CoinselectStrategy::SmallSize => owned_state
77                .into_iter()
78                .rev()
79                .take_while(|owned| {
80                    if calc.is_satisfied(invoiced_state) {
81                        return false;
82                    }
83                    calc.accumulate(&owned.assignment.data).is_ok()
84                })
85                .map(|owned| (owned.addr, owned.assignment.seal))
86                .collect(),
87        };
88        if !calc.is_satisfied(invoiced_state) {
89            return None;
90        };
91        Some(res)
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn display_from_str() {
101        assert_eq!(CoinselectStrategy::Aggregate.to_string(), "aggregate");
102        assert_eq!(CoinselectStrategy::SmallSize.to_string(), "smallsize");
103        assert_eq!(CoinselectStrategy::Aggregate, "aggregate".parse().unwrap());
104        assert_eq!(CoinselectStrategy::SmallSize, "smallsize".parse().unwrap());
105    }
106}