diff --git a/Cargo.lock b/Cargo.lock index b51bc6e..f0b10f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -122,6 +122,22 @@ dependencies = [ "bitflags", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cpufeatures" version = "0.2.5" @@ -167,6 +183,30 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -211,6 +251,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "interview-project" version = "0.1.0" @@ -222,6 +271,7 @@ dependencies = [ "serde", "serde_json", "tokio", + "tokio-native-tls", ] [[package]] @@ -300,7 +350,25 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.42.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] [[package]] @@ -349,12 +417,63 @@ dependencies = [ "libc", ] +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + [[package]] name = "opaque-debug" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -375,7 +494,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -384,6 +503,12 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -486,18 +611,60 @@ dependencies = [ "bitflags", ] +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "ryu" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys 0.36.1", +] + [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "security-framework" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.152" @@ -578,6 +745,20 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "tokio" version = "1.23.0" @@ -595,7 +776,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -609,6 +790,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "typenum" version = "1.16.0" @@ -621,6 +812,12 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -709,6 +906,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + [[package]] name = "windows-sys" version = "0.42.0" @@ -716,12 +926,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_x86_64_msvc 0.42.0", ] [[package]] @@ -730,24 +940,48 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + [[package]] name = "windows_aarch64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + [[package]] name = "windows_i686_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + [[package]] name = "windows_i686_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + [[package]] name = "windows_x86_64_gnu" version = "0.42.0" @@ -760,6 +994,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + [[package]] name = "windows_x86_64_msvc" version = "0.42.0" diff --git a/Cargo.toml b/Cargo.toml index e1adad5..ce92f03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,5 @@ cardano-serialization-lib = "10.2.0" dotenv = "0.15.0" serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.91" -tokio = { version = "1.23.0", features = ["full"] } \ No newline at end of file +tokio = { version = "1.23.0", features = ["full"] } +tokio-native-tls = "0.3.0" \ No newline at end of file diff --git a/README.markdown b/README.markdown index 6aab9b2..10cb26a 100644 --- a/README.markdown +++ b/README.markdown @@ -11,7 +11,7 @@ We need to create a simple Rust program to list the books available for a given 1. Create a free account at https://www.tangocrypto.com/. 1. Copy `.env.dist` to `.env`. 1. Create a testnet app and copy your `APP_ID` and `API_KEY` into `.env`. -1. Implement the `CardanoApi` trait for `TangoClient` in [src/cardano/tango.rs](src/cardano/tango/client.rs). +1. Implement the `CardanoApi` trait for `TangoClient` in [src/cardano/tango/client.rs](src/cardano/tango/client.rs). 1. Implement the `Bookshelf` functionality in [src/model/bookshelf.rs](src/model/bookshelf.rs). **IMPORTANT**: Please do not modify any code in [main.rs](src/main.rs). You can add dependencies to [Cargo.toml](Cargo.toml), add structs and functions in the [cardano](src/cardano) module, [tango](src/cardano/tango) module, and can add (but not remove) functions to the [CardanoApi](src/cardano/api.rs) trait if you wish. diff --git a/src/cardano/mod.rs b/src/cardano/mod.rs index c0d881b..a6405c9 100644 --- a/src/cardano/mod.rs +++ b/src/cardano/mod.rs @@ -1,3 +1,4 @@ pub mod api; pub mod model; pub mod tango; +pub mod request_helper; \ No newline at end of file diff --git a/src/cardano/model.rs b/src/cardano/model.rs index 493db8f..d15a999 100644 --- a/src/cardano/model.rs +++ b/src/cardano/model.rs @@ -1,3 +1,4 @@ +#[derive(Debug)] pub struct Asset { pub policy_id: String, pub asset_name: String, diff --git a/src/cardano/request_helper.rs b/src/cardano/request_helper.rs new file mode 100644 index 0000000..5e8891f --- /dev/null +++ b/src/cardano/request_helper.rs @@ -0,0 +1,120 @@ +use tokio_native_tls::TlsConnector as TlsConnector_Async; +use tokio_native_tls::native_tls::TlsConnector; +use tokio_native_tls::TlsStream; +use tokio::io::{AsyncWriteExt, AsyncReadExt}; +use tokio::net::TcpStream; +use std::io::Write; +use std::io::Read; +use tokio::time::{sleep, Duration, Instant}; +use std::net::*; +use std::vec::Vec; +use anyhow::Context; + +pub struct HttpGetRequest<'a> +{ + host: &'a str, + path: &'a str, + headers: Vec<(&'a str, &'a str)> +} + +impl HttpGetRequest<'_> +{ + pub fn new<'a>(host: &'a str, path: &'a str, headers: Vec<(&'a str, &'a str)>) -> HttpGetRequest<'a> + { + HttpGetRequest { + host, + path, + headers + } + } +} + +pub async fn http_s_get(req: HttpGetRequest<'_>) -> anyhow::Result +{ + println!("making request: GET {} ", req.path); + //1. handshake + let stream = TcpStream::connect((req.host, 443)).await.context("tcp handshake failed")?; + let connector = TlsConnector::new().unwrap(); + let mut stream = TlsConnector_Async::from(connector).connect(req.host, stream).await.context("tls handshake failed")?; + + //2. headers + let mut request = String::from(""); + request.push_str(format!("GET {} HTTP/1.1\r\n", req.path).as_str()); + request.push_str(format!("Host: {}\r\n", req.host).as_str()); + request.push_str(format!("Accept: {}\r\n", "application/json").as_str()); + request.push_str("Connection: close\r\n"); + for header in req.headers + { + request.push_str(format!("{}: {}\r\n", header.0, header.1).as_str()); + } + request.push_str("\r\n"); + + //3. make request + stream.write_all(request.as_bytes()).await.context("Writing to tcp socket failed")?; + + //4. read response + let mut response = String::from(""); + stream.read_to_string(&mut response).await.context("Reading from tcp socket failed")?; + + Ok(response) +} + +pub fn parse_httpStatusCode(response: &String) -> i32 +{ + let line = response.lines().next(); + + match line + { + Some(line) => + { + let from = "HTTP/1.1 ".len(); + let until = "HTTP/1.1 xyz".len(); + if line.len() >= until + { + let status = &line[from..until]; + match status.parse::() + { + Ok(n) => n, + Err(_) => + { + eprintln!("Error parsing status {}.", status); + 500 + }, + } + } + else + { + 500 + } + }, + None => 500 + } +} + +pub fn parse_extractJsonStringFromBody(res: &String) -> anyhow::Result +{ + let mut lines = res.lines(); + let mut s: &str = ""; + + loop + { + s = match lines.next() + { + Some(value) => value, + None => break + } + } + + let first: &str = &s[0..1]; + let last: &str = &s[s.len() - 1..]; + + //TODO: this suffices for now, but do better + if first == "{" && last == "}" + { + Ok(s.to_string()) + } + else + { + Err(anyhow::anyhow!("Error getting json string from response")) + } +} \ No newline at end of file diff --git a/src/cardano/tango/client.rs b/src/cardano/tango/client.rs index a3c72a0..2484621 100644 --- a/src/cardano/tango/client.rs +++ b/src/cardano/tango/client.rs @@ -1,26 +1,120 @@ +use tokio_native_tls::TlsConnector as TlsConnector_Async; +use tokio_native_tls::native_tls::TlsConnector; +use tokio_native_tls::TlsStream; +use tokio::io::{AsyncWriteExt, AsyncReadExt}; +use tokio::net::TcpStream; +use std::io::Write; +use std::io::Read; +use std::io::ErrorKind; +use std::net::*; +use std::vec::Vec; +use serde_json::Value as SerdeValue; +use anyhow::Context; + use std::{collections::HashSet, future::Future}; use serde::de::DeserializeOwned; -use crate::cardano::{api::CardanoApi, model::Asset}; +use crate::cardano::{api::CardanoApi, model::Asset, request_helper::*}; use super::model::ApiListRes; +use super::model::*; // feel free to add/remove properties as needed. -pub struct TangoClient { - base_url: String, - app_id: String, - api_key: String, +pub struct TangoClient +{ + host: String, + base_url: String, + app_id: String, + api_key: String } -impl TangoClient { - pub fn new(base_url: String, app_id: String, api_key: String) -> anyhow::Result { - Ok(TangoClient { - base_url, - app_id, - api_key, - }) +impl TangoClient +{ + pub fn new(base_url: String, app_id: String, api_key: String) -> anyhow::Result + { + let host: &str = base_url.as_str(); + + let host: &str = if host.starts_with("https://") + { + &host["https://".len()..] } + else + { + &host + }; + let host: &str = if host.ends_with("/") + { + &host[..host.len() - 1] + } + else + { + &host + }; + + let host = host.to_owned(); + + Ok(TangoClient { + host, + base_url, + app_id, + api_key + }) + } + + async fn get_addresses(&self, stake_address: &str, cursor: Option) -> anyhow::Result> + { + let mut path = format!("/{}/v1/wallets/{}/addresses", self.app_id, stake_address); + self.make_get_request::
(path, cursor).await + } + + async fn get_address_assets(&self, address: &str, cursor: Option) -> anyhow::Result> + { + let mut path = format!("/{}/v1/addresses/{}/assets", self.app_id, address); + self.make_get_request::(path, cursor).await + } + + async fn get_asset_addresses(&self, asset_id: &str, cursor: Option) -> anyhow::Result> + { + let mut path = format!("/{}/v1/assets/{}/addresses", self.app_id, asset_id); + self.make_get_request::(path, cursor).await + } + + async fn make_get_request(&self, mut path: String, cursor: Option) -> anyhow::Result> + where + T: DeserializeOwned + { + if let Some(x) = cursor + { + path.push_str("?cursor="); + path.push_str(x.as_str()); + } + + let req = HttpGetRequest::new( + self.host.as_str(), + path.as_str(), + vec![ + ("Accept", "application/json"), + ("x-api-key", self.api_key.as_str()) + ] + ); + + let mut res = http_s_get(req).await?; + + let statusCode = parse_httpStatusCode(&res); + + if statusCode == 200 + { + let body = parse_extractJsonStringFromBody(&res)?; + let result: ApiListRes = parse_apiBody(&body)?; + Ok(result) + } + else + { + eprintln!("{}", res); + Err(anyhow::anyhow!("Recieved {} status code", statusCode)) + } + } } /** @@ -41,40 +135,69 @@ where T: DeserializeOwned, T: Clone, { - let mut data = Vec::new(); - let mut cursor = None; - loop { - let res = f(cursor.clone()).await?; - - data.append(&mut res.data.clone()); - cursor = res.cursor; - - match cursor { - None => break, - _ => {} - } + let mut data = Vec::new(); + let mut cursor = None; + loop + { + let res = f(cursor.clone()).await?; + + data.append(&mut res.data.clone()); + cursor = res.cursor; + + match cursor + { + None => break, + _ => {} } + } - Ok(data) + Ok(data) } #[async_trait::async_trait] -impl CardanoApi for TangoClient { - // recommended api: - // https://www.tangocrypto.com/api-reference/#/operations/list-stake_address-addresses - async fn get_all_addresses(&self, stake_address: &str) -> anyhow::Result> { - todo!() - } +impl CardanoApi for TangoClient +{ + // recommended api: + // https://www.tangocrypto.com/api-reference/#/operations/list-stake_address-addresses + async fn get_all_addresses(&self, stake_address: &str) -> anyhow::Result> { + let result = get_all(|cursor| self.get_addresses(stake_address, cursor)).await?; + Ok(result.into_iter().map(|x| x.address).collect()) + } - // recommended api: - // https://www.tangocrypto.com/api-reference/#/operations/list-address-assets - async fn get_address_assets(&self, address: &str) -> anyhow::Result> { - todo!() - } + // recommended api: + // https://www.tangocrypto.com/api-reference/#/operations/list-address-assets + async fn get_address_assets(&self, address: &str) -> anyhow::Result> { + let result = get_all(|cursor| self.get_address_assets(address, cursor)).await?; + Ok(result.into_iter().map(|x| Asset { + policy_id: x.policy_id, + asset_name: x.asset_name, + quantity: x.quantity + }).collect()) + } + + // recommended api: + // https://www.tangocrypto.com/api-reference/#/operations/list-asset-addresses + async fn get_asset_addresses(&self, asset_id: &str) -> anyhow::Result> { + let getAllResult = get_all(|cursor| self.get_asset_addresses(asset_id, cursor)).await?; - // recommended api: - // https://www.tangocrypto.com/api-reference/#/operations/list-asset-addresses - async fn get_asset_addresses(&self, asset_id: &str) -> anyhow::Result> { - todo!() + let mut result = HashSet::new(); + for asset in getAllResult.into_iter() + { + result.insert(asset.address); } + + Ok(result) + } } + +fn parse_apiBody(body: &String) -> anyhow::Result> +where + T: DeserializeOwned +{ + let parsed: serde_json::Result> = serde_json::from_str(body); + match parsed + { + Ok(result) => Ok(result), + Err(_) => Err(anyhow::anyhow!("Error parsing json into custom type")) + } +} \ No newline at end of file diff --git a/src/cardano/tango/model.rs b/src/cardano/tango/model.rs index 2880b35..0b53310 100644 --- a/src/cardano/tango/model.rs +++ b/src/cardano/tango/model.rs @@ -5,26 +5,26 @@ use serde::Deserialize; #[derive(Clone, Debug, Deserialize)] pub struct ApiListRes { - pub data: Vec, - pub cursor: Option, + pub data: Vec, + pub cursor: Option, } #[derive(Clone, Debug, Deserialize)] pub struct Address { - pub address: String, + pub address: String, } #[derive(Clone, Debug, Deserialize)] pub struct AddressAsset { - pub policy_id: String, - pub asset_name: String, - pub fingerprint: String, - pub quantity: i64, + pub policy_id: String, + pub asset_name: String, + pub fingerprint: String, + pub quantity: i64, } #[derive(Clone, Debug, Deserialize)] pub struct AssetAddress { - pub address: String, - pub quantity: i64, - pub share: f64, -} + pub address: String, + pub quantity: i64, + pub share: f64, +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 077073a..5f404bc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -70,4 +70,4 @@ async fn main() -> anyhow::Result<()> { println!("has {}: {}", &id2, bookshelf.has_book(&id2).await); Ok(()) -} +} \ No newline at end of file diff --git a/src/model/bookshelf.rs b/src/model/bookshelf.rs index 9ec467c..92078d1 100644 --- a/src/model/bookshelf.rs +++ b/src/model/bookshelf.rs @@ -1,35 +1,79 @@ use std::{collections::HashSet, sync::Arc}; use crate::cardano::api::CardanoApi; +use crate::cardano::model::Asset; use super::book::{BookId, BookListItem}; + pub struct Bookshelf { - api: Arc>, - stake_address: String, + api: Arc>, + stake_address: String, } impl Bookshelf { - pub fn new(api: Arc>, stake_address: String) -> Self { - Bookshelf { api, stake_address } - } + pub fn new(api: Arc>, stake_address: String) -> Self { + Bookshelf { api, stake_address } + } - /** - * Gets the collection of books available on this bookshelf. - */ - pub async fn get_books( - &self, - policy_ids: HashSet, - ) -> anyhow::Result> { - todo!() + /** + * Gets the collection of books available on this bookshelf. + */ + pub async fn get_books( + &self, + policy_ids: HashSet, + ) -> anyhow::Result> + { + let addresses = self.api.get_all_addresses(self.stake_address.as_str()).await?; + + let mut assets: Vec = Vec::new(); + for addr in addresses.iter() + { + let mut assets_ = self.api.get_address_assets(addr).await?; + assets.append(&mut assets_); } + + let books: Vec = assets.into_iter() + .filter(|x| policy_ids.contains(&x.policy_id)) + .map(|x| BookListItem + { + id: BookId::new(x.policy_id, x.asset_name.clone()), + token_name: x.asset_name + }).collect(); + + Ok(books) + } - /** - * Returns true if the book exists on the bookshelf and false otherwise. - */ - pub async fn has_book(&self, id: &BookId) -> bool { - // bonus points if you can implement this more efficiently than just - // calling get_books and seeing if the BookId exists in that set. - todo!() + /** + * Returns true if the book exists on the bookshelf and false otherwise. + */ + pub async fn has_book(&self, id: &BookId) -> bool + { + // bonus points if you can implement this more efficiently than just + // calling get_books and seeing if the BookId exists in that set. + + //BookId is the same as asset id as expected by the tangocrypto api: + // https://www.tangocrypto.com/api-reference/#/operations/list-asset-addresses + let assetAddresses = self.api.get_asset_addresses(id.0.as_str()).await; + + if let Ok(addresses) = assetAddresses + { + for addr in addresses.iter() + { + let mut assets= self.api.get_address_assets(addr).await; + if let Ok(assets_) = assets + { + if assets_.len() > 0 + { + return true; + } + } + } + false } -} + else + { + false + } + } +} \ No newline at end of file