1use bigdecimal::BigDecimal;
2use futures::stream::Stream;
3use hyper::Uri;
4use uritemplate::UriTemplate;
5use uuid::Uuid;
6
7use crate::{public::Public, request, DateTime, Result};
8
9pub struct Private {
10 _pub: Public,
11 key: String,
12 secret: String,
13}
14
15impl Private {
16 pub fn new(uri: &str, key: &str, secret: &str) -> Self {
17 Self {
18 _pub: Public::new(uri),
19 key: key.to_string(),
20 secret: secret.to_string(),
21 }
22 }
23
24 pub fn accounts<'a>(&'a self) -> impl Stream<Item = Result<Vec<Account>>> + 'a {
32 let limit = 100;
33 let uri = UriTemplate::new("/v2/accounts{?query*}")
34 .set("query", &[("limit", limit.to_string().as_ref())])
35 .build();
36 let request = self.request(&uri);
37 self._pub.get_stream(request)
38 }
39
40 pub fn transactions<'a>(
48 &'a self,
49 account_id: &Uuid,
50 ) -> impl Stream<Item = Result<Vec<Transaction>>> + 'a {
51 let limit = 100;
52 let uri = UriTemplate::new("/v2/accounts/{account}/transactions{?query*}")
53 .set("account", account_id.to_string())
54 .set("query", &[("limit", limit.to_string().as_ref())])
55 .build();
56 let request = self.request(&uri);
57 self._pub.get_stream(request)
58 }
59
60 pub fn list_addresses<'a>(
68 &'a self,
69 account_id: &Uuid,
70 ) -> impl Stream<Item = Result<Vec<Address>>> + 'a {
71 let uri = UriTemplate::new("/v2/accounts/{account}/addresses")
72 .set("account", account_id.to_string())
73 .build();
74 let request = self.request(&uri);
75 self._pub.get_stream(request)
76 }
77
78 fn request(&self, _uri: &str) -> request::Builder {
79 let uri: Uri = (self._pub.uri.to_string() + _uri).parse().unwrap();
80 request::Builder::new_with_auth(&self.key, &self.secret).uri(uri)
81 }
82}
83
84#[derive(Deserialize, Debug)]
85pub struct Account {
86 pub id: String,
88
89 pub r#type: String,
90
91 pub created_at: Option<DateTime>,
92 pub updated_at: Option<DateTime>,
93
94 pub resource: String,
95 pub resource_path: String,
96
97 pub name: String,
98 pub primary: bool,
99
100 pub currency: Currency,
101
102 pub balance: Balance,
103
104 pub allow_deposits: bool,
105 pub allow_withdrawals: bool,
106}
107
108#[derive(Deserialize, Debug)]
109pub struct Balance {
110 pub amount: BigDecimal,
111 pub currency: String,
112}
113
114#[derive(Deserialize, Debug)]
115pub struct Address {
116 pub id: String,
117 pub address: String,
118 pub name: Option<String>,
119 pub created_at: Option<DateTime>,
120 pub updated_at: Option<DateTime>,
121 pub network: String,
122 pub resource: String,
123 pub resource_path: String,
124}
125
126#[derive(Deserialize, Debug)]
127pub struct Transaction {
128 pub id: Uuid,
129
130 pub created_at: Option<DateTime>,
131 pub updated_at: Option<DateTime>,
132
133 pub r#type: String,
134 pub resource: String,
135 pub resource_path: String,
136 pub status: String,
137 pub amount: Balance,
138 pub native_amount: Balance,
139 pub instant_exchange: bool,
140 pub network: Option<Network>,
141 pub from: Option<From>,
142 pub details: TransactionDetails,
143}
144
145#[derive(Deserialize, Debug)]
146pub struct Network {
147 pub status: String,
148}
149
150#[derive(Deserialize, Debug)]
151pub struct From {
152 pub id: Option<Uuid>,
153 pub resource: String,
154 pub resource_path: Option<String>,
155 pub currency: String,
156}
157
158#[derive(Deserialize, Debug)]
159pub struct TransactionDetails {
160 pub title: String,
161 pub subtitle: String,
162}
163
164#[derive(Deserialize, Debug)]
165pub struct Currency {
166 pub code: String,
167 pub name: String,
168 pub color: String,
169 pub sort_index: usize,
170 pub exponent: usize,
171 pub r#type: String,
172 pub address_regex: Option<String>,
173 pub asset_id: Option<Uuid>,
174 pub destination_tag_name: Option<String>,
175 pub destination_tag_regex: Option<String>,
176}
177
178#[derive(Deserialize, Debug, Eq, PartialEq)]
179pub enum Order {
180 #[serde(rename = "asc")]
181 Ascending,
182 #[serde(rename = "desc")]
183 Descending,
184}
185
186#[derive(Deserialize, Debug)]
187pub struct Pagination {
188 pub ending_before: Option<DateTime>,
189 pub starting_after: Option<DateTime>,
190 pub previous_ending_before: Option<String>,
191 pub next_starting_after: Option<String>,
192 pub limit: usize,
193 pub order: Order,
194 pub previous_uri: Option<String>,
195 pub next_uri: Option<String>,
196}
197
198#[test]
199fn test_pagination_deserialize() {
200 let input = r##"
201{
202 "ending_before": null,
203 "starting_after": null,
204 "previous_ending_before": null,
205 "next_starting_after": "d16ec1ba-b3f7-5d6a-a9c8-817930030324",
206 "limit": 25,
207 "order": "desc",
208 "previous_uri": null,
209 "next_uri": "/v2/accounts?starting_after=d16ec1ba-b3f7-5d6a-a9c8-817930030324"
210}"##;
211 let pagination: Pagination = serde_json::from_slice(input.as_bytes()).unwrap();
212 assert_eq!(25, pagination.limit);
213 assert_eq!(Order::Descending, pagination.order);
214}
215
216#[test]
217fn test_account_deserialize() {
218 let input = r##"[
219{
220 "id": "f1bb8f61-7f5d-4f04-9552-bcbafdf856b7",
221 "type": "wallet",
222 "created_at": "2019-07-12T03:27:07Z",
223 "updated_at": "2019-07-12T14:07:57Z",
224 "resource": "account",
225 "resource_path": "/v2/accounts/f1bb8f61-7f5d-4f04-9552-bcbafdf856b7",
226 "name": "EOS Wallet",
227 "primary": true,
228 "currency": {
229 "code": "EOS",
230 "name": "EOS",
231 "color": "#000000",
232 "sort_index": 128,
233 "exponent": 4,
234 "type": "crypto",
235 "address_regex": "(^[a-z1-5.]{1,11}[a-z1-5]$)|(^[a-z1-5.]{12}[a-j1-5]$)",
236 "asset_id": "cc2ddaa5-5a03-4cbf-93ef-e4df102d4311",
237 "destination_tag_name": "EOS Memo",
238 "destination_tag_regex": "^.{1,100}$"
239 },
240 "balance": {
241 "amount": "9.1238",
242 "currency": "EOS"
243 },
244 "allow_deposits": true,
245 "allow_withdrawals": true
246}
247]"##;
248
249 let accounts: Vec<Account> = serde_json::from_slice(input.as_bytes()).unwrap();
250 assert_eq!(accounts.len(), 1);
251}
252
253#[test]
254fn test_transactions_deserialize() {
255 let input = r#"[
256{
257 "id": "9dd482e4-d8ce-46f7-a261-281843bd2855",
258 "type": "send",
259 "status": "completed",
260 "amount": {
261 "amount": "-0.00100000",
262 "currency": "BTC"
263 },
264 "native_amount": {
265 "amount": "-0.01",
266 "currency": "USD"
267 },
268 "description": null,
269 "created_at": "2015-03-11T13:13:35-07:00",
270 "updated_at": "2015-03-26T15:55:43-07:00",
271 "resource": "transaction",
272 "resource_path": "/v2/accounts/af6fd33a-e20c-494a-b3f6-f91d204af4b7/transactions/9dd482e4-d8ce-46f7-a261-281843bd2855",
273 "network": {
274 "status": "off_blockchain",
275 "name": "bitcoin"
276 },
277 "to": {
278 "id": "2dbc3cfb-ed1e-4c10-aedb-aeb1693e01e7",
279 "resource": "user",
280 "resource_path": "/v2/users/2dbc3cfb-ed1e-4c10-aedb-aeb1693e01e7"
281 },
282 "instant_exchange": false,
283 "details": {
284 "title": "Sent bitcoin",
285 "subtitle": "to User 2"
286 }
287},
288{
289 "id": "c1c413d1-acf8-4fcb-a8ed-4e2e4820c6f0",
290 "type": "buy",
291 "status": "pending",
292 "amount": {
293 "amount": "1.00000000",
294 "currency": "BTC"
295 },
296 "native_amount": {
297 "amount": "10.00",
298 "currency": "USD"
299 },
300 "description": null,
301 "created_at": "2015-03-26T13:42:00-07:00",
302 "updated_at": "2015-03-26T15:55:45-07:00",
303 "resource": "transaction",
304 "resource_path": "/v2/accounts/af6fd33a-e20c-494a-b3f6-f91d204af4b7/transactions/c1c413d1-acf8-4fcb-a8ed-4e2e4820c6f0",
305 "buy": {
306 "id": "ae7df6e7-fef1-441d-a6f3-e4661ca6f39a",
307 "resource": "buy",
308 "resource_path": "/v2/accounts/af6fd33a-e20c-494a-b3f6-f91d204af4b7/buys/ae7df6e7-fef1-441d-a6f3-e4661ca6f39a"
309 },
310 "instant_exchange": false,
311 "details": {
312 "title": "Bought bitcoin",
313 "subtitle": "using Capital One Bank"
314 }
315}
316]"#;
317 let transactions: Vec<Transaction> = serde_json::from_slice(input.as_bytes()).unwrap();
318 assert_eq!(transactions.len(), 2);
319}