coinbase_rs/
private.rs

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    ///
25    /// **List accounts**
26    ///
27    /// Lists current user’s accounts to which the authentication method has access to.
28    ///
29    /// https://developers.coinbase.com/api/v2#list-accounts
30    ///
31    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    ///
41    /// **List transactions**
42    ///
43    /// Lists account’s transactions.
44    ///
45    /// https://developers.coinbase.com/api/v2#list-transactions
46    ///
47    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    ///
61    /// **List addresses**
62    ///
63    /// Lists addresses for an account.
64    ///
65    /// https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-addresses#list-addresses
66    ///
67    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    // id appears to be either a UUID or a token name e.g: "LINK"
87    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}