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