sui_rpc/client/v2/
coin_selection.rs1use futures::StreamExt;
2use prost_types::FieldMask;
3use std::str::FromStr;
4
5use sui_sdk_types::Address;
6use sui_sdk_types::StructTag;
7use sui_sdk_types::TypeTag;
8
9use crate::client::v2::Client;
10use crate::client::v2::Result;
11use crate::field::FieldMaskUtil;
12use crate::proto::sui::rpc::v2::ListOwnedObjectsRequest;
13use crate::proto::sui::rpc::v2::Object;
14
15impl Client {
16 pub async fn select_coins(
30 &self,
31 owner_address: &Address,
32 coin_type: &TypeTag,
33 amount: u64,
34 exclude: &[Address],
35 ) -> Result<Vec<Object>> {
36 let coin_struct = StructTag::coin(coin_type.clone());
37 let list_request = ListOwnedObjectsRequest::default()
38 .with_owner(owner_address)
39 .with_object_type(&coin_struct)
40 .with_page_size(500u32)
41 .with_read_mask(FieldMask::from_paths([
42 "object_id",
43 "version",
44 "digest",
45 "balance",
46 "owner",
47 ]));
48
49 let mut coin_stream = Box::pin(self.list_owned_objects(list_request));
50 let mut selected_coins = Vec::new();
51 let mut total = 0u64;
52
53 while let Some(object_result) = coin_stream.next().await {
54 let object = object_result?;
55
56 if Address::from_str(object.object_id()).is_ok_and(|addr| exclude.contains(&addr)) {
57 continue;
58 }
59
60 total = total.saturating_add(object.balance());
61 selected_coins.push(object);
62
63 if total >= amount {
64 return Ok(selected_coins);
65 }
66 }
67
68 Err(tonic::Status::failed_precondition(format!(
69 "Insufficient funds for address [{owner_address}], requested amount: {amount}, total available: {total}"
70 )))
71 }
72
73 pub async fn select_up_to_n_largest_coins(
87 &self,
88 owner_address: &Address,
89 coin_type: &TypeTag,
90 n: usize,
91 exclude: &[Address],
92 ) -> Result<Vec<Object>> {
93 let mut selected_coins = vec![];
94
95 let coin_struct = StructTag::coin(coin_type.clone());
96 let list_request = ListOwnedObjectsRequest::default()
97 .with_owner(owner_address)
98 .with_object_type(&coin_struct)
99 .with_page_size(500u32)
100 .with_read_mask(FieldMask::from_paths([
101 "object_id",
102 "version",
103 "digest",
104 "balance",
105 "owner",
106 ]));
107
108 let mut coin_stream = Box::pin(self.list_owned_objects(list_request));
109
110 while selected_coins.len() < n {
111 match coin_stream.next().await {
112 Some(Ok(object)) => {
113 if !Address::from_str(object.object_id())
114 .is_ok_and(|addr| exclude.contains(&addr))
115 {
116 selected_coins.push(object);
117 }
118 }
119 Some(Err(e)) => return Err(e),
120 None => break,
121 }
122 }
123
124 Ok(selected_coins)
125 }
126}