circles_pathfinder/hub.rs
1//! # Circles Hub Contract Integration
2//!
3//! This module provides standard Circles Hub contract types and utilities for
4//! converting pathfinding results into contract-compatible formats.
5//!
6//! The types defined here match the exact ABI of the Circles Hub smart contract,
7//! ensuring seamless integration with contract calls.
8//!
9//! ## Usage
10//!
11//! ```rust,no_run
12//! use circles_pathfinder::{find_path_with_params, FindPathParams, PathData};
13//! use alloy_primitives::{Address, U256};
14//! use alloy_primitives::aliases::U192;
15//!
16//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
17//! let target_flow = U192::from(1000u64);
18//! let params = FindPathParams {
19//! from: Address::ZERO,
20//! to: Address::from([1u8; 20]),
21//! target_flow: U256::from(target_flow),
22//! use_wrapped_balances: Some(true),
23//! from_tokens: None,
24//! to_tokens: None,
25//! exclude_from_tokens: None,
26//! exclude_to_tokens: None,
27//! simulated_balances: None,
28//! max_transfers: None,
29//! };
30//!
31//! // Find the path
32//! let transfers = find_path_with_params("https://rpc.aboutcircles.com", params.clone()).await?;
33//!
34//! // Create PathData and convert to contract types
35//! let path_data = PathData::from_transfers(&transfers, params.from, params.to, target_flow)?;
36//! let (vertices, edges, streams, coords) = path_data.to_contract_params();
37//!
38//! // Ready for contract calls!
39//! // contract.some_function(vertices, edges, streams, coords).send().await?;
40//! # Ok(())
41//! # }
42//! ```
43
44use crate::{FlowEdge, FlowMatrix, PathfinderError, Stream, create_flow_matrix};
45use alloy_primitives::aliases::U192;
46use alloy_primitives::{Address, Bytes, U256};
47use circles_types::TransferStep;
48
49/// Simplified pathfinding result data structure
50///
51/// This struct contains the raw pathfinding results in a format that can be
52/// easily converted to contract-compatible types. It eliminates the need for
53/// manual field-by-field conversion between different type layers.
54#[derive(Debug, Clone)]
55pub struct PathData {
56 /// Sorted list of all addresses involved in the flow
57 pub flow_vertices: Vec<Address>,
58 /// Flow edges as (stream_sink_id, amount) tuples
59 pub flow_edges: Vec<FlowEdge>, // Vec<(u16, U192)>,
60 /// Streams as (source_coordinate, flow_edge_ids, data) tuples
61 pub streams: Vec<Stream>, // Vec<(u16, Vec<u16>, Vec<u8>)>,
62 /// Packed coordinates as raw bytes
63 pub packed_coordinates: Vec<u8>,
64 /// Source coordinate index
65 pub source_coordinate: U256,
66}
67
68impl PathData {
69 /// Create PathData from transfer steps
70 ///
71 /// This is the main constructor that takes the output from pathfinding
72 /// and creates a PathData structure ready for contract conversion.
73 ///
74 /// # Arguments
75 /// * `transfers` - Vector of transfer steps from pathfinding
76 /// * `from` - Source address
77 /// * `to` - Destination address
78 /// * `target_flow` - Target flow amount
79 ///
80 /// # Returns
81 /// A PathData structure ready for contract conversion
82 ///
83 /// # Errors
84 /// Returns PathfinderError if flow matrix creation fails
85 pub fn from_transfers(
86 transfers: &[TransferStep],
87 from: Address,
88 to: Address,
89 target_flow: U192,
90 ) -> Result<Self, PathfinderError> {
91 // Calculate actual available flow
92 let actual_flow: U192 = transfers
93 .iter()
94 .filter(|t| t.to_address == to)
95 .map(|t| t.value)
96 .sum();
97
98 // Use the smaller of target_flow or actual available flow
99 let flow_amount = if actual_flow < target_flow {
100 actual_flow
101 } else {
102 target_flow
103 };
104
105 // Create flow matrix
106 let matrix = create_flow_matrix(from, to, flow_amount, transfers)?;
107
108 Ok(Self::from_flow_matrix(matrix))
109 }
110
111 /// Create PathData from a FlowMatrix
112 ///
113 /// Internal constructor for converting from the core FlowMatrix type.
114 fn from_flow_matrix(matrix: FlowMatrix) -> Self {
115 Self {
116 flow_vertices: matrix.flow_vertices,
117 flow_edges: matrix.flow_edges,
118 streams: matrix.streams,
119 packed_coordinates: matrix.packed_coordinates,
120 source_coordinate: matrix.source_coordinate,
121 }
122 }
123
124 /// Convert to standard Circles Hub FlowEdge types
125 ///
126 /// Returns a vector of FlowEdge structs with the exact field names
127 /// and types expected by the Circles Hub smart contract.
128 ///
129 /// # Example
130 /// ```rust,no_run
131 /// # use circles_pathfinder::hub::PathData;
132 /// # use alloy_primitives::aliases::U192;
133 /// # use alloy_primitives::U256;
134 /// # use circles_pathfinder::FlowEdge;
135 /// # let path_data = PathData {
136 /// # flow_vertices: vec![],
137 /// # flow_edges: vec![FlowEdge { streamSinkId: 1, amount: U192::from(1000u64) }],
138 /// # streams: vec![],
139 /// # packed_coordinates: vec![],
140 /// # source_coordinate: U256::from(0),
141 /// # };
142 /// let edges = path_data.to_flow_edges();
143 /// assert_eq!(edges[0].streamSinkId, 1);
144 /// assert_eq!(edges[0].amount, U192::from(1000u64));
145 /// ```
146 pub fn to_flow_edges(&self) -> Vec<FlowEdge> {
147 self.flow_edges
148 .iter()
149 .map(|flow_edge| FlowEdge {
150 streamSinkId: flow_edge.streamSinkId,
151 amount: flow_edge.amount,
152 })
153 .collect()
154 }
155
156 /// Convert to standard Circles Hub Stream types
157 ///
158 /// Returns a vector of Stream structs with the exact field names
159 /// and types expected by the Circles Hub smart contract.
160 ///
161 /// # Example
162 /// ```rust,no_run
163 /// # use circles_pathfinder::hub::PathData;
164 /// # use circles_pathfinder::Stream;
165 /// # use alloy_primitives::U256;
166 /// # let path_data = PathData {
167 /// # flow_vertices: vec![],
168 /// # flow_edges: vec![],
169 /// # streams: vec![Stream { sourceCoordinate:0, flowEdgeIds: vec![1, 2], data: vec![0x01, 0x02].into(),}],
170 /// # packed_coordinates: vec![],
171 /// # source_coordinate: U256::from(0),
172 /// # };
173 /// let streams = path_data.to_streams();
174 /// assert_eq!(streams[0].sourceCoordinate, 0);
175 /// assert_eq!(streams[0].flowEdgeIds, vec![1, 2]);
176 /// ```
177 pub fn to_streams(&self) -> Vec<Stream> {
178 self.streams
179 .iter()
180 .map(|stream| Stream {
181 sourceCoordinate: stream.sourceCoordinate,
182 flowEdgeIds: stream.flowEdgeIds.clone(),
183 data: stream.data.clone(),
184 })
185 .collect()
186 }
187
188 /// Get all contract call parameters in one tuple
189 ///
190 /// This convenience method returns all the parameters needed for most
191 /// Circles Hub contract calls in the correct order and format.
192 ///
193 /// # Returns
194 /// A tuple of (flow_vertices, flow_edges, streams, packed_coordinates)
195 /// ready to use in contract function calls.
196 ///
197 /// # Example
198 /// ```rust,no_run
199 /// # use circles_pathfinder::hub::PathData;
200 /// # use alloy_primitives::U256;
201 /// # let path_data = PathData {
202 /// # flow_vertices: vec![],
203 /// # flow_edges: vec![],
204 /// # streams: vec![],
205 /// # packed_coordinates: vec![0x01, 0x02],
206 /// # source_coordinate: U256::from(0),
207 /// # };
208 /// let (vertices, edges, streams, coords) = path_data.to_contract_params();
209 ///
210 /// // Ready for contract calls:
211 /// // contract.transferFlow(vertices, edges, streams, coords).send().await?;
212 /// ```
213 pub fn to_contract_params(&self) -> (Vec<Address>, Vec<FlowEdge>, Vec<Stream>, Bytes) {
214 (
215 self.flow_vertices.clone(),
216 self.to_flow_edges(),
217 self.to_streams(),
218 Bytes::from(self.packed_coordinates.clone()),
219 )
220 }
221
222 /// Get packed coordinates as Bytes
223 ///
224 /// Convenience method to get the packed coordinates in the Bytes format
225 /// expected by contract calls.
226 pub fn to_packed_coordinates(&self) -> Bytes {
227 Bytes::from(self.packed_coordinates.clone())
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234 use alloy_primitives::aliases::U192;
235
236 #[test]
237 fn test_flow_edge_creation() {
238 let edge = FlowEdge {
239 streamSinkId: 1,
240 amount: U192::from(1000u64),
241 };
242
243 assert_eq!(edge.streamSinkId, 1);
244 assert_eq!(edge.amount, U192::from(1000u64));
245 }
246
247 #[test]
248 fn test_stream_creation() {
249 let stream = Stream {
250 sourceCoordinate: 0,
251 flowEdgeIds: vec![1, 2, 3],
252 data: Bytes::from(vec![0x01, 0x02, 0x03]),
253 };
254
255 assert_eq!(stream.sourceCoordinate, 0);
256 assert_eq!(stream.flowEdgeIds, vec![1, 2, 3]);
257 assert_eq!(stream.data, Bytes::from(vec![0x01, 0x02, 0x03]));
258 }
259
260 // #[test]
261 // fn test_path_data_conversions() {
262 // let path_data = PathData {
263 // flow_vertices: vec![Address::ZERO],
264 // flow_edges: vec![(1, U192::from(1000u64))],
265 // streams: vec![(0, vec![0], vec![0x01, 0x02])],
266 // packed_coordinates: vec![0x03, 0x04],
267 // source_coordinate: 0,
268 // };
269
270 // // Test individual conversions
271 // let edges = path_data.to_flow_edges();
272 // assert_eq!(edges.len(), 1);
273 // assert_eq!(edges[0].streamSinkId, 1);
274 // assert_eq!(edges[0].amount, U192::from(1000u64));
275
276 // let streams = path_data.to_streams();
277 // assert_eq!(streams.len(), 1);
278 // assert_eq!(streams[0].sourceCoordinate, 0);
279 // assert_eq!(streams[0].flowEdgeIds, vec![0]);
280 // assert_eq!(streams[0].data, Bytes::from(vec![0x01, 0x02]));
281
282 // // Test combined conversion
283 // let (vertices, edges, streams, coords) = path_data.to_contract_params();
284 // assert_eq!(vertices, vec![Address::ZERO]);
285 // assert_eq!(edges.len(), 1);
286 // assert_eq!(streams.len(), 1);
287 // assert_eq!(coords, Bytes::from(vec![0x03, 0x04]));
288 // }
289
290 #[test]
291 fn test_packed_coordinates_conversion() {
292 let path_data = PathData {
293 flow_vertices: vec![],
294 flow_edges: vec![],
295 streams: vec![],
296 packed_coordinates: vec![0x01, 0x02, 0x03],
297 source_coordinate: U256::from(0),
298 };
299
300 let coords = path_data.to_packed_coordinates();
301 assert_eq!(coords, Bytes::from(vec![0x01, 0x02, 0x03]));
302 }
303}