[
  {
    "path": ".github/workflows/rust.yml",
    "content": "name: Rust\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v2\n    - name: Build\n      run: cargo build --verbose\n    - name: Run tests\n      run: cargo test --verbose\n"
  },
  {
    "path": ".gitignore",
    "content": "target\nCargo.lock\nkeys_real.json\n.idea/\n.vscode/\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: rust\ncache: cargo\nsudo: false\nrust:\n  - 1.21.0 # oldest version supported\n  - stable\n  - nightly\n\nmatrix:\n  allow_failures:\n    - rust: nightly\n\nbefore_script:\n  - if [ ${TRAVIS_RUST_VERSION} = \"nightly\" ]; then\n      cargo install rustfmt --force &&\n      cargo install clippy --force;\n    fi\n\nscript:\n  - if [ ${TRAVIS_RUST_VERSION} = \"nightly\" ]; then\n      cargo fmt -- --write-mode=diff &&\n      cargo clippy;\n    fi\n  - cargo build --verbose\n  - cargo test --verbose\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"coinnect\"\nversion = \"0.5.12\"\nlicense = \"MIT\"\nauthors = [\"Hugues Gaillard <hugues.gaillard@me.com>\", \"Alejandro Inestal <ainestal@gmail.com>\"]\ndescription = \"\"\"\nA Rust library to connect to various crypto-currencies exchanges.\n\"\"\"\ndocumentation = \"https://docs.rs/coinnect/\"\nhomepage = \"https://github.com/hugues31/coinnect\"\nrepository = \"https://github.com/hugues31/coinnect\"\nkeywords = [ \"bitcoin\", \"trading\", \"poloniex\", \"kraken\", \"bitstamp\" ]\nreadme = \"README.md\"\nedition = \"2018\"\n\n[features]\ndefault = []\nbitstamp_private_tests = []\nkraken_private_tests = []\npoloniex_private_tests = []\nbittrex_private_tests = []\n\n[[example]]\nname = \"simple\"\npath = \"examples/simple.rs\"\n\n[[example]]\nname = \"kraken_trading\"\npath = \"examples/kraken_trading.rs\"\n\n[[example]]\nname = \"generic_api\"\npath = \"examples/generic_api.rs\"\n\n[dependencies]\nhyper = \"0.10.10\"\nserde_json = \"1.0.0\"\nhyper-native-tls = \"0.3\"\nlazy_static = \"1.4\"\nbidir-map = \"1.0.0\"\ndata-encoding = \"2.0.0-rc.1\"\nerror-chain = \"0.12\"\nsha2 = \"0.9.5\"\nhmac = \"0.11.0\"\nbigdecimal = \"0.2.1\"\nchrono = \"0.4.0\"\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "![Coinnect](https://raw.githubusercontent.com/hugues31/coinnect/master/coinnect.png)\n===========\n[![crates.io](https://img.shields.io/crates/v/coinnect.svg)](https://crates.io/crates/coinnect)\n[![Downloads from crates.io](https://img.shields.io/crates/d/coinnect.svg)](https://crates.io/crates/coinnect)\n[![Build Status](https://travis-ci.org/hugues31/coinnect.svg?branch=master)](https://travis-ci.org/hugues31/coinnect)\n[![doc.rs](https://docs.rs/coinnect/badge.svg)](https://docs.rs/coinnect/)\n[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\n\nCoinnect is a Rust library aiming to provide a complete access to REST APIs for\nvarious crypto-currencies exchanges (see below for a list of supported\nexchanges).\nAll methods consume HTTPS api. The purpose of this crate is not to stream data\n(you should use websocket/FIX in that case).\n\nYou basically have 2 choices to retrieve data : use the raw API provided by the\nplatform you target or use the generic Coinnect API, which is more user-friendly\nand safe. Ideally, use the raw API when the Coinnect API could not retrieve the\ndata/perform the action you want.\n\n**NOTE:** A new version with Futures support is coming as soon as async-await\nsyntax will be stabilized !\n\n**WARNING:**  This library is highly experimental at the moment. Please do not\ninvest what you can't afford to lose. This is a personal project, I cannot be\nheld responsible for the library malfunction, which can lead to a loss of money.\n\n*The project is licensed under the terms of the MIT License.*\n\n### Exchanges support:\n| Exchange | Raw API supported | Generic API supported | Note |\n|:--------:|:-----------------:|:---------------------:|:----:|\n| Bitstamp | X | X | Not every method is implemented for now.|\n| Kraken   | X | X | - |\n| Poloniex | X | X | - |\n| Bittrex  | X | X | - |\n\nIf your favorite exchange is not listed above, you can vote [here](https://github.com/hugues31/coinnect/issues/54) to add it in the next release of Coinnect.\n\nGeneric API supports:\n - Ticker\n - Orderbook\n - Balances\n - Add a new order\n - ... more to come!\n\nFeel free to make a PR to add support to your favorite exchange ;)\n\n### Documentation\n\n- [Master](https://docs.rs/coinnect/)\n\n\n## Usage\n\nAdd this to your `Cargo.toml`:\n\n```toml\n[dependencies]\ncoinnect = \"0.5\"\n```\n\nand this to your crate root:\n\n```rust\nextern crate coinnect;\n```\n\nFor optional parameters, most methods require an empty `str` (`\"\"`) or\n`Option` (`None`) if you don't want to specify them.\n\nSince 0.2, you have access to a generic API to communicate across exchanges in\nthe same way. Note that this functionality is under active development.\nFor more informations, look at ExchangeApi trait doc.\n\n## Example\n\nThe example below shows you how to connect to Poloniex\n\n```rust\nextern crate coinnect;\n\nuse coinnect::poloniex::api::PoloniexApi;\nuse coinnect::poloniex::credentials::PoloniexCreds;\n\nfn main() {\n    // We create a PoloniexApi by providing API key/secret\n    // You can give an empty str if you only use public methods\n    let creds = PoloniexCreds::new(\"my_optionnal_name\", \"api_key\", \"api_secret\");\n    let mut my_api = PoloniexApi::new(creds).unwrap();\n\n    // Let's look at the ticker!\n    let list_coins = my_api.return_ticker().unwrap();\n\n    for coin in list_coins {\n        // please visit Poloniex API documentation to know how the data is returned\n        // or look at the coinnect documentation\n        let name = coin.0;\n        let price = coin.1.as_object().unwrap().get(\"last\").unwrap().as_str().unwrap();\n\n        println!(\"Coin {} has price : {}\", name, price);\n    }\n}\n\n```\n\nFor more examples, please see [examples](examples/).\n\n## Testing\nYou can run the tests suite with `cargo test` for testing non private data\nrequests (this will ignore tests related to private requests).\nYou can use `cargo test --features \"bitstamp_private_tests\"` to run private\ntests related to bitstamp exchange for example.\nBefore running private tests, make sure you have a `keys_real.json` file at the\nroot with the following structure :\n```json\n{\n    \"account_kraken\": {\n        \"api_key\"   : \"123456789ABCDEF\",\n        \"api_secret\": \"ABC&EF?abcdef\"\n    },\n    \"account_poloniex\": {\n        \"api_key\"   : \"XYXY-XYXY-XYXY-XY\",\n        \"api_secret\": \"A0A0B1B1C2C2\"\n    },\n    \"account_bitstamp\": {\n        \"api_key\"    : \"XYXY-XYXY-XYXY-XY\",\n        \"api_secret\" : \"A0A0B1B1C2C2\",\n        \"customer_id\": \"123456\"\n    }\n}\n```\nYou must insert your real API keys, otherwise private tests may fail. No\naction is performed if you run the tests : no test will open position, or\nwithdraw, etc.\nTests only check for correct authentication method and correct parsing.\nYou can examine the [tests](tests) folder just to be sure and look at the\n[Cargo.toml](Cargo.toml) file for a complete list of features.\n\n\n## Contribution\n\nYour contribution is highly appreciated. Do not hesitate to open an issue or a\npull request. Note that any contribution submitted for inclusion in the project\nwill be licensed according to the terms given in [LICENSE](LICENSE).\n\n## Disclaimer\nThis SOFTWARE PRODUCT is provided by THE PROVIDER \"as is\" and \"with all faults.\"\nTHE PROVIDER makes no representations or warranties of any kind concerning the\nsafety, suitability, lack of viruses, inaccuracies, typographical errors, or\nother harmful components of this SOFTWARE PRODUCT. There are inherent dangers\nin the use of any software, and you are solely responsible for determining\nwhether this SOFTWARE PRODUCT is compatible with your equipment and other\nsoftware installed on your equipment. You are also solely responsible for the\nprotection of your equipment and backup of your data, and THE PROVIDER will not\nbe liable for any damages you may suffer in connection with using, modifying,\nor distributing this SOFTWARE PRODUCT.\n"
  },
  {
    "path": "TODO.md",
    "content": "TODO\n====\n\n- [ ] Implement two-factor auth for supported exchanges\n- [ ] Add links to the documentation (Kraken use external links for example)\n- [ ] Remove .clone() for params in Kraken & Poloniex\n"
  },
  {
    "path": "examples/generic_api.rs",
    "content": "// This example shows how to use the generic API provided by Coinnect.\n// This method is useful if you have to iterate throught multiple accounts of\n// different exchanges and perform the same operation (such as get the current account's balance)\n// You can also use the Coinnect generic API if you want a better error handling since all methods\n// return Result<_, Error>.\n\nextern crate coinnect;\n\nuse coinnect::coinnect::Coinnect;\nuse coinnect::kraken::KrakenCreds;\nuse coinnect::exchange::Exchange::*;\nuse coinnect::types::Pair::*;\n\nfn main() {\n    // We create a Coinnect Generic API\n    // Since Kraken does not need customer_id field, we set it to None\n    let my_creds = KrakenCreds::new(\"my_optionnal_name\", \"api_key\", \"api_secret\");\n    let mut my_api = Coinnect::new(Kraken, my_creds).unwrap();\n    let ticker = my_api.ticker(ETC_BTC);\n\n    println!(\"ETC_BTC last trade price is {}.\",\n             ticker.unwrap().last_trade_price);\n}\n"
  },
  {
    "path": "examples/kraken_trading.rs",
    "content": "// This example shows how to implement a simple trading strategy.\n// We are looking for the pair with the highest rise in price over the last 24 hours and we\n// add a buy order to buy it.\n\n// Please do NOT run this example with your real account unless you know what you're doing.\n\nextern crate coinnect;\n\nuse std::path::PathBuf;\n\nuse coinnect::kraken::{KrakenApi, KrakenCreds};\nuse std::error::Error;\n\nfn main() {\n    // We create a KrakenApi by loading a json file containing API configuration\n    // (see documentation for more info)\n    let path = PathBuf::from(\"keys_real.json\");\n    let my_creds = KrakenCreds::new_from_file(\"account_kraken\", path).unwrap();\n    let mut my_api = KrakenApi::new(my_creds).unwrap();\n\n\n\n    // First, get the list of all pair we can trade with EUR€ as quote\n    // You could use a simple unwrap() or use match to recover from an error for example\n    let pairs_request = match my_api.get_tradable_asset_pairs(\"\", \"\") {\n        Ok(pairs_request) => pairs_request,\n        Err(err) => panic!(\"Error : {:?}, description : {}\", err, err.description()),\n    };\n    let list_all_pairs = pairs_request.get(\"result\").unwrap().as_object().unwrap();\n\n    let mut list_pairs_eur = Vec::new();\n\n    for pair in list_all_pairs {\n        // The map structure is explained in documentation\n        let quote = pair.1\n            .as_object()\n            .unwrap()\n            .get(\"quote\")\n            .unwrap()\n            .as_str()\n            .unwrap();\n        if quote == \"ZEUR\" {\n            let name = pair.0;\n            list_pairs_eur.push(name);\n        }\n    }\n\n    println!(\"List {:?}\", list_pairs_eur);\n\n    // Now that we have the pairs, we choose the one with the highest price variation.\n    // We query the ticker to get the opening and closing price over the last 24 hours for each\n    // pairs in list_pairs_eur. KrakenApi has a blocking timer which prevents from ban if you\n    // make rapid succession of requests (inside a for loop for example). Here, the ticker function\n    // can take a list of pairs as parameter, so 1 request should suffice.\n\n    // Convert Vec into comma separated values String\n    let eur_pairs = format!(\"{:?}\", list_pairs_eur);\n    let eur_pairs = eur_pairs\n        .replace(\"\\\"\", \"\")\n        .replace(\"[\", \"\")\n        .replace(\"]\", \"\")\n        .replace(\" \", \"\");\n\n\n    // Get ticker\n    let ticker_request = my_api.get_ticker_information(&eur_pairs).unwrap();\n    let list_ticker = ticker_request.get(\"result\").unwrap().as_object().unwrap();\n\n    let mut pair_to_buy = \"\";\n    let mut pair_price_var = 0.0;\n    let mut current_price = 0.0;\n\n    for pair in list_ticker {\n        let name = pair.0;\n\n        // WARNING: Kraken uses quotes to encapsulate floating value\n        let pair_info = pair.1.as_object().unwrap();\n        let open_price = pair_info\n            .get(\"o\")\n            .unwrap()\n            .as_str()\n            .unwrap()\n            .parse::<f64>()\n            .unwrap();\n        let close_price_array = pair_info.get(\"c\").unwrap().as_array().unwrap();\n        let close_price = close_price_array[0]\n            .as_str()\n            .unwrap()\n            .parse::<f64>()\n            .unwrap();\n\n        let price_var = (close_price / open_price - 1.0) * 100.0;\n\n        if price_var > pair_price_var {\n            pair_price_var = price_var;\n            pair_to_buy = name;\n            current_price = close_price;\n        }\n    }\n\n    println!(\"{} has the highest price variation ({:.2}%).\",\n             pair_to_buy,\n             pair_price_var);\n\n    // Add a buy limit order for an amount of 100€ for a price of: current_price - 2%\n    let buying_price = current_price - (current_price * 2.0 / 100.0);\n    let volume = 100.0 / buying_price;\n\n    // Specify optional parameters with an empty str (\"\")\n    // See documentation for more informations\n    let _ = my_api.add_standard_order(pair_to_buy, // name of the pair\n                                      \"buy\", // type : buy/sell\n                                      \"limit\", // order type : market/limit/...\n                                      &buying_price.to_string(), // price 1\n                                      \"\", // price 2\n                                      &volume.to_string(), // volume\n                                      \"\", // leverage\n                                      \"\", // oflags (see doc)\n                                      \"\", // starttm\n                                      \"\", // expiretm\n                                      \"\", // userref\n                                      \"\"); // validate\n    // In a real case example, you should check if any error occurs.\n}\n"
  },
  {
    "path": "examples/simple.rs",
    "content": "// This example shows how to connect to your Poloniex account and perform simple operations\n\nextern crate coinnect;\n\nuse coinnect::poloniex::{PoloniexApi, PoloniexCreds};\n\nfn main() {\n    // We create a PoloniexApi by providing API key/secret\n    // You can give an empty String if you only use public methods\n    let creds = PoloniexCreds::new(\"my_optionnal_name\", \"api_key\", \"api_secret\");\n    let mut my_api = PoloniexApi::new(creds).unwrap();\n\n    // Let's look at the ticker!\n    let list_coins = my_api.return_ticker().unwrap();\n\n    for coin in list_coins {\n        // please visit Poloniex API documentation to know how the data is returned\n        // or look at the coinnect documentation\n        let name = coin.0;\n        let price = coin.1\n            .as_object()\n            .unwrap()\n            .get(\"last\")\n            .unwrap()\n            .as_str()\n            .unwrap();\n\n        println!(\"Coin {} has price : {}\", name, price);\n    }\n}\n"
  },
  {
    "path": "get_pairs_name.py",
    "content": "# This python3 script displays all pairs that can be used\n# on Kraken, Poloniex and Bitstamp platform. The pairs can then be copied/pasted\n# into Coinnect. This script does the conversion for Poloniex (see Pair doc).\n\nimport json\nimport ssl\nimport urllib.request\n\n# ugly fix the ssl certificate bug\nssl._create_default_https_context = ssl._create_unverified_context\n\n\n# ╔╗   ╦  ╔╦╗  ╔═╗  ╔╦╗  ╔═╗  ╔╦╗  ╔═╗\n# ╠╩╗  ║   ║   ╚═╗   ║   ╠═╣  ║║║  ╠═╝\n# ╚═╝  ╩   ╩   ╚═╝   ╩   ╩ ╩  ╩ ╩  ╩\nraw_bitstamp_pairs = [\"btcusd\", \"btceur\", \"eurusd\", \"xrpusd\", \"xrpeur\",\n\"xrpbtc\"]\nstandardized_bitstamp_pairs = [\"BTC_USD\", \"BTC_EUR\", \"EUR_USD\", \"XRP_USD\",\n\"XRP_EUR\", \"XRP_BTC\"]\n\n# ╦╔═  ╦═╗  ╔═╗  ╦╔═  ╔═╗  ╔╗╔\n# ╠╩╗  ╠╦╝  ╠═╣  ╠╩╗  ║╣   ║║║\n# ╩ ╩  ╩╚═  ╩ ╩  ╩ ╩  ╚═╝  ╝╚╝\nurl = \"https://api.kraken.com/0/public/AssetPairs\"\nraw_kraken_pairs = list()\nstandardized_kraken_pairs = list()\nwith urllib.request.urlopen(url) as response:\n    html = response.read().decode(\"utf-8\")\n    json_data = json.loads(html)\n    for currency in json_data[\"result\"]:\n        raw_kraken_pairs.append(currency)\n        quote = json_data[\"result\"][currency][\"quote\"][1:] # remove the X or Z\n        base = json_data[\"result\"][currency][\"base\"]\n        old_naming = (\"XETH\", \"XXBT\", \"XETC\", \"XLTC\", \"XICN\", \"XREP\", \"XXDG\",\n        \"XZEC\", \"XXLM\", \"XXMR\", \"XMLN\", \"XXRP\")\n        if base in old_naming:\n            base = base[1:] # remove the X\n        if base == \"XBT\":\n            base = \"BTC\"\n        if quote == \"XBT\":\n            quote = \"BTC\"\n        if json_data[\"result\"][currency][\"altname\"][-2:] == \".d\":\n            quote += \"_d\"\n        standardized_kraken_pairs.append(base + \"_\" + quote)\n\n\n# ╔═╗  ╔═╗  ╦    ╔═╗  ╔╗╔  ╦  ╔═╗  ═╗ ╦\n# ╠═╝  ║ ║  ║    ║ ║  ║║║  ║  ║╣   ╔╩╦╝\n# ╩    ╚═╝  ╩═╝  ╚═╝  ╝╚╝  ╩  ╚═╝  ╩ ╚═\nurl =  \"https://poloniex.com/public?command=returnTicker\"\n\nraw_poloniex_pairs = list()\nwith urllib.request.urlopen(url) as response:\n    html = response.read().decode(\"utf-8\")\n    json_data = json.loads(html)\n    for currency in json_data:\n        raw_poloniex_pairs.append(currency)\n\n# conversion\nstandardized_poloniex_pairs = list()\nfor pair in raw_poloniex_pairs:\n    base, quote = pair.split('_', 1)\n    standardized_poloniex_pairs.append(quote + \"_\" + base)\n\n# ╔╗   ╦  ╔╦╗  ╔╦╗  ╦═╗  ╔═╗  ═╗ ╦\n# ╠╩╗  ║   ║    ║   ╠╦╝  ║╣   ╔╩╦╝\n# ╚═╝  ╩   ╩    ╩   ╩╚═  ╚═╝  ╩ ╚═\nurl = \"https://bittrex.com/api/v1.1/public/getmarketsummaries\"\n\nraw_bittrex_pairs = list()\nwith urllib.request.urlopen(url) as response:\n    html = response.read().decode(\"utf-8\")\n    json_data = json.loads(html)\n    for currency in json_data[\"result\"]:\n        raw_bittrex_pairs.append(currency[\"MarketName\"])\n\n# conversion\nstandardized_bittrex_pairs = list()\nfor pair in raw_bittrex_pairs:\n    base, quote = pair.split('-', 1)\n    standardized_bittrex_pairs.append(quote + \"_\" + base)\n\n\n\n# Generate all possible pairs\nexchanges = [standardized_bitstamp_pairs, standardized_kraken_pairs,\nstandardized_poloniex_pairs, standardized_bittrex_pairs]\n\npairs = list()\nfor exchange in exchanges:\n    for pair in exchange:\n        if pair not in pairs:\n            pairs.append(pair)\npairs = sorted(pairs)\n\nprint(\"SUPPORTED PAIRS\")\nprint(\"===============\")\nfor pair in pairs:\n    print(pair + \",\")\n\nprint(\"\\n\\n\\n\")\nprint(\"BITSTAMP PAIRS\")\nprint(\"==============\")\nfor std, raw in zip(standardized_bitstamp_pairs, raw_bitstamp_pairs):\n    print(\"m.insert({std}, \\\"{raw}\\\");\".format(std=std, raw=raw))\n\nprint(\"\\n\\n\\n\")\nprint(\"KRAKEN PAIRS\")\nprint(\"============\")\nfor std, raw in zip(standardized_kraken_pairs, raw_kraken_pairs):\n    print(\"m.insert({std}, \\\"{raw}\\\");\".format(std=std, raw=raw))\n\nprint(\"\\n\\n\\n\")\nprint(\"POLONIEX PAIRS\")\nprint(\"==============\")\nfor std, raw in zip(standardized_poloniex_pairs, raw_poloniex_pairs):\n    print(\"m.insert({std}, \\\"{raw}\\\");\".format(std=std, raw=raw))\n\nprint(\"\\n\\n\\n\")\nprint(\"BITTREX PAIRS\")\nprint(\"==============\")\nfor std, raw in zip(standardized_bittrex_pairs, raw_bittrex_pairs):\n    print(\"m.insert({std}, \\\"{raw}\\\");\".format(std=std, raw=raw))\n\n\n# CURRENCIES\n# BITTREX\nurl = \"https://bittrex.com/api/v1.1/public/getcurrencies\"\n\nbittrex_currencies = list()\nwith urllib.request.urlopen(url) as response:\n    html = response.read().decode(\"utf-8\")\n    json_data = json.loads(html)\n    for currency in json_data[\"result\"]:\n        print(currency[\"Currency\"] + \",\")\n        bittrex_currencies.append(currency[\"Currency\"])\n\n# Currency enum -> Option<String>\nfor currency in bittrex_currencies:\n    print(\"Currency::\" + currency + \" => Some(\\\"\" +\n          currency + \"\\\".to_string()),\")\n\n# Currency str -> Option<Currency>\nfor currency in bittrex_currencies:\n    print(\"\\\"\" + currency + \"\\\" => Some(Currency::\" + currency + \"),\")\n"
  },
  {
    "path": "src/bitstamp/api.rs",
    "content": "//! Use this module to interact with Bitstamp exchange.\n//! Please see examples for more informations.\n\n\nuse hyper_native_tls::NativeTlsClient;\nuse hyper::Client;\nuse hyper::header::ContentType;\nuse hyper::net::HttpsConnector;\n\nuse serde_json::Value;\nuse serde_json::value::Map;\n\nuse std::collections::HashMap;\nuse std::io::Read;\nuse std::thread;\nuse std::time::Duration;\n\nuse crate::coinnect::Credentials;\nuse crate::exchange::Exchange;\n\nuse crate::error::*;\nuse crate::helpers;\nuse crate::types::Pair;\nuse crate::bitstamp::utils;\nuse crate::types::*;\n\nheader! {\n    #[doc(hidden)]\n    (KeyHeader, \"Key\") => [String]\n}\n\nheader! {\n    #[doc(hidden)]\n    (SignHeader, \"Sign\") => [String]\n}\n\nheader! {\n    #[doc(hidden)]\n    (ContentHeader, \"Content-Type\") => [String]\n}\n\n#[derive(Debug)]\npub struct BitstampApi {\n    last_request: i64, // unix timestamp in ms, to avoid ban\n    api_key: String,\n    api_secret: String,\n    customer_id: String,\n    http_client: Client,\n    burst: bool,\n}\n\n\nimpl BitstampApi {\n    /// Create a new BitstampApi by providing an API key & API secret\n    pub fn new<C: Credentials>(creds: C) -> Result<BitstampApi> {\n        if creds.exchange() != Exchange::Bitstamp {\n            return Err(ErrorKind::InvalidConfigType(Exchange::Bitstamp, creds.exchange()).into());\n        }\n\n        //TODO: Handle correctly TLS errors with error_chain.\n        let ssl = match NativeTlsClient::new() {\n            Ok(res) => res,\n            Err(_) => return Err(ErrorKind::TlsError.into()),\n        };\n\n        let connector = HttpsConnector::new(ssl);\n\n\n        Ok(BitstampApi {\n               last_request: 0,\n               api_key: creds.get(\"api_key\").unwrap_or_default(),\n               api_secret: creds.get(\"api_secret\").unwrap_or_default(),\n               customer_id: creds.get(\"customer_id\").unwrap_or_default(),\n               http_client: Client::with_connector(connector),\n               burst: false, // No burst by default\n           })\n    }\n\n    /// The number of calls in a given period is limited. In order to avoid a ban we limit\n    /// by default the number of api requests.\n    /// This function sets or removes the limitation.\n    /// Burst false implies no block.\n    /// Burst true implies there is a control over the number of calls allowed to the exchange\n    pub fn set_burst(&mut self, burst: bool) {\n        self.burst = burst\n    }\n\n    fn block_or_continue(&self) {\n        if ! self.burst {\n            let threshold: u64 = 1000; // 600 requests per 10 mins = 1 request per second\n            let offset: u64 = helpers::get_unix_timestamp_ms() as u64 - self.last_request as u64;\n            if offset < threshold {\n                let wait_ms = Duration::from_millis(threshold - offset);\n                thread::sleep(wait_ms);\n            }\n        }\n    }\n\n    fn public_query(&mut self, params: &HashMap<&str, &str>) -> Result<Map<String, Value>> {\n\n        let method: &str = params\n            .get(\"method\")\n            .ok_or_else(|| \"Missing \\\"method\\\" field.\")?;\n        let pair: &str = params.get(\"pair\").ok_or_else(|| \"Missing \\\"pair\\\" field.\")?;\n        let url: String = utils::build_url(method, pair);\n\n        self.block_or_continue();\n        let mut response = self.http_client.get(&url).send()?;\n        self.last_request = helpers::get_unix_timestamp_ms();\n        let mut buffer = String::new();\n        response.read_to_string(&mut buffer)?;\n        utils::deserialize_json(&buffer)\n    }\n\n    ///\n    ///\n    /// #Examples\n    ///\n    /// ```json\n    /// extern crate coinnect;\n    /// use coinnect::bitstamp::BitstampApi;\n    /// let mut api = BitstampApi::new(\"\", \"\");\n    /// let  result = api.private_query(\"balance\", \"btcusd\");\n    /// assert_eq!(true, true);\n    /// ```\n    fn private_query(&mut self, params: &HashMap<&str, &str>) -> Result<Map<String, Value>> {\n\n        let method: &str = params\n            .get(\"method\")\n            .ok_or_else(|| \"Missing \\\"method\\\" field.\")?;\n        let pair: &str = params.get(\"pair\").ok_or_else(|| \"Missing \\\"pair\\\" field.\")?;\n        let url: String = utils::build_url(method, pair);\n\n        let nonce = utils::generate_nonce(None);\n        let signature =\n            utils::build_signature(&nonce, &self.customer_id, &self.api_key, &self.api_secret)?;\n\n        let copy_api_key = self.api_key.clone();\n        let mut post_params: &mut HashMap<&str, &str> = &mut HashMap::new();\n        post_params.insert(\"key\", &copy_api_key);\n        post_params.insert(\"signature\", &signature);\n        post_params.insert(\"nonce\", &nonce);\n\n        // copy params into post_params .... bit of a hack but will do for now\n        params.iter().for_each(|(k,v)| {\n            post_params.insert(k,v);\n        });\n\n        helpers::strip_empties(&mut post_params);\n        let post_data = helpers::url_encode_hashmap(post_params);\n        let mut response = self.http_client\n            .post(&url)\n            .header(ContentType::form_url_encoded())\n            .body(&post_data)\n            .send()?;\n\n        let mut buffer = String::new();\n        response.read_to_string(&mut buffer)?;\n        utils::deserialize_json(&buffer)\n    }\n\n    /// Sample output :\n    ///\n    /// ```json\n    /// {\n    /// \"BTC_LTC\":{\n    /// \"last\":\"0.0251\",\"lowestAsk\":\"0.02589999\",\"highestBid\":\"0.0251\",\n    /// \"percentChange\":\"0.02390438\",\"baseVolume\":\"6.16485315\",\"quoteVolume\":\"245.82513926\"},\n    /// \"BTC_NXT\":{\n    /// \"last\":\"0.00005730\",\"lowestAsk\":\"0.00005710\",\"highestBid\":\"0.00004903\",\n    /// \"percentChange\":\"0.16701570\",\"baseVolume\":\"0.45347489\",\"quoteVolume\":\"9094\"},\n    /// ... }\n    /// ```\n    pub fn return_ticker(&mut self, pair: Pair) -> Result<Map<String, Value>> {\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(name) => name,\n            None => return Err(ErrorKind::PairUnsupported.into()),\n        };\n\n        let mut params: HashMap<&str, &str> = HashMap::new();\n        params.insert(\"pair\", pair_name);\n        params.insert(\"method\", \"ticker\");\n        self.public_query(&params)\n    }\n\n    /// Sample output :\n    ///\n    /// ```json\n    /// {\"asks\":[[0.00007600,1164],[0.00007620,1300], ... ], \"bids\":[[0.00006901,200],\n    /// [0.00006900,408], ... ], \"timestamp\": \"1234567890\"}\n    /// ```\n    pub fn return_order_book(&mut self, pair: Pair) -> Result<Map<String, Value>> {\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(name) => name,\n            None => return Err(ErrorKind::PairUnsupported.into()),\n\n        };\n\n        let mut params: HashMap<&str, &str> = HashMap::new();\n        params.insert(\"method\", \"order_book\");\n        params.insert(\"pair\", pair_name);\n        self.public_query(&params)\n    }\n\n    /// Sample output :\n    ///\n    /// ```json\n    /// [{\"date\":\"2014-02-10 04:23:23\",\"type\":\"buy\",\"rate\":\"0.00007600\",\"amount\":\"140\",\n    /// \"total\":\"0.01064\"},\n    /// {\"date\":\"2014-02-10 01:19:37\",\"type\":\"buy\",\"rate\":\"0.00007600\",\"amount\":\"655\",\n    /// \"total\":\"0.04978\"}, ... ]\n    /// ```\n    pub fn return_trade_history(&mut self, pair: Pair) -> Result<Map<String, Value>> {\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(name) => name,\n            None => return Err(ErrorKind::PairUnsupported.into()),\n        };\n\n        let mut params: HashMap<&str, &str> = HashMap::new();\n        params.insert(\"pair\", pair_name);\n        params.insert(\"method\", \"transactions\");\n        self.public_query(&params)\n    }\n\n\n    /// Returns all of your available balances.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"BTC\":\"0.59098578\",\"LTC\":\"3.31117268\", ... }\n    /// ```\n    pub fn return_balances(&mut self) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"method\", \"balance\");\n        params.insert(\"pair\", \"\");\n        self.private_query(&params)\n    }\n\n    /// Add a buy limit order to the exchange\n    /// limit_price\t: If the order gets executed, a new sell order will be placed,\n    /// with \"limit_price\" as its price.\n    /// daily_order (Optional) : Opens buy limit order which will be canceled\n    /// at 0:00 UTC unless it already has been executed. Possible value: True\n    pub fn buy_limit(&mut self,\n                     pair: Pair,\n                     amount: Volume,\n                     price: Price,\n                     price_limit: Option<Price>,\n                     daily_order: Option<bool>)\n                     -> Result<Map<String, Value>> {\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(name) => name,\n            None => return Err(ErrorKind::PairUnsupported.into()),\n        };\n\n        let amount_string = amount.to_string();\n        let price_string = price.to_string();\n        let price_limit_string = match price_limit {\n            Some(limit) => limit.to_string(),\n            None => \"\".to_string(),\n        };\n\n        let mut params = HashMap::new();\n        params.insert(\"method\", \"buy\");\n        params.insert(\"pair\", pair_name);\n\n        params.insert(\"amount\", &amount_string);\n        params.insert(\"price\", &price_string);\n        params.insert(\"limit_price\", &price_limit_string);\n        if let Some(order) = daily_order {\n            let daily_order_str = if order { \"True\" } else { \"\" }; // False is not a possible value\n            params.insert(\"daily_order\", daily_order_str);\n        }\n\n        self.private_query(&params)\n    }\n\n    /// Add a sell limit order to the exchange\n    /// limit_price\t: If the order gets executed, a new sell order will be placed,\n    /// with \"limit_price\" as its price.\n    /// daily_order (Optional) : Opens sell limit order which will be canceled\n    /// at 0:00 UTC unless it already has been executed. Possible value: True\n    pub fn sell_limit(&mut self,\n                      pair: Pair,\n                      amount: Volume,\n                      price: Price,\n                      price_limit: Option<Price>,\n                      daily_order: Option<bool>)\n                      -> Result<Map<String, Value>> {\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(name) => name,\n            None => return Err(ErrorKind::PairUnsupported.into()),\n        };\n\n        let amount_string = amount.to_string();\n        let price_string = price.to_string();\n        let price_limit_string = match price_limit {\n            Some(limit) => limit.to_string(),\n            None => \"\".to_string(),\n        };\n\n        let mut params = HashMap::new();\n        params.insert(\"method\", \"sell\");\n        params.insert(\"pair\", pair_name);\n\n        params.insert(\"amount\", &amount_string);\n        params.insert(\"price\", &price_string);\n        params.insert(\"limit_price\", &price_limit_string);\n        if let Some(order) = daily_order {\n            let daily_order_str = if order { \"True\" } else { \"\" }; // False is not a possible value\n            params.insert(\"daily_order\", daily_order_str);\n        }\n\n        self.private_query(&params)\n    }\n\n    /// Add a market buy order to the exchange\n    /// By placing a market order you acknowledge that the execution of your order depends\n    /// on the market conditions and that these conditions may be subject to sudden changes\n    /// that cannot be foreseen.\n    pub fn buy_market(&mut self, pair: Pair, amount: Volume) -> Result<Map<String, Value>> {\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(name) => name,\n            None => return Err(ErrorKind::PairUnsupported.into()),\n        };\n\n        let amount_string = amount.to_string();\n\n        let mut params = HashMap::new();\n        params.insert(\"method\", \"buy/market\");\n        params.insert(\"pair\", pair_name);\n\n        params.insert(\"amount\", &amount_string);\n\n        self.private_query(&params)\n    }\n\n    /// Add a market sell order to the exchange\n    /// By placing a market order you acknowledge that the execution of your order depends\n    /// on the market conditions and that these conditions may be subject to sudden changes\n    /// that cannot be foreseen.\n    pub fn sell_market(&mut self, pair: Pair, amount: Volume) -> Result<Map<String, Value>> {\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(name) => name,\n            None => return Err(ErrorKind::PairUnsupported.into()),\n        };\n\n        let amount_string = amount.to_string();\n\n        let mut params = HashMap::new();\n        params.insert(\"method\", \"sell/market\");\n        params.insert(\"pair\", pair_name);\n\n        params.insert(\"amount\", &amount_string);\n\n        self.private_query(&params)\n    }\n}\n\n\n#[cfg(test)]\nmod bitstamp_api_tests {\n    use super::*;\n\n    #[test]\n    fn should_block_or_not_block_when_enabled_or_disabled() {\n        let mut api = BitstampApi {\n            last_request: helpers::get_unix_timestamp_ms(),\n            api_key: \"\".to_string(),\n            api_secret: \"\".to_string(),\n            customer_id: \"\".to_string(),\n            http_client: Client::new(),\n            burst: false,\n        };\n\n        let mut counter = 0;\n        loop {\n            api.set_burst(false);\n            let start = helpers::get_unix_timestamp_ms();\n            api.block_or_continue();\n            api.last_request = helpers::get_unix_timestamp_ms();\n\n            let difference = api.last_request - start;\n            assert!(difference >= 999);\n            assert!(difference < 10000);\n\n\n            api.set_burst(true);\n            let start = helpers::get_unix_timestamp_ms();\n            api.block_or_continue();\n            api.last_request = helpers::get_unix_timestamp_ms();\n\n            let difference = api.last_request - start;\n            assert!(difference < 10);\n\n            counter = counter + 1;\n            if counter >= 3 { break; }\n        }\n    }\n}\n"
  },
  {
    "path": "src/bitstamp/credentials.rs",
    "content": "//! Contains the Bitstamp credentials.\n\nuse serde_json;\nuse serde_json::Value;\n\nuse crate::coinnect::Credentials;\nuse crate::exchange::Exchange;\nuse crate::helpers;\nuse crate::error::*;\n\nuse std::collections::HashMap;\nuse std::str::FromStr;\nuse std::fs::File;\nuse std::io::Read;\nuse std::path::PathBuf;\n\n#[derive(Debug, Clone)]\npub struct BitstampCreds {\n    exchange: Exchange,\n    name: String,\n    data: HashMap<String, String>,\n}\n\nimpl BitstampCreds {\n    /// Create a new `BitstampCreds` from arguments.\n    pub fn new(name: &str, api_key: &str, api_secret: &str, customer_id: &str) -> Self {\n        let mut creds = BitstampCreds {\n            data: HashMap::new(),\n            exchange: Exchange::Bitstamp,\n            name: if name.is_empty() {\n                \"BitstampClient\".to_string()\n            } else {\n                name.to_string()\n            },\n        };\n\n\n        //if api_key.is_empty() {\n        //warning!(\"No API key set for the Bitstamp client\");\n        //}\n        creds\n            .data\n            .insert(\"api_key\".to_string(), api_key.to_string());\n\n        //if api_secret.is_empty() {\n        //warning!(\"No API secret set for the Bitstamp client\");\n        //}\n        creds\n            .data\n            .insert(\"api_secret\".to_string(), api_secret.to_string());\n\n        //if api_secret.is_empty() {\n        //warning!(\"No API customer ID set for the Bitstamp client\");\n        //}\n        creds\n            .data\n            .insert(\"customer_id\".to_string(), customer_id.to_string());\n\n        creds\n    }\n\n\n    /// Create a new `BitstampCreds` from a json configuration file. This file must follow this\n    /// structure:\n    ///\n    /// ```json\n    /// {\n    ///     \"account_kraken\": {\n    ///         \"exchange\"  : \"kraken\",\n    ///         \"api_key\"   : \"123456789ABCDEF\",\n    ///         \"api_secret\": \"ABC&EF?abcdef\"\n    ///     },\n    ///     \"account_bitstamp\": {\n    ///         \"exchange\"   : \"bitstamp\",\n    ///         \"api_key\"    : \"1234567890ABCDEF1234567890ABCDEF\",\n    ///         \"api_secret\" : \"1234567890ABCDEF1234567890ABCDEF\",\n    ///         \"customer_id\": \"123456\"\n    ///     }\n    /// }\n    /// ```\n    /// For this example, you could use load your Bitstamp account with\n    /// `BitstampAPI::new(BitstampCreds::new_from_file(\"account_bitstamp\", Path::new(\"/keys.json\")))`\n    pub fn new_from_file(name: &str, path: PathBuf) -> Result<Self> {\n        let mut f = File::open(&path)?;\n        let mut buffer = String::new();\n        f.read_to_string(&mut buffer)?;\n\n        let data: Value = serde_json::from_str(&buffer)?;\n        let json_obj = data.as_object()\n            .ok_or_else(|| ErrorKind::BadParse)?\n            .get(name)\n            .ok_or_else(|| ErrorKind::MissingField(name.to_string()))?;\n\n        let api_key = helpers::get_json_string(json_obj, \"api_key\")?;\n        let api_secret = helpers::get_json_string(json_obj, \"api_secret\")?;\n        let customer_id = helpers::get_json_string(json_obj, \"customer_id\")?;\n        let exchange = {\n            let exchange_str = helpers::get_json_string(json_obj, \"exchange\")?;\n            Exchange::from_str(exchange_str)\n                .chain_err(|| ErrorKind::InvalidFieldValue(\"exchange\".to_string()))?\n        };\n\n        if exchange != Exchange::Bitstamp {\n            return Err(ErrorKind::InvalidConfigType(Exchange::Bitstamp, exchange).into());\n        }\n\n        Ok(BitstampCreds::new(name, api_key, api_secret, customer_id))\n    }\n}\n\nimpl Credentials for BitstampCreds {\n    /// Return a value from the credentials.\n    fn get(&self, key: &str) -> Option<String> {\n        if let Some(res) = self.data.get(key) {\n            Some(res.clone())\n        } else {\n            None\n        }\n    }\n\n    fn name(&self) -> String {\n        self.name.clone()\n    }\n\n    fn exchange(&self) -> Exchange {\n        self.exchange\n    }\n}\n"
  },
  {
    "path": "src/bitstamp/generic_api.rs",
    "content": "//! Use this module to interact with Bitstamp through a Generic API.\n//! This a more convenient and safe way to deal with the exchange since methods return a Result<>\n//! but this generic API does not provide all the functionnality that Bitstamp offers.\n\nuse crate::exchange::ExchangeApi;\nuse crate::bitstamp::api::BitstampApi;\nuse crate::bitstamp::utils;\n\nuse crate::error::*;\nuse crate::types::*;\nuse crate::helpers;\n\nimpl ExchangeApi for BitstampApi {\n    fn ticker(&mut self, pair: Pair) -> Result<Ticker> {\n\n        let result = self.return_ticker(pair)?;\n\n        let price = helpers::from_json_bigdecimal(&result[\"last\"], \"last\")?;\n        let ask = helpers::from_json_bigdecimal(&result[\"ask\"], \"ask\")?;\n        let bid = helpers::from_json_bigdecimal(&result[\"bid\"], \"bid\")?;\n        let vol = helpers::from_json_bigdecimal(&result[\"volume\"], \"volume\")?;\n\n        Ok(Ticker {\n               timestamp: helpers::get_unix_timestamp_ms(),\n               pair: pair,\n               last_trade_price: price,\n               lowest_ask: ask,\n               highest_bid: bid,\n               volume: Some(vol),\n           })\n    }\n\n    fn orderbook(&mut self, pair: Pair) -> Result<Orderbook> {\n\n        let raw_response = self.return_order_book(pair)?;\n\n        let result = utils::parse_result(&raw_response)?;\n\n        let mut ask_offers = Vec::new();\n        let mut bid_offers = Vec::new();\n\n        let ask_array =\n            result[\"asks\"]\n                .as_array()\n                .ok_or_else(|| ErrorKind::InvalidFieldFormat(format!(\"{}\", result[\"asks\"])))?;\n        let bid_array =\n            result[\"bids\"]\n                .as_array()\n                .ok_or_else(|| ErrorKind::InvalidFieldFormat(format!(\"{}\", result[\"asks\"])))?;\n\n        for ask in ask_array {\n            let price = helpers::from_json_bigdecimal(&ask[0], \"ask price\")?;\n            let volume = helpers::from_json_bigdecimal(&ask[1], \"ask volume\")?;\n\n            ask_offers.push((price, volume));\n        }\n\n        for bid in bid_array {\n            let price = helpers::from_json_bigdecimal(&bid[0], \"bid price\")?;\n            let volume = helpers::from_json_bigdecimal(&bid[1], \"bid volume\")?;\n\n            bid_offers.push((price, volume));\n        }\n\n        Ok(Orderbook {\n            timestamp: helpers::get_unix_timestamp_ms(),\n            pair: pair,\n            asks: ask_offers,\n            bids: bid_offers,\n        })\n    }\n\n    fn add_order(&mut self,\n                 order_type: OrderType,\n                 pair: Pair,\n                 quantity: Volume,\n                 price: Option<Price>)\n                 -> Result<OrderInfo> {\n        //let pair_name = match utils::get_pair_string(&pair) {\n        //Some(name) => name,\n        //None => return Err(ErrorKind::PairUnsupported.into()),\n        //};\n\n        let result = match order_type {\n            OrderType::BuyLimit => {\n                if price.is_none() {\n                    return Err(ErrorKind::MissingPrice.into());\n                }\n\n                // Unwrap safe here with the check above.\n                self.buy_limit(pair, quantity, price.unwrap(), None, None)\n            }\n            OrderType::BuyMarket => self.buy_market(pair, quantity),\n            OrderType::SellLimit => {\n                if price.is_none() {\n                    return Err(ErrorKind::MissingPrice.into());\n                }\n\n                // Unwrap safe here with the check above.\n                self.sell_limit(pair, quantity, price.unwrap(), None, None)\n            }\n            OrderType::SellMarket => self.sell_market(pair, quantity),\n        };\n\n        Ok(OrderInfo {\n               timestamp: helpers::get_unix_timestamp_ms(),\n               identifier: vec![result?[\"id\"]\n                                    .as_str()\n                                    .ok_or_else(|| {\n                                                    ErrorKind::MissingField(\"id\".to_string())\n                                                })?\n                                    .to_string()],\n           })\n    }\n\n    /// Return the balances for each currency on the account\n    fn balances(&mut self) -> Result<Balances> {\n        let raw_response = self.return_balances()?;\n        let result = utils::parse_result(&raw_response)?;\n\n        let mut balances = Balances::new();\n\n        for (key, val) in result.iter() {\n            let currency = utils::get_currency_enum(key);\n\n            match currency {\n                Some(c) => {\n                    let amount = helpers::from_json_bigdecimal(&val, \"amount\")?;\n\n                    balances.insert(c, amount);\n                },\n                _ => ()\n            }\n        }\n\n        Ok(balances)\n    }\n}\n"
  },
  {
    "path": "src/bitstamp/mod.rs",
    "content": "//! Use this module to interact with Bitstamp exchange.\n\npub mod api;\npub mod generic_api;\npub mod credentials;\npub mod utils;\n\npub use self::credentials::BitstampCreds;\npub use self::api::BitstampApi;\n"
  },
  {
    "path": "src/bitstamp/utils.rs",
    "content": "use bidir_map::BidirMap;\n\nuse hmac::{Hmac, Mac, NewMac};\nuse sha2::{Sha256};\n\nuse serde_json;\nuse serde_json::Value;\nuse serde_json::value::Map;\n\nuse crate::error::*;\nuse crate::helpers;\nuse crate::types::Currency;\nuse crate::types::Pair;\nuse crate::types::Pair::*;\n\nlazy_static! {\n    static ref PAIRS_STRING: BidirMap<Pair, &'static str> = {\n        let mut m = BidirMap::new();\n        m.insert(BTC_USD, \"btcusd\");\n        m.insert(BTC_EUR, \"btceur\");\n        m.insert(EUR_USD, \"eurusd\");\n        m.insert(XRP_USD, \"xrpusd\");\n        m.insert(XRP_EUR, \"xrpeur\");\n        m.insert(XRP_BTC, \"xrpbtc\");\n        m.insert(LTC_USD, \"ltcusd\");\n        m.insert(LTC_EUR, \"ltceur\");\n        m.insert(LTC_BTC, \"ltcbtc\");\n        m.insert(ETH_USD, \"ethusd\");\n        m.insert(ETH_EUR, \"etheur\");\n        m.insert(ETH_BTC, \"ethbtc\");\n        m.insert(BCH_USD, \"bchusd\");\n        m.insert(BCH_EUR, \"bcheur\");\n        m.insert(BCH_BTC, \"bchbtc\");\n        m\n    };\n}\n\n/// Return the name associated to pair used by Bitstamp\n/// If the Pair is not supported, None is returned.\npub fn get_pair_string(pair: &Pair) -> Option<&&str> {\n    PAIRS_STRING.get_by_first(pair)\n}\n\n/// Return the Pair enum associated to the string used by Bitstamp\n/// If the Pair is not supported, None is returned.\npub fn get_pair_enum(pair: &str) -> Option<&Pair> {\n    PAIRS_STRING.get_by_second(&pair)\n}\n\npub fn build_signature(nonce: &str,\n                       customer_id: &str,\n                       api_key: &str,\n                       api_secret: &str)\n                       -> Result<String> {\n    const C: &'static [u8] = b\"0123456789ABCDEF\";\n\n    let message = nonce.to_owned() + customer_id + api_key;\n\n    let mut mac = Hmac::<Sha256>::new_from_slice(api_secret.as_bytes()).unwrap();\n\n    mac.update(message.as_bytes());\n    let result = mac.finalize();\n\n    let raw_signature = result.into_bytes();\n    let mut signature = Vec::with_capacity(raw_signature.len() * 2);\n    for &byte in &raw_signature {\n        signature.push(C[(byte >> 4) as usize]);\n        signature.push(C[(byte & 0xf) as usize]);\n    }\n    // TODO: Handle correctly the from_utf8 errors with error_chain.\n    Ok(String::from_utf8(signature)?)\n}\n\npub fn build_url(method: &str, pair: &str) -> String {\n    \"https://www.bitstamp.net/api/v2/\".to_string() + method + \"/\" + pair + \"/\"\n}\n\npub fn deserialize_json(json_string: &str) -> Result<Map<String, Value>> {\n    let data: Value = match serde_json::from_str(json_string) {\n        Ok(data) => data,\n        Err(_) => return Err(ErrorKind::BadParse.into()),\n    };\n\n    match data.as_object() {\n        Some(value) => Ok(value.clone()),\n        None => Err(ErrorKind::BadParse.into()),\n    }\n}\n\npub fn generate_nonce(fixed_nonce: Option<String>) -> String {\n    match fixed_nonce {\n        Some(v) => v,\n        None => helpers::get_unix_timestamp_ms().to_string(),\n    }\n}\n\n/// If error array is null, return the result (encoded in a json object)\n/// else return the error string found in array\npub fn parse_result(response: &Map<String, Value>) -> Result<Map<String, Value>> {\n    let error_msg = match response.get(\"error\") {\n        Some(error) => {\n            error\n                .as_str()\n                .ok_or_else(|| ErrorKind::InvalidFieldFormat(\"error\".to_string()))?\n        }\n        None => return Ok(response.clone()),\n    };\n\n    match error_msg.as_ref() {\n        \"Invalid command.\" => Err(ErrorKind::InvalidArguments.into()),\n        \"Invalid API key/secret pair.\" => Err(ErrorKind::BadCredentials.into()),\n        \"Total must be at least 0.0001.\" => Err(ErrorKind::InsufficientOrderSize.into()),\n        other => Err(ErrorKind::ExchangeSpecificError(other.to_string()).into()),\n    }\n}\n\n/// Return the currency enum associated with the\n/// string used by Bitstamp. If no currency is found,\n/// return None\n/// # Examples\n///\n/// ```\n/// use coinnect::bitstamp::utils::get_currency_enum;\n/// use coinnect::types::Currency;\n///\n/// let currency = get_currency_enum(\"usd_balance\");\n/// assert_eq!(Some(Currency::USD), currency);\n/// ```\npub fn get_currency_enum(currency: &str) -> Option<Currency> {\n    match currency {\n        \"usd_balance\" => Some(Currency::USD),\n        \"btc_balance\" => Some(Currency::BTC),\n        \"eur_balance\" => Some(Currency::EUR),\n        \"xrp_balance\" => Some(Currency::XRP),\n        _ => None,\n    }\n}\n\n/// Return the currency string associated with the\n/// enum used by Bitstamp. If no currency is found,\n/// return None\n/// # Examples\n///\n/// ```\n/// use coinnect::bitstamp::utils::get_currency_string;\n/// use coinnect::types::Currency;\n///\n/// let currency = get_currency_string(Currency::USD);\n/// assert_eq!(currency, Some(\"USD\".to_string()));\n/// ```\npub fn get_currency_string(currency: Currency) -> Option<String> {\n    match currency {\n        Currency::USD => Some(\"USD\".to_string()),\n        Currency::BTC => Some(\"BTC\".to_string()),\n        Currency::EUR => Some(\"EUR\".to_string()),\n        Currency::XRP => Some(\"XRP\".to_string()),\n        _ => None,\n    }\n}\n"
  },
  {
    "path": "src/bittrex/api.rs",
    "content": "//! Use this module to interact with the raw-original API provided by Bittrex.\n//! WARNING: Special attention should be paid to error management: parsing number, etc.\n\nuse hmac::{Hmac, Mac, NewMac};\nuse sha2::{Sha512};\n\nuse hyper_native_tls::NativeTlsClient;\nuse hyper::Client;\nuse hyper::header;\nuse hyper::net::HttpsConnector;\n\nuse data_encoding::HEXLOWER;\n\nuse serde_json::Value;\nuse serde_json::value::Map;\n\nuse std::collections::HashMap;\nuse std::io::Read;\nuse std::thread;\nuse std::time::Duration;\nuse std::str;\n\nuse crate::error::*;\nuse crate::helpers;\n\nuse crate::exchange::Exchange;\nuse crate::coinnect::Credentials;\nuse crate::bittrex::utils;\n\nheader! {\n    #[doc(hidden)]\n    (ApiSign, \"apisign\") => [String]\n}\n\n#[derive(Debug)]\npub struct BittrexApi {\n    last_request: i64, // unix timestamp in ms, to avoid ban\n    api_key: String,\n    api_secret: String,\n    http_client: Client,\n    burst: bool,\n}\n\n\nimpl BittrexApi {\n    /// Create a new BittrexApi by providing an API key & API secret\n    pub fn new<C: Credentials>(creds: C) -> Result<BittrexApi> {\n        if creds.exchange() != Exchange::Bittrex {\n            return Err(ErrorKind::InvalidConfigType(Exchange::Bittrex, creds.exchange()).into());\n        }\n\n        // TODO: implement correctly the TLS error in error_chain.\n        let ssl = match NativeTlsClient::new() {\n            Ok(res) => res,\n            Err(_) => return Err(ErrorKind::TlsError.into()),\n        };\n        let connector = HttpsConnector::new(ssl);\n\n        Ok(BittrexApi {\n               last_request: 0,\n               api_key: creds.get(\"api_key\").unwrap_or_default(),\n               api_secret: creds.get(\"api_secret\").unwrap_or_default(),\n               http_client: Client::with_connector(connector),\n               burst: false,\n           })\n    }\n\n    /// The number of calls in a given period is limited. In order to avoid a ban we limit\n    /// by default the number of api requests.\n    /// This function sets or removes the limitation.\n    /// Burst false implies no block.\n    /// Burst true implies there is a control over the number of calls allowed to the exchange\n    pub fn set_burst(&mut self, burst: bool) {\n        self.burst = burst\n    }\n\n    pub fn block_or_continue(&self) {\n        if ! self.burst {\n            let threshold: u64 = 500; // 1 request/500ms\n            let offset: u64 = helpers::get_unix_timestamp_ms() as u64 - self.last_request as u64;\n            if offset < threshold {\n                let wait_ms = Duration::from_millis(threshold - offset);\n                thread::sleep(wait_ms);\n            }\n        }\n    }\n\n    fn public_query(&mut self,\n                    method: &str,\n                    params: &mut HashMap<&str, &str>)\n                    -> Result<Map<String, Value>> {\n\n        helpers::strip_empties(params);\n\n        let url = \"https://bittrex.com/api/v1.1\".to_string() + method + \"?\" +\n                  &helpers::url_encode_hashmap(params);\n\n        self.block_or_continue();\n        //TODO: Handle correctly http errors with error_chain.\n        let mut response = match self.http_client.get(&url).send() {\n            Ok(response) => response,\n            Err(err) => return Err(ErrorKind::ServiceUnavailable(err.to_string()).into()),\n        };\n        self.last_request = helpers::get_unix_timestamp_ms();\n        let mut buffer = String::new();\n        response.read_to_string(&mut buffer)?;\n        utils::deserialize_json(&buffer)\n    }\n\n    fn private_query(&mut self,\n                     method: &str,\n                     mut params: &mut HashMap<&str, &str>)\n                     -> Result<Map<String, Value>> {\n        let nonce = helpers::get_unix_timestamp_ms().to_string();\n        let mut initial_params: HashMap<&str, &str> = HashMap::new();\n        \n        initial_params.insert(\"nonce\", &nonce);\n        initial_params.insert(\"apikey\", &self.api_key);\n\n        let base_url = \"https://bittrex.com/api/v1.1\".to_string() + method + \"?apikey=\" +\n        &self.api_key + \"&nonce=\" + &nonce;\n        \n        let url = if params.is_empty() {\n            base_url\n        } else {\n            base_url + \"&\" + &helpers::url_encode_hashmap(&mut params)\n        };\n \n        let hmac_key = self.api_secret.as_bytes();\n        let mut mac = Hmac::<Sha512>::new_from_slice(&hmac_key[..]).unwrap();\n        mac.update(url.as_bytes());\n\n        let mut custom_header = header::Headers::new();\n\n        let signature = HEXLOWER.encode(&mac.finalize().into_bytes());\n\n        custom_header.set(ApiSign(signature));\n\n        let mut res = match self.http_client\n                  .post(&url)\n                  .headers(custom_header)\n                  .send() {\n            Ok(res) => res,\n            Err(err) => return Err(ErrorKind::ServiceUnavailable(err.to_string()).into()),\n        };\n\n        let mut buffer = String::new();\n        res.read_to_string(&mut buffer)?;\n        utils::deserialize_json(&buffer)\n    }\n\n    /// Used to get the open and available trading markets at Bittrex along with other meta data.\n    ///\n    /// ```json\n    ///    {\n    /// \t\"success\" : true,\n    /// \t\"message\" : \"\",\n    /// \t\"result\" : [{\n    /// \t\t\t\"MarketCurrency\" : \"LTC\",\n    /// \t\t\t\"BaseCurrency\" : \"BTC\",\n    /// \t\t\t\"MarketCurrencyLong\" : \"Litecoin\",\n    /// \t\t\t\"BaseCurrencyLong\" : \"Bitcoin\",\n    /// \t\t\t\"MinTradeSize\" : 0.01000000,\n    /// \t\t\t\"MarketName\" : \"BTC-LTC\",\n    /// \t\t\t\"IsActive\" : true,\n    /// \t\t\t\"Created\" : \"2014-02-13T00:00:00\"\n    /// \t\t}, {\n    /// \t\t\t\"MarketCurrency\" : \"DOGE\",\n    /// \t\t\t\"BaseCurrency\" : \"BTC\",\n    /// \t\t\t\"MarketCurrencyLong\" : \"Dogecoin\",\n    /// \t\t\t\"BaseCurrencyLong\" : \"Bitcoin\",\n    /// \t\t\t\"MinTradeSize\" : 100.00000000,\n    /// \t\t\t\"MarketName\" : \"BTC-DOGE\",\n    /// \t\t\t\"IsActive\" : true,\n    /// \t\t\t\"Created\" : \"2014-02-13T00:00:00\"\n    /// \t\t}\n    ///     ]\n    /// }\n    /// ```\n    pub fn get_markets(&mut self) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        self.public_query(\"/public/getmarkets\", &mut params)\n    }\n\n    /// Used to get all supported currencies at Bittrex along with other meta data.\n    /// \n    /// ```json\n    /// {\n    ///     \"success\" : true,\n    ///     \"message\" : \"\",\n    ///     \"result\" : [{\n    ///             \"Currency\" : \"BTC\",\n    ///             \"CurrencyLong\" : \"Bitcoin\",\n    ///             \"MinConfirmation\" : 2,\n    ///             \"TxFee\" : 0.00020000,\n    ///             \"IsActive\" : true,\n    ///             \"CoinType\" : \"BITCOIN\",\n    ///             \"BaseAddress\" : null\n    ///         }, {\n    ///             \"Currency\" : \"LTC\",\n    ///             \"CurrencyLong\" : \"Litecoin\",\n    ///             \"MinConfirmation\" : 5,\n    ///             \"TxFee\" : 0.00200000,\n    ///             \"IsActive\" : true,\n    ///             \"CoinType\" : \"BITCOIN\",\n    ///             \"BaseAddress\" : null\n    ///         }\n    ///     ]\n    /// }\n    /// ```\n    pub fn get_currencies(&mut self) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        self.public_query(\"/public/getcurrencies\", &mut params)\n    }\n\n    /// Used to get the current tick values for a market.\n    /// \"market\" required a string literal for the market (ex: BTC-LTC)\n    /// \n    /// ````json\n    /// {\n    /// \t\"success\" : true,\n    /// \t\"message\" : \"\",\n    /// \t\"result\" : {\n    /// \t\t\"Bid\" : 2.05670368,\n    /// \t\t\"Ask\" : 3.35579531,\n    /// \t\t\"Last\" : 3.35579531\n    /// \t}\n    /// }\n    /// ```\n    pub fn get_ticker(&mut self, market: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"market\", market);\n        self.public_query(\"/public/getticker\", &mut params)\n    }\n\n    /// Used to get the last 24 hour summary of all active exchanges\n    /// \n    /// ````json\n    /// {\n    ///     \"success\" : true,\n    ///     \"message\" : \"\",\n    ///     \"result\" : [{\n    ///             \"MarketName\" : \"BTC-888\",\n    ///             \"High\" : 0.00000919,\n    ///             \"Low\" : 0.00000820,\n    ///             \"Volume\" : 74339.61396015,\n    ///             \"Last\" : 0.00000820,\n    ///             \"BaseVolume\" : 0.64966963,\n    ///             \"TimeStamp\" : \"2014-07-09T07:19:30.15\",\n    ///             \"Bid\" : 0.00000820,\n    ///             \"Ask\" : 0.00000831,\n    ///             \"OpenBuyOrders\" : 15,\n    ///             \"OpenSellOrders\" : 15,\n    ///             \"PrevDay\" : 0.00000821,\n    ///             \"Created\" : \"2014-03-20T06:00:00\",\n    ///             \"DisplayMarketName\" : null\n    ///         }, {\n    ///             \"MarketName\" : \"BTC-A3C\",\n    ///             \"High\" : 0.00000072,\n    ///             \"Low\" : 0.00000001,\n    ///             \"Volume\" : 166340678.42280999,\n    ///             \"Last\" : 0.00000005,\n    ///             \"BaseVolume\" : 17.59720424,\n    ///             \"TimeStamp\" : \"2014-07-09T07:21:40.51\",\n    ///             \"Bid\" : 0.00000004,\n    ///             \"Ask\" : 0.00000005,\n    ///             \"OpenBuyOrders\" : 18,\n    ///             \"OpenSellOrders\" : 18,\n    ///             \"PrevDay\" : 0.00000002,\n    ///             \"Created\" : \"2014-05-30T07:57:49.637\",\n    ///             \"DisplayMarketName\" : null\n    ///         }\n    ///     ]\n    /// }\n    /// ```\n    pub fn get_market_summaries(&mut self) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        self.public_query(\"/public/getmarketsummaries\", &mut params)\n    }\n\n    /// Used to get the last 24 hour summary of all active exchanges\n    /// \"market\" required a string literal for the market (ex: BTC-LTC)\n    /// \n    /// ```json\n    /// {\n    /// \t\"success\" : true,\n    /// \t\"message\" : \"\",\n    /// \t\"result\" : [{\n    /// \t\t\t\"MarketName\" : \"BTC-LTC\",\n    /// \t\t\t\"High\" : 0.01350000,\n    /// \t\t\t\"Low\" : 0.01200000,\n    /// \t\t\t\"Volume\" : 3833.97619253,\n    /// \t\t\t\"Last\" : 0.01349998,\n    /// \t\t\t\"BaseVolume\" : 47.03987026,\n    /// \t\t\t\"TimeStamp\" : \"2014-07-09T07:22:16.72\",\n    /// \t\t\t\"Bid\" : 0.01271001,\n    /// \t\t\t\"Ask\" : 0.01291100,\n    /// \t\t\t\"OpenBuyOrders\" : 45,\n    /// \t\t\t\"OpenSellOrders\" : 45,\n    /// \t\t\t\"PrevDay\" : 0.01229501,\n    /// \t\t\t\"Created\" : \"2014-02-13T00:00:00\",\n    /// \t\t\t\"DisplayMarketName\" : null\n    /// \t\t}\n    ///     ]\n    /// }\n    /// ```\n    pub fn get_market_summary(&mut self, market: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"market\", market);\n        self.public_query(\"/public/getmarketsummary\", &mut params)\n    }\n\n    /// Used to get retrieve the orderbook for a given market\n    /// \"market\" required a string literal for the market (ex: BTC-LTC)\n    /// \"order_type\" required \"buy\", \"sell\" or \"both\" to identify the type of orderbook to return.\n    /// \n    /// ```json\n    /// {\n    ///     \"success\" : true,\n    ///     \"message\" : \"\",\n    ///     \"result\" : {\n    ///         \"buy\" : [{\n    ///                 \"Quantity\" : 12.37000000,\n    ///                 \"Rate\" : 0.02525000\n    ///             }\n    ///         ],\n    ///         \"sell\" : [{\n    ///                 \"Quantity\" : 32.55412402,\n    ///                 \"Rate\" : 0.02540000\n    ///             }, {\n    ///                 \"Quantity\" : 60.00000000,\n    ///                 \"Rate\" : 0.02550000\n    ///             }, {\n    ///                 \"Quantity\" : 60.00000000,\n    ///                 \"Rate\" : 0.02575000\n    ///             }, {\n    ///                 \"Quantity\" : 84.00000000,\n    ///                 \"Rate\" : 0.02600000\n    ///             }\n    ///         ]\n    ///     }\n    /// }\n    /// ```\n    pub fn get_order_book(&mut self, market: &str, order_type: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"market\", market);\n        params.insert(\"type\", order_type);\n        self.public_query(\"/public/getorderbook\", &mut params)\n    }\n\n    /// Used to retrieve the latest trades that have occured for a specific market.\n    /// \"market\" required a string literal for the market (ex: BTC-LTC)\n    /// \n    /// ```json\n    /// {\n    /// \t\"success\" : true,\n    /// \t\"message\" : \"\",\n    /// \t\"result\" : [{\n    /// \t\t\t\"Id\" : 319435,\n    /// \t\t\t\"TimeStamp\" : \"2014-07-09T03:21:20.08\",\n    /// \t\t\t\"Quantity\" : 0.30802438,\n    /// \t\t\t\"Price\" : 0.01263400,\n    /// \t\t\t\"Total\" : 0.00389158,\n    /// \t\t\t\"FillType\" : \"FILL\",\n    /// \t\t\t\"OrderType\" : \"BUY\"\n    /// \t\t}, {\n    /// \t\t\t\"Id\" : 319433,\n    /// \t\t\t\"TimeStamp\" : \"2014-07-09T03:21:20.08\",\n    /// \t\t\t\"Quantity\" : 0.31820814,\n    /// \t\t\t\"Price\" : 0.01262800,\n    /// \t\t\t\"Total\" : 0.00401833,\n    /// \t\t\t\"FillType\" : \"PARTIAL_FILL\",\n    /// \t\t\t\"OrderType\" : \"BUY\"\n    /// \t\t}, {\n    /// \t\t\t\"Id\" : 319379,\n    /// \t\t\t\"TimeStamp\" : \"2014-07-09T02:58:48.127\",\n    /// \t\t\t\"Quantity\" : 49.64643541,\n    /// \t\t\t\"Price\" : 0.01263200,\n    /// \t\t\t\"Total\" : 0.62713377,\n    /// \t\t\t\"FillType\" : \"FILL\",\n    /// \t\t\t\"OrderType\" : \"SELL\"\n    /// \t\t}, {\n    /// \t\t\t\"Id\" : 319378,\n    /// \t\t\t\"TimeStamp\" : \"2014-07-09T02:58:46.27\",\n    /// \t\t\t\"Quantity\" : 0.35356459,\n    /// \t\t\t\"Price\" : 0.01263200,\n    /// \t\t\t\"Total\" : 0.00446622,\n    /// \t\t\t\"FillType\" : \"PARTIAL_FILL\",\n    /// \t\t\t\"OrderType\" : \"BUY\"\n    /// \t\t}\n    /// \t]\n    /// }\n    /// ```\n    pub fn get_market_history(&mut self, market: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"market\", market);\n        self.public_query(\"/public/getmarkethistory\", &mut params)\n    }\n\n    /// Used to place a buy order in a specific market. Use buylimit to place limit orders.\n    /// Make sure you have the proper permissions set on your API keys for this call to work.\n    /// \"market\" required a string literal for the market (ex: BTC-LTC)\n    /// \"quantity\" required the amount to purchase\n    /// \"rate\" required the rate at which to place the order.\n    /// \n    /// ```json\n    /// {\n    /// \t\"success\" : true,\n    /// \t\"message\" : \"\",\n    /// \t\"result\" : {\n    /// \t\t\t\"uuid\" : \"e606d53c-8d70-11e3-94b5-425861b86ab6\"\n    /// \t}\n    /// }\n    /// ```\n    pub fn buy_limit(&mut self, market: &str, quantity: &str, rate: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"market\", market);\n        params.insert(\"quantity\", quantity);\n        params.insert(\"rate\", rate);\n        self.private_query(\"/market/buylimit\", &mut params)\n    }\n\n    /// Used to place a sell order in a specific market. Use selllimit to place limit orders.\n    /// Make sure you have the proper permissions set on your API keys for this call to work.\n    /// \"market\" required a string literal for the market (ex: BTC-LTC)\n    /// \"quantity\" required the amount to purchase\n    /// \"rate\" required the rate at which to place the order.\n    /// \n    /// ```json\n    /// {\n    /// \t\"success\" : true,\n    /// \t\"message\" : \"\",\n    /// \t\"result\" : {\n    /// \t\t\t\"uuid\" : \"614c34e4-8d71-11e3-94b5-425861b86ab6\"\n    /// \t}\n    /// }\n    /// ```\n    pub fn sell_limit(&mut self, market: &str, quantity: &str, rate: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"market\", market);\n        params.insert(\"quantity\", quantity);\n        params.insert(\"rate\", rate);\n        self.private_query(\"/market/selllimit\", &mut params)\n    }\n\n    /// Used to cancel a buy or sell order.\n    /// \"uuid\" required uuid of buy or sell order\n    /// \n    /// ```json\n    /// {\n    /// \"success\" : true,\n    /// \"message\" : \"\",\n    /// \"result\" : null\n    /// }\n    /// ```\n    pub fn cancel(&mut self, uuid: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"uuid\", uuid);\n        self.private_query(\"/market/cancel\", &mut params)\n    }\n\n    /// Get all orders that you currently have opened. A specific market can be requested\n    /// \"market\" optional a string literal for the market (ie. BTC-LTC)\n    /// \n    /// ```json\n    /// {\n    /// \t\"success\" : true,\n    /// \t\"message\" : \"\",\n    /// \t\"result\" : [{\n    /// \t\t\t\"Uuid\" : null,\n    /// \t\t\t\"OrderUuid\" : \"09aa5bb6-8232-41aa-9b78-a5a1093e0211\",\n    /// \t\t\t\"Exchange\" : \"BTC-LTC\",\n    /// \t\t\t\"OrderType\" : \"LIMIT_SELL\",\n    /// \t\t\t\"Quantity\" : 5.00000000,\n    /// \t\t\t\"QuantityRemaining\" : 5.00000000,\n    /// \t\t\t\"Limit\" : 2.00000000,\n    /// \t\t\t\"CommissionPaid\" : 0.00000000,\n    /// \t\t\t\"Price\" : 0.00000000,\n    /// \t\t\t\"PricePerUnit\" : null,\n    /// \t\t\t\"Opened\" : \"2014-07-09T03:55:48.77\",\n    /// \t\t\t\"Closed\" : null,\n    /// \t\t\t\"CancelInitiated\" : false,\n    /// \t\t\t\"ImmediateOrCancel\" : false,\n    /// \t\t\t\"IsConditional\" : false,\n    /// \t\t\t\"Condition\" : null,\n    /// \t\t\t\"ConditionTarget\" : null\n    /// \t\t}, {\n    /// \t\t\t\"Uuid\" : null,\n    /// \t\t\t\"OrderUuid\" : \"8925d746-bc9f-4684-b1aa-e507467aaa99\",\n    /// \t\t\t\"Exchange\" : \"BTC-LTC\",\n    /// \t\t\t\"OrderType\" : \"LIMIT_BUY\",\n    /// \t\t\t\"Quantity\" : 100000.00000000,\n    /// \t\t\t\"QuantityRemaining\" : 100000.00000000,\n    /// \t\t\t\"Limit\" : 0.00000001,\n    /// \t\t\t\"CommissionPaid\" : 0.00000000,\n    /// \t\t\t\"Price\" : 0.00000000,\n    /// \t\t\t\"PricePerUnit\" : null,\n    /// \t\t\t\"Opened\" : \"2014-07-09T03:55:48.583\",\n    /// \t\t\t\"Closed\" : null,\n    /// \t\t\t\"CancelInitiated\" : false,\n    /// \t\t\t\"ImmediateOrCancel\" : false,\n    /// \t\t\t\"IsConditional\" : false,\n    /// \t\t\t\"Condition\" : null,\n    /// \t\t\t\"ConditionTarget\" : null\n    /// \t\t}\n    /// \t]\n    /// }\n    /// ```\n    pub fn get_open_orders(&mut self, market: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"market\", market);\n        self.private_query(\"/market/getopenorders\", &mut params)\n    }\n\n    /// Used to retrieve all balances from your account\n    /// \n    /// ```json\n    /// {\n    /// \t\"success\" : true,\n    /// \t\"message\" : \"\",\n    /// \t\"result\" : [{\n    /// \t\t\t\"Currency\" : \"DOGE\",\n    /// \t\t\t\"Balance\" : 0.00000000,\n    /// \t\t\t\"Available\" : 0.00000000,\n    /// \t\t\t\"Pending\" : 0.00000000,\n    /// \t\t\t\"CryptoAddress\" : \"DLxcEt3AatMyr2NTatzjsfHNoB9NT62HiF\",\n    /// \t\t\t\"Requested\" : false,\n    /// \t\t\t\"Uuid\" : null\n    /// \n    /// \t\t}, {\n    /// \t\t\t\"Currency\" : \"BTC\",\n    /// \t\t\t\"Balance\" : 14.21549076,\n    /// \t\t\t\"Available\" : 14.21549076,\n    /// \t\t\t\"Pending\" : 0.00000000,\n    /// \t\t\t\"CryptoAddress\" : \"1Mrcdr6715hjda34pdXuLqXcju6qgwHA31\",\n    /// \t\t\t\"Requested\" : false,\n    /// \t\t\t\"Uuid\" : null\n    /// \t\t}\n    /// \t]\n    /// }\n    /// ```\n    pub fn get_balances(&mut self) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        self.private_query(\"/account/getbalances\", &mut params)\n    }\n\n    /// Used to retrieve the balance from your account for a specific currency.\n    /// \"currency\" required a string literal for the currency (ex: LTC)\n    /// \n    /// ```json\n    /// {\n    /// \t\"success\" : true,\n    /// \t\"message\" : \"\",\n    /// \t\"result\" : {\n    /// \t\t\"Currency\" : \"BTC\",\n    /// \t\t\"Balance\" : 4.21549076,\n    /// \t\t\"Available\" : 4.21549076,\n    /// \t\t\"Pending\" : 0.00000000,\n    /// \t\t\"CryptoAddress\" : \"1MacMr6715hjds342dXuLqXcju6fgwHA31\",\n    /// \t\t\"Requested\" : false,\n    /// \t\t\"Uuid\" : null\n    /// \t}\n    /// }\n    /// ```\n    pub fn get_balance(&mut self, currency: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"currency\", currency);\n        self.private_query(\"/account/getbalance\", &mut params)\n    }\n\n    /// Used to retrieve or generate an address for a specific currency.\n    /// If one does not exist, the call will fail and return ADDRESS_GENERATING until one is available.\n    /// \"currency\" required a string literal for the currency (ex: LTC)\n    /// \n    /// ```json\n    /// {\n    /// \t\"success\" : true,\n    /// \t\"message\" : \"\",\n    /// \t\"result\" : {\n    /// \t\t\"Currency\" : \"VTC\",\n    /// \t\t\"Address\" : \"Vy5SKeKGXUHKS2WVpJ76HYuKAu3URastUo\"\n    /// \t}\n    /// }\n    /// ```\n    pub fn get_deposit_address(&mut self, currency: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"currency\", currency);\n        self.private_query(\"/account/getdepositaddress\", &mut params)\n    }\n\n    /// Used to withdraw funds from your account. note: please account for txfee.\n    /// \"currency\" required a string literal for the currency (ie. BTC)\n    /// \"quantity\" required the quantity of coins to withdraw\n    /// \"address\" required the address where to send the funds.\n    /// \"paymentid\" optional used for CryptoNotes/BitShareX/Nxt optional field (memo/paymentid)\n    /// \n    /// ```json\n    /// {\n    /// \t\"success\" : true,\n    /// \t\"message\" : \"\",\n    /// \t\"result\" : {\n    /// \t\t\t\"uuid\" : \"68b5a16c-92de-11e3-ba3b-425861b86ab6\"\n    /// \t}\n    /// }\n    /// ```\n    pub fn withdraw(&mut self, currency: &str, quantity: &str, address: &str, paymentid: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"currency\", currency);\n        params.insert(\"quantity\", quantity);\n        params.insert(\"address\", address);\n        params.insert(\"paymentid\", paymentid);\n        self.private_query(\"/account/withdraw\", &mut params)\n    }\n\n    /// Used to retrieve a single order by uuid.\n    /// \"uuid\" required the uuid of the buy or sell order\n    /// \n    /// ```json\n    /// {\n    /// \t\"success\" : true,\n    /// \t\"message\" : \"\",\n    /// \t\"result\" : {\n    /// \t\t\"AccountId\" : null,\n    /// \t\t\"OrderUuid\" : \"0cb4c4e4-bdc7-4e13-8c13-430e587d2cc1\",\n    /// \t\t\"Exchange\" : \"BTC-SHLD\",\n    /// \t\t\"Type\" : \"LIMIT_BUY\",\n    /// \t\t\"Quantity\" : 1000.00000000,\n    /// \t\t\"QuantityRemaining\" : 1000.00000000,\n    /// \t\t\"Limit\" : 0.00000001,\n    /// \t\t\"Reserved\" : 0.00001000,\n    /// \t\t\"ReserveRemaining\" : 0.00001000,\n    /// \t\t\"CommissionReserved\" : 0.00000002,\n    /// \t\t\"CommissionReserveRemaining\" : 0.00000002,\n    /// \t\t\"CommissionPaid\" : 0.00000000,\n    /// \t\t\"Price\" : 0.00000000,\n    /// \t\t\"PricePerUnit\" : null,\n    /// \t\t\"Opened\" : \"2014-07-13T07:45:46.27\",\n    /// \t\t\"Closed\" : null,\n    /// \t\t\"IsOpen\" : true,\n    /// \t\t\"Sentinel\" : \"6c454604-22e2-4fb4-892e-179eede20972\",\n    /// \t\t\"CancelInitiated\" : false,\n    /// \t\t\"ImmediateOrCancel\" : false,\n    /// \t\t\"IsConditional\" : false,\n    /// \t\t\"Condition\" : \"NONE\",\n    /// \t\t\"ConditionTarget\" : null\n    /// \t}\n    /// }\n    /// ```\n    pub fn get_order(&mut self, uuid: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"uuid\", uuid);\n        self.private_query(\"/account/getorder\", &mut params)\n    }\n\n    /// Used to retrieve your order history.\n    /// \"market\" optional a string literal for the market (ie. BTC-LTC).\n    /// If ommited, will return for all markets\n    /// \n    /// ```json\n    /// {\n    /// \t\"success\" : true,\n    /// \t\"message\" : \"\",\n    /// \t\"result\" : [{\n    /// \t\t\t\"OrderUuid\" : \"fd97d393-e9b9-4dd1-9dbf-f288fc72a185\",\n    /// \t\t\t\"Exchange\" : \"BTC-LTC\",\n    /// \t\t\t\"TimeStamp\" : \"2014-07-09T04:01:00.667\",\n    /// \t\t\t\"OrderType\" : \"LIMIT_BUY\",\n    /// \t\t\t\"Limit\" : 0.00000001,\n    /// \t\t\t\"Quantity\" : 100000.00000000,\n    /// \t\t\t\"QuantityRemaining\" : 100000.00000000,\n    /// \t\t\t\"Commission\" : 0.00000000,\n    /// \t\t\t\"Price\" : 0.00000000,\n    /// \t\t\t\"PricePerUnit\" : null,\n    /// \t\t\t\"IsConditional\" : false,\n    /// \t\t\t\"Condition\" : null,\n    /// \t\t\t\"ConditionTarget\" : null,\n    /// \t\t\t\"ImmediateOrCancel\" : false\n    /// \t\t}, {\n    /// \t\t\t\"OrderUuid\" : \"17fd64d1-f4bd-4fb6-adb9-42ec68b8697d\",\n    /// \t\t\t\"Exchange\" : \"BTC-ZS\",\n    /// \t\t\t\"TimeStamp\" : \"2014-07-08T20:38:58.317\",\n    /// \t\t\t\"OrderType\" : \"LIMIT_SELL\",\n    /// \t\t\t\"Limit\" : 0.00002950,\n    /// \t\t\t\"Quantity\" : 667.03644955,\n    /// \t\t\t\"QuantityRemaining\" : 0.00000000,\n    /// \t\t\t\"Commission\" : 0.00004921,\n    /// \t\t\t\"Price\" : 0.01968424,\n    /// \t\t\t\"PricePerUnit\" : 0.00002950,\n    /// \t\t\t\"IsConditional\" : false,\n    /// \t\t\t\"Condition\" : null,\n    /// \t\t\t\"ConditionTarget\" : null,\n    /// \t\t\t\"ImmediateOrCancel\" : false\n    /// \t\t}\n    /// \t]\n    /// }\n    /// ```\n    pub fn get_order_history(&mut self, market: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"market\", market);\n        self.private_query(\"/account/getorderhistory\", &mut params)\n    }\n\n    /// Used to retrieve your withdrawal history.\n    /// \"currency\" optional\ta string literal for the currecy (ie. BTC).\n    /// If omitted, will return for all currencies\n    /// \n    /// ```json\n    /// {\n\t/// \"success\" : true,\n\t/// \"message\" : \"\",\n\t/// \"result\" : [{\n\t/// \t\t\"PaymentUuid\" : \"b52c7a5c-90c6-4c6e-835c-e16df12708b1\",\n\t/// \t\t\"Currency\" : \"BTC\",\n\t/// \t\t\"Amount\" : 17.00000000,\n\t/// \t\t\"Address\" : \"1DeaaFBdbB5nrHj87x3NHS4onvw1GPNyAu\",\n\t/// \t\t\"Opened\" : \"2014-07-09T04:24:47.217\",\n\t/// \t\t\"Authorized\" : true,\n\t/// \t\t\"PendingPayment\" : false,\n\t/// \t\t\"TxCost\" : 0.00020000,\n\t/// \t\t\"TxId\" : null,\n\t/// \t\t\"Canceled\" : true,\n\t/// \t\t\"InvalidAddress\" : false\n\t/// \t}, {\n\t/// \t\t\"PaymentUuid\" : \"f293da98-788c-4188-a8f9-8ec2c33fdfcf\",\n\t/// \t\t\"Currency\" : \"XC\",\n\t/// \t\t\"Amount\" : 7513.75121715,\n\t/// \t\t\"Address\" : \"XVnSMgAd7EonF2Dgc4c9K14L12RBaW5S5J\",\n\t/// \t\t\"Opened\" : \"2014-07-08T23:13:31.83\",\n\t/// \t\t\"Authorized\" : true,\n\t/// \t\t\"PendingPayment\" : false,\n\t/// \t\t\"TxCost\" : 0.00002000,\n\t/// \t\t\"TxId\" : \"b4a575c2a71c7e56d02ab8e26bb1ef0a2f6cf2094f6ca2116476a569c1e84f6e\",\n\t/// \t\t\"Canceled\" : false,\n\t/// \t\t\"InvalidAddress\" : false\n\t/// \t}\n\t///  ]\n    /// }\n    /// ```\n    pub fn get_withdrawal_history(&mut self, currency: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"currency\", currency);\n        self.private_query(\"/account/getwithdrawalhistory\", &mut params)\n    }\n\n    /// Used to retrieve your deposit history.\n    /// \"currency\" optional a string literal for the currecy (ie. BTC).\n    /// If omitted, will return for all currencies\n    /// \n    /// ```json\n    /// {\n    /// \t\"success\" : true,\n    /// \t\"message\" : \"\",\n    /// \t\"result\" : [{\n    /// \t\t\t\"PaymentUuid\" : \"554ec664-8842-4fe9-b491-06225becbd59\",\n    /// \t\t\t\"Currency\" : \"BTC\",\n    /// \t\t\t\"Amount\" : 0.00156121,\n    /// \t\t\t\"Address\" : \"1K37yQZaGrPKNTZ5KNP792xw8f7XbXxetE\",\n    /// \t\t\t\"Opened\" : \"2014-07-11T03:41:25.323\",\n    /// \t\t\t\"Authorized\" : true,\n    /// \t\t\t\"PendingPayment\" : false,\n    /// \t\t\t\"TxCost\" : 0.00020000,\n    /// \t\t\t\"TxId\" : \"70cf6fdccb9bd38e1a930e13e4ae6299d678ed6902da710fa3cc8d164f9be126\",\n    /// \t\t\t\"Canceled\" : false,\n    /// \t\t\t\"InvalidAddress\" : false\n    /// \t\t}, {\n    /// \t\t\t\"PaymentUuid\" : \"d3fdf168-3d8e-40b6-8fe4-f46e2a7035ea\",\n    /// \t\t\t\"Currency\" : \"BTC\",\n    /// \t\t\t\"Amount\" : 0.11800000,\n    /// \t\t\t\"Address\" : \"1Mrcar6715hjds34pdXuLqXcju6QgwHA31\",\n    /// \t\t\t\"O\n    /// \t\t\tpened\" : \"2014-07-03T20:27:07.163\",\n    /// \t\t\t\"Authorized\" : true,\n    /// \t\t\t\"PendingPayment\" : false,\n    /// \t\t\t\"TxCost\" : 0.00020000,\n    /// \t\t\t\"TxId\" : \"3efd41b3a051433a888eed3ecc174c1d025a5e2b486eb418eaaec5efddda22de\",\n    /// \t\t\t\"Canceled\" : false,\n    /// \t\t\t\"InvalidAddress\" : false\n    /// \t\t}\n    ///     ]\n    /// }\n    /// ```\n    pub fn get_deposit_history(&mut self, currency: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"currency\", currency);\n        self.private_query(\"/account/getdeposithistory\", &mut params)\n    }\n}\n"
  },
  {
    "path": "src/bittrex/credentials.rs",
    "content": "//! Contains the Bittrex credentials.\n\nuse std::collections::HashMap;\nuse std::str::FromStr;\n\nuse serde_json;\nuse serde_json::Value;\n\nuse crate::coinnect::Credentials;\nuse crate::exchange::Exchange;\nuse crate::helpers;\nuse crate::error::*;\n\nuse std::fs::File;\nuse std::io::Read;\nuse std::path::PathBuf;\n\n#[derive(Debug, Clone)]\npub struct BittrexCreds {\n    exchange: Exchange,\n    name: String,\n    data: HashMap<String, String>,\n}\n\nimpl BittrexCreds {\n    /// Create a new `BittrexCreds` from arguments.\n    pub fn new(name: &str, api_key: &str, api_secret: &str) -> Self {\n        let mut creds = BittrexCreds {\n            data: HashMap::new(),\n            exchange: Exchange::Bittrex,\n            name: if name.is_empty() {\n                \"BittrexClient\".to_string()\n            } else {\n                name.to_string()\n            },\n        };\n\n\n        //if api_key.is_empty() {\n        //warning!(\"No API key set for the Bitstamp client\");\n        //}\n        creds\n            .data\n            .insert(\"api_key\".to_string(), api_key.to_string());\n\n        //if api_secret.is_empty() {\n        //warning!(\"No API secret set for the Bitstamp client\");\n        //}\n        creds\n            .data\n            .insert(\"api_secret\".to_string(), api_secret.to_string());\n\n        creds\n    }\n\n\n    /// Create a new `BittrexCreds` from a json configuration file. This file must follow this\n    /// structure:\n    ///\n    /// ```json\n    /// {\n    ///     \"account_bittrex\": {\n    ///         \"exchange\"  : \"bittrex\",\n    ///         \"api_key\"   : \"123456789ABCDEF\",\n    ///         \"api_secret\": \"ABC&EF?abcdef\"\n    ///     },\n    ///     \"account_bitstamp\": {\n    ///         \"exchange\"   : \"bitstamp\",\n    ///         \"api_key\"    : \"1234567890ABCDEF1234567890ABCDEF\",\n    ///         \"api_secret\" : \"1234567890ABCDEF1234567890ABCDEF\",\n    ///         \"customer_id\": \"123456\"\n    ///     }\n    /// }\n    /// ```\n    /// For this example, you could use load your Bittrex account with\n    /// `BittrexAPI::new(BittrexCreds::new_from_file(\"account_bittrex\", Path::new(\"/keys.json\")))`\n    pub fn new_from_file(name: &str, path: PathBuf) -> Result<Self> {\n        let mut f = File::open(&path)?;\n        let mut buffer = String::new();\n        f.read_to_string(&mut buffer)?;\n\n        let data: Value = serde_json::from_str(&buffer)?;\n        let json_obj = data.as_object()\n            .ok_or_else(|| ErrorKind::BadParse)?\n            .get(name)\n            .ok_or_else(|| ErrorKind::MissingField(name.to_string()))?;\n        let api_key = helpers::get_json_string(json_obj, \"api_key\")?;\n        let api_secret = helpers::get_json_string(json_obj, \"api_secret\")?;\n        let exchange = {\n            let exchange_str = helpers::get_json_string(json_obj, \"exchange\")?;\n            Exchange::from_str(exchange_str)\n                .chain_err(|| ErrorKind::InvalidFieldValue(\"exchange\".to_string()))?\n        };\n\n        if exchange != Exchange::Bittrex {\n            return Err(ErrorKind::InvalidConfigType(Exchange::Bittrex, exchange).into());\n        }\n\n        Ok(BittrexCreds::new(name, api_key, api_secret))\n    }\n}\n\nimpl Credentials for BittrexCreds {\n    /// Return a value from the credentials.\n    fn get(&self, key: &str) -> Option<String> {\n        if let Some(res) = self.data.get(key) {\n            Some(res.clone())\n        } else {\n            None\n        }\n    }\n\n    fn name(&self) -> String {\n        self.name.clone()\n    }\n\n    fn exchange(&self) -> Exchange {\n        self.exchange\n    }\n}\n"
  },
  {
    "path": "src/bittrex/generic_api.rs",
    "content": "//! Use this module to interact with Bittrex through a Generic API.\n//! This a more convenient and safe way to deal with the exchange since methods return a Result<>\n//! but this generic API does not provide all the functionnality that Bittrex offers.\n\nuse bigdecimal::BigDecimal;\nuse std::str::FromStr;\n\nuse crate::exchange::ExchangeApi;\nuse crate::bittrex::api::BittrexApi;\n\nuse crate::error::*;\nuse crate::types::*;\nuse crate::bittrex::utils;\nuse crate::helpers;\n\nimpl ExchangeApi for BittrexApi {\n    fn ticker(&mut self, pair: Pair) -> Result<Ticker> {\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(name) => name,\n            None => return Err(ErrorKind::PairUnsupported.into()),\n        };\n\n        let raw_response = self.get_market_summary(pair_name)?;\n\n        let result = utils::parse_result(&raw_response)?;\n        let result_array = result.as_array();\n        let result_obj = result_array.unwrap()[0].as_object().unwrap();\n\n        let price_str = result_obj.get(\"Last\").unwrap().as_f64().unwrap().to_string();\n        let price = BigDecimal::from_str(&price_str).unwrap();\n\n        let ask_str = result_obj.get(\"Ask\").unwrap().as_f64().unwrap().to_string();\n        let ask = BigDecimal::from_str(&ask_str).unwrap();\n\n        let bid_str = result_obj.get(\"Bid\").unwrap().as_f64().unwrap().to_string();\n        let bid = BigDecimal::from_str(&bid_str).unwrap();\n\n        let volume_str = result_obj.get(\"Volume\").unwrap().as_f64().unwrap().to_string();\n        let vol = BigDecimal::from_str(&volume_str).unwrap();\n\n        Ok(Ticker {\n            timestamp: helpers::get_unix_timestamp_ms(),\n            pair: pair,\n            last_trade_price: price,\n            lowest_ask: ask,\n            highest_bid: bid,\n            volume: Some(vol),\n        })\n\n    }\n\n    fn orderbook(&mut self, pair: Pair) -> Result<Orderbook> {\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(name) => name,\n            None => return Err(ErrorKind::PairUnsupported.into()),\n        };\n\n        let raw_response = self.get_order_book(pair_name, \"both\")?;\n\n        let result = utils::parse_result(&raw_response)?;\n\n        let mut ask_offers = Vec::new();    // buy orders\n        let mut bid_offers = Vec::new();    // sell orders\n\n        let buy_orders = result[\"buy\"].as_array()\n        .ok_or_else(|| ErrorKind::InvalidFieldFormat(format!(\"{}\", result[\"buy\"])))?;\n\n        let sell_orders = result[\"sell\"].as_array()\n        .ok_or_else(|| ErrorKind::InvalidFieldFormat(format!(\"{}\", result[\"sell\"])))?;\n\n        for ask in buy_orders {\n            let ask_obj = ask.as_object().unwrap();\n\n            let price_str = ask_obj.get(\"Rate\").unwrap().as_f64().unwrap().to_string();\n            let price = BigDecimal::from_str(&price_str).unwrap();\n\n\n            let volume_str = ask_obj.get(\"Quantity\").unwrap().as_f64().unwrap().to_string();\n            let volume = BigDecimal::from_str(&volume_str).unwrap();\n\n            ask_offers.push((price, volume));\n        }\n\n        for bid in sell_orders {\n            let bid_obj = bid.as_object().unwrap();\n\n            let price_str = bid_obj.get(\"Rate\").unwrap().as_f64().unwrap().to_string();\n            let price = BigDecimal::from_str(&price_str).unwrap();\n\n\n            let volume_str = bid_obj.get(\"Quantity\").unwrap().as_f64().unwrap().to_string();\n            let volume = BigDecimal::from_str(&volume_str).unwrap();\n\n            bid_offers.push((price, volume));\n        }\n\n        Ok(Orderbook {\n            timestamp: helpers::get_unix_timestamp_ms(),\n            pair: pair,\n            asks: ask_offers,\n            bids: bid_offers,\n        })\n    }\n\n    fn add_order(&mut self,\n                 order_type: OrderType,\n                 pair: Pair,\n                 quantity: Volume,\n                 price: Option<Price>)\n                 -> Result<OrderInfo> {\n\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(pair_str) => pair_str,\n            None => return Err(ErrorKind::PairUnsupported.into())\n        };\n\n        let raw_response = match order_type {\n            OrderType::BuyLimit => {\n                if price.is_none() {\n                    return Err(ErrorKind::MissingPrice.into());\n                }\n                self.buy_limit(pair_name, &quantity.to_string(), &price.unwrap().to_string())\n            }\n            OrderType::BuyMarket => {\n                let min_price = \"0.000000001\";\n                self.buy_limit(pair_name, &quantity.to_string(), min_price)\n            }\n            OrderType::SellLimit => {\n                if price.is_none() {\n                    return Err(ErrorKind::MissingPrice.into());\n                }\n                self.sell_limit(pair_name, &quantity.to_string(), &price.unwrap().to_string())\n            }\n            OrderType::SellMarket => {\n                let max_price = \"999999999.99\";\n                self.buy_limit(pair_name, &quantity.to_string(), max_price)\n            }\n        }?;\n\n        let result = utils::parse_result(&raw_response)?;\n\n        let result_obj = result.as_object().unwrap();\n\n        Ok(OrderInfo {\n               timestamp: helpers::get_unix_timestamp_ms(),\n               identifier: vec![result_obj.get(\"uuid\").unwrap().as_str().unwrap().to_string()],\n        })\n    }\n\n    fn balances(&mut self) -> Result<Balances> {\n        let raw_response = self.get_balances()?;\n\n        let result = utils::parse_result(&raw_response)?;\n\n        let result_array = result.as_array().unwrap();\n\n        let mut balances = Balances::new();\n\n        for currency in result_array {\n            let currency_obj = currency.as_object().unwrap();\n            let currency_str = currency_obj.get(\"Currency\").unwrap().as_str().unwrap();\n            let currency = utils::get_currency_enum(&currency_str);\n\n            match currency {\n                Some(c) => {\n                    let amount_str = currency_obj.get(\"Available\").unwrap().as_f64().unwrap().to_string();\n                    let amount = BigDecimal::from_str(&amount_str).unwrap();\n                    balances.insert(c, amount);\n                },\n                _ => ()\n            }\n        }\n        Ok(balances)\n    }\n}\n"
  },
  {
    "path": "src/bittrex/mod.rs",
    "content": "//! Use this module to interact with Bittrex exchange.\n//! See examples for more informations.\n\npub mod api;\npub mod generic_api;\npub mod credentials;\npub mod utils;\n\npub use self::credentials::BittrexCreds;\npub use self::api::BittrexApi;\n"
  },
  {
    "path": "src/bittrex/utils.rs",
    "content": "use bidir_map::BidirMap;\nuse serde_json;\nuse serde_json::Value;\nuse serde_json::value::Map;\n\nuse crate::error::*;\nuse crate::types::Currency;\nuse crate::types::Pair;\nuse crate::types::Pair::*;\n\nlazy_static! {\n    static ref PAIRS_STRING: BidirMap<Pair, &'static str> = {\n        let mut m = BidirMap::new();\n        m.insert(_1ST_BTC, \"BTC-1ST\");\n        m.insert(_2GIVE_BTC, \"BTC-2GIVE\");\n        m.insert(ABY_BTC, \"BTC-ABY\");\n        m.insert(ADA_BTC, \"BTC-ADA\");\n        m.insert(ADT_BTC, \"BTC-ADT\");\n        m.insert(ADX_BTC, \"BTC-ADX\");\n        m.insert(AEON_BTC, \"BTC-AEON\");\n        m.insert(AGRS_BTC, \"BTC-AGRS\");\n        m.insert(AMP_BTC, \"BTC-AMP\");\n        m.insert(ANT_BTC, \"BTC-ANT\");\n        m.insert(APX_BTC, \"BTC-APX\");\n        m.insert(ARDR_BTC, \"BTC-ARDR\");\n        m.insert(ARK_BTC, \"BTC-ARK\");\n        m.insert(AUR_BTC, \"BTC-AUR\");\n        m.insert(BAT_BTC, \"BTC-BAT\");\n        m.insert(BAY_BTC, \"BTC-BAY\");\n        m.insert(BCC_BTC, \"BTC-BCC\");\n        m.insert(BCY_BTC, \"BTC-BCY\");\n        m.insert(BITB_BTC, \"BTC-BITB\");\n        m.insert(BLITZ_BTC, \"BTC-BLITZ\");\n        m.insert(BLK_BTC, \"BTC-BLK\");\n        m.insert(BLOCK_BTC, \"BTC-BLOCK\");\n        m.insert(BNT_BTC, \"BTC-BNT\");\n        m.insert(BRK_BTC, \"BTC-BRK\");\n        m.insert(BRX_BTC, \"BTC-BRX\");\n        m.insert(BSD_BTC, \"BTC-BSD\");\n        m.insert(BTCD_BTC, \"BTC-BTCD\");\n        m.insert(BTS_BTC, \"BTC-BTS\");\n        m.insert(BURST_BTC, \"BTC-BURST\");\n        m.insert(BYC_BTC, \"BTC-BYC\");\n        m.insert(CANN_BTC, \"BTC-CANN\");\n        m.insert(CFI_BTC, \"BTC-CFI\");\n        m.insert(CLAM_BTC, \"BTC-CLAM\");\n        m.insert(CLOAK_BTC, \"BTC-CLOAK\");\n        m.insert(CLUB_BTC, \"BTC-CLUB\");\n        m.insert(COVAL_BTC, \"BTC-COVAL\");\n        m.insert(CPC_BTC, \"BTC-CPC\");\n        m.insert(CRB_BTC, \"BTC-CRB\");\n        m.insert(CRW_BTC, \"BTC-CRW\");\n        m.insert(CURE_BTC, \"BTC-CURE\");\n        m.insert(CVC_BTC, \"BTC-CVC\");\n        m.insert(DASH_BTC, \"BTC-DASH\");\n        m.insert(DCR_BTC, \"BTC-DCR\");\n        m.insert(DCT_BTC, \"BTC-DCT\");\n        m.insert(DGB_BTC, \"BTC-DGB\");\n        m.insert(DGD_BTC, \"BTC-DGD\");\n        m.insert(DMD_BTC, \"BTC-DMD\");\n        m.insert(DNT_BTC, \"BTC-DNT\");\n        m.insert(DOGE_BTC, \"BTC-DOGE\");\n        m.insert(DOPE_BTC, \"BTC-DOPE\");\n        m.insert(DTB_BTC, \"BTC-DTB\");\n        m.insert(DYN_BTC, \"BTC-DYN\");\n        m.insert(EBST_BTC, \"BTC-EBST\");\n        m.insert(EDG_BTC, \"BTC-EDG\");\n        m.insert(EFL_BTC, \"BTC-EFL\");\n        m.insert(EGC_BTC, \"BTC-EGC\");\n        m.insert(EMC_BTC, \"BTC-EMC\");\n        m.insert(EMC2_BTC, \"BTC-EMC2\");\n        m.insert(ENRG_BTC, \"BTC-ENRG\");\n        m.insert(ERC_BTC, \"BTC-ERC\");\n        m.insert(ETC_BTC, \"BTC-ETC\");\n        m.insert(ETH_BTC, \"BTC-ETH\");\n        m.insert(EXCL_BTC, \"BTC-EXCL\");\n        m.insert(EXP_BTC, \"BTC-EXP\");\n        m.insert(FAIR_BTC, \"BTC-FAIR\");\n        m.insert(FCT_BTC, \"BTC-FCT\");\n        m.insert(FLDC_BTC, \"BTC-FLDC\");\n        m.insert(FLO_BTC, \"BTC-FLO\");\n        m.insert(FTC_BTC, \"BTC-FTC\");\n        m.insert(FUN_BTC, \"BTC-FUN\");\n        m.insert(GAM_BTC, \"BTC-GAM\");\n        m.insert(GAME_BTC, \"BTC-GAME\");\n        m.insert(GBG_BTC, \"BTC-GBG\");\n        m.insert(GBYTE_BTC, \"BTC-GBYTE\");\n        m.insert(GCR_BTC, \"BTC-GCR\");\n        m.insert(GEO_BTC, \"BTC-GEO\");\n        m.insert(GLD_BTC, \"BTC-GLD\");\n        m.insert(GNO_BTC, \"BTC-GNO\");\n        m.insert(GNT_BTC, \"BTC-GNT\");\n        m.insert(GOLOS_BTC, \"BTC-GOLOS\");\n        m.insert(GRC_BTC, \"BTC-GRC\");\n        m.insert(GRS_BTC, \"BTC-GRS\");\n        m.insert(GUP_BTC, \"BTC-GUP\");\n        m.insert(HMQ_BTC, \"BTC-HMQ\");\n        m.insert(INCNT_BTC, \"BTC-INCNT\");\n        m.insert(INFX_BTC, \"BTC-INFX\");\n        m.insert(IOC_BTC, \"BTC-IOC\");\n        m.insert(ION_BTC, \"BTC-ION\");\n        m.insert(IOP_BTC, \"BTC-IOP\");\n        m.insert(KMD_BTC, \"BTC-KMD\");\n        m.insert(KORE_BTC, \"BTC-KORE\");\n        m.insert(LBC_BTC, \"BTC-LBC\");\n        m.insert(LGD_BTC, \"BTC-LGD\");\n        m.insert(LMC_BTC, \"BTC-LMC\");\n        m.insert(LSK_BTC, \"BTC-LSK\");\n        m.insert(LTC_BTC, \"BTC-LTC\");\n        m.insert(LUN_BTC, \"BTC-LUN\");\n        m.insert(MAID_BTC, \"BTC-MAID\");\n        m.insert(MANA_BTC, \"BTC-MANA\");\n        m.insert(MCO_BTC, \"BTC-MCO\");\n        m.insert(MEME_BTC, \"BTC-MEME\");\n        m.insert(MLN_BTC, \"BTC-MLN\");\n        m.insert(MONA_BTC, \"BTC-MONA\");\n        m.insert(MTL_BTC, \"BTC-MTL\");\n        m.insert(MUE_BTC, \"BTC-MUE\");\n        m.insert(MUSIC_BTC, \"BTC-MUSIC\");\n        m.insert(MYST_BTC, \"BTC-MYST\");\n        m.insert(NAV_BTC, \"BTC-NAV\");\n        m.insert(NBT_BTC, \"BTC-NBT\");\n        m.insert(NEO_BTC, \"BTC-NEO\");\n        m.insert(NEOS_BTC, \"BTC-NEOS\");\n        m.insert(NLG_BTC, \"BTC-NLG\");\n        m.insert(NMR_BTC, \"BTC-NMR\");\n        m.insert(NXC_BTC, \"BTC-NXC\");\n        m.insert(NXS_BTC, \"BTC-NXS\");\n        m.insert(NXT_BTC, \"BTC-NXT\");\n        m.insert(OK_BTC, \"BTC-OK\");\n        m.insert(OMG_BTC, \"BTC-OMG\");\n        m.insert(OMNI_BTC, \"BTC-OMNI\");\n        m.insert(PART_BTC, \"BTC-PART\");\n        m.insert(PAY_BTC, \"BTC-PAY\");\n        m.insert(PDC_BTC, \"BTC-PDC\");\n        m.insert(PINK_BTC, \"BTC-PINK\");\n        m.insert(PIVX_BTC, \"BTC-PIVX\");\n        m.insert(PKB_BTC, \"BTC-PKB\");\n        m.insert(POT_BTC, \"BTC-POT\");\n        m.insert(PPC_BTC, \"BTC-PPC\");\n        m.insert(PTC_BTC, \"BTC-PTC\");\n        m.insert(PTOY_BTC, \"BTC-PTOY\");\n        m.insert(QRL_BTC, \"BTC-QRL\");\n        m.insert(QTUM_BTC, \"BTC-QTUM\");\n        m.insert(QWARK_BTC, \"BTC-QWARK\");\n        m.insert(RADS_BTC, \"BTC-RADS\");\n        m.insert(RBY_BTC, \"BTC-RBY\");\n        m.insert(RDD_BTC, \"BTC-RDD\");\n        m.insert(REP_BTC, \"BTC-REP\");\n        m.insert(RISE_BTC, \"BTC-RISE\");\n        m.insert(RLC_BTC, \"BTC-RLC\");\n        m.insert(SAFEX_BTC, \"BTC-SAFEX\");\n        m.insert(SALT_BTC, \"BTC-SALT\");\n        m.insert(SBD_BTC, \"BTC-SBD\");\n        m.insert(SC_BTC, \"BTC-SC\");\n        m.insert(SEQ_BTC, \"BTC-SEQ\");\n        m.insert(SHIFT_BTC, \"BTC-SHIFT\");\n        m.insert(SIB_BTC, \"BTC-SIB\");\n        m.insert(SLR_BTC, \"BTC-SLR\");\n        m.insert(SLS_BTC, \"BTC-SLS\");\n        m.insert(SNGLS_BTC, \"BTC-SNGLS\");\n        m.insert(SNRG_BTC, \"BTC-SNRG\");\n        m.insert(SNT_BTC, \"BTC-SNT\");\n        m.insert(SPHR_BTC, \"BTC-SPHR\");\n        m.insert(SPR_BTC, \"BTC-SPR\");\n        m.insert(START_BTC, \"BTC-START\");\n        m.insert(STEEM_BTC, \"BTC-STEEM\");\n        m.insert(STORJ_BTC, \"BTC-STORJ\");\n        m.insert(STRAT_BTC, \"BTC-STRAT\");\n        m.insert(SWIFT_BTC, \"BTC-SWIFT\");\n        m.insert(SWT_BTC, \"BTC-SWT\");\n        m.insert(SYNX_BTC, \"BTC-SYNX\");\n        m.insert(SYS_BTC, \"BTC-SYS\");\n        m.insert(THC_BTC, \"BTC-THC\");\n        m.insert(TIME_BTC, \"BTC-TIME\");\n        m.insert(TIX_BTC, \"BTC-TIX\");\n        m.insert(TKN_BTC, \"BTC-TKN\");\n        m.insert(TKS_BTC, \"BTC-TKS\");\n        m.insert(TRIG_BTC, \"BTC-TRIG\");\n        m.insert(TRST_BTC, \"BTC-TRST\");\n        m.insert(TRUST_BTC, \"BTC-TRUST\");\n        m.insert(TX_BTC, \"BTC-TX\");\n        m.insert(UBQ_BTC, \"BTC-UBQ\");\n        m.insert(UNB_BTC, \"BTC-UNB\");\n        m.insert(VIA_BTC, \"BTC-VIA\");\n        m.insert(VOX_BTC, \"BTC-VOX\");\n        m.insert(VRC_BTC, \"BTC-VRC\");\n        m.insert(VRM_BTC, \"BTC-VRM\");\n        m.insert(VTC_BTC, \"BTC-VTC\");\n        m.insert(VTR_BTC, \"BTC-VTR\");\n        m.insert(WAVES_BTC, \"BTC-WAVES\");\n        m.insert(WINGS_BTC, \"BTC-WINGS\");\n        m.insert(XAUR_BTC, \"BTC-XAUR\");\n        m.insert(XCP_BTC, \"BTC-XCP\");\n        m.insert(XDN_BTC, \"BTC-XDN\");\n        m.insert(XEL_BTC, \"BTC-XEL\");\n        m.insert(XEM_BTC, \"BTC-XEM\");\n        m.insert(XLM_BTC, \"BTC-XLM\");\n        m.insert(XMG_BTC, \"BTC-XMG\");\n        m.insert(XMR_BTC, \"BTC-XMR\");\n        m.insert(XMY_BTC, \"BTC-XMY\");\n        m.insert(XRP_BTC, \"BTC-XRP\");\n        m.insert(XST_BTC, \"BTC-XST\");\n        m.insert(XVC_BTC, \"BTC-XVC\");\n        m.insert(XVG_BTC, \"BTC-XVG\");\n        m.insert(XWC_BTC, \"BTC-XWC\");\n        m.insert(XZC_BTC, \"BTC-XZC\");\n        m.insert(ZCL_BTC, \"BTC-ZCL\");\n        m.insert(ZEC_BTC, \"BTC-ZEC\");\n        m.insert(ZEN_BTC, \"BTC-ZEN\");\n        m.insert(_1ST_ETH, \"ETH-1ST\");\n        m.insert(ADT_ETH, \"ETH-ADT\");\n        m.insert(ADX_ETH, \"ETH-ADX\");\n        m.insert(ANT_ETH, \"ETH-ANT\");\n        m.insert(BAT_ETH, \"ETH-BAT\");\n        m.insert(BCC_ETH, \"ETH-BCC\");\n        m.insert(BNT_ETH, \"ETH-BNT\");\n        m.insert(BTS_ETH, \"ETH-BTS\");\n        m.insert(CFI_ETH, \"ETH-CFI\");\n        m.insert(CRB_ETH, \"ETH-CRB\");\n        m.insert(CVC_ETH, \"ETH-CVC\");\n        m.insert(DASH_ETH, \"ETH-DASH\");\n        m.insert(DGB_ETH, \"ETH-DGB\");\n        m.insert(DGD_ETH, \"ETH-DGD\");\n        m.insert(DNT_ETH, \"ETH-DNT\");\n        m.insert(ETC_ETH, \"ETH-ETC\");\n        m.insert(FCT_ETH, \"ETH-FCT\");\n        m.insert(FUN_ETH, \"ETH-FUN\");\n        m.insert(GNO_ETH, \"ETH-GNO\");\n        m.insert(GNT_ETH, \"ETH-GNT\");\n        m.insert(GUP_ETH, \"ETH-GUP\");\n        m.insert(HMQ_ETH, \"ETH-HMQ\");\n        m.insert(LGD_ETH, \"ETH-LGD\");\n        m.insert(LTC_ETH, \"ETH-LTC\");\n        m.insert(LUN_ETH, \"ETH-LUN\");\n        m.insert(MANA_ETH, \"ETH-MANA\");\n        m.insert(MCO_ETH, \"ETH-MCO\");\n        m.insert(MTL_ETH, \"ETH-MTL\");\n        m.insert(MYST_ETH, \"ETH-MYST\");\n        m.insert(NEO_ETH, \"ETH-NEO\");\n        m.insert(NMR_ETH, \"ETH-NMR\");\n        m.insert(OMG_ETH, \"ETH-OMG\");\n        m.insert(PAY_ETH, \"ETH-PAY\");\n        m.insert(PTOY_ETH, \"ETH-PTOY\");\n        m.insert(QRL_ETH, \"ETH-QRL\");\n        m.insert(QTUM_ETH, \"ETH-QTUM\");\n        m.insert(REP_ETH, \"ETH-REP\");\n        m.insert(RLC_ETH, \"ETH-RLC\");\n        m.insert(SALT_ETH, \"ETH-SALT\");\n        m.insert(SC_ETH, \"ETH-SC\");\n        m.insert(SNGLS_ETH, \"ETH-SNGLS\");\n        m.insert(SNT_ETH, \"ETH-SNT\");\n        m.insert(STORJ_ETH, \"ETH-STORJ\");\n        m.insert(STRAT_ETH, \"ETH-STRAT\");\n        m.insert(TIME_ETH, \"ETH-TIME\");\n        m.insert(TIX_ETH, \"ETH-TIX\");\n        m.insert(TKN_ETH, \"ETH-TKN\");\n        m.insert(TRST_ETH, \"ETH-TRST\");\n        m.insert(WAVES_ETH, \"ETH-WAVES\");\n        m.insert(WINGS_ETH, \"ETH-WINGS\");\n        m.insert(XEM_ETH, \"ETH-XEM\");\n        m.insert(XLM_ETH, \"ETH-XLM\");\n        m.insert(XMR_ETH, \"ETH-XMR\");\n        m.insert(XRP_ETH, \"ETH-XRP\");\n        m.insert(ZEC_ETH, \"ETH-ZEC\");\n        m.insert(BCC_USDT, \"USDT-BCC\");\n        m.insert(BTC_USDT, \"USDT-BTC\");\n        m.insert(DASH_USDT, \"USDT-DASH\");\n        m.insert(ETC_USDT, \"USDT-ETC\");\n        m.insert(ETH_USDT, \"USDT-ETH\");\n        m.insert(LTC_USDT, \"USDT-LTC\");\n        m.insert(NEO_USDT, \"USDT-NEO\");\n        m.insert(OMG_USDT, \"USDT-OMG\");\n        m.insert(XMR_USDT, \"USDT-XMR\");\n        m.insert(XRP_USDT, \"USDT-XRP\");\n        m.insert(ZEC_USDT, \"USDT-ZEC\");\n        m\n    };\n}\n\n/// Return the name associated to pair used by Bittrex\n/// If the Pair is not supported, None is returned.\npub fn get_pair_string(pair: &Pair) -> Option<&&str> {\n    PAIRS_STRING.get_by_first(pair)\n}\n\n/// Return the Pair enum associated to the string used by Bittrex\n/// If the Pair is not supported, None is returned.\npub fn get_pair_enum(pair: &str) -> Option<&Pair> {\n    PAIRS_STRING.get_by_second(&pair)\n}\n\npub fn deserialize_json(json_string: &str) -> Result<Map<String, Value>> {\n    let data: Value = match serde_json::from_str(json_string) {\n        Ok(data) => data,\n        Err(_) => return Err(ErrorKind::BadParse.into()),\n    };\n\n    match data.as_object() {\n        Some(value) => Ok(value.clone()),\n        None => Err(ErrorKind::BadParse.into()),\n    }\n}\n\n/// If error array is null, return the result (which can be an array, object or null)\n/// else return the error string found in array\npub fn parse_result(response: &Map<String, Value>) -> Result<Value> {\n    let is_success = match response[\"success\"].as_bool() {\n        Some(is_success) => {\n            is_success\n        }\n        None => return Err(ErrorKind::BadParse.into()),\n    };\n\n    if is_success {\n        Ok(response.get(\"result\").unwrap().clone())\n    }\n    else {\n        let error_message = response.get(\"message\")\n        .ok_or_else(|| ErrorKind::MissingField(\"message\".to_string()))?\n        .as_str()\n        .ok_or_else(|| ErrorKind::InvalidFieldFormat(\"message\".to_string()))?;\n\n        match error_message.as_ref() {\n            \"MIN_TRADE_REQUIREMENT_NOT_MET\" => Err(ErrorKind::InsufficientOrderSize.into()),\n            \"INVALID_PERMISSION\" => Err(ErrorKind::PermissionDenied.into()),\n            _ => Err(ErrorKind::ExchangeSpecificError(error_message.to_string()).into()),\n        }\n    }\n\n\n}\n\n/// Return the currency enum associated with the\n/// string used by Bittrex. If no currency is found,\n/// return None\n/// # Examples\n///\n/// ```\n/// use coinnect::bittrex::utils::get_currency_enum;\n/// use coinnect::types::Currency;\n///\n/// let currency = get_currency_enum(\"1ST\");\n/// assert_eq!(Some(Currency::_1ST), currency);\n/// ```\npub fn get_currency_enum(currency: &str) -> Option<Currency> {\n    match currency {\n        \"1ST\" => Some(Currency::_1ST),\n        \"2GIVE\" => Some(Currency::_2GIVE),\n        \"8BIT\" => Some(Currency::_8BIT),\n        \"ABY\" => Some(Currency::ABY),\n        \"ADA\" => Some(Currency::ADA),\n        \"ADC\" => Some(Currency::ADC),\n        \"ADT\" => Some(Currency::ADT),\n        \"ADX\" => Some(Currency::ADX),\n        \"AEON\" => Some(Currency::AEON),\n        \"AGRS\" => Some(Currency::AGRS),\n        \"AM\" => Some(Currency::AM),\n        \"AMP\" => Some(Currency::AMP),\n        \"AMS\" => Some(Currency::AMS),\n        \"ANT\" => Some(Currency::ANT),\n        \"APEX\" => Some(Currency::APEX),\n        \"APX\" => Some(Currency::APX),\n        \"ARB\" => Some(Currency::ARB),\n        \"ARDR\" => Some(Currency::ARDR),\n        \"ARK\" => Some(Currency::ARK),\n        \"AUR\" => Some(Currency::AUR),\n        \"BAT\" => Some(Currency::BAT),\n        \"BAY\" => Some(Currency::BAY),\n        \"BCC\" => Some(Currency::BCC),\n        \"BCY\" => Some(Currency::BCY),\n        \"BITB\" => Some(Currency::BITB),\n        \"BITCNY\" => Some(Currency::BITCNY),\n        \"BITS\" => Some(Currency::BITS),\n        \"BITZ\" => Some(Currency::BITZ),\n        \"BLC\" => Some(Currency::BLC),\n        \"BLITZ\" => Some(Currency::BLITZ),\n        \"BLK\" => Some(Currency::BLK),\n        \"BLOCK\" => Some(Currency::BLOCK),\n        \"BNT\" => Some(Currency::BNT),\n        \"BOB\" => Some(Currency::BOB),\n        \"BRK\" => Some(Currency::BRK),\n        \"BRX\" => Some(Currency::BRX),\n        \"BSD\" => Some(Currency::BSD),\n        \"BSTY\" => Some(Currency::BSTY),\n        \"BTA\" => Some(Currency::BTA),\n        \"BTC\" => Some(Currency::BTC),\n        \"BTCD\" => Some(Currency::BTCD),\n        \"BTS\" => Some(Currency::BTS),\n        \"BURST\" => Some(Currency::BURST),\n        \"BYC\" => Some(Currency::BYC),\n        \"CANN\" => Some(Currency::CANN),\n        \"CCN\" => Some(Currency::CCN),\n        \"CFI\" => Some(Currency::CFI),\n        \"CLAM\" => Some(Currency::CLAM),\n        \"CLOAK\" => Some(Currency::CLOAK),\n        \"CLUB\" => Some(Currency::CLUB),\n        \"COVAL\" => Some(Currency::COVAL),\n        \"CPC\" => Some(Currency::CPC),\n        \"CRB\" => Some(Currency::CRB),\n        \"CRBIT\" => Some(Currency::CRBIT),\n        \"CRW\" => Some(Currency::CRW),\n        \"CRYPT\" => Some(Currency::CRYPT),\n        \"CURE\" => Some(Currency::CURE),\n        \"CVC\" => Some(Currency::CVC),\n        \"DAR\" => Some(Currency::DAR),\n        \"DASH\" => Some(Currency::DASH),\n        \"DCR\" => Some(Currency::DCR),\n        \"DCT\" => Some(Currency::DCT),\n        \"DGB\" => Some(Currency::DGB),\n        \"DGC\" => Some(Currency::DGC),\n        \"DGD\" => Some(Currency::DGD),\n        \"DMD\" => Some(Currency::DMD),\n        \"DNT\" => Some(Currency::DNT),\n        \"DOGE\" => Some(Currency::DOGE),\n        \"DOPE\" => Some(Currency::DOPE),\n        \"DRACO\" => Some(Currency::DRACO),\n        \"DTB\" => Some(Currency::DTB),\n        \"DTC\" => Some(Currency::DTC),\n        \"DYN\" => Some(Currency::DYN),\n        \"EBST\" => Some(Currency::EBST),\n        \"EDG\" => Some(Currency::EDG),\n        \"EFL\" => Some(Currency::EFL),\n        \"EGC\" => Some(Currency::EGC),\n        \"EMC\" => Some(Currency::EMC),\n        \"EMC2\" => Some(Currency::EMC2),\n        \"ENRG\" => Some(Currency::ENRG),\n        \"ERC\" => Some(Currency::ERC),\n        \"ETC\" => Some(Currency::ETC),\n        \"ETH\" => Some(Currency::ETH),\n        \"EXCL\" => Some(Currency::EXCL),\n        \"EXP\" => Some(Currency::EXP),\n        \"FAIR\" => Some(Currency::FAIR),\n        \"FC2\" => Some(Currency::FC2),\n        \"FCT\" => Some(Currency::FCT),\n        \"FLDC\" => Some(Currency::FLDC),\n        \"FLO\" => Some(Currency::FLO),\n        \"FRK\" => Some(Currency::FRK),\n        \"FSC2\" => Some(Currency::FSC2),\n        \"FTC\" => Some(Currency::FTC),\n        \"FUN\" => Some(Currency::FUN),\n        \"GAM\" => Some(Currency::GAM),\n        \"GAME\" => Some(Currency::GAME),\n        \"GBG\" => Some(Currency::GBG),\n        \"GBYTE\" => Some(Currency::GBYTE),\n        \"GCR\" => Some(Currency::GCR),\n        \"GEMZ\" => Some(Currency::GEMZ),\n        \"GEO\" => Some(Currency::GEO),\n        \"GHC\" => Some(Currency::GHC),\n        \"GLD\" => Some(Currency::GLD),\n        \"GNO\" => Some(Currency::GNO),\n        \"GNT\" => Some(Currency::GNT),\n        \"GOLOS\" => Some(Currency::GOLOS),\n        \"GP\" => Some(Currency::GP),\n        \"GRC\" => Some(Currency::GRC),\n        \"GRS\" => Some(Currency::GRS),\n        \"GRT\" => Some(Currency::GRT),\n        \"GUP\" => Some(Currency::GUP),\n        \"HKG\" => Some(Currency::HKG),\n        \"HMQ\" => Some(Currency::HMQ),\n        \"HYPER\" => Some(Currency::HYPER),\n        \"HZ\" => Some(Currency::HZ),\n        \"INCNT\" => Some(Currency::INCNT),\n        \"INFX\" => Some(Currency::INFX),\n        \"IOC\" => Some(Currency::IOC),\n        \"ION\" => Some(Currency::ION),\n        \"IOP\" => Some(Currency::IOP),\n        \"J\" => Some(Currency::J),\n        \"KMD\" => Some(Currency::KMD),\n        \"KORE\" => Some(Currency::KORE),\n        \"KR\" => Some(Currency::KR),\n        \"LBC\" => Some(Currency::LBC),\n        \"LGD\" => Some(Currency::LGD),\n        \"LMC\" => Some(Currency::LMC),\n        \"LSK\" => Some(Currency::LSK),\n        \"LTC\" => Some(Currency::LTC),\n        \"LUN\" => Some(Currency::LUN),\n        \"LXC\" => Some(Currency::LXC),\n        \"MAID\" => Some(Currency::MAID),\n        \"MANA\" => Some(Currency::MANA),\n        \"MAX\" => Some(Currency::MAX),\n        \"MCO\" => Some(Currency::MCO),\n        \"MEC\" => Some(Currency::MEC),\n        \"MEME\" => Some(Currency::MEME),\n        \"METAL\" => Some(Currency::METAL),\n        \"MLN\" => Some(Currency::MLN),\n        \"MND\" => Some(Currency::MND),\n        \"MONA\" => Some(Currency::MONA),\n        \"MTL\" => Some(Currency::MTL),\n        \"MTR\" => Some(Currency::MTR),\n        \"MUE\" => Some(Currency::MUE),\n        \"MUSIC\" => Some(Currency::MUSIC),\n        \"MYST\" => Some(Currency::MYST),\n        \"MZC\" => Some(Currency::MZC),\n        \"NAUT\" => Some(Currency::NAUT),\n        \"NAV\" => Some(Currency::NAV),\n        \"NBT\" => Some(Currency::NBT),\n        \"NEO\" => Some(Currency::NEO),\n        \"NEOS\" => Some(Currency::NEOS),\n        \"NET\" => Some(Currency::NET),\n        \"NEU\" => Some(Currency::NEU),\n        \"NLG\" => Some(Currency::NLG),\n        \"NMR\" => Some(Currency::NMR),\n        \"NTRN\" => Some(Currency::NTRN),\n        \"NXC\" => Some(Currency::NXC),\n        \"NXS\" => Some(Currency::NXS),\n        \"NXT\" => Some(Currency::NXT),\n        \"OC\" => Some(Currency::OC),\n        \"OK\" => Some(Currency::OK),\n        \"OMG\" => Some(Currency::OMG),\n        \"OMNI\" => Some(Currency::OMNI),\n        \"ORB\" => Some(Currency::ORB),\n        \"PART\" => Some(Currency::PART),\n        \"PAY\" => Some(Currency::PAY),\n        \"PDC\" => Some(Currency::PDC),\n        \"PINK\" => Some(Currency::PINK),\n        \"PIVX\" => Some(Currency::PIVX),\n        \"PKB\" => Some(Currency::PKB),\n        \"POT\" => Some(Currency::POT),\n        \"PPC\" => Some(Currency::PPC),\n        \"PRIME\" => Some(Currency::PRIME),\n        \"PTC\" => Some(Currency::PTC),\n        \"PTOY\" => Some(Currency::PTOY),\n        \"PXI\" => Some(Currency::PXI),\n        \"QRL\" => Some(Currency::QRL),\n        \"QTUM\" => Some(Currency::QTUM),\n        \"QWARK\" => Some(Currency::QWARK),\n        \"RADS\" => Some(Currency::RADS),\n        \"RBY\" => Some(Currency::RBY),\n        \"RDD\" => Some(Currency::RDD),\n        \"REP\" => Some(Currency::REP),\n        \"RISE\" => Some(Currency::RISE),\n        \"RLC\" => Some(Currency::RLC),\n        \"ROOT\" => Some(Currency::ROOT),\n        \"SAFEX\" => Some(Currency::SAFEX),\n        \"SALT\" => Some(Currency::SALT),\n        \"SBD\" => Some(Currency::SBD),\n        \"SC\" => Some(Currency::SC),\n        \"SCOT\" => Some(Currency::SCOT),\n        \"SCRT\" => Some(Currency::SCRT),\n        \"SEQ\" => Some(Currency::SEQ),\n        \"SFR\" => Some(Currency::SFR),\n        \"SHIFT\" => Some(Currency::SHIFT),\n        \"SIB\" => Some(Currency::SIB),\n        \"SLG\" => Some(Currency::SLG),\n        \"SLING\" => Some(Currency::SLING),\n        \"SLR\" => Some(Currency::SLR),\n        \"SLS\" => Some(Currency::SLS),\n        \"SNGLS\" => Some(Currency::SNGLS),\n        \"SNRG\" => Some(Currency::SNRG),\n        \"SNT\" => Some(Currency::SNT),\n        \"SOON\" => Some(Currency::SOON),\n        \"SPHR\" => Some(Currency::SPHR),\n        \"SPR\" => Some(Currency::SPR),\n        \"SPRTS\" => Some(Currency::SPRTS),\n        \"SSD\" => Some(Currency::SSD),\n        \"START\" => Some(Currency::START),\n        \"STEEM\" => Some(Currency::STEEM),\n        \"STEPS\" => Some(Currency::STEPS),\n        \"STORJ\" => Some(Currency::STORJ),\n        \"STRAT\" => Some(Currency::STRAT),\n        \"STV\" => Some(Currency::STV),\n        \"SWIFT\" => Some(Currency::SWIFT),\n        \"SWING\" => Some(Currency::SWING),\n        \"SWT\" => Some(Currency::SWT),\n        \"SYNX\" => Some(Currency::SYNX),\n        \"SYS\" => Some(Currency::SYS),\n        \"TES\" => Some(Currency::TES),\n        \"THC\" => Some(Currency::THC),\n        \"TIME\" => Some(Currency::TIME),\n        \"TIT\" => Some(Currency::TIT),\n        \"TIX\" => Some(Currency::TIX),\n        \"TKN\" => Some(Currency::TKN),\n        \"TKS\" => Some(Currency::TKS),\n        \"TRI\" => Some(Currency::TRI),\n        \"TRIG\" => Some(Currency::TRIG),\n        \"TRK\" => Some(Currency::TRK),\n        \"TROLL\" => Some(Currency::TROLL),\n        \"TRST\" => Some(Currency::TRST),\n        \"TRUST\" => Some(Currency::TRUST),\n        \"TX\" => Some(Currency::TX),\n        \"U\" => Some(Currency::U),\n        \"UBQ\" => Some(Currency::UBQ),\n        \"UFO\" => Some(Currency::UFO),\n        \"UNB\" => Some(Currency::UNB),\n        \"UNIQ\" => Some(Currency::UNIQ),\n        \"UNIT\" => Some(Currency::UNIT),\n        \"UNO\" => Some(Currency::UNO),\n        \"USDT\" => Some(Currency::USDT),\n        \"UTC\" => Some(Currency::UTC),\n        \"VIA\" => Some(Currency::VIA),\n        \"VIOR\" => Some(Currency::VIOR),\n        \"VIRAL\" => Some(Currency::VIRAL),\n        \"VOX\" => Some(Currency::VOX),\n        \"VPN\" => Some(Currency::VPN),\n        \"VRC\" => Some(Currency::VRC),\n        \"VRM\" => Some(Currency::VRM),\n        \"VTC\" => Some(Currency::VTC),\n        \"VTR\" => Some(Currency::VTR),\n        \"WARP\" => Some(Currency::WARP),\n        \"WAVES\" => Some(Currency::WAVES),\n        \"WINGS\" => Some(Currency::WINGS),\n        \"XAUR\" => Some(Currency::XAUR),\n        \"XBB\" => Some(Currency::XBB),\n        \"XC\" => Some(Currency::XC),\n        \"XCO\" => Some(Currency::XCO),\n        \"XCP\" => Some(Currency::XCP),\n        \"XDN\" => Some(Currency::XDN),\n        \"XDQ\" => Some(Currency::XDQ),\n        \"XEL\" => Some(Currency::XEL),\n        \"XEM\" => Some(Currency::XEM),\n        \"XLM\" => Some(Currency::XLM),\n        \"XMG\" => Some(Currency::XMG),\n        \"XMR\" => Some(Currency::XMR),\n        \"XMY\" => Some(Currency::XMY),\n        \"XPY\" => Some(Currency::XPY),\n        \"XQN\" => Some(Currency::XQN),\n        \"XRP\" => Some(Currency::XRP),\n        \"XSEED\" => Some(Currency::XSEED),\n        \"XST\" => Some(Currency::XST),\n        \"XTC\" => Some(Currency::XTC),\n        \"XVC\" => Some(Currency::XVC),\n        \"XVG\" => Some(Currency::XVG),\n        \"XWC\" => Some(Currency::XWC),\n        \"XZC\" => Some(Currency::XZC),\n        \"YBC\" => Some(Currency::YBC),\n        \"ZCL\" => Some(Currency::ZCL),\n        \"ZEC\" => Some(Currency::ZEC),\n        \"ZEN\" => Some(Currency::ZEN),\n        _ => None,\n    }\n}\n\n/// Return the currency String associated with the\n/// string used by Bittrex. If no currency is found,\n/// return None\n/// # Examples\n///\n/// ```\n/// use coinnect::bittrex::utils::get_currency_string;\n/// use coinnect::types::Currency;\n///\n/// let currency = get_currency_string(Currency::_1ST);\n/// assert_eq!(currency, Some(\"1ST\".to_string()));\n/// ```\npub fn get_currency_string(currency: Currency) -> Option<String> {\n    match currency {\n    Currency::_1ST => Some(\"1ST\".to_string()),\n    Currency::_2GIVE => Some(\"2GIVE\".to_string()),\n    Currency::_8BIT => Some(\"8BIT\".to_string()),\n    Currency::ABY => Some(\"ABY\".to_string()),\n    Currency::ADA => Some(\"ADA\".to_string()),\n    Currency::ADC => Some(\"ADC\".to_string()),\n    Currency::ADT => Some(\"ADT\".to_string()),\n    Currency::ADX => Some(\"ADX\".to_string()),\n    Currency::AEON => Some(\"AEON\".to_string()),\n    Currency::AGRS => Some(\"AGRS\".to_string()),\n    Currency::AM => Some(\"AM\".to_string()),\n    Currency::AMP => Some(\"AMP\".to_string()),\n    Currency::AMS => Some(\"AMS\".to_string()),\n    Currency::ANT => Some(\"ANT\".to_string()),\n    Currency::APEX => Some(\"APEX\".to_string()),\n    Currency::APX => Some(\"APX\".to_string()),\n    Currency::ARB => Some(\"ARB\".to_string()),\n    Currency::ARDR => Some(\"ARDR\".to_string()),\n    Currency::ARK => Some(\"ARK\".to_string()),\n    Currency::AUR => Some(\"AUR\".to_string()),\n    Currency::BAT => Some(\"BAT\".to_string()),\n    Currency::BAY => Some(\"BAY\".to_string()),\n    Currency::BCC => Some(\"BCC\".to_string()),\n    Currency::BCY => Some(\"BCY\".to_string()),\n    Currency::BITB => Some(\"BITB\".to_string()),\n    Currency::BITCNY => Some(\"BITCNY\".to_string()),\n    Currency::BITS => Some(\"BITS\".to_string()),\n    Currency::BITZ => Some(\"BITZ\".to_string()),\n    Currency::BLC => Some(\"BLC\".to_string()),\n    Currency::BLITZ => Some(\"BLITZ\".to_string()),\n    Currency::BLK => Some(\"BLK\".to_string()),\n    Currency::BLOCK => Some(\"BLOCK\".to_string()),\n    Currency::BNT => Some(\"BNT\".to_string()),\n    Currency::BOB => Some(\"BOB\".to_string()),\n    Currency::BRK => Some(\"BRK\".to_string()),\n    Currency::BRX => Some(\"BRX\".to_string()),\n    Currency::BSD => Some(\"BSD\".to_string()),\n    Currency::BSTY => Some(\"BSTY\".to_string()),\n    Currency::BTA => Some(\"BTA\".to_string()),\n    Currency::BTC => Some(\"BTC\".to_string()),\n    Currency::BTCD => Some(\"BTCD\".to_string()),\n    Currency::BTS => Some(\"BTS\".to_string()),\n    Currency::BURST => Some(\"BURST\".to_string()),\n    Currency::BYC => Some(\"BYC\".to_string()),\n    Currency::CANN => Some(\"CANN\".to_string()),\n    Currency::CCN => Some(\"CCN\".to_string()),\n    Currency::CFI => Some(\"CFI\".to_string()),\n    Currency::CLAM => Some(\"CLAM\".to_string()),\n    Currency::CLOAK => Some(\"CLOAK\".to_string()),\n    Currency::CLUB => Some(\"CLUB\".to_string()),\n    Currency::COVAL => Some(\"COVAL\".to_string()),\n    Currency::CPC => Some(\"CPC\".to_string()),\n    Currency::CRB => Some(\"CRB\".to_string()),\n    Currency::CRBIT => Some(\"CRBIT\".to_string()),\n    Currency::CRW => Some(\"CRW\".to_string()),\n    Currency::CRYPT => Some(\"CRYPT\".to_string()),\n    Currency::CURE => Some(\"CURE\".to_string()),\n    Currency::CVC => Some(\"CVC\".to_string()),\n    Currency::DAR => Some(\"DAR\".to_string()),\n    Currency::DASH => Some(\"DASH\".to_string()),\n    Currency::DCR => Some(\"DCR\".to_string()),\n    Currency::DCT => Some(\"DCT\".to_string()),\n    Currency::DGB => Some(\"DGB\".to_string()),\n    Currency::DGC => Some(\"DGC\".to_string()),\n    Currency::DGD => Some(\"DGD\".to_string()),\n    Currency::DMD => Some(\"DMD\".to_string()),\n    Currency::DNT => Some(\"DNT\".to_string()),\n    Currency::DOGE => Some(\"DOGE\".to_string()),\n    Currency::DOPE => Some(\"DOPE\".to_string()),\n    Currency::DRACO => Some(\"DRACO\".to_string()),\n    Currency::DTB => Some(\"DTB\".to_string()),\n    Currency::DTC => Some(\"DTC\".to_string()),\n    Currency::DYN => Some(\"DYN\".to_string()),\n    Currency::EBST => Some(\"EBST\".to_string()),\n    Currency::EDG => Some(\"EDG\".to_string()),\n    Currency::EFL => Some(\"EFL\".to_string()),\n    Currency::EGC => Some(\"EGC\".to_string()),\n    Currency::EMC => Some(\"EMC\".to_string()),\n    Currency::EMC2 => Some(\"EMC2\".to_string()),\n    Currency::ENRG => Some(\"ENRG\".to_string()),\n    Currency::ERC => Some(\"ERC\".to_string()),\n    Currency::ETC => Some(\"ETC\".to_string()),\n    Currency::ETH => Some(\"ETH\".to_string()),\n    Currency::EXCL => Some(\"EXCL\".to_string()),\n    Currency::EXP => Some(\"EXP\".to_string()),\n    Currency::FAIR => Some(\"FAIR\".to_string()),\n    Currency::FC2 => Some(\"FC2\".to_string()),\n    Currency::FCT => Some(\"FCT\".to_string()),\n    Currency::FLDC => Some(\"FLDC\".to_string()),\n    Currency::FLO => Some(\"FLO\".to_string()),\n    Currency::FRK => Some(\"FRK\".to_string()),\n    Currency::FSC2 => Some(\"FSC2\".to_string()),\n    Currency::FTC => Some(\"FTC\".to_string()),\n    Currency::FUN => Some(\"FUN\".to_string()),\n    Currency::GAM => Some(\"GAM\".to_string()),\n    Currency::GAME => Some(\"GAME\".to_string()),\n    Currency::GBG => Some(\"GBG\".to_string()),\n    Currency::GBYTE => Some(\"GBYTE\".to_string()),\n    Currency::GCR => Some(\"GCR\".to_string()),\n    Currency::GEMZ => Some(\"GEMZ\".to_string()),\n    Currency::GEO => Some(\"GEO\".to_string()),\n    Currency::GHC => Some(\"GHC\".to_string()),\n    Currency::GLD => Some(\"GLD\".to_string()),\n    Currency::GNO => Some(\"GNO\".to_string()),\n    Currency::GNT => Some(\"GNT\".to_string()),\n    Currency::GOLOS => Some(\"GOLOS\".to_string()),\n    Currency::GP => Some(\"GP\".to_string()),\n    Currency::GRC => Some(\"GRC\".to_string()),\n    Currency::GRS => Some(\"GRS\".to_string()),\n    Currency::GRT => Some(\"GRT\".to_string()),\n    Currency::GUP => Some(\"GUP\".to_string()),\n    Currency::HKG => Some(\"HKG\".to_string()),\n    Currency::HMQ => Some(\"HMQ\".to_string()),\n    Currency::HYPER => Some(\"HYPER\".to_string()),\n    Currency::HZ => Some(\"HZ\".to_string()),\n    Currency::INCNT => Some(\"INCNT\".to_string()),\n    Currency::INFX => Some(\"INFX\".to_string()),\n    Currency::IOC => Some(\"IOC\".to_string()),\n    Currency::ION => Some(\"ION\".to_string()),\n    Currency::IOP => Some(\"IOP\".to_string()),\n    Currency::J => Some(\"J\".to_string()),\n    Currency::KMD => Some(\"KMD\".to_string()),\n    Currency::KORE => Some(\"KORE\".to_string()),\n    Currency::KR => Some(\"KR\".to_string()),\n    Currency::LBC => Some(\"LBC\".to_string()),\n    Currency::LGD => Some(\"LGD\".to_string()),\n    Currency::LMC => Some(\"LMC\".to_string()),\n    Currency::LSK => Some(\"LSK\".to_string()),\n    Currency::LTC => Some(\"LTC\".to_string()),\n    Currency::LUN => Some(\"LUN\".to_string()),\n    Currency::LXC => Some(\"LXC\".to_string()),\n    Currency::MAID => Some(\"MAID\".to_string()),\n    Currency::MANA => Some(\"MANA\".to_string()),\n    Currency::MAX => Some(\"MAX\".to_string()),\n    Currency::MCO => Some(\"MCO\".to_string()),\n    Currency::MEC => Some(\"MEC\".to_string()),\n    Currency::MEME => Some(\"MEME\".to_string()),\n    Currency::METAL => Some(\"METAL\".to_string()),\n    Currency::MLN => Some(\"MLN\".to_string()),\n    Currency::MND => Some(\"MND\".to_string()),\n    Currency::MONA => Some(\"MONA\".to_string()),\n    Currency::MTL => Some(\"MTL\".to_string()),\n    Currency::MTR => Some(\"MTR\".to_string()),\n    Currency::MUE => Some(\"MUE\".to_string()),\n    Currency::MUSIC => Some(\"MUSIC\".to_string()),\n    Currency::MYST => Some(\"MYST\".to_string()),\n    Currency::MZC => Some(\"MZC\".to_string()),\n    Currency::NAUT => Some(\"NAUT\".to_string()),\n    Currency::NAV => Some(\"NAV\".to_string()),\n    Currency::NBT => Some(\"NBT\".to_string()),\n    Currency::NEO => Some(\"NEO\".to_string()),\n    Currency::NEOS => Some(\"NEOS\".to_string()),\n    Currency::NET => Some(\"NET\".to_string()),\n    Currency::NEU => Some(\"NEU\".to_string()),\n    Currency::NLG => Some(\"NLG\".to_string()),\n    Currency::NMR => Some(\"NMR\".to_string()),\n    Currency::NTRN => Some(\"NTRN\".to_string()),\n    Currency::NXC => Some(\"NXC\".to_string()),\n    Currency::NXS => Some(\"NXS\".to_string()),\n    Currency::NXT => Some(\"NXT\".to_string()),\n    Currency::OC => Some(\"OC\".to_string()),\n    Currency::OK => Some(\"OK\".to_string()),\n    Currency::OMG => Some(\"OMG\".to_string()),\n    Currency::OMNI => Some(\"OMNI\".to_string()),\n    Currency::ORB => Some(\"ORB\".to_string()),\n    Currency::PART => Some(\"PART\".to_string()),\n    Currency::PAY => Some(\"PAY\".to_string()),\n    Currency::PDC => Some(\"PDC\".to_string()),\n    Currency::PINK => Some(\"PINK\".to_string()),\n    Currency::PIVX => Some(\"PIVX\".to_string()),\n    Currency::PKB => Some(\"PKB\".to_string()),\n    Currency::POT => Some(\"POT\".to_string()),\n    Currency::PPC => Some(\"PPC\".to_string()),\n    Currency::PRIME => Some(\"PRIME\".to_string()),\n    Currency::PTC => Some(\"PTC\".to_string()),\n    Currency::PTOY => Some(\"PTOY\".to_string()),\n    Currency::PXI => Some(\"PXI\".to_string()),\n    Currency::QRL => Some(\"QRL\".to_string()),\n    Currency::QTUM => Some(\"QTUM\".to_string()),\n    Currency::QWARK => Some(\"QWARK\".to_string()),\n    Currency::RADS => Some(\"RADS\".to_string()),\n    Currency::RBY => Some(\"RBY\".to_string()),\n    Currency::RDD => Some(\"RDD\".to_string()),\n    Currency::REP => Some(\"REP\".to_string()),\n    Currency::RISE => Some(\"RISE\".to_string()),\n    Currency::RLC => Some(\"RLC\".to_string()),\n    Currency::ROOT => Some(\"ROOT\".to_string()),\n    Currency::SAFEX => Some(\"SAFEX\".to_string()),\n    Currency::SALT => Some(\"SALT\".to_string()),\n    Currency::SBD => Some(\"SBD\".to_string()),\n    Currency::SC => Some(\"SC\".to_string()),\n    Currency::SCOT => Some(\"SCOT\".to_string()),\n    Currency::SCRT => Some(\"SCRT\".to_string()),\n    Currency::SEQ => Some(\"SEQ\".to_string()),\n    Currency::SFR => Some(\"SFR\".to_string()),\n    Currency::SHIFT => Some(\"SHIFT\".to_string()),\n    Currency::SIB => Some(\"SIB\".to_string()),\n    Currency::SLG => Some(\"SLG\".to_string()),\n    Currency::SLING => Some(\"SLING\".to_string()),\n    Currency::SLR => Some(\"SLR\".to_string()),\n    Currency::SLS => Some(\"SLS\".to_string()),\n    Currency::SNGLS => Some(\"SNGLS\".to_string()),\n    Currency::SNRG => Some(\"SNRG\".to_string()),\n    Currency::SNT => Some(\"SNT\".to_string()),\n    Currency::SOON => Some(\"SOON\".to_string()),\n    Currency::SPHR => Some(\"SPHR\".to_string()),\n    Currency::SPR => Some(\"SPR\".to_string()),\n    Currency::SPRTS => Some(\"SPRTS\".to_string()),\n    Currency::SSD => Some(\"SSD\".to_string()),\n    Currency::START => Some(\"START\".to_string()),\n    Currency::STEEM => Some(\"STEEM\".to_string()),\n    Currency::STEPS => Some(\"STEPS\".to_string()),\n    Currency::STORJ => Some(\"STORJ\".to_string()),\n    Currency::STRAT => Some(\"STRAT\".to_string()),\n    Currency::STV => Some(\"STV\".to_string()),\n    Currency::SWIFT => Some(\"SWIFT\".to_string()),\n    Currency::SWING => Some(\"SWING\".to_string()),\n    Currency::SWT => Some(\"SWT\".to_string()),\n    Currency::SYNX => Some(\"SYNX\".to_string()),\n    Currency::SYS => Some(\"SYS\".to_string()),\n    Currency::TES => Some(\"TES\".to_string()),\n    Currency::THC => Some(\"THC\".to_string()),\n    Currency::TIME => Some(\"TIME\".to_string()),\n    Currency::TIT => Some(\"TIT\".to_string()),\n    Currency::TIX => Some(\"TIX\".to_string()),\n    Currency::TKN => Some(\"TKN\".to_string()),\n    Currency::TKS => Some(\"TKS\".to_string()),\n    Currency::TRI => Some(\"TRI\".to_string()),\n    Currency::TRIG => Some(\"TRIG\".to_string()),\n    Currency::TRK => Some(\"TRK\".to_string()),\n    Currency::TROLL => Some(\"TROLL\".to_string()),\n    Currency::TRST => Some(\"TRST\".to_string()),\n    Currency::TRUST => Some(\"TRUST\".to_string()),\n    Currency::TX => Some(\"TX\".to_string()),\n    Currency::U => Some(\"U\".to_string()),\n    Currency::UBQ => Some(\"UBQ\".to_string()),\n    Currency::UFO => Some(\"UFO\".to_string()),\n    Currency::UNB => Some(\"UNB\".to_string()),\n    Currency::UNIQ => Some(\"UNIQ\".to_string()),\n    Currency::UNIT => Some(\"UNIT\".to_string()),\n    Currency::UNO => Some(\"UNO\".to_string()),\n    Currency::USDT => Some(\"USDT\".to_string()),\n    Currency::UTC => Some(\"UTC\".to_string()),\n    Currency::VIA => Some(\"VIA\".to_string()),\n    Currency::VIOR => Some(\"VIOR\".to_string()),\n    Currency::VIRAL => Some(\"VIRAL\".to_string()),\n    Currency::VOX => Some(\"VOX\".to_string()),\n    Currency::VPN => Some(\"VPN\".to_string()),\n    Currency::VRC => Some(\"VRC\".to_string()),\n    Currency::VRM => Some(\"VRM\".to_string()),\n    Currency::VTC => Some(\"VTC\".to_string()),\n    Currency::VTR => Some(\"VTR\".to_string()),\n    Currency::WARP => Some(\"WARP\".to_string()),\n    Currency::WAVES => Some(\"WAVES\".to_string()),\n    Currency::WINGS => Some(\"WINGS\".to_string()),\n    Currency::XAUR => Some(\"XAUR\".to_string()),\n    Currency::XBB => Some(\"XBB\".to_string()),\n    Currency::XC => Some(\"XC\".to_string()),\n    Currency::XCO => Some(\"XCO\".to_string()),\n    Currency::XCP => Some(\"XCP\".to_string()),\n    Currency::XDN => Some(\"XDN\".to_string()),\n    Currency::XDQ => Some(\"XDQ\".to_string()),\n    Currency::XEL => Some(\"XEL\".to_string()),\n    Currency::XEM => Some(\"XEM\".to_string()),\n    Currency::XLM => Some(\"XLM\".to_string()),\n    Currency::XMG => Some(\"XMG\".to_string()),\n    Currency::XMR => Some(\"XMR\".to_string()),\n    Currency::XMY => Some(\"XMY\".to_string()),\n    Currency::XPY => Some(\"XPY\".to_string()),\n    Currency::XQN => Some(\"XQN\".to_string()),\n    Currency::XRP => Some(\"XRP\".to_string()),\n    Currency::XSEED => Some(\"XSEED\".to_string()),\n    Currency::XST => Some(\"XST\".to_string()),\n    Currency::XTC => Some(\"XTC\".to_string()),\n    Currency::XVC => Some(\"XVC\".to_string()),\n    Currency::XVG => Some(\"XVG\".to_string()),\n    Currency::XWC => Some(\"XWC\".to_string()),\n    Currency::XZC => Some(\"XZC\".to_string()),\n    Currency::YBC => Some(\"YBC\".to_string()),\n    Currency::ZCL => Some(\"ZCL\".to_string()),\n    Currency::ZEC => Some(\"ZEC\".to_string()),\n    Currency::ZEN => Some(\"ZEN\".to_string()),\n        _ => None,\n    }\n}\n"
  },
  {
    "path": "src/coinnect.rs",
    "content": "//! Use this module to create a generic API.\n\nuse std::path::PathBuf;\n\nuse crate::exchange::{Exchange, ExchangeApi};\nuse crate::bitstamp::{BitstampApi, BitstampCreds};\nuse crate::kraken::{KrakenApi, KrakenCreds};\nuse crate::poloniex::{PoloniexApi, PoloniexCreds};\nuse crate::bittrex::{BittrexApi, BittrexCreds};\nuse crate::gdax::{GdaxApi, GdaxCreds};\nuse crate::error::*;\n\npub trait Credentials {\n    /// Get an element from the credentials.\n    fn get(&self, cred: &str) -> Option<String>;\n    /// Return the targeted `Exchange`.\n    fn exchange(&self) -> Exchange;\n    /// Return the client name.\n    fn name(&self) -> String;\n}\n\n#[derive(Debug)]\npub struct Coinnect;\n\nimpl Coinnect {\n    /// Create a new CoinnectApi by providing an API key & API secret\n    pub fn new<C: Credentials>(exchange: Exchange, creds: C) -> Result<Box<dyn ExchangeApi>> {\n        match exchange {\n            Exchange::Bitstamp => Ok(Box::new(BitstampApi::new(creds)?)),\n            Exchange::Kraken => Ok(Box::new(KrakenApi::new(creds)?)),\n            Exchange::Poloniex => Ok(Box::new(PoloniexApi::new(creds)?)),\n            Exchange::Bittrex => Ok(Box::new(BittrexApi::new(creds)?)),\n            Exchange::Gdax => Ok(Box::new(GdaxApi::new(creds)?)),\n        }\n    }\n\n    /// Create a new CoinnectApi from a json configuration file. This file must follow this\n    /// structure:\n    ///\n    /// For this example, you could use load your Bitstamp account with\n    /// `new_from_file(Exchange::Bitstamp, \"account_bitstamp\", Path::new(\"/keys.json\"))`\n    pub fn new_from_file(exchange: Exchange,\n                         name: &str,\n                         path: PathBuf)\n                         -> Result<Box<dyn ExchangeApi>> {\n        match exchange {\n            Exchange::Bitstamp => {\n                Ok(Box::new(BitstampApi::new(BitstampCreds::new_from_file(name, path)?)?))\n            }\n            Exchange::Kraken => {\n                Ok(Box::new(KrakenApi::new(KrakenCreds::new_from_file(name, path)?)?))\n            }\n            Exchange::Poloniex => {\n                Ok(Box::new(PoloniexApi::new(PoloniexCreds::new_from_file(name, path)?)?))\n            }\n            Exchange::Bittrex => {\n                Ok(Box::new(BittrexApi::new(BittrexCreds::new_from_file(name, path)?)?))\n            }\n            Exchange::Gdax => {\n                Ok(Box::new(GdaxApi::new(GdaxCreds::new_from_file(name, path)?)?))\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/error.rs",
    "content": "//! This module contains enum Error.\n//! Error type represents all possible errors that can occur when dealing\n//! with the generic or any dedicated-exchange API\n\n\nuse serde_json;\nuse hyper;\nuse data_encoding;\nuse crate::exchange::Exchange;\n\nerror_chain!{\n    types {\n        Error, ErrorKind, ResultExt, Result;\n    }\n\n    foreign_links {\n        Json(serde_json::Error);\n        ParseFloat(::std::num::ParseFloatError);\n        ParseString(::std::string::FromUtf8Error);\n        Hyper(hyper::Error);\n        DataDecoding(data_encoding::DecodeError);\n        Io(::std::io::Error);\n    }\n\n    errors {\n        BadParse {\n            description(\"ParsingError\")\n                display(\"The response could not be parsed.\")\n        }\n\n        ServiceUnavailable(reason: String) {\n            description(\"ServiceUnavailable\")\n                display(\"Host could not be reached: {}.\", reason)\n        }\n\n        BadCredentials {\n            description(\"BadCredentials\")\n                display(\"The informations provided do not allow authentication.\")\n        }\n\n        RateLimitExceeded {\n            description(\"RateLimitExceeded\")\n                display(\"API call rate limit exceeded.\")\n        }\n\n        PairUnsupported {\n            description(\"PairUnsupported\")\n                display(\"This pair is not supported.\")\n        }\n\n        InvalidArguments {\n            description(\"InvalidArguments\")\n                display(\"Arguments passed do not conform to the protocol.\")\n        }\n\n        ExchangeSpecificError(reason: String) {\n            description(\"ExchangeSpecificError\")\n                display(\"Exchange error: {}\", reason)\n        }\n\n        TlsError {\n            description(\"TlsError\")\n                display(\"Fail to initialize TLS client.\")\n        }\n\n        InvalidFieldFormat(field: String) {\n            description(\"InvalidFieldFormat\")\n                display(\"Fail to parse field \\\"{}\\\".\", field)\n        }\n\n        InvalidFieldValue(field: String) {\n            description(\"InvalidFieldValue\")\n                display(\"Invalid value for field \\\"{}\\\".\", field)\n        }\n\n        MissingField(field: String) {\n            description(\"MissingFiled\")\n                display(\"Missing field \\\"{}\\\".\", field)\n        }\n\n        InsufficientFunds {\n            description(\"InsufficientFunds\")\n                display(\"You haven't enough founds.\")\n        }\n\n        InsufficientOrderSize {\n            description(\"InsufficientOrderSize\")\n                display(\"Your order is not big enough.\")\n        }\n\n        MissingPrice{\n            description(\"MissingPrice\")\n                display(\"No price specified.\")\n        }\n\n        InvalidConfigType(expected: Exchange, find: Exchange){\n            description(\"InvalidConfigType\")\n                display(\"Invalid config: \\nExpected: {:?}\\nFind: {:?}\", expected, find)\n        }\n\n        InvalidExchange(value: String) {\n            description(\"InvalidExchange\")\n                display(\"Invalid exchange: \\\"{}\\\"\", value)\n        }\n\n        InvalidNonce {\n            description(\"InvalidNonce\")\n                display(\"Invalid nonce\")\n        }\n\n        PermissionDenied {\n            description(\"PermissionDenied\")\n                display(\"The operation cannot be done with the provided credentials\")\n        }\n    }\n}\n"
  },
  {
    "path": "src/exchange.rs",
    "content": "//! This module contains Exchange enum.\n\nuse std::fmt::Debug;\nuse std::convert::Into;\nuse std::str::FromStr;\n\nuse crate::error::*;\nuse crate::types::*;\n\n\n\n\n#[derive(Debug, PartialEq, Clone, Copy)]\npub enum Exchange {\n    Bitstamp,\n    Kraken,\n    Poloniex,\n    Bittrex,\n    Gdax,\n}\n\nimpl Into<String> for Exchange {\n    fn into(self) -> String {\n        match self {\n            Exchange::Bitstamp => \"Bitstamp\".to_string(),\n            Exchange::Kraken => \"Kraken\".to_string(),\n            Exchange::Poloniex => \"Poloniex\".to_string(),\n            Exchange::Bittrex => \"Bittrex\".to_string(),\n            Exchange::Gdax => \"Gdax\".to_string(),\n        }\n    }\n}\n\nimpl FromStr for Exchange {\n    type Err = Error;\n\n    fn from_str(input: &str) -> ::std::result::Result<Self, Self::Err> {\n        match input.to_lowercase().as_str() {\n            \"bitstamp\" => Ok(Exchange::Bitstamp),\n            \"kraken\" => Ok(Exchange::Kraken),\n            \"poloniex\" => Ok(Exchange::Poloniex),\n            \"bittrex\" => Ok(Exchange::Bittrex),\n            \"gdax\" => Ok(Exchange::Gdax),\n            _ => Err(ErrorKind::InvalidExchange(input.to_string()).into()),\n        }\n    }\n}\n\npub trait ExchangeApi: Debug {\n    /// Return a Ticker for the Pair specified.\n    fn ticker(&mut self, pair: Pair) -> Result<Ticker>;\n\n    /// Return an Orderbook for the specified Pair.\n    fn orderbook(&mut self, pair: Pair) -> Result<Orderbook>;\n\n    /// Place an order directly to the exchange.\n    /// Quantity is in quote currency. So if you want to buy 1 Bitcoin for X€ (pair BTC_EUR),\n    /// base currency (right member in the pair) is BTC and quote/counter currency is BTC (left\n    /// member in the pair).\n    /// So quantity = 1.\n    ///\n    /// A good practice is to store the return type (OrderInfo) somewhere since it can later be used\n    /// to modify or cancel the order.\n    fn add_order(&mut self,\n                 order_type: OrderType,\n                 pair: Pair,\n                 quantity: Volume,\n                 price: Option<Price>)\n                 -> Result<OrderInfo>;\n\n    /// Retrieve the current amounts of all the currencies that the account holds\n    /// The amounts returned are available (not used to open an order)\n    fn balances(&mut self) -> Result<Balances>;\n}\n"
  },
  {
    "path": "src/gdax/api.rs",
    "content": "//! Use this module to interact with Gdax exchange.\n//! Please see examples for more informations.\n\n\nuse hyper_native_tls::NativeTlsClient;\nuse hyper::Client;\nuse hyper::header::{ContentType,UserAgent};\nuse hyper::net::HttpsConnector;\n\nuse serde_json::Value;\nuse serde_json::value::Map;\n\nuse std::collections::HashMap;\nuse std::io::Read;\nuse std::thread;\nuse std::time::Duration;\n\nuse crate::coinnect::Credentials;\nuse crate::exchange::Exchange;\n\nuse crate::error::*;\nuse crate::helpers;\nuse crate::types::Pair;\nuse crate::gdax::utils;\nuse crate::types::*;\n\nheader! {\n    #[doc(hidden)]\n    (KeyHeader, \"Key\") => [String]\n}\n\nheader! {\n    #[doc(hidden)]\n    (SignHeader, \"Sign\") => [String]\n}\n\nheader! {\n    #[doc(hidden)]\n    (ContentHeader, \"Content-Type\") => [String]\n}\n\n#[derive(Debug)]\npub struct GdaxApi {\n    last_request: i64, // unix timestamp in ms, to avoid ban\n    api_key: String,\n    api_secret: String,\n    customer_id: String,\n    http_client: Client,\n    burst: bool,\n}\n\n\nimpl GdaxApi {\n    /// Create a new GdaxApi by providing an API key & API secret\n    pub fn new<C: Credentials>(creds: C) -> Result<GdaxApi> {\n        if creds.exchange() != Exchange::Gdax {\n            return Err(ErrorKind::InvalidConfigType(Exchange::Gdax, creds.exchange()).into());\n        }\n\n        //TODO: Handle correctly TLS errors with error_chain.\n        let ssl = match NativeTlsClient::new() {\n            Ok(res) => res,\n            Err(_) => return Err(ErrorKind::TlsError.into()),\n        };\n\n        let connector = HttpsConnector::new(ssl);\n\n\n        Ok(GdaxApi {\n               last_request: 0,\n               api_key: creds.get(\"api_key\").unwrap_or_default(),\n               api_secret: creds.get(\"api_secret\").unwrap_or_default(),\n               customer_id: creds.get(\"customer_id\").unwrap_or_default(),\n               http_client: Client::with_connector(connector),\n               burst: false, // No burst by default\n           })\n    }\n\n    /// The number of calls in a given period is limited. In order to avoid a ban we limit\n    /// by default the number of api requests.\n    /// This function sets or removes the limitation.\n    /// Burst false implies no block.\n    /// Burst true implies there is a control over the number of calls allowed to the exchange\n    pub fn set_burst(&mut self, burst: bool) {\n        self.burst = burst\n    }\n\n    fn block_or_continue(&self) {\n        if ! self.burst {\n            let threshold: u64 = 334; // 3 requests/sec = 1/3*1000\n            let offset: u64 = helpers::get_unix_timestamp_ms() as u64 - self.last_request as u64;\n            if offset < threshold {\n                let wait_ms = Duration::from_millis(threshold - offset);\n                thread::sleep(wait_ms);\n            }\n        }\n    }\n\n    fn public_query(&mut self, params: &HashMap<&str, &str>) -> Result<Map<String, Value>> {\n\n        let method: &str = params\n            .get(\"method\")\n            .ok_or_else(|| \"Missing \\\"method\\\" field.\")?;\n        let pair: &str = params.get(\"pair\").ok_or_else(|| \"Missing \\\"pair\\\" field.\")?;\n        let url: String = utils::build_url(method, pair);\n\n        self.block_or_continue();\n        let mut response = self.http_client\n            .get(&url)\n            .header(UserAgent(\"coinnect\".to_string()))\n            .send()?;\n\n        self.last_request = helpers::get_unix_timestamp_ms();\n        let mut buffer = String::new();\n        response.read_to_string(&mut buffer)?;\n        utils::deserialize_json(&buffer)\n    }\n\n    ///\n    ///\n    /// #Examples\n    ///\n    /// ```json\n    /// extern crate coinnect;\n    /// use coinnect::gdax::GdaxApi;\n    /// let mut api = GdaxApi::new(\"\", \"\");\n    /// let  result = api.private_query(\"balance\", \"btcusd\");\n    /// assert_eq!(true, true);\n    /// ```\n    fn private_query(&mut self, params: &HashMap<&str, &str>) -> Result<Map<String, Value>> {\n\n        let method: &str = params\n            .get(\"method\")\n            .ok_or_else(|| \"Missing \\\"method\\\" field.\")?;\n        let pair: &str = params.get(\"pair\").ok_or_else(|| \"Missing \\\"pair\\\" field.\")?;\n        let url: String = utils::build_url(method, pair);\n\n        let nonce = utils::generate_nonce(None);\n        let signature =\n            utils::build_signature(&nonce, &self.customer_id, &self.api_key, &self.api_secret)?;\n\n        let copy_api_key = self.api_key.clone();\n        let mut post_params: &mut HashMap<&str, &str> = &mut HashMap::new();\n        post_params.insert(\"key\", &copy_api_key);\n        post_params.insert(\"signature\", &signature);\n        post_params.insert(\"nonce\", &nonce);\n\n        // copy params into post_params .... bit of a hack but will do for now\n        params.iter().for_each(|(k,v)| {\n            post_params.insert(k,v);\n        });\n\n        helpers::strip_empties(&mut post_params);\n        let post_data = helpers::url_encode_hashmap(post_params);\n        let mut response = self.http_client\n            .post(&url)\n            .header(ContentType::form_url_encoded())\n            .body(&post_data)\n            .send()?;\n\n        let mut buffer = String::new();\n        response.read_to_string(&mut buffer)?;\n        utils::deserialize_json(&buffer)\n    }\n\n    /// Sample output :\n    ///\n    /// ```json\n    /// {\n    /// \"BTC_LTC\":{\n    /// \"last\":\"0.0251\",\"lowestAsk\":\"0.02589999\",\"highestBid\":\"0.0251\",\n    /// \"percentChange\":\"0.02390438\",\"baseVolume\":\"6.16485315\",\"quoteVolume\":\"245.82513926\"},\n    /// \"BTC_NXT\":{\n    /// \"last\":\"0.00005730\",\"lowestAsk\":\"0.00005710\",\"highestBid\":\"0.00004903\",\n    /// \"percentChange\":\"0.16701570\",\"baseVolume\":\"0.45347489\",\"quoteVolume\":\"9094\"},\n    /// ... }\n    /// ```\n    pub fn return_ticker(&mut self, pair: Pair) -> Result<Map<String, Value>> {\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(name) => name,\n            None => return Err(ErrorKind::PairUnsupported.into()),\n        };\n\n        let mut params: HashMap<&str, &str> = HashMap::new();\n        params.insert(\"pair\", pair_name);\n        params.insert(\"method\", \"ticker\");\n        self.public_query(&params)\n    }\n\n    /// Sample output :\n    ///\n    /// ```json\n    /// {\"asks\":[[0.00007600,1164],[0.00007620,1300], ... ], \"bids\":[[0.00006901,200],\n    /// [0.00006900,408], ... ], \"timestamp\": \"1234567890\"}\n    /// ```\n    pub fn return_order_book(&mut self, pair: Pair) -> Result<Map<String, Value>> {\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(name) => name,\n            None => return Err(ErrorKind::PairUnsupported.into()),\n\n        };\n\n        let mut params: HashMap<&str, &str> = HashMap::new();\n        params.insert(\"method\", \"order_book\");\n        params.insert(\"pair\", pair_name);\n        self.public_query(&params)\n    }\n\n    /// Sample output :\n    ///\n    /// ```json\n    /// [{\"date\":\"2014-02-10 04:23:23\",\"type\":\"buy\",\"rate\":\"0.00007600\",\"amount\":\"140\",\n    /// \"total\":\"0.01064\"},\n    /// {\"date\":\"2014-02-10 01:19:37\",\"type\":\"buy\",\"rate\":\"0.00007600\",\"amount\":\"655\",\n    /// \"total\":\"0.04978\"}, ... ]\n    /// ```\n    pub fn return_trade_history(&mut self, pair: Pair) -> Result<Map<String, Value>> {\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(name) => name,\n            None => return Err(ErrorKind::PairUnsupported.into()),\n        };\n\n        let mut params: HashMap<&str, &str> = HashMap::new();\n        params.insert(\"pair\", pair_name);\n        params.insert(\"method\", \"transactions\");\n        self.public_query(&params)\n    }\n\n\n    /// Returns all of your available balances.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"BTC\":\"0.59098578\",\"LTC\":\"3.31117268\", ... }\n    /// ```\n    pub fn return_balances(&mut self) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"method\", \"balance\");\n        params.insert(\"pair\", \"\");\n        self.private_query(&params)\n    }\n\n    /// Add a buy limit order to the exchange\n    /// limit_price\t: If the order gets executed, a new sell order will be placed,\n    /// with \"limit_price\" as its price.\n    /// daily_order (Optional) : Opens buy limit order which will be canceled\n    /// at 0:00 UTC unless it already has been executed. Possible value: True\n    pub fn buy_limit(&mut self,\n                     pair: Pair,\n                     amount: Volume,\n                     price: Price,\n                     price_limit: Option<Price>,\n                     daily_order: Option<bool>)\n                     -> Result<Map<String, Value>> {\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(name) => name,\n            None => return Err(ErrorKind::PairUnsupported.into()),\n        };\n\n        let amount_string = amount.to_string();\n        let price_string = price.to_string();\n        let price_limit_string = match price_limit {\n            Some(limit) => limit.to_string(),\n            None => \"\".to_string(),\n        };\n\n        let mut params = HashMap::new();\n        params.insert(\"method\", \"buy\");\n        params.insert(\"pair\", pair_name);\n\n        params.insert(\"amount\", &amount_string);\n        params.insert(\"price\", &price_string);\n        params.insert(\"limit_price\", &price_limit_string);\n        if let Some(order) = daily_order {\n            let daily_order_str = if order { \"True\" } else { \"\" }; // False is not a possible value\n            params.insert(\"daily_order\", daily_order_str);\n        }\n\n        self.private_query(&params)\n    }\n\n    /// Add a sell limit order to the exchange\n    /// limit_price\t: If the order gets executed, a new sell order will be placed,\n    /// with \"limit_price\" as its price.\n    /// daily_order (Optional) : Opens sell limit order which will be canceled\n    /// at 0:00 UTC unless it already has been executed. Possible value: True\n    pub fn sell_limit(&mut self,\n                      pair: Pair,\n                      amount: Volume,\n                      price: Price,\n                      price_limit: Option<Price>,\n                      daily_order: Option<bool>)\n                      -> Result<Map<String, Value>> {\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(name) => name,\n            None => return Err(ErrorKind::PairUnsupported.into()),\n        };\n\n        let amount_string = amount.to_string();\n        let price_string = price.to_string();\n        let price_limit_string = match price_limit {\n            Some(limit) => limit.to_string(),\n            None => \"\".to_string(),\n        };\n\n        let mut params = HashMap::new();\n        params.insert(\"method\", \"sell\");\n        params.insert(\"pair\", pair_name);\n\n        params.insert(\"amount\", &amount_string);\n        params.insert(\"price\", &price_string);\n        params.insert(\"limit_price\", &price_limit_string);\n        if let Some(order) = daily_order {\n            let daily_order_str = if order { \"True\" } else { \"\" }; // False is not a possible value\n            params.insert(\"daily_order\", daily_order_str);\n        }\n\n        self.private_query(&params)\n    }\n\n    /// Add a market buy order to the exchange\n    /// By placing a market order you acknowledge that the execution of your order depends\n    /// on the market conditions and that these conditions may be subject to sudden changes\n    /// that cannot be foreseen.\n    pub fn buy_market(&mut self, pair: Pair, amount: Volume) -> Result<Map<String, Value>> {\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(name) => name,\n            None => return Err(ErrorKind::PairUnsupported.into()),\n        };\n\n        let amount_string = amount.to_string();\n\n        let mut params = HashMap::new();\n        params.insert(\"method\", \"buy/market\");\n        params.insert(\"pair\", pair_name);\n\n        params.insert(\"amount\", &amount_string);\n\n        self.private_query(&params)\n    }\n\n    /// Add a market sell order to the exchange\n    /// By placing a market order you acknowledge that the execution of your order depends\n    /// on the market conditions and that these conditions may be subject to sudden changes\n    /// that cannot be foreseen.\n    pub fn sell_market(&mut self, pair: Pair, amount: Volume) -> Result<Map<String, Value>> {\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(name) => name,\n            None => return Err(ErrorKind::PairUnsupported.into()),\n        };\n\n        let amount_string = amount.to_string();\n\n        let mut params = HashMap::new();\n        params.insert(\"method\", \"sell/market\");\n        params.insert(\"pair\", pair_name);\n\n        params.insert(\"amount\", &amount_string);\n\n        self.private_query(&params)\n    }\n}\n\n\n#[cfg(test)]\nmod gdax_api_tests {\n    use super::*;\n\n    #[test]\n    fn should_block_or_not_block_when_enabled_or_disabled() {\n        let mut api = GdaxApi {\n            last_request: helpers::get_unix_timestamp_ms(),\n            api_key: \"\".to_string(),\n            api_secret: \"\".to_string(),\n            customer_id: \"\".to_string(),\n            http_client: Client::new(),\n            burst: false,\n        };\n\n        let mut counter = 0;\n        loop {\n            api.set_burst(false);\n            let start = helpers::get_unix_timestamp_ms();\n            api.block_or_continue();\n            api.last_request = helpers::get_unix_timestamp_ms();\n\n            let difference = api.last_request - start;\n            assert!(difference >= 334);\n            assert!(difference < 10000);\n\n\n            api.set_burst(true);\n            let start = helpers::get_unix_timestamp_ms();\n            api.block_or_continue();\n            api.last_request = helpers::get_unix_timestamp_ms();\n\n            let difference = api.last_request - start;\n            assert!(difference < 10);\n\n            counter = counter + 1;\n            if counter >= 3 { break; }\n        }\n    }\n}\n"
  },
  {
    "path": "src/gdax/credentials.rs",
    "content": "//! Contains the Gdax credentials.\n\nuse serde_json;\nuse serde_json::Value;\n\nuse crate::coinnect::Credentials;\nuse crate::exchange::Exchange;\nuse crate::helpers;\nuse crate::error::*;\n\nuse std::collections::HashMap;\nuse std::str::FromStr;\nuse std::fs::File;\nuse std::io::Read;\nuse std::path::PathBuf;\n\n#[derive(Debug, Clone)]\npub struct GdaxCreds {\n    exchange: Exchange,\n    name: String,\n    data: HashMap<String, String>,\n}\n\nimpl GdaxCreds {\n    /// Create a new `GdaxCreds` from a json configuration file. This file must follow this\n    /// structure:\n    ///\n    /// ```json\n    /// {\n    ///     \"account_gdax\": {\n    ///         \"exchange\"  : \"gdax\",\n    ///         \"api_key\"   : \"123456789ABCDEF\",\n    ///         \"api_secret\": \"ABC&EF?abcdef\",\n    ///         \"passphrase\": \"123456\"\n    ///     },\n    ///     \"account_bitstamp\": {\n    ///         \"exchange\"   : \"bitstamp\",\n    ///         \"api_key\"    : \"1234567890ABCDEF1234567890ABCDEF\",\n    ///         \"api_secret\" : \"1234567890ABCDEF1234567890ABCDEF\",\n    ///         \"customer_id\": \"123456\"\n    ///     }\n    /// }\n    /// ```\n    /// For this example, you could use load your Gdax account with\n    /// `GdaxAPI::new(GdaxCreds::new_from_file(\"account_gdax\", Path::new(\"/keys.json\")))`\n    pub fn new_from_file(name: &str, path: PathBuf) -> Result<Self> {\n        let mut f = File::open(&path)?;\n        let mut buffer = String::new();\n        f.read_to_string(&mut buffer)?;\n\n        let data: Value = serde_json::from_str(&buffer)?;\n        let json_obj = data.as_object()\n            .ok_or_else(|| ErrorKind::BadParse)?\n            .get(name)\n            .ok_or_else(|| ErrorKind::MissingField(name.to_string()))?;\n\n        let api_key = helpers::get_json_string(json_obj, \"api_key\")?;\n        let api_secret = helpers::get_json_string(json_obj, \"api_secret\")?;\n        let passphrase = helpers::get_json_string(json_obj, \"passphrase\")?;\n        let exchange = {\n            let exchange_str = helpers::get_json_string(json_obj, \"exchange\")?;\n            Exchange::from_str(exchange_str)\n                .chain_err(|| ErrorKind::InvalidFieldValue(\"exchange\".to_string()))?\n        };\n\n        if exchange != Exchange::Gdax {\n            return Err(ErrorKind::InvalidConfigType(Exchange::Gdax, exchange).into());\n        }\n\n        Ok(GdaxCreds::new(name, api_key, api_secret, passphrase))\n    }\n\n\n    /// Create a new `GdaxCreds` from arguments.\n    pub fn new(name: &str, api_key: &str, api_secret: &str, passphrase: &str) -> Self {\n        let mut creds = GdaxCreds {\n            data: HashMap::new(),\n            exchange: Exchange::Gdax,\n            name: if name.is_empty() {\n                \"GdaxClient\".to_string()\n            } else {\n                name.to_string()\n            },\n        };\n\n\n        //if api_key.is_empty() {\n        //warning!(\"No API key set for the Gdax client\");\n        //}\n        creds\n            .data\n            .insert(\"api_key\".to_string(), api_key.to_string());\n\n        //if api_secret.is_empty() {\n        //warning!(\"No API secret set for the Gdax client\");\n        //}\n        creds\n            .data\n            .insert(\"api_secret\".to_string(), api_secret.to_string());\n\n        //if api_secret.is_empty() {\n        //warning!(\"No API customer ID set for the Gdax client\");\n        //}\n        creds\n            .data\n            .insert(\"passphrase\".to_string(), passphrase.to_string());\n\n        creds\n    }\n}\n\nimpl Credentials for GdaxCreds {\n    /// Return a value from the credentials.\n    fn get(&self, key: &str) -> Option<String> {\n        if let Some(res) = self.data.get(key) {\n            Some(res.clone())\n        } else {\n            None\n        }\n    }\n\n    fn name(&self) -> String {\n        self.name.clone()\n    }\n\n    fn exchange(&self) -> Exchange {\n        self.exchange\n    }\n}\n"
  },
  {
    "path": "src/gdax/generic_api.rs",
    "content": "//! Use this module to interact with Gdax through a Generic API.\n//! This a more convenient and safe way to deal with the exchange since methods return a Result<>\n//! but this generic API does not provide all the functionnality that Gdax offers.\n\nuse crate::exchange::ExchangeApi;\nuse crate::gdax::api::GdaxApi;\nuse crate::gdax::utils;\n\nuse crate::error::*;\nuse crate::types::*;\nuse crate::helpers;\n\nimpl ExchangeApi for GdaxApi {\n    fn ticker(&mut self, pair: Pair) -> Result<Ticker> {\n\n        let result = self.return_ticker(pair)?;\n\n        let price = helpers::from_json_bigdecimal(&result[\"price\"], \"price\")?;\n        let ask = helpers::from_json_bigdecimal(&result[\"ask\"], \"ask\")?;\n        let bid = helpers::from_json_bigdecimal(&result[\"bid\"], \"bid\")?;\n        let vol = helpers::from_json_bigdecimal(&result[\"volume\"], \"volume\")?;\n\n        Ok(Ticker {\n               timestamp: helpers::get_unix_timestamp_ms(),\n               pair,\n               last_trade_price: price,\n               lowest_ask: ask,\n               highest_bid: bid,\n               volume: Some(vol),\n           })\n    }\n\n    fn orderbook(&mut self, pair: Pair) -> Result<Orderbook> {\n\n        let raw_response = self.return_order_book(pair)?;\n\n        let result = utils::parse_result(&raw_response)?;\n\n        let mut ask_offers = Vec::new();\n        let mut bid_offers = Vec::new();\n\n        let ask_array =\n            result[\"asks\"]\n                .as_array()\n                .ok_or_else(|| ErrorKind::InvalidFieldFormat(format!(\"{}\", result[\"asks\"])))?;\n        let bid_array =\n            result[\"bids\"]\n                .as_array()\n                .ok_or_else(|| ErrorKind::InvalidFieldFormat(format!(\"{}\", result[\"asks\"])))?;\n\n        for ask in ask_array {\n            let price = helpers::from_json_bigdecimal(&ask[0], \"ask price\")?;\n            let volume = helpers::from_json_bigdecimal(&ask[1], \"ask volume\")?;\n\n            ask_offers.push((price, volume));\n        }\n\n        for bid in bid_array {\n            let price = helpers::from_json_bigdecimal(&bid[0], \"bid price\")?;\n            let volume = helpers::from_json_bigdecimal(&bid[1], \"bid volume\")?;\n\n            bid_offers.push((price, volume));\n        }\n\n        Ok(Orderbook {\n            timestamp: helpers::get_unix_timestamp_ms(),\n            pair: pair,\n            asks: ask_offers,\n            bids: bid_offers,\n        })\n    }\n\n    fn add_order(&mut self,\n                 order_type: OrderType,\n                 pair: Pair,\n                 quantity: Volume,\n                 price: Option<Price>)\n                 -> Result<OrderInfo> {\n        //let pair_name = match utils::get_pair_string(&pair) {\n        //Some(name) => name,\n        //None => return Err(ErrorKind::PairUnsupported.into()),\n        //};\n\n        let result = match order_type {\n            OrderType::BuyLimit => {\n                if price.is_none() {\n                    return Err(ErrorKind::MissingPrice.into());\n                }\n\n                // Unwrap safe here with the check above.\n                self.buy_limit(pair, quantity, price.unwrap(), None, None)\n            }\n            OrderType::BuyMarket => self.buy_market(pair, quantity),\n            OrderType::SellLimit => {\n                if price.is_none() {\n                    return Err(ErrorKind::MissingPrice.into());\n                }\n\n                // Unwrap safe here with the check above.\n                self.sell_limit(pair, quantity, price.unwrap(), None, None)\n            }\n            OrderType::SellMarket => self.sell_market(pair, quantity),\n        };\n\n        Ok(OrderInfo {\n               timestamp: helpers::get_unix_timestamp_ms(),\n               identifier: vec![result?[\"id\"]\n                                    .as_str()\n                                    .ok_or_else(|| {\n                                                    ErrorKind::MissingField(\"id\".to_string())\n                                                })?\n                                    .to_string()],\n           })\n    }\n\n    /// Return the balances for each currency on the account\n    fn balances(&mut self) -> Result<Balances> {\n        let raw_response = self.return_balances()?;\n        let result = utils::parse_result(&raw_response)?;\n\n        let mut balances = Balances::new();\n\n        for (key, val) in result.iter() {\n            let currency = utils::get_currency_enum(key);\n\n            match currency {\n                Some(c) => {\n                    let amount = helpers::from_json_bigdecimal(&val, \"amount\")?;\n\n                    balances.insert(c, amount);\n                },\n                _ => ()\n            }\n        }\n\n        Ok(balances)\n    }\n}\n"
  },
  {
    "path": "src/gdax/mod.rs",
    "content": "//! Use this module to interact with Bitstamp exchange.\n\npub mod api;\npub mod generic_api;\npub mod credentials;\npub mod utils;\n\npub use self::credentials::GdaxCreds;\npub use self::api::GdaxApi;\n"
  },
  {
    "path": "src/gdax/utils.rs",
    "content": "use bidir_map::BidirMap;\n\nuse hmac::{Hmac, Mac, NewMac};\nuse sha2::{Sha256};\n\nuse serde_json;\nuse serde_json::Value;\nuse serde_json::value::Map;\n\nuse crate::error::*;\nuse crate::helpers;\nuse crate::types::Currency;\nuse crate::types::Pair;\nuse crate::types::Pair::*;\n\ntype HmacSha256 = Hmac<Sha256>;\n\nlazy_static! {\n    static ref PAIRS_STRING: BidirMap<Pair, &'static str> = {\n        let mut m = BidirMap::new();\n        m.insert(BCH_USD, \"bch-usd\");\n        m.insert(LTC_EUR, \"ltc-eur\");\n        m.insert(LTC_USD, \"ltc-usd\");\n        m.insert(LTC_BTC, \"ltc-btc\");\n        m.insert(ETH_EUR, \"eth-eur\");\n        m.insert(ETH_USD, \"eth-usd\");\n        m.insert(ETH_BTC, \"eth-btc\");\n        m.insert(BTC_GBP, \"btc-gbp\");\n        m.insert(BTC_EUR, \"btc-eur\");\n        m.insert(BTC_USD, \"btc-usd\");\n        m\n    };\n}\n\n/// Return the name associated to pair used by Bitstamp\n/// If the Pair is not supported, None is returned.\npub fn get_pair_string(pair: &Pair) -> Option<&&str> {\n    PAIRS_STRING.get_by_first(pair)\n}\n\n/// Return the Pair enum associated to the string used by Bitstamp\n/// If the Pair is not supported, None is returned.\npub fn get_pair_enum(pair: &str) -> Option<&Pair> {\n    PAIRS_STRING.get_by_second(&pair)\n}\n\npub fn build_signature(nonce: &str,\n                       passphrase: &str,\n                       api_key: &str,\n                       api_secret: &str)\n                       -> Result<String> {\n    const C: &'static [u8] = b\"0123456789ABCDEF\";\n\n    let message = nonce.to_owned() + passphrase + api_key;\n\n    let mut mac = HmacSha256::new_from_slice(api_secret.as_bytes()).unwrap();\n\n    mac.update(message.as_bytes());\n    let result = mac.finalize();\n\n    let raw_signature = result.into_bytes();\n    let mut signature = Vec::with_capacity(raw_signature.len() * 2);\n    for &byte in &raw_signature {\n        signature.push(C[(byte >> 4) as usize]);\n        signature.push(C[(byte & 0xf) as usize]);\n    }\n    // TODO: Handle correctly the from_utf8 errors with error_chain.\n    Ok(String::from_utf8(signature)?)\n}\n\npub fn build_url(method: &str, pair: &str) -> String {\n    match method {\n        \"ticker\" => \"https://api.gdax.com/products/\".to_string() + pair + \"/ticker\",\n        \"order_book\" => \"https://api.gdax.com/products/\".to_string() + pair + \"/book\",\n        \"transactions\" => \"https://api.gdax.com/accounts/\".to_string() + pair + \"/ledger\",\n        _ => \"not implemented yet\".to_string(),\n    }\n}\n\npub fn deserialize_json(json_string: &str) -> Result<Map<String, Value>> {\n    let data: Value = match serde_json::from_str(json_string) {\n        Ok(data) => data,\n        Err(_) => return Err(ErrorKind::BadParse.into()),\n    };\n\n    match data.as_object() {\n        Some(value) => Ok(value.clone()),\n        None => Err(ErrorKind::BadParse.into()),\n    }\n}\n\npub fn generate_nonce(fixed_nonce: Option<String>) -> String {\n    match fixed_nonce {\n        Some(v) => v,\n        None => helpers::get_unix_timestamp_ms().to_string(),\n    }\n}\n\n/// If error array is null, return the result (encoded in a json object)\n/// else return the error string found in array\npub fn parse_result(response: &Map<String, Value>) -> Result<Map<String, Value>> {\n    let error_msg = match response.get(\"error\") {\n        Some(error) => {\n            error\n                .as_str()\n                .ok_or_else(|| ErrorKind::InvalidFieldFormat(\"error\".to_string()))?\n        }\n        None => return Ok(response.clone()),\n    };\n\n    match error_msg.as_ref() {\n        \"Invalid command.\" => Err(ErrorKind::InvalidArguments.into()),\n        \"Invalid API key/secret pair.\" => Err(ErrorKind::BadCredentials.into()),\n        \"Total must be at least 0.0001.\" => Err(ErrorKind::InsufficientOrderSize.into()),\n        other => Err(ErrorKind::ExchangeSpecificError(other.to_string()).into()),\n    }\n}\n\n/// Return the currency enum associated with the\n/// string used by Bitstamp. If no currency is found,\n/// return None\n/// # Examples\n///\n/// ```\n/// use coinnect::gdax::utils::get_currency_enum;\n/// use coinnect::types::Currency;\n///\n/// let currency = get_currency_enum(\"usd_balance\");\n/// assert_eq!(Some(Currency::USD), currency);\n/// ```\npub fn get_currency_enum(currency: &str) -> Option<Currency> {\n    match currency {\n        \"btc_balance\" => Some(Currency::BTC),\n        \"eur_balance\" => Some(Currency::EUR),\n        \"ltc_balance\" => Some(Currency::LTC),\n        \"gbp_balance\" => Some(Currency::GBP),\n        \"usd_balance\" => Some(Currency::USD),\n        \"eth_balance\" => Some(Currency::ETH),\n        \"bch_balance\" => Some(Currency::BCH),\n        _ => None,\n    }\n}\n\n/// Return the currency string associated with the\n/// enum used by Gdax. If no currency is found,\n/// return None\n/// # Examples\n///\n/// ```\n/// use coinnect::gdax::utils::get_currency_string;\n/// use coinnect::types::Currency;\n///\n/// let currency = get_currency_string(Currency::USD);\n/// assert_eq!(currency, Some(\"USD\".to_string()));\n/// ```\npub fn get_currency_string(currency: Currency) -> Option<String> {\n    match currency {\n        Currency::BTC => Some(\"BTC\".to_string()),\n        Currency::EUR => Some(\"EUR\".to_string()),\n        Currency::LTC => Some(\"LTC\".to_string()),\n        Currency::GBP => Some(\"GBP\".to_string()),\n        Currency::USD => Some(\"USD\".to_string()),\n        Currency::ETH => Some(\"ETH\".to_string()),\n        Currency::BCH => Some(\"BCH\".to_string()),\n        _ => None,\n    }\n}\n"
  },
  {
    "path": "src/helpers/mod.rs",
    "content": "use serde_json::Value;\nuse crate::error::*;\nuse bigdecimal::BigDecimal;\nuse std::str::FromStr;\n\nuse std::collections::HashMap;\nuse chrono::prelude::*;\n\n// Helper functions\n\npub fn url_encode_hashmap(hashmap: &HashMap<&str, &str>) -> String {\n    if hashmap.is_empty() {\n        return \"\".to_string();\n    }\n    let mut acc = \"\".to_string();\n    for (name, param) in hashmap {\n        acc += &(name.to_string() + \"=\" + param + \"&\");\n    }\n    acc.pop(); // remove the last \"&\"\n    acc\n}\n\npub fn get_unix_timestamp_ms() -> i64 {\n    let now = Utc::now();\n    let seconds: i64 = now.timestamp();\n    let nanoseconds: i64 = now.nanosecond() as i64;\n    (seconds * 1000) + (nanoseconds / 1000 / 1000)\n}\n\npub fn get_unix_timestamp_us() -> i64 {\n    let now = Utc::now();\n    let seconds: i64 = now.timestamp();\n    let nanoseconds: i64 = now.nanosecond() as i64;\n    (seconds * 1000 * 1000) + (nanoseconds / 1000)\n}\n\npub fn strip_empties(x: &mut HashMap<&str, &str>) {\n    let empties: Vec<_> = x.iter()\n        .filter(|&(_, &v)| v.is_empty())\n        .map(|(k, _)| (*k).clone())\n        .collect();\n    for empty in empties {\n        x.remove(&empty);\n    }\n}\n\npub fn get_json_string<'a>(json_obj: &'a Value, key: &str) -> Result<&'a str> {\n    Ok(json_obj\n           .get(key)\n           .ok_or_else(|| ErrorKind::MissingField(key.to_string()))?\n           .as_str()\n           .ok_or_else(|| ErrorKind::InvalidFieldFormat(key.to_string()))?)\n}\n\npub fn from_json_bigdecimal(json_obj: &Value, key: &str) -> Result<BigDecimal> {\n    let num = json_obj\n        .as_str()\n        .ok_or_else(|| ErrorKind::MissingField(key.to_string()))?;\n\n    Ok(BigDecimal::from_str(num).chain_err(|| ErrorKind::InvalidFieldFormat(key.to_string()))?)\n}\n"
  },
  {
    "path": "src/kraken/api.rs",
    "content": "//! Use this module to interact with the raw-original API provided by Kraken.\n//! It is recommended to use a nonce window setting of 5000 for your API key when sending requests in quick succession in order to avoid nonce errors.\n//! WARNING: Special attention should be paid to error management: parsing number, etc.\n\nuse hmac::{Hmac, Mac, NewMac};\nuse sha2::{Sha256, Sha512, Digest};\n\nuse hyper_native_tls::NativeTlsClient;\nuse hyper::Client;\nuse hyper::header;\nuse hyper::net::HttpsConnector;\n\nuse data_encoding::BASE64;\n\nuse serde_json::Value;\nuse serde_json::value::Map;\n\nuse std::collections::HashMap;\nuse std::io::Read;\nuse std::thread;\nuse std::time::Duration;\nuse std::str;\n\nuse crate::error::*;\nuse crate::helpers;\n\nuse crate::exchange::Exchange;\nuse crate::coinnect::Credentials;\nuse crate::kraken::utils;\n\nheader! {\n    #[doc(hidden)]\n    (KeyHeader, \"API-Key\") => [String]\n}\n\nheader! {\n    #[doc(hidden)]\n    (SignHeader, \"API-Sign\") => [String]\n}\n\n#[derive(Debug)]\npub struct KrakenApi {\n    last_request: i64, // unix timestamp in ms, to avoid ban\n    api_key: String,\n    api_secret: String,\n    otp: Option<String>, // two-factor password (if two-factor enabled, otherwise not required)\n    http_client: Client,\n    burst: bool,\n}\n\n\nimpl KrakenApi {\n    /// Create a new KrakenApi by providing an API key & API secret\n    pub fn new<C: Credentials>(creds: C) -> Result<KrakenApi> {\n        if creds.exchange() != Exchange::Kraken {\n            return Err(ErrorKind::InvalidConfigType(Exchange::Kraken, creds.exchange()).into());\n        }\n\n        // TODO: implement correctly the TLS error in error_chain.\n        let ssl = match NativeTlsClient::new() {\n            Ok(res) => res,\n            Err(_) => return Err(ErrorKind::TlsError.into()),\n        };\n        let connector = HttpsConnector::new(ssl);\n\n        Ok(KrakenApi {\n               last_request: 0,\n               api_key: creds.get(\"api_key\").unwrap_or_default(),\n               api_secret: creds.get(\"api_secret\").unwrap_or_default(),\n               otp: None,\n               http_client: Client::with_connector(connector),\n               burst: false,\n           })\n    }\n\n\n    /// Use to provide your two-factor password (if two-factor enabled, otherwise not required)\n    pub fn set_two_pass_auth(&mut self, otp: String) {\n        self.otp = Some(otp);\n    }\n\n    /// The number of calls in a given period is limited. In order to avoid a ban we limit\n    /// by default the number of api requests.\n    /// This function sets or removes the limitation.\n    /// Burst false implies no block.\n    /// Burst true implies there is a control over the number of calls allowed to the exchange\n    pub fn set_burst(&mut self, burst: bool) {\n        self.burst = burst\n    }\n\n    pub fn block_or_continue(&self) {\n        if ! self.burst {\n            let threshold: u64 = 2000; // 1 request/2sec\n            let offset: u64 = helpers::get_unix_timestamp_ms() as u64 - self.last_request as u64;\n            if offset < threshold {\n                let wait_ms = Duration::from_millis(threshold - offset);\n                thread::sleep(wait_ms);\n            }\n        }\n    }\n\n    fn public_query(&mut self,\n                    method: &str,\n                    params: &mut HashMap<&str, &str>)\n                    -> Result<Map<String, Value>> {\n        helpers::strip_empties(params);\n        let url = \"https://api.kraken.com/0/public/\".to_string() + method + \"?\" +\n                  &helpers::url_encode_hashmap(params);\n\n        self.block_or_continue();\n        //TODO: Handle correctly http errors with error_chain.\n        let mut response = match self.http_client.get(&url).send() {\n            Ok(response) => response,\n            Err(err) => return Err(ErrorKind::ServiceUnavailable(err.to_string()).into()),\n        };\n        self.last_request = helpers::get_unix_timestamp_ms();\n        let mut buffer = String::new();\n        response.read_to_string(&mut buffer)?;\n        utils::deserialize_json(&buffer)\n    }\n\n    fn private_query(&mut self,\n                     method: &str,\n                     mut params: &mut HashMap<&str, &str>)\n                     -> Result<Map<String, Value>> {\n        let url = \"https://api.kraken.com/0/private/\".to_string() + method;\n\n        let urlpath = \"/0/private/\".to_string() + method;\n\n        let nonce = helpers::get_unix_timestamp_ms().to_string();\n        helpers::strip_empties(&mut params);\n\n        let mut params = params.clone(); // TODO: Remove .clone()\n        params.insert(\"nonce\", &nonce);\n\n        if let Some(ref password) = self.otp {\n            params.insert(\"otp\", password);\n        }\n\n        let postdata = helpers::url_encode_hashmap(&params);\n\n        let signature = self.create_signature(urlpath, &postdata, &nonce)?;\n\n        let mut custom_header = header::Headers::new();\n        custom_header.set(KeyHeader(self.api_key.clone()));\n        custom_header.set(SignHeader(signature));\n\n        let mut res = match self.http_client\n                  .post(&url)\n                  .body(&postdata)\n                  .headers(custom_header)\n                  .send() {\n            Ok(res) => res,\n            Err(err) => return Err(ErrorKind::ServiceUnavailable(err.to_string()).into()),\n        };\n\n        let mut buffer = String::new();\n        res.read_to_string(&mut buffer)?;\n        utils::deserialize_json(&buffer)\n    }\n\n    fn create_signature(&self, urlpath: String, postdata: &str, nonce: &str) -> Result<String> {\n        let message_presha256 = nonce.to_string() + postdata;\n\n        let mut sha256 = Sha256::default();\n        sha256.update(&message_presha256.as_bytes());\n\n        let output = sha256.finalize();\n\n        let mut concatenated = urlpath.as_bytes().to_vec();\n        for elem in output {\n            concatenated.push(elem);\n        }\n\n        let hmac_key = BASE64.decode(self.api_secret.as_bytes())?;\n        let mut mac = Hmac::<Sha512>::new_from_slice(&hmac_key[..]).unwrap();\n        mac.update(&concatenated);\n        Ok(BASE64.encode(&mac.finalize().into_bytes()))\n    }\n\n    /// Result: Server's time\n    ///\n    /// ```json\n    /// unixtime =  as unix timestamp\n    /// rfc1123 = as RFC 1123 time format\n    /// ```\n    /// Note: This is to aid in approximating the skew time between the server and client.\n    pub fn get_server_time(&mut self) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        self.public_query(\"Time\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// info = info to retrieve (optional):\n    ///     info = all info (default)\n    /// aclass = asset class (optional):\n    ///     currency (default)\n    /// asset = comma delimited list of assets to get info on (optional.  default = all for\n    /// given asset class)\n    /// ```\n    /// Result: array of asset names and their info:\n    ///\n    /// ```json\n    /// <asset_name> = asset name\n    /// altname = alternate name\n    /// aclass = asset class\n    /// decimals = scaling decimal places for record keeping\n    /// display_decimals = scaling decimal places for output display\n    /// ```\n    pub fn get_asset_info(&mut self,\n                          info: &str,\n                          aclass: &str,\n                          asset: &str)\n                          -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"info\", info);\n        params.insert(\"aclass\", aclass);\n        params.insert(\"asset\", asset);\n        self.public_query(\"Assets\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// info = info to retrieve (optional):\n    ///     info = all info (default)\n    ///     leverage = leverage info\n    ///     fees = fees schedule\n    ///     margin = margin info\n    /// pair = comma delimited list of asset pairs to get info on (optional.  default = all)\n    /// ```\n    ///\n    /// Result: array of pair names and their info\n    ///\n    /// ```json\n    /// <pair_name> = pair name\n    ///     altname = alternate pair name\n    ///     aclass_base = asset class of base component\n    ///     base = asset id of base component\n    ///     aclass_quote = asset class of quote component\n    ///     quote = asset id of quote component\n    ///     lot = volume lot size\n    ///     pair_decimals = scaling decimal places for pair\n    ///     lot_decimals = scaling decimal places for volume\n    ///     lot_multiplier = amount to multiply lot volume by to get currency volume\n    ///     leverage_buy = array of leverage amounts available when buying\n    ///     leverage_sell = array of leverage amounts available when selling\n    ///     fees = fee schedule array in [volume, percent fee] tuples\n    ///     fees_maker = maker fee schedule array in [volume, percent fee] tuples (if on\n    ///     maker/taker)\n    ///     fee_volume_currency = volume discount currency\n    ///     margin_call = margin call level\n    ///     margin_stop = stop-out/liquidation margin level\n    /// ```\n    pub fn get_tradable_asset_pairs(&mut self,\n                                    info: &str,\n                                    pair: &str)\n                                    -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"info\", info);\n        params.insert(\"pair\", pair);\n        self.public_query(\"AssetPairs\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// pair = comma delimited list of asset pairs to get info on\n    /// ```\n    ///\n    /// Result: array of pair names and their ticker info\n    ///\n    /// ```json\n    /// <pair_name> = pair name\n    /// a = ask array(<price>, <whole lot volume>, <lot volume>),\n    /// b = bid array(<price>, <whole lot volume>, <lot volume>),\n    /// c = last trade closed array(<price>, <lot volume>),\n    /// v = volume array(<today>, <last 24 hours>),\n    /// p = volume weighted average price array(<today>, <last 24 hours>),\n    /// t = number of trades array(<today>, <last 24 hours>),\n    /// l = low array(<today>, <last 24 hours>),\n    /// h = high array(<today>, <last 24 hours>),\n    /// o = today's opening price\n    /// ```\n    pub fn get_ticker_information(&mut self, pair: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"pair\", pair);\n        self.public_query(\"Ticker\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// pair = asset pair to get OHLC data for\n    /// interval = time frame interval in minutes (optional):\n    ///     1 (default), 5, 15, 30, 60, 240, 1440, 10080, 21600\n    /// since = return committed OHLC data since given id (optional.  exclusive)\n    /// ```\n    ///\n    /// Result: array of pair name and OHLC data\n    ///\n    /// ```json\n    /// <pair_name> = pair name\n    ///     array of array entries(<time>, <open>, <high>, <low>, <close>, <vwap>, <volume>,\n    ///     <count>)\n    /// last = id to be used as since when polling for new, committed OHLC data\n    /// ```\n    ///\n    /// Note: the last entry in the OHLC array is for the current, not-yet-committed frame and will\n    /// always be present, regardless of the value of \"since\".\n    pub fn get_ohlc_data(&mut self,\n                         pair: &str,\n                         interval: &str,\n                         since: &str)\n                         -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"pair\", pair);\n        params.insert(\"interval\", interval);\n        params.insert(\"since\", since);\n        self.public_query(\"OHLC\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// pair = asset pair to get market depth for\n    /// count = maximum number of asks/bids (optional)\n    /// ```\n    /// Result: array of pair name and market depth\n    ///\n    /// ```json\n    /// <pair_name> = pair name\n    ///     asks = ask side array of array entries(<price>, <volume>, <timestamp>)\n    ///     bids = bid side array of array entries(<price>, <volume>, <timestamp>)\n    /// ```\n    pub fn get_order_book(&mut self, pair: &str, count: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"pair\", pair);\n        params.insert(\"count\", count);\n        self.public_query(\"Depth\", &mut params)\n    }\n\n\n    /// Input:\n    ///\n    /// ```json\n    /// pair = asset pair to get trade data for\n    /// since = return trade data since given id (optional.  exclusive)\n    /// ```\n    /// Result: array of pair name and recent trade data\n    ///\n    /// ```json\n    /// <pair_name> = pair name\n    ///     array of array entries(<price>, <volume>, <time>, <buy/sell>, <market/limit>,\n    /// <miscellaneous>)\n    /// last = id to be used as since when polling for new trade data\n    /// ```\n    pub fn get_recent_trades(&mut self, pair: &str, since: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"pair\", pair);\n        params.insert(\"since\", since);\n        self.public_query(\"Trades\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// pair = asset pair to get spread data for\n    /// since = return spread data since given id (optional.  inclusive)\n    /// ```\n    ///\n    /// Result: array of pair name and recent spread data\n    ///\n    /// ```json\n    /// <pair_name> = pair name\n    ///     array of array entries(<time>, <bid>, <ask>)\n    /// last = id to be used as since when polling for new spread data\n    /// ```\n    /// Note: \"since\" is inclusive so any returned data with the same time as the previous set\n    /// should overwrite all of the previous set's entries at that time\n    pub fn get_recent_spread_data(&mut self,\n                                  pair: &str,\n                                  since: &str)\n                                  -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"pair\", pair);\n        params.insert(\"since\", since);\n        self.public_query(\"Spread\", &mut params)\n    }\n\n    /// Result: array of asset names and balance amount\n    pub fn get_account_balance(&mut self) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        self.private_query(\"Balance\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// aclass = asset class (optional):\n    ///     currency (default)\n    /// asset = base asset used to determine balance (default = ZUSD)\n    /// ```\n    /// Result: array of trade balance info\n    ///\n    /// ```json\n    /// eb = equivalent balance (combined balance of all currencies)\n    /// tb = trade balance (combined balance of all equity currencies)\n    /// m = margin amount of open positions\n    /// n = unrealized net profit/loss of open positions\n    /// c = cost basis of open positions\n    /// v = current floating valuation of open positions\n    /// e = equity = trade balance + unrealized net profit/loss\n    /// mf = free margin = equity - initial margin (maximum margin available to open new positions)\n    /// ml = margin level = (equity / initial margin) * 100\n    /// ```\n    /// Note: Rates used for the floating valuation is the midpoint of the best bid and ask prices\n    pub fn get_trade_balance(&mut self, aclass: &str, asset: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"aclass\", aclass);\n        params.insert(\"asset\", asset);\n        self.private_query(\"TradeBalance\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// trades = whether or not to include trades in output (optional.  default = false)\n    /// userref = restrict results to given user reference id (optional)\n    /// ```\n    ///\n    /// Result: array of order info in open array with txid as the key\n    ///\n    /// ```json\n    /// refid = Referral order transaction id that created this order\n    /// userref = user reference id\n    /// status = status of order:\n    ///     pending = order pending book entry\n    ///     open = open order\n    ///     closed = closed order\n    ///     canceled = order canceled\n    ///     expired = order expired\n    /// opentm = unix timestamp of when order was placed\n    /// starttm = unix timestamp of order start time (or 0 if not set)\n    /// expiretm = unix timestamp of order end time (or 0 if not set)\n    /// descr = order description info\n    ///     pair = asset pair\n    ///     type = type of order (buy/sell)\n    ///     ordertype = order type (See Add standard order)\n    ///     price = primary price\n    ///     price2 = secondary price\n    ///     leverage = amount of leverage\n    ///     order = order description\n    ///     close = conditional close order description (if conditional close set)\n    /// vol = volume of order (base currency unless viqc set in oflags)\n    /// vol_exec = volume executed (base currency unless viqc set in oflags)\n    /// cost = total cost (quote currency unless unless viqc set in oflags)\n    /// fee = total fee (quote currency)\n    /// price = average price (quote currency unless viqc set in oflags)\n    /// stopprice = stop price (quote currency, for trailing stops)\n    /// limitprice = triggered limit price (quote currency, when limit based order type triggered)\n    /// misc = comma delimited list of miscellaneous info\n    ///     stopped = triggered by stop price\n    ///     touched = triggered by touch price\n    ///     liquidated = liquidation\n    ///     partial = partial fill\n    /// oflags = comma delimited list of order flags\n    ///     viqc = volume in quote currency\n    ///     fcib = prefer fee in base currency (default if selling)\n    ///     fciq = prefer fee in quote currency (default if buying)\n    ///     nompp = no market price protection\n    /// trades = array of trade ids related to order (if trades info requested and data available)\n    /// ```\n    ///\n    /// Note: Unless otherwise stated, costs, fees, prices, and volumes are in the asset pair's\n    /// scale, not the currency's scale. For example, if the asset pair uses a lot size that has\n    /// a scale of 8, the volume will use a scale of 8, even if the currency it represents only has\n    /// a scale of 2.\n    /// Similarly, if the asset pair's pricing scale is 5, the scale will remain as 5, even if the\n    /// underlying currency has a scale of 8.\n    pub fn get_open_orders(&mut self, trades: &str, userref: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"trades\", trades);\n        params.insert(\"userref\", userref);\n        self.private_query(\"OpenOrders\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// trades = whether or not to include trades in output (optional.  default = false)\n    /// userref = restrict results to given user reference id (optional)\n    /// start = starting unix timestamp or order tx id of results (optional.  exclusive)\n    /// end = ending unix timestamp or order tx id of results (optional.  inclusive)\n    /// ofs = result offset\n    /// closetime = which time to use (optional)\n    ///     open\n    ///     close\n    ///     both (default)\n    /// ```\n    ///\n    /// Result: array of order info\n    ///\n    /// ```json\n    /// closed = array of order info.  See Get open orders.  Additional fields:\n    ///     closetm = unix timestamp of when order was closed\n    ///     reason = additional info on status (if any)\n    /// count = amount of available order info matching criteria\n    /// ```\n    /// Note: Times given by order tx ids are more accurate than unix timestamps. If an order tx id\n    /// is given for the time, the order's open time is used\n    pub fn get_closed_orders(&mut self,\n                             trades: &str,\n                             userref: &str,\n                             start: &str,\n                             end: &str,\n                             ofs: &str,\n                             closetime: &str)\n                             -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"trades\", trades);\n        params.insert(\"userref\", userref);\n        params.insert(\"start\", start);\n        params.insert(\"end\", end);\n        params.insert(\"ofs\", ofs);\n        params.insert(\"closetime\", closetime);\n        self.private_query(\"OpenOrders\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// trades = whether or not to include trades in output (optional.  default = false)\n    /// userref = restrict results to given user reference id (optional)\n    /// txid = comma delimited list of transaction ids to query info about (20 maximum)\n    /// ```\n    /// Result: associative array of orders info\n    ///\n    /// ```json\n    /// <order_txid> = order info.  See Get open orders/Get closed orders\n    /// ```\n    pub fn query_orders_info(&mut self,\n                             trades: &str,\n                             userref: &str,\n                             txid: &str)\n                             -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"trades\", trades);\n        params.insert(\"userref\", userref);\n        params.insert(\"txid\", txid);\n        self.private_query(\"QueryOrders\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// type = type of trade (optional)\n    ///     all = all types (default)\n    ///     any position = any position (open or closed)\n    ///     closed position = positions that have been closed\n    ///     closing position = any trade closing all or part of a position\n    ///     no position = non-positional trades\n    /// trades = whether or not to include trades related to position in output (optional.\n    /// default = false)\n    /// start = starting unix timestamp or trade tx id of results (optional.  exclusive)\n    /// end = ending unix timestamp or trade tx id of results (optional.  inclusive)\n    /// ofs = result offset\n    /// ```\n    /// Result: array of trade info\n    ///\n    /// ```json\n    /// trades = array of trade info with txid as the key\n    ///     ordertxid = order responsible for execution of trade\n    ///     pair = asset pair\n    ///     time = unix timestamp of trade\n    ///     type = type of order (buy/sell)\n    ///     ordertype = order type\n    ///     price = average price order was executed at (quote currency)\n    ///     cost = total cost of order (quote currency)\n    ///     fee = total fee (quote currency)\n    ///     vol = volume (base currency)\n    ///     margin = initial margin (quote currency)\n    ///     misc = comma delimited list of miscellaneous info\n    ///         closing = trade closes all or part of a position\n    /// count = amount of available trades info matching criteria\n    /// If the trade opened a position, the follow fields are also present in the trade info:\n    ///\n    ///     posstatus = position status (open/closed)\n    ///     cprice = average price of closed portion of position (quote currency)\n    ///     ccost = total cost of closed portion of position (quote currency)\n    ///     cfee = total fee of closed portion of position (quote currency)\n    ///     cvol = total fee of closed portion of position (quote currency)\n    ///     cmargin = total margin freed in closed portion of position (quote currency)\n    ///     net = net profit/loss of closed portion of position (quote currency, quote currency\n    ///     scale)\n    ///     trades = list of closing trades for position (if available)\n    /// ```\n    ///\n    /// Note:\n    ///\n    /// Unless otherwise stated, costs, fees, prices, and volumes are in the asset pair's scale,\n    /// not the currency's scale.\n    /// Times given by trade tx ids are more accurate than unix timestamps.\n    pub fn get_trades_history(&mut self,\n                              type_trade: &str,\n                              trades: &str,\n                              start: &str,\n                              end: &str,\n                              ofs: &str)\n                              -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"type\", type_trade);\n        params.insert(\"trades\", trades);\n        params.insert(\"start\", start);\n        params.insert(\"end\", end);\n        params.insert(\"ofs\", ofs);\n        self.private_query(\"TradesHistory\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// txid = comma delimited list of transaction ids to query info about (20 maximum)\n    /// trades = whether or not to include trades related to position in output (optional.\n    /// default = false)\n    /// ```\n    // Result: associative array of trades info\n    ///\n    /// ```json\n    /// <trade_txid> = trade info.  See Get trades history\n    /// ```\n    pub fn query_trades_info(&mut self, txid: &str, trades: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"txid\", txid);\n        params.insert(\"trades\", trades);\n        self.private_query(\"QueryTrades\", &mut params)\n    }\n    /// Input:\n    ///\n    /// ```json\n    /// txid = comma delimited list of transaction ids to restrict output to\n    /// docalcs = whether or not to include profit/loss calculations (optional.  default = false)\n    /// ```\n    /// Result: associative array of open position info\n    ///\n    /// ```json\n    /// <position_txid> = open position info\n    ///     ordertxid = order responsible for execution of trade\n    ///     pair = asset pair\n    ///     time = unix timestamp of trade\n    ///     type = type of order used to open position (buy/sell)\n    ///     ordertype = order type used to open position\n    ///     cost = opening cost of position (quote currency unless viqc set in oflags)\n    ///     fee = opening fee of position (quote currency)\n    ///     vol = position volume (base currency unless viqc set in oflags)\n    ///     vol_closed = position volume closed (base currency unless viqc set in oflags)\n    ///     margin = initial margin (quote currency)\n    ///     value = current value of remaining position (if docalcs requested.  quote currency)\n    ///     net = unrealized profit/loss of remaining position (if docalcs requested.  quote\n    ///           currency, quote currency scale)\n    ///     misc = comma delimited list of miscellaneous info\n    ///     oflags = comma delimited list of order flags\n    ///         viqc = volume in quote currency\n    /// ```\n    ///\n    /// Note: Unless otherwise stated, costs, fees, prices, and volumes are in the asset pair's\n    /// scale, not the currency's scale.\n    pub fn get_open_positions(&mut self, txid: &str, docalcs: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"txid\", txid);\n        params.insert(\"docalcs\", docalcs);\n        self.private_query(\"OpenPositions\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// aclass = asset class (optional):\n    ///     currency (default)\n    /// asset = comma delimited list of assets to restrict output to (optional.  default = all)\n    /// type = type of ledger to retrieve (optional):\n    ///     all (default)\n    ///     deposit\n    ///     withdrawal\n    ///     trade\n    ///     margin\n    /// start = starting unix timestamp or ledger id of results (optional.  exclusive)\n    /// end = ending unix timestamp or ledger id of results (optional.  inclusive)\n    /// ofs = result offset\n    /// ```\n    /// Result: associative array of ledgers info\n    ///\n    /// ```json\n    /// <ledger_id> = ledger info\n    ///     refid = reference id\n    ///     time = unx timestamp of ledger\n    ///     type = type of ledger entry\n    ///     aclass = asset class\n    ///     asset = asset\n    ///     amount = transaction amount\n    ///     fee = transaction fee\n    ///     balance = resulting balance\n    /// ```\n    /// Note: Times given by ledger ids are more accurate than unix timestamps.\n    pub fn get_ledgers_info(&mut self,\n                            aclass: &str,\n                            asset: &str,\n                            type_ledger: &str,\n                            start: &str,\n                            end: &str,\n                            ofs: &str)\n                            -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"aclass\", aclass);\n        params.insert(\"asset\", asset);\n        params.insert(\"type_ledger\", type_ledger);\n        params.insert(\"start\", start);\n        params.insert(\"end\", end);\n        params.insert(\"ofs\", ofs);\n        self.private_query(\"Ledgers\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// id = comma delimited list of ledger ids to query info about (20 maximum)\n    /// ```\n    /// Result: associative array of ledgers info\n    ///\n    /// ```json\n    /// <ledger_id> = ledger info.  See Get ledgers info\n    /// ```\n    pub fn query_ledgers(&mut self, id: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"id\", id);\n        self.private_query(\"QueryLedgers\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// pair = comma delimited list of asset pairs to get fee info on (optional)\n    /// fee-info = whether or not to include fee info in results (optional)\n    /// ```\n    /// Result: associative array\n    ///\n    /// ```json\n    /// currency = volume currency\n    /// volume = current discount volume\n    /// fees = array of asset pairs and fee tier info (if requested)\n    ///     fee = current fee in percent\n    ///     minfee = minimum fee for pair (if not fixed fee)\n    ///     maxfee = maximum fee for pair (if not fixed fee)\n    ///     nextfee = next tier's fee for pair (if not fixed fee.  nil if at lowest fee tier)\n    ///     nextvolume = volume level of next tier (if not fixed fee.  nil if at lowest fee tier)\n    ///     tiervolume = volume level of current tier (if not fixed fee.  nil if at lowest fee tier)\n    /// fees_maker = array of asset pairs and maker fee tier info (if requested) for any pairs on\n    ///             maker/taker schedule\n    ///     fee = current fee in percent\n    ///     minfee = minimum fee for pair (if not fixed fee)\n    ///     maxfee = maximum fee for pair (if not fixed fee)\n    ///     nextfee = next tier's fee for pair (if not fixed fee.  nil if at lowest fee tier)\n    ///     nextvolume = volume level of next tier (if not fixed fee.  nil if at lowest fee tier)\n    ///     tiervolume = volume level of current tier (if not fixed fee.  nil if at lowest fee tier)\n    /// ```\n    /// Note: If an asset pair is on a maker/taker fee schedule, the taker side is given in \"fees\"\n    /// and maker side in \"fees_maker\". For pairs not on maker/taker, they will only be given in\n    /// \"fees\".\n    pub fn get_trade_volume(&mut self, pair: &str, fee_info: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"pair\", pair);\n        params.insert(\"fee-info\", fee_info);\n        self.private_query(\"TradeVolume\", &mut params)\n    }\n\n    // TODO: add optional closing order\n    /// Input:\n    ///\n    /// ```json\n    /// pair = asset pair\n    /// type = type of order (buy/sell)\n    /// ordertype = order type:\n    ///     market\n    ///     limit (price = limit price)\n    ///     stop-loss (price = stop loss price)\n    ///     take-profit (price = take profit price)\n    ///     stop-loss-profit (price = stop loss price, price2 = take profit price)\n    ///     stop-loss-profit-limit (price = stop loss price, price2 = take profit price)\n    ///     stop-loss-limit (price = stop loss trigger price, price2 = triggered limit price)\n    ///     take-profit-limit (price = take profit trigger price, price2 = triggered limit price)\n    ///     trailing-stop (price = trailing stop offset)\n    ///     trailing-stop-limit (price = trailing stop offset, price2 = triggered limit offset)\n    ///     stop-loss-and-limit (price = stop loss price, price2 = limit price)\n    ///     settle-position\n    /// price = price (optional.  dependent upon ordertype)\n    /// price2 = secondary price (optional.  dependent upon ordertype)\n    /// volume = order volume in lots\n    /// leverage = amount of leverage desired (optional.  default = none)\n    /// oflags = comma delimited list of order flags (optional):\n    ///     viqc = volume in quote currency (not available for leveraged orders)\n    ///     fcib = prefer fee in base currency\n    ///     fciq = prefer fee in quote currency\n    ///     nompp = no market price protection\n    ///     post = post only order (available when ordertype = limit)\n    /// starttm = scheduled start time (optional):\n    ///     0 = now (default)\n    ///     +<n> = schedule start time <n> seconds from now\n    ///     <n> = unix timestamp of start time\n    /// expiretm = expiration time (optional):\n    ///     0 = no expiration (default)\n    ///     +<n> = expire <n> seconds from now\n    ///     <n> = unix timestamp of expiration time\n    /// userref = user reference id.  32-bit signed number.  (optional)\n    /// validate = validate inputs only.  do not submit order (optional)\n    ///\n    /// optional closing order to add to system when order gets filled:\n    ///     close[ordertype] = order type\n    ///     close[price] = price\n    ///     close[price2] = secondary price\n    /// ```\n    /// Result:\n    ///\n    /// ```json\n    /// descr = order description info\n    ///     order = order description\n    ///     close = conditional close order description (if conditional close set)\n    /// txid = array of transaction ids for order (if order was added successfully)\n    /// Errors: errors include (but are not limited to):\n    ///\n    /// EGeneral:Invalid arguments\n    /// EService:Unavailable\n    /// ETrade:Invalid request\n    /// EOrder:Cannot open position\n    /// EOrder:Cannot open opposing position\n    /// EOrder:Margin allowance exceeded\n    /// EOrder:Margin level too low\n    /// EOrder:Insufficient margin (exchange does not have sufficient funds to allow margin trading)\n    /// EOrder:Insufficient funds (insufficient user funds)\n    /// EOrder:Order minimum not met (volume too low)\n    /// EOrder:Orders limit exceeded\n    /// EOrder:Positions limit exceeded\n    /// EOrder:Rate limit exceeded\n    /// EOrder:Scheduled orders limit exceeded\n    /// EOrder:Unknown position\n    /// ```\n    /// Note:\n    ///\n    /// See Get tradable asset pairs for specifications on asset pair prices, lots, and leverage.\n    /// Prices can be preceded by +, -, or # to signify the price as a relative amount (with the\n    ///     exception of trailing stops, which are always relative). + adds the amount to the\n    ///     current offered price. - subtracts the amount from the current offered price. # will\n    ///     either add or subtract the amount to the current offered price, depending on the type\n    ///     and order type used. Relative prices can be suffixed with a % to signify the relative\n    ///     amount as a percentage of the offered price.\n    /// For orders using leverage, 0 can be used for the volume to auto-fill the volume needed to\n    /// close out your position.\n    /// If you receive the error \"EOrder:Trading agreement required\", refer to your API key\n    /// management page for further details.\n    pub fn add_standard_order(&mut self,\n                              pair: &str,\n                              type_order: &str,\n                              ordertype: &str,\n                              price: &str,\n                              price2: &str,\n                              volume: &str,\n                              leverage: &str,\n                              oflags: &str,\n                              starttm: &str,\n                              expiretm: &str,\n                              userref: &str,\n                              validate: &str)\n                              -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"pair\", pair);\n        params.insert(\"type\", type_order);\n        params.insert(\"ordertype\", ordertype);\n        params.insert(\"price\", price);\n        params.insert(\"price2\", price2);\n        params.insert(\"volume\", volume);\n        params.insert(\"leverage\", leverage);\n        params.insert(\"oflags\", oflags);\n        params.insert(\"starttm\", starttm);\n        params.insert(\"expiretm\", expiretm);\n        params.insert(\"userref\", userref);\n        params.insert(\"validate\", validate);\n        self.private_query(\"AddOrder\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// txid = transaction id\n    /// ```\n    /// Result:\n    ///\n    /// ```json\n    /// count = number of orders canceled\n    /// pending = if set, order(s) is/are pending cancellation\n    /// ```\n    /// Note: txid may be a user reference id.\n    pub fn cancel_open_order(&mut self, txid: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"txid\", txid);\n        self.private_query(\"CancelOrder\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// aclass = asset class (optional):\n    ///     currency (default)\n    /// asset = asset being deposited\n    /// ```\n    /// Result: associative array of deposit methods:\n    ///\n    /// ```json\n    /// method = name of deposit method\n    /// limit = maximum net amount that can be deposited right now, or false if no limit\n    /// fee = amount of fees that will be paid\n    /// address-setup-fee = whether or not method has an address setup fee (optional)\n    /// ```\n    pub fn get_deposit_methods(&mut self, aclass: &str, asset: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"aclass\", aclass);\n        params.insert(\"asset\", asset);\n        self.private_query(\"DepositMethods\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// aclass = asset class (optional):\n    ///     currency (default)\n    /// asset = asset being deposited\n    /// method = name of the deposit method\n    /// new = whether or not to generate a new address (optional.  default = false)\n    /// ```\n    /// Result: associative array of deposit addresses:\n    ///\n    /// ```json\n    /// address = deposit address\n    /// expiretm = expiration time in unix timestamp, or 0 if not expiring\n    /// new = whether or not address has ever been used\n    /// ```\n    pub fn get_deposit_addresses(&mut self,\n                                 aclass: &str,\n                                 asset: &str,\n                                 method: &str,\n                                 new: &str)\n                                 -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"aclass\", aclass);\n        params.insert(\"asset\", asset);\n        params.insert(\"method\", method);\n        params.insert(\"new\", new);\n        self.private_query(\"DepositAddresses\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// aclass = asset class (optional):\n    ///     currency (default)\n    /// asset = asset being deposited\n    /// method = name of the deposit method\n    /// ```\n    /// Result: array of array deposit status information:\n    ///\n    /// ```json\n    /// method = name of the deposit method used\n    /// aclass = asset class\n    /// asset = asset X-ISO4217-A3 code\n    /// refid = reference id\n    /// txid = method transaction id\n    /// info = method transaction information\n    /// amount = amount deposited\n    /// fee = fees paid\n    /// time = unix timestamp when request was made\n    /// status = status of deposit\n    /// status-prop = additional status properties (if available)\n    ///     return = a return transaction initiated by Kraken\n    ///     onhold = deposit is on hold pending review\n    /// ```\n    /// For information about the status, please refer to the IFEX financial transaction states.\n    pub fn get_status_of_recent_deposits(&mut self,\n                                         aclass: &str,\n                                         asset: &str,\n                                         method: &str)\n                                         -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"aclass\", aclass);\n        params.insert(\"asset\", asset);\n        params.insert(\"method\", method);\n        self.private_query(\"DepositStatus\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// aclass = asset class (optional):\n    ///     currency (default)\n    /// asset = asset being withdrawn\n    /// key = withdrawal key name, as set up on your account\n    /// amount = amount to withdraw\n    /// ```\n    /// Result: associative array of withdrawal info:\n    ///\n    /// ```json\n    /// method = name of the withdrawal method that will be used\n    /// limit = maximum net amount that can be withdrawn right now\n    /// fee = amount of fees that will be paid\n    /// ```\n    pub fn get_withdrawal_information(&mut self,\n                                      aclass: &str,\n                                      asset: &str,\n                                      key: &str,\n                                      amount: &str)\n                                      -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"aclass\", aclass);\n        params.insert(\"asset\", asset);\n        params.insert(\"key\", key);\n        params.insert(\"amount\", amount);\n        self.private_query(\"WithdrawInfo\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// aclass = asset class (optional):\n    ///     currency (default)\n    /// asset = asset being withdrawn\n    /// key = withdrawal key name, as set up on your account\n    /// amount = amount to withdraw, including fees\n    /// ```\n    /// Result: associative array of withdrawal transaction:\n    ///\n    /// ```json\n    /// refid = reference id\n    /// ```\n    pub fn withdraw_funds(&mut self,\n                          aclass: &str,\n                          asset: &str,\n                          key: &str,\n                          amount: &str)\n                          -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"aclass\", aclass);\n        params.insert(\"asset\", asset);\n        params.insert(\"key\", key);\n        params.insert(\"amount\", amount);\n        self.private_query(\"Withdraw\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// aclass = asset class (optional):\n    ///     currency (default)\n    /// asset = asset being withdrawn\n    /// method = withdrawal method name (optional)\n    /// ```\n    /// Result: array of array withdrawal status information:\n    ///\n    /// ```json\n    /// method = name of the withdrawal method used\n    /// aclass = asset class\n    /// asset = asset X-ISO4217-A3 code\n    /// refid = reference id\n    /// txid = method transaction id\n    /// info = method transaction information\n    /// amount = amount withdrawn\n    /// fee = fees paid\n    /// time = unix timestamp when request was made\n    /// status = status of withdrawal\n    /// status-prop = additional status properties (if available)\n    ///     cancel-pending = cancelation requested\n    ///     canceled = canceled\n    ///     cancel-denied = cancelation requested but was denied\n    ///     return = a return transaction initiated by Kraken; it cannot be canceled\n    ///     onhold = withdrawal is on hold pending review\n    /// ```\n    /// For information about the status, please refer to the IFEX financial transaction states.\n    pub fn get_status_of_recent_withdrawals(&mut self,\n                                            aclass: &str,\n                                            asset: &str,\n                                            method: &str)\n                                            -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"aclass\", aclass);\n        params.insert(\"asset\", asset);\n        params.insert(\"method\", method);\n        self.private_query(\"WithdrawStatus\", &mut params)\n    }\n\n    /// Result: returns a token to be used when authenticating with private websockets API:\n    ///\n    /// ```json\n    /// expires = time in seconds when token expires\n    /// token = the token to be used for authentication\n    /// ```\n    pub fn get_websockets_token(&mut self) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        self.private_query(\"GetWebSocketsToken\", &mut params)\n    }\n\n    /// Input:\n    ///\n    /// ```json\n    /// aclass = asset class (optional):\n    ///     currency (default)\n    /// asset = asset being withdrawn\n    /// refid = withdrawal reference id\n    /// ```\n    /// Result:\n    /// ```json\n    /// true on success\n    /// ```\n    ///\n    /// Note: Cancelation cannot be guaranteed. This will put in a cancelation request. Depending\n    /// upon how far along the withdrawal process is, it may not be possible to cancel the\n    /// withdrawal.\n    pub fn request_withdrawal_cancelation(&mut self,\n                                          aclass: &str,\n                                          asset: &str,\n                                          refid: &str)\n                                          -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"aclass\", aclass);\n        params.insert(\"asset\", asset);\n        params.insert(\"refid\", refid);\n        self.private_query(\"WithdrawCancel\", &mut params)\n    }\n}\n\n#[cfg(test)]\nmod kraken_api_tests {\n    use super::*;\n\n    #[test]\n    fn should_block_or_not_block_when_enabled_or_disabled() {\n        let mut api = KrakenApi {\n            last_request: helpers::get_unix_timestamp_ms(),\n            api_key: \"\".to_string(),\n            api_secret: \"\".to_string(),\n            otp: None,\n            http_client: Client::new(),\n            burst: false,\n        };\n\n        let mut counter = 0;\n        loop {\n            api.set_burst(false);\n            let start = helpers::get_unix_timestamp_ms();\n            api.block_or_continue();\n            api.last_request = helpers::get_unix_timestamp_ms();\n\n            let difference = api.last_request - start;\n            assert!(difference >= 1999);\n            assert!(difference < 10000);\n\n\n            api.set_burst(true);\n            let start = helpers::get_unix_timestamp_ms();\n            api.block_or_continue();\n            api.last_request = helpers::get_unix_timestamp_ms();\n\n            let difference = api.last_request - start;\n            assert!(difference < 10);\n\n            counter = counter + 1;\n            if counter >= 3 { break; }\n        }\n    }\n}\n"
  },
  {
    "path": "src/kraken/credentials.rs",
    "content": "//! Contains the Kraken credentials.\n\nuse std::collections::HashMap;\nuse std::str::FromStr;\n\nuse serde_json;\nuse serde_json::Value;\n\nuse crate::coinnect::Credentials;\nuse crate::exchange::Exchange;\nuse crate::helpers;\nuse crate::error::*;\n\nuse std::fs::File;\nuse std::io::Read;\nuse std::path::PathBuf;\n\n#[derive(Debug, Clone)]\npub struct KrakenCreds {\n    exchange: Exchange,\n    name: String,\n    data: HashMap<String, String>,\n}\n\nimpl KrakenCreds {\n    /// Create a new `KrakenCreds` from arguments.\n    pub fn new(name: &str, api_key: &str, api_secret: &str) -> Self {\n        let mut creds = KrakenCreds {\n            data: HashMap::new(),\n            exchange: Exchange::Kraken,\n            name: if name.is_empty() {\n                \"KrakenClient\".to_string()\n            } else {\n                name.to_string()\n            },\n        };\n\n\n        //if api_key.is_empty() {\n        //warning!(\"No API key set for the Bitstamp client\");\n        //}\n        creds\n            .data\n            .insert(\"api_key\".to_string(), api_key.to_string());\n\n        //if api_secret.is_empty() {\n        //warning!(\"No API secret set for the Bitstamp client\");\n        //}\n        creds\n            .data\n            .insert(\"api_secret\".to_string(), api_secret.to_string());\n\n        creds\n    }\n\n\n    /// Create a new `KrakenCreds` from a json configuration file. This file must follow this\n    /// structure:\n    ///\n    /// ```json\n    /// {\n    ///     \"account_kraken\": {\n    ///         \"exchange\"  : \"kraken\",\n    ///         \"api_key\"   : \"123456789ABCDEF\",\n    ///         \"api_secret\": \"ABC&EF?abcdef\"\n    ///     },\n    ///     \"account_bitstamp\": {\n    ///         \"exchange\"   : \"bitstamp\",\n    ///         \"api_key\"    : \"1234567890ABCDEF1234567890ABCDEF\",\n    ///         \"api_secret\" : \"1234567890ABCDEF1234567890ABCDEF\",\n    ///         \"customer_id\": \"123456\"\n    ///     }\n    /// }\n    /// ```\n    /// For this example, you could use load your Kraken account with\n    /// `KrakenAPI::new(KrakenCreds::new_from_file(\"account_kraken\", Path::new(\"/keys.json\")))`\n    pub fn new_from_file(name: &str, path: PathBuf) -> Result<Self> {\n        let mut f = File::open(&path)?;\n        let mut buffer = String::new();\n        f.read_to_string(&mut buffer)?;\n\n        let data: Value = serde_json::from_str(&buffer)?;\n        let json_obj = data.as_object()\n            .ok_or_else(|| ErrorKind::BadParse)?\n            .get(name)\n            .ok_or_else(|| ErrorKind::MissingField(name.to_string()))?;\n        let api_key = helpers::get_json_string(json_obj, \"api_key\")?;\n        let api_secret = helpers::get_json_string(json_obj, \"api_secret\")?;\n        let exchange = {\n            let exchange_str = helpers::get_json_string(json_obj, \"exchange\")?;\n            Exchange::from_str(exchange_str)\n                .chain_err(|| ErrorKind::InvalidFieldValue(\"exchange\".to_string()))?\n        };\n\n        if exchange != Exchange::Kraken {\n            return Err(ErrorKind::InvalidConfigType(Exchange::Kraken, exchange).into());\n        }\n\n        Ok(KrakenCreds::new(name, api_key, api_secret))\n    }\n}\n\nimpl Credentials for KrakenCreds {\n    /// Return a value from the credentials.\n    fn get(&self, key: &str) -> Option<String> {\n        if let Some(res) = self.data.get(key) {\n            Some(res.clone())\n        } else {\n            None\n        }\n    }\n\n    fn name(&self) -> String {\n        self.name.clone()\n    }\n\n    fn exchange(&self) -> Exchange {\n        self.exchange\n    }\n}\n"
  },
  {
    "path": "src/kraken/generic_api.rs",
    "content": "//! Use this module to interact with Kraken through a Generic API.\n//! This a more convenient and safe way to deal with the exchange since methods return a Result<>\n//! but this generic API does not provide all the functionnality that Kraken offers.\n\nuse crate::exchange::ExchangeApi;\nuse crate::kraken::api::KrakenApi;\n\nuse crate::error::*;\nuse crate::types::*;\nuse crate::kraken::utils;\nuse crate::helpers;\n\nimpl ExchangeApi for KrakenApi {\n    fn ticker(&mut self, pair: Pair) -> Result<Ticker> {\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(name) => name,\n            None => return Err(ErrorKind::PairUnsupported.into()),\n        };\n\n        let raw_response = self.get_ticker_information(pair_name)?;\n\n        let result = utils::parse_result(&raw_response)?;\n\n        let price = helpers::from_json_bigdecimal(&result[*pair_name][\"c\"][0], \"c\")?;\n        let ask = helpers::from_json_bigdecimal(&result[*pair_name][\"a\"][0], \"a\")?;\n        let bid = helpers::from_json_bigdecimal(&result[*pair_name][\"b\"][0], \"b\")?;\n        let vol = helpers::from_json_bigdecimal(&result[*pair_name][\"v\"][0], \"v\")?;\n\n        Ok(Ticker {\n               timestamp: helpers::get_unix_timestamp_ms(),\n               pair: pair,\n               last_trade_price: price,\n               lowest_ask: ask,\n               highest_bid: bid,\n               volume: Some(vol),\n           })\n\n    }\n\n    fn orderbook(&mut self, pair: Pair) -> Result<Orderbook> {\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(name) => name,\n            None => return Err(ErrorKind::PairUnsupported.into()),\n        };\n\n        let raw_response = self.get_order_book(pair_name, \"1000\")?; // 1000 entries max\n\n        let result = utils::parse_result(&raw_response)?;\n\n        let mut ask_offers = Vec::new();\n        let mut bid_offers = Vec::new();\n\n        let ask_array =\n            result[*pair_name][\"asks\"]\n                .as_array()\n                .ok_or_else(|| {\n                                ErrorKind::InvalidFieldFormat(format!(\"{}.asks\",\n                                                                      result[*pair_name]))\n                            })?;\n        let bid_array =\n            result[*pair_name][\"bids\"]\n                .as_array()\n                .ok_or_else(|| {\n                                ErrorKind::InvalidFieldFormat(format!(\"{}.bids\",\n                                                                      result[*pair_name]))\n                            })?;\n\n        for ask in ask_array {\n            let price = helpers::from_json_bigdecimal(&ask[0], \"ask price\")?;\n            let volume = helpers::from_json_bigdecimal(&ask[1], \"ask volume\")?;\n\n            ask_offers.push((price, volume));\n        }\n\n        for bid in bid_array {\n            let price = helpers::from_json_bigdecimal(&bid[0], \"bid price\")?;\n            let volume = helpers::from_json_bigdecimal(&bid[1], \"bid volume\")?;\n\n            bid_offers.push((price, volume));\n        }\n\n        Ok(Orderbook {\n               timestamp: helpers::get_unix_timestamp_ms(),\n               pair: pair,\n               asks: ask_offers,\n               bids: bid_offers,\n           })\n    }\n\n    fn add_order(&mut self,\n                 order_type: OrderType,\n                 pair: Pair,\n                 quantity: Volume,\n                 price: Option<Price>)\n                 -> Result<OrderInfo> {\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(name) => name,\n            None => return Err(ErrorKind::PairUnsupported.into()),\n        };\n\n        let direction = match order_type {\n            OrderType::BuyLimit | OrderType::BuyMarket => \"buy\",\n            OrderType::SellLimit | OrderType::SellMarket => \"sell\",\n        };\n\n        let order_type_str = match order_type {\n            OrderType::BuyLimit | OrderType::SellLimit => \"limit\",\n            OrderType::BuyMarket | OrderType::SellMarket => \"market\",\n        };\n\n        let mut price_str = \"\".to_string();\n        if price.is_some() {\n            price_str = price.unwrap().to_string()\n        };\n\n        let raw_response = self.add_standard_order(pair_name,\n                                                   direction,\n                                                   order_type_str,\n                                                   &price_str,\n                                                   \"\",\n                                                   &quantity.to_string(),\n                                                   \"\",\n                                                   \"\",\n                                                   \"\",\n                                                   \"\",\n                                                   \"\",\n                                                   \"\")?;\n\n        let result = utils::parse_result(&raw_response)?;\n\n        let mut txids = Vec::new();\n\n        let list_id =\n            result[\"txid\"]\n                .as_array()\n                .ok_or_else(|| ErrorKind::InvalidFieldFormat(format!(\"{}\", result[\"txid\"])))?;\n\n        for id in list_id {\n            txids.push(id.as_str()\n                           .ok_or_else(|| ErrorKind::InvalidFieldFormat(format!(\"{}\", id)))?\n                           .to_string());\n        }\n\n        Ok(OrderInfo {\n               timestamp: helpers::get_unix_timestamp_ms(),\n               identifier: txids,\n           })\n    }\n\n    fn balances(&mut self) -> Result<Balances> {\n        let raw_response = self.get_account_balance()?;\n        let result = utils::parse_result(&raw_response)?;\n\n        let mut balances = Balances::new();\n\n        for (key, val) in result.iter() {\n            let currency = utils::get_currency_enum(key);\n\n            match currency {\n                Some(c) => {\n                    let amount = helpers::from_json_bigdecimal(&val, \"amount\")?;\n\n                    balances.insert(c, amount);\n                },\n                _ => ()\n            }\n        }\n\n        Ok(balances)\n    }\n}\n"
  },
  {
    "path": "src/kraken/mod.rs",
    "content": "//! Use this module to interact with Kraken exchange.\n//! See examples for more informations.\n\npub mod api;\npub mod generic_api;\npub mod credentials;\npub mod utils;\n\npub use self::credentials::KrakenCreds;\npub use self::api::KrakenApi;\n"
  },
  {
    "path": "src/kraken/utils.rs",
    "content": "use bidir_map::BidirMap;\nuse serde_json;\nuse serde_json::Value;\nuse serde_json::value::Map;\n\nuse crate::error::*;\nuse crate::types::Currency;\nuse crate::types::Pair;\nuse crate::types::Pair::*;\n\nlazy_static! {\n    static ref PAIRS_STRING: BidirMap<Pair, &'static str> = {\n        let mut m = BidirMap::new();\n        m.insert(BCH_EUR, \"BCHEUR\");\n        m.insert(BCH_USD, \"BCHUSD\");\n        m.insert(BCH_BTC, \"BCHXBT\");\n        m.insert(DASH_EUR, \"DASHEUR\");\n        m.insert(DASH_USD, \"DASHUSD\");\n        m.insert(DASH_BTC, \"DASHXBT\");\n        m.insert(EOS_ETH, \"EOSETH\");\n        m.insert(EOS_BTC, \"EOSXBT\");\n        m.insert(GNO_ETH, \"GNOETH\");\n        m.insert(GNO_BTC, \"GNOXBT\");\n        m.insert(USDT_USD, \"USDTZUSD\");\n        m.insert(ETC_ETH, \"XETCXETH\");\n        m.insert(ETC_BTC, \"XETCXXBT\");\n        m.insert(ETC_EUR, \"XETCZEUR\");\n        m.insert(ETC_USD, \"XETCZUSD\");\n        m.insert(ETH_BTC, \"XETHXXBT\");\n        m.insert(ETH_BTC_d, \"XETHXXBT.d\");\n        m.insert(ETH_CAD, \"XETHZCAD\");\n        m.insert(ETH_CAD_d, \"XETHZCAD.d\");\n        m.insert(ETH_EUR, \"XETHZEUR\");\n        m.insert(ETH_EUR_d, \"XETHZEUR.d\");\n        m.insert(ETH_GBP, \"XETHZGBP\");\n        m.insert(ETH_GBP_d, \"XETHZGBP.d\");\n        m.insert(ETH_JPY, \"XETHZJPY\");\n        m.insert(ETH_JPY_d, \"XETHZJPY.d\");\n        m.insert(ETH_USD, \"XETHZUSD\");\n        m.insert(ETH_USD_d, \"XETHZUSD.d\");\n        m.insert(ICN_ETH, \"XICNXETH\");\n        m.insert(ICN_BTC, \"XICNXXBT\");\n        m.insert(LTC_BTC, \"XLTCXXBT\");\n        m.insert(LTC_EUR, \"XLTCZEUR\");\n        m.insert(LTC_USD, \"XLTCZUSD\");\n        m.insert(MLN_ETH, \"XMLNXETH\");\n        m.insert(MLN_BTC, \"XMLNXXBT\");\n        m.insert(REP_ETH, \"XREPXETH\");\n        m.insert(REP_BTC, \"XREPXXBT\");\n        m.insert(REP_EUR, \"XREPZEUR\");\n        m.insert(BTC_CAD, \"XXBTZCAD\");\n        m.insert(BTC_CAD_d, \"XXBTZCAD.d\");\n        m.insert(BTC_EUR, \"XXBTZEUR\");\n        m.insert(BTC_EUR_d, \"XXBTZEUR.d\");\n        m.insert(BTC_GBP, \"XXBTZGBP\");\n        m.insert(BTC_GBP_d, \"XXBTZGBP.d\");\n        m.insert(BTC_JPY, \"XXBTZJPY\");\n        m.insert(BTC_JPY_d, \"XXBTZJPY.d\");\n        m.insert(BTC_USD, \"XXBTZUSD\");\n        m.insert(BTC_USD_d, \"XXBTZUSD.d\");\n        m.insert(XDG_BTC, \"XXDGXXBT\");\n        m.insert(XLM_BTC, \"XXLMXXBT\");\n        m.insert(XMR_BTC, \"XXMRXXBT\");\n        m.insert(XMR_EUR, \"XXMRZEUR\");\n        m.insert(XMR_USD, \"XXMRZUSD\");\n        m.insert(XRP_BTC, \"XXRPXXBT\");\n        m.insert(XRP_EUR, \"XXRPZEUR\");\n        m.insert(XRP_USD, \"XXRPZUSD\");\n        m.insert(ZEC_BTC, \"XZECXXBT\");\n        m.insert(ZEC_EUR, \"XZECZEUR\");\n        m.insert(ZEC_USD, \"XZECZUSD\");\n        m\n    };\n}\n\n/// Return the name associated to pair used by Kraken\n/// If the Pair is not supported, None is returned.\npub fn get_pair_string(pair: &Pair) -> Option<&&str> {\n    PAIRS_STRING.get_by_first(pair)\n}\n\n/// Return the Pair enum associated to the string used by Kraken\n/// If the Pair is not supported, None is returned.\npub fn get_pair_enum(pair: &str) -> Option<&Pair> {\n    PAIRS_STRING.get_by_second(&pair)\n}\n\npub fn deserialize_json(json_string: &str) -> Result<Map<String, Value>> {\n    let data: Value = match serde_json::from_str(json_string) {\n        Ok(data) => data,\n        Err(_) => return Err(ErrorKind::BadParse.into()),\n    };\n\n    match data.as_object() {\n        Some(value) => Ok(value.clone()),\n        None => Err(ErrorKind::BadParse.into()),\n    }\n}\n\n/// If error array is null, return the result (encoded in a json object)\n/// else return the error string found in array\npub fn parse_result(response: &Map<String, Value>) -> Result<Map<String, Value>> {\n    let error_array = match response.get(\"error\") {\n        Some(array) => {\n            array\n                .as_array()\n                .ok_or_else(|| ErrorKind::InvalidFieldFormat(\"error\".to_string()))?\n        }\n        None => return Err(ErrorKind::BadParse.into()),\n    };\n    if error_array.is_empty() {\n        return Ok(response\n                      .get(\"result\")\n                      .ok_or_else(|| ErrorKind::MissingField(\"result\".to_string()))?\n                      .as_object()\n                      .ok_or_else(|| ErrorKind::InvalidFieldFormat(\"result\".to_string()))?\n                      .clone());\n    }\n    let error_msg = error_array[0]\n        .as_str()\n        .ok_or_else(|| ErrorKind::InvalidFieldFormat(error_array[0].to_string()))?\n        .to_string();\n\n    //TODO: Parse correctly the reason for \"EService:Unavailable\".\n    match error_msg.as_ref() {\n        \"EService:Unavailable\" => {\n            Err(ErrorKind::ServiceUnavailable(\"Unknown...\".to_string()).into())\n        }\n        \"EAPI:Invalid key\" => Err(ErrorKind::BadCredentials.into()),\n        \"EAPI:Invalid nonce\" => Err(ErrorKind::InvalidNonce.into()),\n        \"EOrder:Rate limit exceeded\" => Err(ErrorKind::RateLimitExceeded.into()),\n        \"EQuery:Unknown asset pair\" => Err(ErrorKind::PairUnsupported.into()),\n        \"EGeneral:Invalid arguments\" => Err(ErrorKind::InvalidArguments.into()),\n        \"EGeneral:Permission denied\" => Err(ErrorKind::PermissionDenied.into()),\n        \"EOrder:Insufficient funds\" => Err(ErrorKind::InsufficientFunds.into()),\n        \"EOrder:Order minimum not met\" => Err(ErrorKind::InsufficientOrderSize.into()),\n        other => Err(ErrorKind::ExchangeSpecificError(other.to_string()).into()),\n    }\n}\n\n/// Return the currency enum associated with the\n/// string used by Kraken. If no currency is found,\n/// return None\n/// # Examples\n///\n/// ```\n/// use coinnect::kraken::utils::get_currency_enum;\n/// use coinnect::types::Currency;\n///\n/// let currency = get_currency_enum(\"ZUSD\");\n/// assert_eq!(Some(Currency::USD), currency);\n/// ```\npub fn get_currency_enum(currency: &str) -> Option<Currency> {\n    match currency {\n        \"ZEUR\" => Some(Currency::EUR),\n        \"ZCAD\" => Some(Currency::CAD),\n        \"ZGBP\" => Some(Currency::GBP),\n        \"ZJPY\" => Some(Currency::JPY),\n        \"ZUSD\" => Some(Currency::USD),\n        \"XDASH\" => Some(Currency::DASH),\n        \"XETC\" => Some(Currency::ETC),\n        \"XETH\" => Some(Currency::ETH),\n        \"XGNO\" => Some(Currency::GNO),\n        \"XICN\" => Some(Currency::ICN),\n        \"XLTC\" => Some(Currency::LTC),\n        \"XMLN\" => Some(Currency::MLN),\n        \"XREP\" => Some(Currency::REP),\n        \"XUSDT\" => Some(Currency::USDT),\n        \"XXBT\" => Some(Currency::BTC),\n        \"XXDG\" => Some(Currency::XDG),\n        \"XXLM\" => Some(Currency::XLM),\n        \"XXMR\" => Some(Currency::XMR),\n        \"XXRP\" => Some(Currency::XRP),\n        \"XZEC\" => Some(Currency::ZEC),\n        _ => None,\n    }\n}\n\n/// Return the currency String associated with the\n/// string used by Kraken. If no currency is found,\n/// return None\n/// # Examples\n///\n/// ```\n/// use coinnect::kraken::utils::get_currency_string;\n/// use coinnect::types::Currency;\n///\n/// let currency = get_currency_string(Currency::BTC);\n/// assert_eq!(currency, Some(\"XXBT\".to_string()));\n/// ```\npub fn get_currency_string(currency: Currency) -> Option<String> {\n    match currency {\n        Currency::EUR => Some(\"ZEUR\".to_string()),\n        Currency::CAD => Some(\"ZCAD\".to_string()),\n        Currency::GBP => Some(\"ZGBP\".to_string()),\n        Currency::JPY => Some(\"ZJPY\".to_string()),\n        Currency::USD => Some(\"ZUSD\".to_string()),\n        Currency::DASH => Some(\"XDASH\".to_string()),\n        Currency::ETC => Some(\"XETC\".to_string()),\n        Currency::ETH => Some(\"XETH\".to_string()),\n        Currency::GNO => Some(\"XGNO\".to_string()),\n        Currency::ICN => Some(\"XICN\".to_string()),\n        Currency::LTC => Some(\"XLTC\".to_string()),\n        Currency::MLN => Some(\"XMLN\".to_string()),\n        Currency::REP => Some(\"XREP\".to_string()),\n        Currency::USDT => Some(\"XUSDT\".to_string()),\n        Currency::BTC => Some(\"XXBT\".to_string()),\n        Currency::XDG => Some(\"XXDG\".to_string()),\n        Currency::XLM => Some(\"XXLM\".to_string()),\n        Currency::XMR => Some(\"XXMR\".to_string()),\n        Currency::XRP => Some(\"XXRP\".to_string()),\n        Currency::ZEC => Some(\"XZEC\".to_string()),\n        _ => None,\n    }\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "//! ![Coinnect](https://raw.githubusercontent.com/hugues31/coinnect/master/coinnect.png)\n//!\n//! Coinnect is a Rust library aiming to provide a complete access to REST APIs for various\n//! crypto-currencies exchanges (see below for a list of supported exchanges).\n//! All methods consume HTTPS api. The purpose of this crate is not\n//! to stream data (you should use websocket/FIX in that case).\n//!\n//! For optional parameters, most methods require an empty str (`\"\"`) or `Option` (`None`) if\n//! you don't want to specify them.\n//!\n//! ### Exchanges support:\n//! - [x] Poloniex\n//! - [x] Kraken\n//! - [x] Bitstamp (partial)\n//! - [x] Bittrex\n//! - [x] Gdax\n//!\n//! # WARNING\n//! This library is highly experimental at the moment. Please do not invest what you\n//! can't afford to loose. This is a personal project, I can not be held responsible for\n//! the library malfunction, which can lead to a loss of money.\n\n// error_chain can make a lot of recursions.\n#![recursion_limit=\"128\"]\n\n// Allow lint customization.\n#![allow(unknown_lints)]\n\n// Move all the clippy warning in deny.\n#![deny(clippy)]\n\n// Avoid warning for the Crypto-currency about quotes.\n#![allow(doc_markdown)]\n\n#[macro_use]\nextern crate hyper;\nextern crate sha2;\nextern crate hmac;\nextern crate hyper_native_tls;\nextern crate serde_json;\nextern crate chrono;\n#[macro_use]\nextern crate lazy_static;\nextern crate bidir_map;\nextern crate data_encoding;\n#[macro_use]\nextern crate error_chain;\nextern crate bigdecimal;\n\npub mod coinnect;\npub mod exchange;\npub mod error;\npub mod types;\nmod helpers;\n\npub mod bitstamp;\npub mod poloniex;\npub mod kraken;\npub mod bittrex;\npub mod gdax;\n"
  },
  {
    "path": "src/poloniex/api.rs",
    "content": "//! Use this module to interact with Poloniex exchange.\n//! See examples for more informations.\n\nuse hmac::{Hmac, Mac, NewMac};\nuse sha2::Sha512;\n\nuse hyper_native_tls::NativeTlsClient;\nuse hyper::Client;\nuse hyper::header;\nuse hyper::net::HttpsConnector;\n\nuse data_encoding::HEXLOWER;\n\nuse serde_json::Value;\nuse serde_json::value::Map;\n\nuse std::collections::HashMap;\nuse std::io::Read;\nuse std::thread;\nuse std::time::Duration;\n\nuse crate::error::*;\nuse crate::helpers;\n\nuse crate::exchange::Exchange;\nuse crate::coinnect::Credentials;\nuse crate::poloniex::utils;\n\nheader! {\n    #[doc(hidden)]\n    (KeyHeader, \"Key\") => [String]\n}\n\nheader! {\n    #[doc(hidden)]\n    (SignHeader, \"Sign\") => [String]\n}\n\nheader! {\n    #[doc(hidden)]\n    (ContentHeader, \"Content-Type\") => [String]\n}\n\n#[derive(Debug, Copy, Clone)]\npub enum PlaceOrderOption {\n    FillOrKill,\n    ImmediateOrCancel,\n    PostOnly,\n}\nimpl PlaceOrderOption {\n    fn repr(&self) -> &'static str {\n        match self {\n            &PlaceOrderOption::FillOrKill => \"fillOrKill\",\n            &PlaceOrderOption::ImmediateOrCancel => \"immediateOrCancel\",\n            &PlaceOrderOption::PostOnly => \"postOnly\",\n        }\n    }\n}\n\n#[derive(Debug, Copy, Clone)]\npub enum MoveOrderOption {\n    ImmediateOrCancel,\n    PostOnly,\n}\nimpl MoveOrderOption {\n    fn repr(&self) -> &'static str {\n        match self {\n            &MoveOrderOption::ImmediateOrCancel => \"immediateOrCancel\",\n            &MoveOrderOption::PostOnly => \"postOnly\",\n        }\n    }\n}\n\n#[derive(Debug)]\npub struct PoloniexApi {\n    last_request: i64, // unix timestamp in ms, to avoid ban\n    api_key: String,\n    api_secret: String,\n    http_client: Client,\n    burst: bool,\n}\n\nimpl PoloniexApi {\n    /// Create a new PoloniexApi by providing an API key & API secret\n    pub fn new<C: Credentials>(creds: C) -> Result<PoloniexApi> {\n        if creds.exchange() != Exchange::Poloniex {\n            return Err(ErrorKind::InvalidConfigType(Exchange::Poloniex, creds.exchange()).into());\n        }\n\n        //TODO: Handle correctly the TLS errors with error_chain.\n        let ssl = match NativeTlsClient::new() {\n            Ok(res) => res,\n            Err(_) => return Err(ErrorKind::TlsError.into()),\n        };\n        let connector = HttpsConnector::new(ssl);\n\n        Ok(PoloniexApi {\n            last_request: 0,\n            api_key: creds.get(\"api_key\").unwrap_or_default(),\n            api_secret: creds.get(\"api_secret\").unwrap_or_default(),\n            http_client: Client::with_connector(connector),\n            burst: false,\n        })\n    }\n\n    /// The number of calls in a given period is limited. In order to avoid a ban we limit\n    /// by default the number of api requests.\n    /// This function sets or removes the limitation.\n    /// Burst false implies no block.\n    /// Burst true implies there is a control over the number of calls allowed to the exchange\n    pub fn set_burst(&mut self, burst: bool) {\n        self.burst = burst\n    }\n\n    fn block_or_continue(&self) {\n        if !self.burst {\n            let threshold: u64 = 167; // 6 requests/sec = 1/6*1000\n            let offset: u64 = helpers::get_unix_timestamp_ms() as u64 - self.last_request as u64;\n            if offset < threshold {\n                let wait_ms = Duration::from_millis(threshold - offset);\n                thread::sleep(wait_ms);\n            }\n        }\n    }\n\n    fn public_query(&mut self, method: &str, params: &HashMap<&str, &str>) -> Result<Map<String, Value>> {\n        let mut params = params.clone();\n        helpers::strip_empties(&mut params);\n        let url = \"https://poloniex.com/public?command=\".to_string() + method + \"&\" + &helpers::url_encode_hashmap(&params);\n\n        self.block_or_continue();\n        let mut response = match self.http_client.get(&url).send() {\n            Ok(response) => response,\n            Err(err) => return Err(ErrorKind::ServiceUnavailable(err.to_string()).into()),\n        };\n        self.last_request = helpers::get_unix_timestamp_ms();\n        let mut buffer = String::new();\n        response.read_to_string(&mut buffer)?;\n\n        if method == \"returnChartData\" {\n            return utils::deserialize_json_array(&buffer);\n        }\n        utils::deserialize_json(&buffer)\n    }\n\n    fn private_query(&mut self, method: &str, params: &HashMap<&str, &str>) -> Result<Map<String, Value>> {\n        let unix_timestamp = helpers::get_unix_timestamp_us().to_string();\n        let mut post_params = params.clone();\n        post_params.insert(\"command\", method);\n        post_params.insert(\"nonce\", &unix_timestamp);\n        helpers::strip_empties(&mut post_params);\n        let post_data = helpers::url_encode_hashmap(&post_params);\n\n        let mut mac = Hmac::<Sha512>::new_from_slice(self.api_secret.as_bytes()).unwrap();\n        mac.update(post_data.as_bytes());\n\n        let sign = HEXLOWER.encode(&mac.finalize().into_bytes());\n\n        let mut custom_header = header::Headers::new();\n        custom_header.set(KeyHeader(self.api_key.to_owned()));\n        custom_header.set(SignHeader(sign));\n        custom_header.set(ContentHeader(\n            \"application/x-www-form-urlencoded\".to_owned(),\n        ));\n\n        self.block_or_continue();\n\n        let mut response = match self.http_client\n            .post(\"https://poloniex.com/tradingApi\")\n            .body(&post_data)\n            .headers(custom_header)\n            .send()\n        {\n            Ok(response) => response,\n            Err(err) => return Err(ErrorKind::ServiceUnavailable(err.to_string()).into()),\n        };\n        self.last_request = helpers::get_unix_timestamp_ms();\n\n        let mut buffer = String::new();\n        response.read_to_string(&mut buffer)?;\n        if method == \"returnChartData\" {\n            return utils::deserialize_json_array(&buffer);\n        }\n        utils::deserialize_json(&buffer)\n    }\n\n    /// Sample output :\n    ///\n    /// ```json\n    /// {\n    /// \"BTC_LTC\":{\n    /// \"last\":\"0.0251\",\"lowestAsk\":\"0.02589999\",\"highestBid\":\"0.0251\",\n    /// \"percentChange\":\"0.02390438\",\"baseVolume\":\"6.16485315\",\"quoteVolume\":\"245.82513926\"},\n    /// \"BTC_NXT\":{\n    /// \"last\":\"0.00005730\",\"lowestAsk\":\"0.00005710\",\"highestBid\":\"0.00004903\",\n    /// \"percentChange\":\"0.16701570\",\"baseVolume\":\"0.45347489\",\"quoteVolume\":\"9094\"},\n    /// ... }\n    /// ```\n    pub fn return_ticker(&mut self) -> Result<Map<String, Value>> {\n        let params = HashMap::new();\n        self.public_query(\"returnTicker\", &params)\n    }\n\n    /// Sample output :\n    ///\n    /// ```json\n    /// {\"BTC_LTC\":{\"BTC\":\"2.23248854\",\"LTC\":\"87.10381314\"},\"BTC_NXT\":{\"BTC\":\"0.981616\",\n    /// \"NXT\":\"14145\"},\n    /// ... \"totalBTC\":\"81.89657704\",\"totalLTC\":\"78.52083806\"}\n    /// ```\n    pub fn return_24_volume(&mut self) -> Result<Map<String, Value>> {\n        let params = HashMap::new();\n        self.public_query(\"return24Volume\", &params)\n    }\n\n    /// Sample output :\n    ///\n    /// ```json\n    /// {\"asks\":[[0.00007600,1164],[0.00007620,1300], ... ], \"bids\":[[0.00006901,200],\n    /// [0.00006900,408], ... ], \"isFrozen\": 0, \"seq\": 18849}\n    /// ```\n    pub fn return_order_book(&mut self, currency_pair: &str, depth: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"currencyPair\", currency_pair);\n        params.insert(\"depth\", depth);\n        self.public_query(\"returnOrderBook\", &params)\n    }\n\n    /// Sample output :\n    ///\n    /// ```json\n    /// [{\"date\":\"2014-02-10 04:23:23\",\"type\":\"buy\",\"rate\":\"0.00007600\",\"amount\":\"140\",\n    /// \"total\":\"0.01064\"},\n    /// {\"date\":\"2014-02-10 01:19:37\",\"type\":\"buy\",\"rate\":\"0.00007600\",\"amount\":\"655\",\n    /// \"total\":\"0.04978\"}, ... ]\n    /// ```\n    pub fn return_trade_history(&mut self, currency_pair: &str, start: &str, end: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"currencyPair\", currency_pair);\n        params.insert(\"start\", start);\n        params.insert(\"end\", end);\n        self.public_query(\"returnTradeHistory\", &params)\n    }\n\n    /// Sample output :\n    ///\n    /// ```json\n    /// {\"data\": {\"date\":1405699200,\"high\":0.0045388,\"low\":0.00403001,\"open\":0.00404545,\"close\":0.00427592,\n    /// \"volume\":44.11655644,\"quoteVolume\":10259.29079097,\"weightedAverage\":0.00430015}, ...}\n    /// ```\n    pub fn return_chart_data(&mut self, currency_pair: &str, start: &str, end: &str, period: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"currencyPair\", currency_pair);\n        params.insert(\"start\", start);\n        params.insert(\"end\", end);\n        params.insert(\"period\", period);\n        self.public_query(\"returnChartData\", &params)\n    }\n\n    /// Sample output :\n    ///\n    /// ```json\n    /// {\"1CR\":{\"maxDailyWithdrawal\":10000,\"txFee\":0.01,\"minConf\":3,\"disabled\":0},\n    /// \"ABY\":{\"maxDailyWithdrawal\":10000000,\"txFee\":0.01,\"minConf\":8,\"disabled\":0}, ... }\n    /// ```\n    pub fn return_currencies(&mut self) -> Result<Map<String, Value>> {\n        let params = HashMap::new();\n        self.public_query(\"returnCurrencies\", &params)\n    }\n\n    /// Sample output :\n    ///\n    /// ```json\n    /// {\"offers\":[{\"rate\":\"0.00200000\",\"amount\":\"64.66305732\",\"rangeMin\":2,\"rangeMax\":8}, ... ],\n    /// \"demands\":[{\"rate\":\"0.00170000\",\"amount\":\"26.54848841\",\"rangeMin\":2,\"rangeMax\":2}, ... ]}\n    /// ```\n    pub fn return_loan_orders(&mut self, currency: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"currency\", currency);\n        self.public_query(\"returnLoanOrders\", &params)\n    }\n\n    /// Returns all of your available balances.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"BTC\":\"0.59098578\",\"LTC\":\"3.31117268\", ... }\n    /// ```\n    pub fn return_balances(&mut self) -> Result<Map<String, Value>> {\n        let params = HashMap::new();\n        self.private_query(\"returnBalances\", &params)\n    }\n\n    /// Returns all of your balances, including available balance, balance on orders,\n    /// and the estimated BTC value of your balance. By default, this call is limited to your\n    /// exchange account; set the \"account\" POST parameter to \"all\" to include your margin and\n    /// lending accounts.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"LTC\":{\"available\":\"5.015\",\"onOrders\":\"1.0025\",\"btcValue\":\"0.078\"},\"NXT\":{...}, ... }\n    /// ```\n    pub fn return_complete_balances(&mut self) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"account\", \"all\");\n        self.private_query(\"returnCompleteBalances\", &params)\n    }\n\n    /// Returns all of your deposit addresses.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"BTC\":\"19YqztHmspv2egyD6jQM3yn81x5t5krVdJ\",\"LTC\":\"LPgf9kjv9H1Vuh4XSaKhzBe8JHdou1WgUB\",\n    /// ... \"ITC\":\"Press Generate..\" ... }\n    /// ```\n    pub fn return_deposit_addresses(&mut self) -> Result<Map<String, Value>> {\n        let params = HashMap::new();\n        self.private_query(\"returnDepositAddresses\", &params)\n    }\n\n    /// Generates a new deposit address for the currency specified by the \"currency\" POST parameter.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"success\":1,\"response\":\"CKXbbs8FAVbtEa397gJHSutmrdrBrhUMxe\"}\n    /// ```\n    pub fn generate_new_address(&mut self, currency: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"currency\", currency);\n        self.private_query(\"generateNewAddress\", &params)\n    }\n\n    /// Returns your deposit and withdrawal history within a range, specified by the \"start\" and\n    /// \"end\" POST parameters,\n    /// both of which should be given as UNIX timestamps.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"deposits\":\n    /// [{\"currency\":\"BTC\",\"address\":\"...\",\"amount\":\"0.01006132\",\"confirmations\":10,\n    /// \"txid\":\"17f819a91369a9ff6c4a34216d434597cfc1b4a3d0489b46bd6f924137a47701\",\n    /// \"timestamp\":1399305798,\"status\":\"COMPLETE\"},\n    /// {\"currency\":\"BTC\",\"address\":\"...\",\"amount\":\"0.00404104\",\"confirmations\":10,\n    /// \"txid\":\"7acb90965b252e55a894b535ef0b0b65f45821f2899e4a379d3e43799604695c\",\n    /// \"timestamp\":1399245916,\"status\":\"COMPLETE\"}],\n    /// \"withdrawals\":[{\"withdrawalNumber\":134933,\"currency\":\"BTC\",\n    /// \"address\":\"1N2i5n8DwTGzUq2Vmn9TUL8J1vdr1XBDFg\",\"amount\":\"5.00010000\",\n    /// \"timestamp\":1399267904,\n    /// \"status\":\"COMPLETE: 36e483efa6aff9fd53a235177579d98451c4eb237c210e66cd2b9a2d4a988f8e\",\n    /// \"ipAddress\":\"...\"}]}\n    /// ```\n    pub fn return_deposits_withdrawals(&mut self, start: &str, end: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"start\", start);\n        params.insert(\"end\", end);\n        self.private_query(\"returnDepositsWithdrawals\", &params)\n    }\n\n    ///Returns your open orders for a given market, specified by the \"currencyPair\" POST parameter,\n    /// e.g. \"BTC_XCP\". Set \"currencyPair\" to \"all\" to return open orders for all markets.\n    ///\n    /// Sample output for single market:\n    ///\n    /// ```json\n    /// [{\"orderNumber\":\"120466\",\"type\":\"sell\",\"rate\":\"0.025\",\"amount\":\"100\",\"total\":\"2.5\"},\n    /// {\"orderNumber\":\"120467\",\"type\":\"sell\",\"rate\":\"0.04\",\"amount\":\"100\",\"total\":\"4\"}, ... ]\n    /// ```\n    ///\n    /// ```json\n    /// Or, for all markets:\n    /// {\"BTC_1CR\":[],\"BTC_AC\":[{\"orderNumber\":\"120466\",\"type\":\"sell\",\"rate\":\"0.025\",\n    /// \"amount\":\"100\",\"total\":\"2.5\"},\n    /// {\"orderNumber\":\"120467\",\"type\":\"sell\",\"rate\":\"0.04\",\"amount\":\"100\",\"total\":\"4\"}], ... }\n    /// ```\n    pub fn return_open_orders(&mut self, currency_pair: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"currencyPair\", currency_pair);\n        self.private_query(\"returnOpenOrders\", &params)\n    }\n\n    /// Returns your trade history for a given market, specified by the \"currencyPair\" POST\n    /// parameter.\n    /// You may specify \"all\" as the currencyPair to receive your trade history for all markets.\n    /// You may optionally specify a range via \"start\" and/or \"end\" POST parameters, given in UNIX\n    /// timestamp format;\n    /// if you do not specify a range, it will be limited to one day.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// [{ \"globalTradeID\": 25129732, \"tradeID\": \"6325758\", \"date\": \"2016-04-05 08:08:40\",\n    /// \"rate\": \"0.02565498\", \"amount\": \"0.10000000\", \"total\": \"0.00256549\", \"fee\": \"0.00200000\",\n    /// \"orderNumber\": \"34225313575\", \"type\": \"sell\", \"category\": \"exchange\" },\n    /// { \"globalTradeID\": 25129628, \"tradeID\": \"6325741\", \"date\": \"2016-04-05 08:07:55\",\n    /// \"rate\": \"0.02565499\", \"amount\": \"0.10000000\", \"total\": \"0.00256549\",\n    /// \"fee\": \"0.00200000\", \"orderNumber\": \"34225195693\", \"type\": \"buy\", \"category\": \"exchange\" },\n    /// ... ]\n    /// ```\n    ///\n    /// Or, for all markets:\n    ///\n    /// ```json\n    /// {\"BTC_MAID\": [ { \"globalTradeID\": 29251512, \"tradeID\": \"1385888\",\n    /// \"date\": \"2016-05-03 01:29:55\", \"rate\": \"0.00014243\", \"amount\": \"353.74692925\",\n    /// \"total\": \"0.05038417\", \"fee\": \"0.00200000\", \"orderNumber\": \"12603322113\", \"type\": \"buy\",\n    /// \"category\": \"settlement\" },\n    /// { \"globalTradeID\": 29251511, \"tradeID\": \"1385887\", \"date\": \"2016-05-03 01:29:55\",\n    /// \"rate\": \"0.00014111\", \"amount\": \"311.24262497\", \"total\": \"0.04391944\", \"fee\": \"0.00200000\",\n    /// \"orderNumber\": \"12603319116\", \"type\": \"sell\", \"category\": \"marginTrade\" }, ... ],\n    /// \"BTC_LTC\":[ ... ] ... }\n    /// ```\n    pub fn return_private_trade_history(&mut self, currency_pair: &str, start: &str, end: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"currencyPair\", currency_pair);\n        params.insert(\"start\", start);\n        params.insert(\"end\", end);\n        self.private_query(\"returnTradeHistory\", &params)\n    }\n\n    /// Returns all trades involving a given order, specified by the \"orderNumber\" POST parameter.\n    /// If no trades for the order have occurred or you specify an order that does not belong to\n    /// you, you will receive an error.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// [{\"globalTradeID\": 20825863, \"tradeID\": 147142, \"currencyPair\": \"BTC_XVC\", \"type\": \"buy\",\n    /// \"rate\": \"0.00018500\", \"amount\": \"455.34206390\", \"total\": \"0.08423828\", \"fee\": \"0.00200000\",\n    /// \"date\": \"2016-03-14 01:04:36\"}, ...]\n    /// ```\n    pub fn return_order_trades(&mut self, order_number: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"orderNumber\", order_number);\n        self.private_query(\"returnOrderTrades\", &params)\n    }\n\n    /// Places a limit buy order in a given market. Required POST parameters are \"currencyPair\",\n    /// \"rate\", and \"amount\".\n    /// If successful, the method will return the order number.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"orderNumber\":31226040,\"resultingTrades\":[{\"amount\":\"338.8732\",\n    /// \"date\":\"2014-10-18 23:03:21\", \"rate\":\"0.00000173\",\"total\":\"0.00058625\",\"tradeID\":\"16164\",\n    /// \"type\":\"buy\"}]}\n    /// ```\n    pub fn buy<O>(&mut self, currency_pair: &str, rate: &str, amount: &str, option: O) -> Result<Map<String, Value>>\n    where\n        O: Into<Option<PlaceOrderOption>>,\n    {\n        let mut params = HashMap::new();\n        params.insert(\"currencyPair\", currency_pair);\n        params.insert(\"rate\", rate);\n        params.insert(\"amount\", amount);\n        option.into().map(|o| params.insert(o.repr(), \"1\"));\n        self.private_query(\"buy\", &params)\n    }\n\n    /// Places a sell order in a given market. Parameters and output are the same as for the buy\n    /// method.\n    pub fn sell<O>(&mut self, currency_pair: &str, rate: &str, amount: &str, option: O) -> Result<Map<String, Value>>\n    where\n        O: Into<Option<PlaceOrderOption>>,\n    {\n        let mut params = HashMap::new();\n        params.insert(\"currencyPair\", currency_pair);\n        params.insert(\"rate\", rate);\n        params.insert(\"amount\", amount);\n        option.into().map(|o| params.insert(o.repr(), \"1\"));\n        self.private_query(\"sell\", &params)\n    }\n\n    /// Cancels an order you have placed in a given market.\n    /// Required POST parameter is \"orderNumber\". If successful, the method will return:\n    /// {\"success\":1}\n    pub fn cancel_order(&mut self, order_number: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"orderNumber\", order_number);\n        self.private_query(\"cancelOrder\", &params)\n    }\n\n    /// Cancels an order and places a new one of the same type in a single atomic transaction,\n    /// meaning either both operations will succeed or both will fail.\n    /// Required POST parameters are \"orderNumber\" and \"rate\"; you may optionally\n    /// specify \"amount\" if you wish to change the amount of the new order.\n    /// \"postOnly\" or \"immediateOrCancel\" may be specified for exchange orders, but will have no\n    /// effect on margin orders.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"success\":1,\"orderNumber\":\"239574176\",\"resultingTrades\":{\"BTC_BTS\":[]}}\n    /// ```\n    pub fn move_order<O>(&mut self, order_number: &str, rate: &str, option: O) -> Result<Map<String, Value>>\n    where\n        O: Into<Option<MoveOrderOption>>,\n    {\n        // TODO: add optional parameters\n        let mut params = HashMap::new();\n        params.insert(\"orderNumber\", order_number);\n        params.insert(\"rate\", rate);\n        option.into().map(|o| params.insert(o.repr(), \"1\"));\n        self.private_query(\"moveOrder\", &params)\n    }\n\n    /// Immediately places a withdrawal for a given currency, with no email confirmation.\n    /// In order to use this method, the withdrawal privilege must be enabled for your API key.\n    /// Required POST parameters are \"currency\", \"amount\", and \"address\".\n    /// For XMR withdrawals, you may optionally specify \"paymentId\".\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"response\":\"Withdrew 2398 NXT.\"}\n    /// ```\n    pub fn withdraw(&mut self, currency: &str, amount: &str, address: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"currency\", currency);\n        params.insert(\"amount\", amount);\n        params.insert(\"address\", address);\n        self.private_query(\"withdraw\", &params)\n    }\n\n    /// If you are enrolled in the maker-taker fee schedule, returns your current\n    /// trading fees and trailing 30-day volume in BTC. This information is updated once every\n    /// 24 hours.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"makerFee\": \"0.00140000\", \"takerFee\": \"0.00240000\", \"thirtyDayVolume\": \"612.00248891\",\n    /// \"nextTier\": \"1200.00000000\"}\n    /// ```\n    pub fn return_free_info(&mut self) -> Result<Map<String, Value>> {\n        let params = HashMap::new();\n        self.private_query(\"returnFeeInfo\", &params)\n    }\n\n    /// Returns your balances sorted by account. You may optionally specify the \"account\" POST\n    /// parameter if you wish to fetch only the balances of one account. Please note that balances\n    /// in your margin account may not be accessible if you have any open margin positions or\n    /// orders.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"exchange\":{\"BTC\":\"1.19042859\",\"BTM\":\"386.52379392\",\"CHA\":\"0.50000000\",\n    /// \"DASH\":\"120.00000000\", \"STR\":\"3205.32958001\", \"VNL\":\"9673.22570147\"},\n    /// \"margin\":{\"BTC\":\"3.90015637\", \"DASH\":\"250.00238240\",\"XMR\":\"497.12028113\"},\n    /// \"lending\":{\"DASH\":\"0.01174765\",\"LTC\":\"11.99936230\"}}\n    /// ```\n    pub fn return_available_account_balances(&mut self, account: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"account\", account);\n        self.private_query(\"returnAvailableAccountBalances\", &params)\n    }\n\n    /// Returns your current tradable balances for each currency in each market for which\n    /// margin trading is enabled. Please note that these balances may vary continually with\n    /// market conditions.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"BTC_DASH\":{\"BTC\":\"8.50274777\",\"DASH\":\"654.05752077\"},\"BTC_LTC\":{\"BTC\":\"8.50274777\",\n    /// \"LTC\":\"1214.67825290\"},\"BTC_XMR\":{\"BTC\":\"8.50274777\",\"XMR\":\"3696.84685650\"}}\n    /// ```\n    pub fn return_tradable_balances(&mut self) -> Result<Map<String, Value>> {\n        let params = HashMap::new();\n        self.private_query(\"returnTradableBalances\", &params)\n    }\n\n    /// Transfers funds from one account to another (e.g. from your exchange account to your\n    /// margin account). Required POST parameters are \"currency\", \"amount\", \"fromAccount\",\n    /// and \"toAccount\".\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"success\":1,\"message\":\"Transferred 2 BTC from exchange to margin account.\"}\n    /// ```\n    pub fn transfer_balance(&mut self, currency: &str, amount: &str, from_account: &str, to_account: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"currency\", currency);\n        params.insert(\"amount\", amount);\n        params.insert(\"fromAccount\", from_account);\n        params.insert(\"toAccount\", to_account);\n        self.private_query(\"transferBalance\", &params)\n    }\n\n    /// Returns a summary of your entire margin account. This is the same information you will\n    /// find in the Margin Account section of the Margin Trading page, under the Markets list.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"totalValue\": \"0.00346561\",\"pl\": \"-0.00001220\",\"lendingFees\": \"0.00000000\",\n    /// \"netValue\": \"0.00345341\",\"totalBorrowedValue\": \"0.00123220\",\"currentMargin\": \"2.80263755\"}\n    /// ```\n    pub fn return_margin_account_summary(&mut self) -> Result<Map<String, Value>> {\n        let params = HashMap::new();\n        self.private_query(\"returnMarginAccountSummary\", &params)\n    }\n\n    /// Places a margin buy order in a given market. Required POST parameters are\n    /// \"currencyPair\", \"rate\", and \"amount\". You may optionally specify a maximum lending\n    /// rate using the \"lendingRate\" parameter. If successful, the method will return the order\n    /// number and any trades immediately resulting from your order.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"success\":1,\"message\":\"Margin order placed.\",\"orderNumber\":\"154407998\",\n    /// \"resultingTrades\":{\"BTC_DASH\":[{\"amount\":\"1.00000000\",\"date\":\"2015-05-10 22:47:05\",\n    /// \"rate\":\"0.01383692\",\"total\":\"0.01383692\",\"tradeID\":\"1213556\",\"type\":\"buy\"}]}}\n    /// ```\n    pub fn margin_buy(&mut self, currency_pair: &str, rate: &str, amount: &str, lending_rate: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"currencyPair\", currency_pair);\n        params.insert(\"rate\", rate);\n        params.insert(\"amount\", amount);\n        params.insert(\"lendingRate\", lending_rate);\n        self.private_query(\"marginBuy\", &params)\n    }\n\n    /// Places a margin sell order in a given market. Required POST parameters are\n    /// \"currencyPair\", \"rate\", and \"amount\". You may optionally specify a maximum lending\n    /// rate using the \"lendingRate\" parameter. If successful, the method will return the order\n    /// number and any trades immediately resulting from your order.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"success\":1,\"message\":\"Margin order placed.\",\"orderNumber\":\"154407998\",\n    /// \"resultingTrades\":{\"BTC_DASH\":[{\"amount\":\"1.00000000\",\"date\":\"2015-05-10 22:47:05\",\n    /// \"rate\":\"0.01383692\",\"total\":\"0.01383692\",\"tradeID\":\"1213556\",\"type\":\"sell\"}]}}\n    /// ```\n    pub fn margin_sell(&mut self, currency_pair: &str, rate: &str, amount: &str, lending_rate: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"currencyPair\", currency_pair);\n        params.insert(\"rate\", rate);\n        params.insert(\"amount\", amount);\n        params.insert(\"lendingRate\", lending_rate);\n        self.private_query(\"marginSell\", &params)\n    }\n\n    /// Returns information about your margin position in a given market, specified by the\n    /// \"currencyPair\" POST parameter. You may set \"currencyPair\" to \"all\" if you wish to fetch all\n    /// of your margin positions at once. If you have no margin position in the specified market,\n    /// \"type\" will be set to \"none\". \"liquidationPrice\" is an estimate, and does not necessarily\n    /// represent the price at which an actual forced liquidation will occur. If you have no\n    /// liquidation price, the value will be -1.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"amount\":\"40.94717831\",\"total\":\"-0.09671314\",\"basePrice\":\"0.00236190\",\n    /// \"liquidationPrice\":-1,\"pl\":\"-0.00058655\", \"lendingFees\":\"-0.00000038\",\"type\":\"long\"}\n    /// ```\n    pub fn get_margin_position(&mut self, currency_pair: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"currencyPair\", currency_pair);\n        self.private_query(\"getMarginPosition\", &params)\n    }\n\n    /// Closes your margin position in a given market (specified by the \"currencyPair\" POST\n    /// parameter) using a market order. This call will also return success if you do not have an\n    /// open position in the specified market.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"success\":1,\"message\":\"Successfully closed margin position.\",\n    /// \"resultingTrades\":{\"BTC_XMR\":[{\"amount\":\"7.09215901\",\"date\":\"2015-05-10 22:38:49\",\n    /// \"rate\":\"0.00235337\",\"total\":\"0.01669047\",\"tradeID\":\"1213346\",\"type\":\"sell\"},\n    /// {\"amount\":\"24.00289920\",\"date\":\"2015-05-10 22:38:49\",\"rate\":\"0.00235321\",\n    /// \"total\":\"0.05648386\",\"tradeID\":\"1213347\",\"type\":\"sell\"}]}}\n    /// ```\n    pub fn close_margin_position(&mut self, currency_pair: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"currencyPair\", currency_pair);\n        self.private_query(\"closeMarginPosition\", &params)\n    }\n\n    /// Creates a loan offer for a given currency. Required POST parameters are \"currency\",\n    /// \"amount\", \"duration\", \"autoRenew\" (0 or 1), and \"lendingRate\".\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"success\":1,\"message\":\"Loan order placed.\",\"orderID\":10590}\n    /// ```\n    pub fn create_loan_offer(&mut self, currency: &str, amount: &str, duration: &str, auto_renew: &str, lending_rate: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"currency\", currency);\n        params.insert(\"amount\", amount);\n        params.insert(\"duration\", duration);\n        params.insert(\"autoRenew\", auto_renew);\n        params.insert(\"lendingRate\", lending_rate);\n        self.private_query(\"createLoanOffer\", &params)\n    }\n\n    /// Cancels a loan offer specified by the \"orderNumber\" POST parameter.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"success\":1,\"message\":\"Loan offer canceled.\"}\n    /// ```\n    pub fn cancel_loan_offer(&mut self, order_number: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"orderNumber\", order_number);\n        self.private_query(\"cancelLoanOffer\", &params)\n    }\n\n    /// Returns your open loan offers for each currency.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"BTC\":[{\"id\":10595,\"rate\":\"0.00020000\",\"amount\":\"3.00000000\",\"duration\":2,\"autoRenew\":1,\n    /// \"date\":\"2015-05-10 23:33:50\"}],\"LTC\":[{\"id\":10598,\"rate\":\"0.00002100\",\n    /// \"amount\":\"10.00000000\",\"duration\":2,\"autoRenew\":1,\"date\":\"2015-05-10 23:34:35\"}]}\n    /// ```\n    pub fn return_open_loan_offers(&mut self) -> Result<Map<String, Value>> {\n        let params = HashMap::new();\n        self.private_query(\"returnOpenLoanOffers\", &params)\n    }\n\n    /// Returns your active loans for each currency.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"provided\":[{\"id\":75073,\"currency\":\"LTC\",\"rate\":\"0.00020000\",\"amount\":\"0.72234880\",\n    /// \"range\":2,\"autoRenew\":0,\"date\":\"2015-05-10 23:45:05\",\"fees\":\"0.00006000\"},\n    /// {\"id\":74961,\"currency\":\"LTC\",\"rate\":\"0.00002000\",\"amount\":\"4.43860711\",\"range\":2,\n    /// \"autoRenew\":0,\"date\":\"2015-05-10 23:45:05\",\"fees\":\"0.00006000\"}],\n    /// \"used\":[{\"id\":75238,\"currency\":\"BTC\",\"rate\":\"0.00020000\",\"amount\":\"0.04843834\",\"range\":2,\n    /// \"date\":\"2015-05-10 23:51:12\",\"fees\":\"-0.00000001\"}]}\n    /// ```\n    pub fn return_active_loans(&mut self) -> Result<Map<String, Value>> {\n        let params = HashMap::new();\n        self.private_query(\"returnActiveLoans\", &params)\n    }\n\n    /// Returns your lending history within a time range specified by the \"start\" and \"end\" POST\n    /// parameters as UNIX timestamps. \"limit\" may also be specified to limit the number of rows\n    /// returned.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// [{ \"id\": 175589553, \"currency\": \"BTC\", \"rate\": \"0.00057400\", \"amount\": \"0.04374404\",\n    /// \"duration\": \"0.47610000\", \"interest\": \"0.00001196\", \"fee\": \"-0.00000179\",\n    /// \"earned\": \"0.00001017\", \"open\": \"2016-09-28 06:47:26\", \"close\": \"2016-09-28 18:13:03\" }]\n    /// ```\n    pub fn return_lending_history(&mut self, start: &str, end: &str, limit: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"start\", start);\n        params.insert(\"end\", end);\n        params.insert(\"limit\", limit);\n        self.private_query(\"returnLendingHistory\", &params)\n    }\n\n    /// Toggles the autoRenew setting on an active loan, specified by the \"orderNumber\" POST\n    /// parameter. If successful, \"message\" will indicate the new autoRenew setting.\n    ///\n    /// Sample output:\n    ///\n    /// ```json\n    /// {\"success\":1,\"message\":0}\n    /// ```\n    pub fn toggle_auto_renew(&mut self, order_number: &str) -> Result<Map<String, Value>> {\n        let mut params = HashMap::new();\n        params.insert(\"orderNumber\", order_number);\n        self.private_query(\"toggleAutoRenew\", &params)\n    }\n}\n\n#[cfg(test)]\nmod poloniex_api_tests {\n    use super::*;\n\n    #[test]\n    fn should_block_or_not_block_when_enabled_or_disabled() {\n        let mut api = PoloniexApi {\n            last_request: helpers::get_unix_timestamp_ms(),\n            api_key: \"\".to_string(),\n            api_secret: \"\".to_string(),\n            http_client: Client::new(),\n            burst: false,\n        };\n\n        let mut counter = 0;\n        loop {\n            api.set_burst(false);\n            let start = helpers::get_unix_timestamp_ms();\n            api.block_or_continue();\n            api.last_request = helpers::get_unix_timestamp_ms();\n\n            let difference = api.last_request - start;\n            assert!(difference >= 166);\n            assert!(difference < 1000);\n\n            api.set_burst(true);\n            let start = helpers::get_unix_timestamp_ms();\n            api.block_or_continue();\n            api.last_request = helpers::get_unix_timestamp_ms();\n\n            let difference = api.last_request - start;\n            assert!(difference < 10);\n\n            counter = counter + 1;\n            if counter >= 3 {\n                break;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/poloniex/credentials.rs",
    "content": "//! Contains the Poloniex credentials.\n\nuse std::collections::HashMap;\nuse std::str::FromStr;\n\nuse serde_json;\nuse serde_json::Value;\n\nuse crate::coinnect::Credentials;\nuse crate::exchange::Exchange;\nuse crate::helpers;\nuse crate::error::*;\n\nuse std::fs::File;\nuse std::io::Read;\nuse std::path::PathBuf;\n\n#[derive(Debug, Clone)]\npub struct PoloniexCreds {\n    exchange: Exchange,\n    name: String,\n    data: HashMap<String, String>,\n}\n\nimpl PoloniexCreds {\n    /// Create a new `PoloniexCreds` from arguments.\n    pub fn new(name: &str, api_key: &str, api_secret: &str) -> Self {\n        let mut creds = PoloniexCreds {\n            data: HashMap::new(),\n            exchange: Exchange::Poloniex,\n            name: if name.is_empty() {\n                \"PoloniexClient\".to_string()\n            } else {\n                name.to_string()\n            },\n        };\n\n\n        //if api_key.is_empty() {\n        //warning!(\"No API key set for the Bitstamp client\");\n        //}\n        creds\n            .data\n            .insert(\"api_key\".to_string(), api_key.to_string());\n\n        //if api_secret.is_empty() {\n        //warning!(\"No API secret set for the Bitstamp client\");\n        //}\n        creds\n            .data\n            .insert(\"api_secret\".to_string(), api_secret.to_string());\n\n        creds\n    }\n\n\n    /// Create a new `PoloniexCreds` from a json configuration file. This file must follow this\n    /// structure:\n    ///\n    /// ```json\n    /// {\n    ///     \"account_poloniex\": {\n    ///         \"exchange\"  : \"poloniex\",\n    ///         \"api_key\"   : \"123456789ABCDEF\",\n    ///         \"api_secret\": \"ABC&EF?abcdef\"\n    ///     },\n    ///     \"account_bitstamp\": {\n    ///         \"exchange\"   : \"bitstamp\",\n    ///         \"api_key\"    : \"1234567890ABCDEF1234567890ABCDEF\",\n    ///         \"api_secret\" : \"1234567890ABCDEF1234567890ABCDEF\",\n    ///         \"customer_id\": \"123456\"\n    ///     }\n    /// }\n    /// ```\n    /// For this example, you could use load your Poloniex account with\n    /// `PoloniexAPI::new(PoloniexCreds::new_from_file(\"account_kraken\", Path::new(\"/keys.json\")))`\n    pub fn new_from_file(name: &str, path: PathBuf) -> Result<Self> {\n        let mut f = File::open(&path)?;\n        let mut buffer = String::new();\n        f.read_to_string(&mut buffer)?;\n\n        let data: Value = serde_json::from_str(&buffer)?;\n        let json_obj = data.as_object()\n            .ok_or_else(|| ErrorKind::BadParse)?\n            .get(name)\n            .ok_or_else(|| ErrorKind::MissingField(name.to_string()))?;\n        let api_key = helpers::get_json_string(json_obj, \"api_key\")?;\n        let api_secret = helpers::get_json_string(json_obj, \"api_secret\")?;\n        let exchange = {\n            let exchange_str = helpers::get_json_string(json_obj, \"exchange\")?;\n            Exchange::from_str(exchange_str)\n                .chain_err(|| ErrorKind::InvalidFieldValue(\"exchange\".to_string()))?\n        };\n\n        if exchange != Exchange::Poloniex {\n            return Err(ErrorKind::InvalidConfigType(Exchange::Poloniex, exchange).into());\n        }\n\n        Ok(PoloniexCreds::new(name, api_key, api_secret))\n    }\n}\n\nimpl Credentials for PoloniexCreds {\n    /// Return a value from the credentials.\n    fn get(&self, key: &str) -> Option<String> {\n        if let Some(res) = self.data.get(key) {\n            Some(res.clone())\n        } else {\n            None\n        }\n    }\n\n    fn name(&self) -> String {\n        self.name.clone()\n    }\n\n    fn exchange(&self) -> Exchange {\n        self.exchange\n    }\n}\n"
  },
  {
    "path": "src/poloniex/generic_api.rs",
    "content": "//! Use this module to interact with Poloniex through a Generic API.\n//! This a more convenient and safe way to deal with the exchange since methods return a Result<>\n//! but this generic API does not provide all the functionnality that Poloniex offers.\n\nuse crate::exchange::ExchangeApi;\nuse crate::poloniex::api::PoloniexApi;\n\nuse bigdecimal::BigDecimal;\nuse std::str::FromStr;\n\nuse crate::error::*;\nuse crate::types::*;\nuse crate::poloniex::utils;\nuse crate::helpers;\n\nimpl ExchangeApi for PoloniexApi {\n    fn ticker(&mut self, pair: Pair) -> Result<Ticker> {\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(name) => name,\n            None => return Err(ErrorKind::PairUnsupported.into()),\n        };\n        let raw_response = self.return_ticker()?;\n\n        let result = utils::parse_result(&raw_response)?;\n\n        let price = helpers::from_json_bigdecimal(&result[*pair_name][\"last\"], \"last\")?;\n        let ask = helpers::from_json_bigdecimal(&result[*pair_name][\"lowestAsk\"], \"lowestAsk\")?;\n        let bid = helpers::from_json_bigdecimal(&result[*pair_name][\"highestBid\"], \"highestBid\")?;\n        let vol = helpers::from_json_bigdecimal(&result[*pair_name][\"quoteVolume\"], \"quoteVolume\")?;\n\n        Ok(Ticker {\n            timestamp: helpers::get_unix_timestamp_ms(),\n            pair: pair,\n            last_trade_price: price,\n            lowest_ask: ask,\n            highest_bid: bid,\n            volume: Some(vol),\n        })\n    }\n\n    fn orderbook(&mut self, pair: Pair) -> Result<Orderbook> {\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(name) => name,\n            None => return Err(ErrorKind::PairUnsupported.into()),\n        };\n        let raw_response = self.return_order_book(pair_name, \"1000\")?; // 1000 entries max\n\n        let result = utils::parse_result(&raw_response)?;\n\n        let mut ask_offers = Vec::new();\n        let mut bid_offers = Vec::new();\n\n        let ask_array = result[\"asks\"]\n            .as_array()\n            .ok_or_else(|| ErrorKind::InvalidFieldFormat(format!(\"{}\", result[\"asks\"])))?;\n        let bid_array = result[\"bids\"]\n            .as_array()\n            .ok_or_else(|| ErrorKind::InvalidFieldFormat(format!(\"{}\", result[\"asks\"])))?;\n\n        for ask in ask_array {\n            let price = helpers::from_json_bigdecimal(&ask[0], \"ask price\")?;\n            let volume_str = ask[1].as_f64().unwrap().to_string();\n            let volume = BigDecimal::from_str(&volume_str).unwrap();\n\n            ask_offers.push((price, volume));\n        }\n\n        for bid in bid_array {\n            let price = helpers::from_json_bigdecimal(&bid[0], \"bid price\")?;\n            let volume_str = bid[1].as_f64().unwrap().to_string();\n            let volume = BigDecimal::from_str(&volume_str).unwrap();\n\n            bid_offers.push((price, volume));\n        }\n\n        Ok(Orderbook {\n            timestamp: helpers::get_unix_timestamp_ms(),\n            pair: pair,\n            asks: ask_offers,\n            bids: bid_offers,\n        })\n    }\n\n    fn add_order(&mut self, order_type: OrderType, pair: Pair, quantity: Volume, price: Option<Price>) -> Result<OrderInfo> {\n        let pair_name = match utils::get_pair_string(&pair) {\n            Some(name) => name,\n            None => return Err(ErrorKind::PairUnsupported.into()),\n        };\n\n        // The trick is to use minimal (0.0) and \"maximum\" (999..) price to simulate market order\n        let raw_response = match order_type {\n            // Unwrap safe here with the check above.\n            OrderType::BuyLimit => {\n                if price.is_none() {\n                    return Err(ErrorKind::MissingPrice.into());\n                }\n\n                self.buy(\n                    pair_name,\n                    &price.unwrap().to_string(),\n                    &quantity.to_string(),\n                    None,\n                )\n            }\n            OrderType::BuyMarket => self.buy(\n                pair_name,\n                \"9999999999999999999\",\n                &quantity.to_string(),\n                None,\n            ),\n            OrderType::SellLimit => {\n                if price.is_none() {\n                    return Err(ErrorKind::MissingPrice.into());\n                }\n\n                self.sell(\n                    pair_name,\n                    &price.unwrap().to_string(),\n                    &quantity.to_string(),\n                    None,\n                )\n            }\n            OrderType::SellMarket => self.sell(pair_name, \"0.0\", &quantity.to_string(), None),\n        }?;\n\n        let result = utils::parse_result(&raw_response)?;\n\n        Ok(OrderInfo {\n            timestamp: helpers::get_unix_timestamp_ms(),\n            identifier: vec![\n                result[\"orderNumber\"]\n                    .as_f64()\n                    .ok_or_else(|| ErrorKind::MissingField(\"orderNumber\".to_string()))?\n                    .to_string(),\n            ],\n        })\n    }\n\n    fn balances(&mut self) -> Result<Balances> {\n        let raw_response = self.return_balances()?;\n        let result = utils::parse_result(&raw_response)?;\n\n        let mut balances = Balances::new();\n\n        for (key, val) in result.iter() {\n            let currency = utils::get_currency_enum(key);\n\n            if currency.is_some() {\n                let amount = helpers::from_json_bigdecimal(&val, \"amount\")?;\n                balances.insert(currency.unwrap(), amount);\n            }\n        }\n        Ok(balances)\n    }\n}\n"
  },
  {
    "path": "src/poloniex/mod.rs",
    "content": "//! Use this module to interact with Poloniex exchange.\n\npub mod api;\npub mod generic_api;\npub mod credentials;\npub mod utils;\n\npub use self::credentials::PoloniexCreds;\npub use self::api::PoloniexApi;\npub use self::api::{MoveOrderOption, PlaceOrderOption};\n"
  },
  {
    "path": "src/poloniex/utils.rs",
    "content": "use bidir_map::BidirMap;\nuse serde_json;\nuse serde_json::Value;\nuse serde_json::value::Map;\n\nuse crate::error::*;\nuse crate::types::Currency;\nuse crate::types::Pair;\nuse crate::types::Pair::*;\n\nlazy_static! {\n    static ref PAIRS_STRING: BidirMap<Pair, &'static str> = {\n        let mut m = BidirMap::new();\n        m.insert(BCN_BTC, \"BTC_BCN\");\n        m.insert(BELA_BTC, \"BTC_BELA\");\n        m.insert(BLK_BTC, \"BTC_BLK\");\n        m.insert(BTCD_BTC, \"BTC_BTCD\");\n        m.insert(BTM_BTC, \"BTC_BTM\");\n        m.insert(BTS_BTC, \"BTC_BTS\");\n        m.insert(BURST_BTC, \"BTC_BURST\");\n        m.insert(CLAM_BTC, \"BTC_CLAM\");\n        m.insert(DASH_BTC, \"BTC_DASH\");\n        m.insert(DGB_BTC, \"BTC_DGB\");\n        m.insert(DOGE_BTC, \"BTC_DOGE\");\n        m.insert(EMC2_BTC, \"BTC_EMC2\");\n        m.insert(FLDC_BTC, \"BTC_FLDC\");\n        m.insert(FLO_BTC, \"BTC_FLO\");\n        m.insert(GAME_BTC, \"BTC_GAME\");\n        m.insert(GRC_BTC, \"BTC_GRC\");\n        m.insert(HUC_BTC, \"BTC_HUC\");\n        m.insert(LTC_BTC, \"BTC_LTC\");\n        m.insert(MAID_BTC, \"BTC_MAID\");\n        m.insert(OMNI_BTC, \"BTC_OMNI\");\n        m.insert(NAUT_BTC, \"BTC_NAUT\");\n        m.insert(NAV_BTC, \"BTC_NAV\");\n        m.insert(NEOS_BTC, \"BTC_NEOS\");\n        m.insert(NMC_BTC, \"BTC_NMC\");\n        m.insert(NOTE_BTC, \"BTC_NOTE\");\n        m.insert(NXT_BTC, \"BTC_NXT\");\n        m.insert(PINK_BTC, \"BTC_PINK\");\n        m.insert(POT_BTC, \"BTC_POT\");\n        m.insert(PPC_BTC, \"BTC_PPC\");\n        m.insert(RIC_BTC, \"BTC_RIC\");\n        m.insert(SJCX_BTC, \"BTC_SJCX\");\n        m.insert(STR_BTC, \"BTC_STR\");\n        m.insert(SYS_BTC, \"BTC_SYS\");\n        m.insert(VIA_BTC, \"BTC_VIA\");\n        m.insert(XVC_BTC, \"BTC_XVC\");\n        m.insert(VRC_BTC, \"BTC_VRC\");\n        m.insert(VTC_BTC, \"BTC_VTC\");\n        m.insert(XBC_BTC, \"BTC_XBC\");\n        m.insert(XCP_BTC, \"BTC_XCP\");\n        m.insert(XEM_BTC, \"BTC_XEM\");\n        m.insert(XMR_BTC, \"BTC_XMR\");\n        m.insert(XPM_BTC, \"BTC_XPM\");\n        m.insert(XRP_BTC, \"BTC_XRP\");\n        m.insert(BTC_USDT, \"USDT_BTC\");\n        m.insert(DASH_USDT, \"USDT_DASH\");\n        m.insert(LTC_USDT, \"USDT_LTC\");\n        m.insert(NXT_USDT, \"USDT_NXT\");\n        m.insert(STR_USDT, \"USDT_STR\");\n        m.insert(XMR_USDT, \"USDT_XMR\");\n        m.insert(XRP_USDT, \"USDT_XRP\");\n        m.insert(BCN_XMR, \"XMR_BCN\");\n        m.insert(BLK_XMR, \"XMR_BLK\");\n        m.insert(BTCD_XMR, \"XMR_BTCD\");\n        m.insert(DASH_XMR, \"XMR_DASH\");\n        m.insert(LTC_XMR, \"XMR_LTC\");\n        m.insert(MAID_XMR, \"XMR_MAID\");\n        m.insert(NXT_XMR, \"XMR_NXT\");\n        m.insert(ETH_BTC, \"BTC_ETH\");\n        m.insert(ETH_USDT, \"USDT_ETH\");\n        m.insert(SC_BTC, \"BTC_SC\");\n        m.insert(BCY_BTC, \"BTC_BCY\");\n        m.insert(EXP_BTC, \"BTC_EXP\");\n        m.insert(FCT_BTC, \"BTC_FCT\");\n        m.insert(RADS_BTC, \"BTC_RADS\");\n        m.insert(AMP_BTC, \"BTC_AMP\");\n        m.insert(DCR_BTC, \"BTC_DCR\");\n        m.insert(LSK_BTC, \"BTC_LSK\");\n        m.insert(LSK_ETH, \"ETH_LSK\");\n        m.insert(LBC_BTC, \"BTC_LBC\");\n        m.insert(STEEM_BTC, \"BTC_STEEM\");\n        m.insert(STEEM_ETH, \"ETH_STEEM\");\n        m.insert(SBD_BTC, \"BTC_SBD\");\n        m.insert(ETC_BTC, \"BTC_ETC\");\n        m.insert(ETC_ETH, \"ETH_ETC\");\n        m.insert(ETC_USDT, \"USDT_ETC\");\n        m.insert(REP_BTC, \"BTC_REP\");\n        m.insert(REP_USDT, \"USDT_REP\");\n        m.insert(REP_ETH, \"ETH_REP\");\n        m.insert(ARDR_BTC, \"BTC_ARDR\");\n        m.insert(ZEC_BTC, \"BTC_ZEC\");\n        m.insert(ZEC_ETH, \"ETH_ZEC\");\n        m.insert(ZEC_USDT, \"USDT_ZEC\");\n        m.insert(ZEC_XMR, \"XMR_ZEC\");\n        m.insert(STRAT_BTC, \"BTC_STRAT\");\n        m.insert(NXC_BTC, \"BTC_NXC\");\n        m.insert(PASC_BTC, \"BTC_PASC\");\n        m.insert(GNT_BTC, \"BTC_GNT\");\n        m.insert(GNT_ETH, \"ETH_GNT\");\n        m.insert(GNO_BTC, \"BTC_GNO\");\n        m.insert(GNO_ETH, \"ETH_GNO\");\n        m.insert(BCH_BTC, \"BTC_BCH\");\n        m.insert(BCH_ETH, \"ETH_BCH\");\n        m.insert(BCH_USDT, \"USDT_BCH\");\n        m.insert(ZRX_BTC, \"BTC_ZRX\");\n        m.insert(ZRX_ETH, \"ETH_ZRX\");\n        m.insert(CVC_BTC, \"BTC_CVC\");\n        m.insert(CVC_ETH, \"ETH_CVC\");\n        m.insert(OMG_BTC, \"BTC_OMG\");\n        m.insert(OMG_ETH, \"ETH_OMG\");\n        m.insert(GAS_BTC, \"BTC_GAS\");\n        m.insert(GAS_ETH, \"ETH_GAS\");\n        m.insert(STORJ_BTC, \"BTC_STORJ\");\n        m\n    };\n}\n\n/// Return the name associated to pair used by Poloniex\n/// If the Pair is not supported, None is returned.\npub fn get_pair_string(pair: &Pair) -> Option<&&str> {\n    PAIRS_STRING.get_by_first(pair)\n}\n\n/// Return the Pair enum associated to the string used by Poloniex\n/// If the Pair is not supported, None is returned.\npub fn get_pair_enum(pair: &str) -> Option<&Pair> {\n    PAIRS_STRING.get_by_second(&pair)\n}\n\npub fn deserialize_json(json_string: &str) -> Result<Map<String, Value>> {\n    let data: Value = match serde_json::from_str(json_string) {\n        Ok(data) => data,\n        Err(_) => return Err(ErrorKind::BadParse.into()),\n    };\n\n    match data.as_object() {\n        Some(value) => Ok(value.clone()),\n        None => Err(ErrorKind::BadParse.into()),\n    }\n}\n\n/// Convert a JSON array into a map containing a Vec for the \"data\" key\npub fn deserialize_json_array(json_string: &str) -> Result<Map<String, Value>> {\n    let data: Value = match serde_json::from_str(json_string) {\n        Ok(data) => data,\n        Err(_) => return Err(ErrorKind::BadParse.into()),\n    };\n\n    if data.is_array() {\n        let mut map = Map::new();\n        map.insert(\"data\".to_string(), data);\n        Ok(map)\n    }\n\n    else {\n        Err(ErrorKind::BadParse.into())\n    }\n}\n\n\n/// If error array is null, return the result (encoded in a json object)\n/// else return the error string found in array\npub fn parse_result(response: &Map<String, Value>) -> Result<Map<String, Value>> {\n    let error_msg = match response.get(\"error\") {\n        Some(error) => {\n            error\n                .as_str()\n                .ok_or_else(|| ErrorKind::InvalidFieldFormat(\"error\".to_string()))?\n        }\n        None => return Ok(response.clone()),\n    };\n\n    match error_msg.as_ref() {\n        \"Invalid command.\" => Err(ErrorKind::InvalidArguments.into()),\n        \"Invalid API key/secret pair.\" => Err(ErrorKind::BadCredentials.into()),\n        \"Total must be at least 0.0001.\" => Err(ErrorKind::InsufficientOrderSize.into()),\n        other => Err(ErrorKind::ExchangeSpecificError(other.to_string()).into()),\n    }\n}\n\n/// Return the currency enum associated with the\n/// string used by Poloniex. If no currency is found,\n/// return None\n/// # Examples\n///\n/// ```\n/// use coinnect::poloniex::utils::get_currency_enum;\n/// use coinnect::types::Currency;\n///\n/// let currency = get_currency_enum(\"BTC\").unwrap();\n/// assert_eq!(currency, Currency::BTC);\n/// ```\npub fn get_currency_enum(currency: &str) -> Option<Currency> {\n    match currency {\n        \"AMP\" => Some(Currency::AMP),\n        \"BTC\" => Some(Currency::BTC),\n        \"ARDR\" => Some(Currency::ARDR),\n        \"ETH\" => Some(Currency::ETH),\n        \"ETC\" => Some(Currency::ETC),\n        \"LBC\" => Some(Currency::LBC),\n        \"XMR\" => Some(Currency::XMR),\n        \"XPM\" => Some(Currency::XPM),\n        \"XRP\" => Some(Currency::XRP),\n        \"XVC\" => Some(Currency::XVC),\n        \"ZEC\" => Some(Currency::ZEC),\n        _ => None,\n    }\n}\n\n/// Return the currency string associated with the\n/// enum used by Poloniex. If no currency is found,\n/// return None\n/// # Examples\n///\n/// ```\n/// use coinnect::poloniex::utils::get_currency_string;\n/// use coinnect::types::Currency;\n///\n/// let currency = get_currency_string(Currency::BTC);\n/// assert_eq!(currency, Some(\"BTC\".to_string()));\n/// ```\npub fn get_currency_string(currency: Currency) -> Option<String> {\n    match currency {\n        Currency::AMP => Some(\"AMP\".to_string()),\n        Currency::BTC => Some(\"BTC\".to_string()),\n        Currency::ARDR => Some(\"ARDR\".to_string()),\n        Currency::ETH => Some(\"ETH\".to_string()),\n        Currency::ETC => Some(\"ETC\".to_string()),\n        Currency::LBC => Some(\"LBC\".to_string()),\n        Currency::XMR => Some(\"XMR\".to_string()),\n        Currency::XPM => Some(\"XPM\".to_string()),\n        Currency::XRP => Some(\"XRP\".to_string()),\n        Currency::XVC => Some(\"XVC\".to_string()),\n        Currency::ZEC => Some(\"ZEC\".to_string()),\n        _ => None,\n    }\n}\n"
  },
  {
    "path": "src/types.rs",
    "content": "//! Types definition used for handling returned data when generic API is used.\n\nuse std::collections::HashMap;\nuse bigdecimal::BigDecimal;\nuse std::str::FromStr;\n\n\npub type Amount = BigDecimal;\npub type Price = BigDecimal;\npub type Volume = BigDecimal;\n\npub type Balances = HashMap<Currency, Amount>;\n\n#[derive(Debug)]\npub struct Ticker {\n    /// UNIX timestamp in ms (when the response was received)\n    pub timestamp: i64,\n    /// The Pair corresponding to the Ticker returned (maybe useful later for asynchronous APIs)\n    pub pair: Pair,\n    /// Last trade price found in the history\n    pub last_trade_price: Price,\n    /// Lowest ask price found in Orderbook\n    pub lowest_ask: Price,\n    /// Highest bid price found in Orderbook\n    pub highest_bid: Price,\n    // Bittrex does not support Volume for ticker so volume could be None\n    /// Last 24 hours volume (quote-volume)\n    pub volume: Option<Volume>,\n}\n\n#[derive(Debug)]\npub struct Orderbook {\n    /// UNIX timestamp in ms (when the response was received)\n    pub timestamp: i64,\n    /// The Pair corresponding to the Orderbook returned (maybe useful later for asynchronous APIs)\n    pub pair: Pair,\n    /// Vec containing the ask offers (by ascending price)\n    pub asks: Vec<(Price, Volume)>,\n    /// Vec containing the bid offers (by descending price)\n    pub bids: Vec<(Price, Volume)>,\n}\n\nimpl Orderbook {\n    /// Convenient function that returns the average price from the orderbook\n    /// Return None if Orderbook is empty\n    /// `Average price = (lowest ask + highest bid)/2`\n    pub fn avg_price(&self) -> Option<Price> {\n        if self.asks.is_empty() || self.bids.is_empty() {\n            return None;\n        }\n        Some(\n            (self.asks[0].0.clone() + self.bids[0].0.clone())\n            /\n            BigDecimal::from_str(\"2.0\").unwrap()\n        )\n    }\n}\n\n#[derive(Debug)]\npub struct OrderInfo {\n    /// UNIX timestamp in ms (when the response was received)\n    pub timestamp: i64,\n    /// This identifiers list is specific to the platform you use. You must store it somewhere if\n    /// you want to modify/cancel the order later\n    pub identifier: Vec<String>,\n}\n\n#[derive(Debug, PartialEq)]\npub enum OrderType {\n    BuyLimit,\n    SellLimit,\n    BuyMarket,\n    SellMarket,\n}\n\n/// Currency lists all currencies that can be traded on supported exchanges.\n/// Update date : 27/10/2017.\n/// Note : 1ST, 2GIVE, 8BIT have been renammed \"_1ST\", \"_2GIVE\" and \"_8BIT\" since variables name\n/// cannot start with a number.\n#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]\n#[allow(non_camel_case_types)]\npub enum Currency {\n    _1ST,\n    _2GIVE,\n    _8BIT,\n    ABY,\n    ADA,\n    ADC,\n    ADT,\n    ADX,\n    AEON,\n    AGRS,\n    AM,\n    AMP,\n    AMS,\n    ANT,\n    APEX,\n    APX,\n    ARB,\n    ARDR,\n    ARK,\n    AUR,\n    BAT,\n    BAY,\n    BCC,\n    BCH,\n    BCN,\n    BCY,\n    BELA,\n    BITB,\n    BITCNY,\n    BITS,\n    BITZ,\n    BLC,\n    BLITZ,\n    BLK,\n    BLOCK,\n    BNT,\n    BOB,\n    BRK,\n    BRX,\n    BSD,\n    BSTY,\n    BTA,\n    BTC,\n    BTCD,\n    BTM,\n    BTS,\n    BURST,\n    BYC,\n    CAD,\n    CANN,\n    CCN,\n    CFI,\n    CLAM,\n    CLOAK,\n    CLUB,\n    COVAL,\n    CPC,\n    CRB,\n    CRBIT,\n    CRW,\n    CRYPT,\n    CURE,\n    CVC,\n    DAR,\n    DASH,\n    DCR,\n    DCT,\n    DGB,\n    DGC,\n    DGD,\n    DMD,\n    DNT,\n    DOGE,\n    DOPE,\n    DRACO,\n    DTB,\n    DTC,\n    DYN,\n    EBST,\n    EDG,\n    EFL,\n    EGC,\n    EMC,\n    EMC2,\n    ENRG,\n    EOS,\n    ERC,\n    ETC,\n    ETH,\n    EUR,\n    EXCL,\n    EXP,\n    FAIR,\n    FC2,\n    FCT,\n    FLDC,\n    FLO,\n    FRK,\n    FSC2,\n    FTC,\n    FUN,\n    GAM,\n    GAME,\n    GBG,\n    GBP,\n    GBYTE,\n    GCR,\n    GEMZ,\n    GEO,\n    GHC,\n    GLD,\n    GNO,\n    GNT,\n    GOLOS,\n    GP,\n    GRC,\n    GRS,\n    GRT,\n    GUP,\n    HKG,\n    HMQ,\n    HUC,\n    HYPER,\n    HZ,\n    ICN,\n    INCNT,\n    INFX,\n    IOC,\n    ION,\n    IOP,\n    J,\n    JPY,\n    KMD,\n    KORE,\n    KR,\n    LBC,\n    LGD,\n    LMC,\n    LSK,\n    LTC,\n    LUN,\n    LXC,\n    MAID,\n    MANA,\n    MAX,\n    MCO,\n    MEC,\n    MEME,\n    METAL,\n    MLN,\n    MND,\n    MONA,\n    MTL,\n    MTR,\n    MUE,\n    MUSIC,\n    MYST,\n    MZC,\n    NAUT,\n    NAV,\n    NBT,\n    NEO,\n    NEOS,\n    NET,\n    NEU,\n    NLG,\n    NMC,\n    NMR,\n    NOTE,\n    NTRN,\n    NXC,\n    NXS,\n    NXT,\n    OC,\n    OK,\n    OMG,\n    OMNI,\n    ORB,\n    PART,\n    PASC,\n    PAY,\n    PDC,\n    PINK,\n    PIVX,\n    PKB,\n    POT,\n    PPC,\n    PRIME,\n    PTC,\n    PTOY,\n    PXI,\n    QRL,\n    QTUM,\n    QWARK,\n    RADS,\n    RBY,\n    RDD,\n    REP,\n    RIC,\n    RISE,\n    RLC,\n    ROOT,\n    SAFEX,\n    SALT,\n    SBD,\n    SC,\n    SCOT,\n    SCRT,\n    SEQ,\n    SFR,\n    SHIFT,\n    SIB,\n    SJCX,\n    SLG,\n    SLING,\n    SLR,\n    SLS,\n    SNGLS,\n    SNRG,\n    SNT,\n    SOON,\n    SPHR,\n    SPR,\n    SPRTS,\n    SSD,\n    START,\n    STEEM,\n    STEPS,\n    STORJ,\n    STR,\n    STRAT,\n    STV,\n    SWIFT,\n    SWING,\n    SWT,\n    SYNX,\n    SYS,\n    TES,\n    THC,\n    TIME,\n    TIT,\n    TIX,\n    TKN,\n    TKS,\n    TRI,\n    TRIG,\n    TRK,\n    TROLL,\n    TRST,\n    TRUST,\n    TX,\n    U,\n    UBQ,\n    UFO,\n    UNB,\n    UNIQ,\n    UNIT,\n    UNO,\n    USD,\n    USDT,\n    UTC,\n    VIA,\n    VIOR,\n    VIRAL,\n    VOX,\n    VPN,\n    VRC,\n    VRM,\n    VTC,\n    VTR,\n    WARP,\n    WAVES,\n    WINGS,\n    XAUR,\n    XBB,\n    XBC,\n    XC,\n    XCO,\n    XCP,\n    XDG,\n    XDN,\n    XDQ,\n    XEL,\n    XEM,\n    XLM,\n    XMG,\n    XMR,\n    XMY,\n    XPM,\n    XPY,\n    XQN,\n    XRP,\n    XSEED,\n    XST,\n    XTC,\n    XVC,\n    XVG,\n    XWC,\n    XZC,\n    YBC,\n    ZCL,\n    ZEC,\n    ZEN,\n}\n\n/// Pair lists all pairs that can be traded on supported exchanges.\n/// Update date : 27/10/2017.\n///\n/// Order of quote currency <-> base currency is important. For example, Kraken supports ZEC_BTC\n/// but Poloniex is doing the opposite inside their API: BTC_ZEC, which equal to 1/ZEC_BTC.\n/// So: ZEC_BTC != BTC_ZEC but Poloniex ignores this and decided that BTC_ZEC = ZEC_BTC, so\n/// that 1 ZEC = ZEC_BTC pair value. To standardize, the following pair uses the standard notation.\n/// (so all Poloniex pair has been flipped)\n///\n/// Note : Kraken uses 'XBT' instead of 'BTC' (so the XBT/EUR pair becomes BTC/EUR).\n///\n/// To summarize, Kraken uses the pair 'ZEC_XBT', whereas Poloniex uses the 'BTC_ZEC' pair. With\n/// the standardization proposed above these 2 pairs become 'ZEC_BTC', that are comparable in\n/// value accross the 2 exchanges.\n/// Pairs with \"_d\" at the end : dark pool\n/// \n/// Note 2 : 1ST and 2GIVE have been renammed \"_1ST\" and \"_2GIVE\" since variables name cannot start\n/// with a number.\n#[derive(Debug, Copy, Clone, PartialEq)]\n#[allow(non_camel_case_types)]\npub enum Pair {\n    _1ST_BTC,\n    _1ST_ETH,\n    _2GIVE_BTC,\n    ABY_BTC,\n    ADA_BTC,\n    ADT_BTC,\n    ADT_ETH,\n    ADX_BTC,\n    ADX_ETH,\n    AEON_BTC,\n    AGRS_BTC,\n    AMP_BTC,\n    ANT_BTC,\n    ANT_ETH,\n    APX_BTC,\n    ARDR_BTC,\n    ARK_BTC,\n    AUR_BTC,\n    BAT_BTC,\n    BAT_ETH,\n    BAY_BTC,\n    BCC_BTC,\n    BCC_ETH,\n    BCC_USDT,\n    BCH_BTC,\n    BCH_ETH,\n    BCH_EUR,\n    BCH_USD,\n    BCH_USDT,\n    BCN_BTC,\n    BCN_XMR,\n    BCY_BTC,\n    BELA_BTC,\n    BITB_BTC,\n    BLITZ_BTC,\n    BLK_BTC,\n    BLK_XMR,\n    BLOCK_BTC,\n    BNT_BTC,\n    BNT_ETH,\n    BRK_BTC,\n    BRX_BTC,\n    BSD_BTC,\n    BTCD_BTC,\n    BTCD_XMR,\n    BTC_CAD,\n    BTC_CAD_d,\n    BTC_EUR,\n    BTC_EUR_d,\n    BTC_GBP,\n    BTC_GBP_d,\n    BTC_JPY,\n    BTC_JPY_d,\n    BTC_USD,\n    BTC_USDT,\n    BTC_USD_d,\n    BTM_BTC,\n    BTS_BTC,\n    BTS_ETH,\n    BURST_BTC,\n    BYC_BTC,\n    CANN_BTC,\n    CFI_BTC,\n    CFI_ETH,\n    CLAM_BTC,\n    CLOAK_BTC,\n    CLUB_BTC,\n    COVAL_BTC,\n    CPC_BTC,\n    CRB_BTC,\n    CRB_ETH,\n    CRW_BTC,\n    CURE_BTC,\n    CVC_BTC,\n    CVC_ETH,\n    DASH_BTC,\n    DASH_ETH,\n    DASH_EUR,\n    DASH_USD,\n    DASH_USDT,\n    DASH_XMR,\n    DCR_BTC,\n    DCT_BTC,\n    DGB_BTC,\n    DGB_ETH,\n    DGD_BTC,\n    DGD_ETH,\n    DMD_BTC,\n    DNT_BTC,\n    DNT_ETH,\n    DOGE_BTC,\n    DOPE_BTC,\n    DTB_BTC,\n    DYN_BTC,\n    EBST_BTC,\n    EDG_BTC,\n    EFL_BTC,\n    EGC_BTC,\n    EMC2_BTC,\n    EMC_BTC,\n    ENRG_BTC,\n    EOS_BTC,\n    EOS_ETH,\n    ERC_BTC,\n    ETC_BTC,\n    ETC_ETH,\n    ETC_EUR,\n    ETC_USD,\n    ETC_USDT,\n    ETH_BTC,\n    ETH_BTC_d,\n    ETH_CAD,\n    ETH_CAD_d,\n    ETH_EUR,\n    ETH_EUR_d,\n    ETH_GBP,\n    ETH_GBP_d,\n    ETH_JPY,\n    ETH_JPY_d,\n    ETH_USD,\n    ETH_USDT,\n    ETH_USD_d,\n    EUR_USD,\n    EXCL_BTC,\n    EXP_BTC,\n    FAIR_BTC,\n    FCT_BTC,\n    FCT_ETH,\n    FLDC_BTC,\n    FLO_BTC,\n    FTC_BTC,\n    FUN_BTC,\n    FUN_ETH,\n    GAME_BTC,\n    GAM_BTC,\n    GAS_BTC,\n    GAS_ETH,\n    GBG_BTC,\n    GBYTE_BTC,\n    GCR_BTC,\n    GEO_BTC,\n    GLD_BTC,\n    GNO_BTC,\n    GNO_ETH,\n    GNT_BTC,\n    GNT_ETH,\n    GOLOS_BTC,\n    GRC_BTC,\n    GRS_BTC,\n    GUP_BTC,\n    GUP_ETH,\n    HMQ_BTC,\n    HMQ_ETH,\n    HUC_BTC,\n    ICN_BTC,\n    ICN_ETH,\n    INCNT_BTC,\n    INFX_BTC,\n    IOC_BTC,\n    ION_BTC,\n    IOP_BTC,\n    KMD_BTC,\n    KORE_BTC,\n    LBC_BTC,\n    LGD_BTC,\n    LGD_ETH,\n    LMC_BTC,\n    LSK_BTC,\n    LSK_ETH,\n    LTC_BTC,\n    LTC_ETH,\n    LTC_EUR,\n    LTC_USD,\n    LTC_USDT,\n    LTC_XMR,\n    LUN_BTC,\n    LUN_ETH,\n    MAID_BTC,\n    MAID_XMR,\n    MANA_BTC,\n    MANA_ETH,\n    MCO_BTC,\n    MCO_ETH,\n    MEME_BTC,\n    MLN_BTC,\n    MLN_ETH,\n    MONA_BTC,\n    MTL_BTC,\n    MTL_ETH,\n    MUE_BTC,\n    MUSIC_BTC,\n    MYST_BTC,\n    MYST_ETH,\n    NAUT_BTC,\n    NAV_BTC,\n    NBT_BTC,\n    NEOS_BTC,\n    NEO_BTC,\n    NEO_ETH,\n    NEO_USDT,\n    NLG_BTC,\n    NMC_BTC,\n    NMR_BTC,\n    NMR_ETH,\n    NOTE_BTC,\n    NXC_BTC,\n    NXS_BTC,\n    NXT_BTC,\n    NXT_USDT,\n    NXT_XMR,\n    OK_BTC,\n    OMG_BTC,\n    OMG_ETH,\n    OMG_USDT,\n    OMNI_BTC,\n    PART_BTC,\n    PASC_BTC,\n    PAY_BTC,\n    PAY_ETH,\n    PDC_BTC,\n    PINK_BTC,\n    PIVX_BTC,\n    PKB_BTC,\n    POT_BTC,\n    PPC_BTC,\n    PTC_BTC,\n    PTOY_BTC,\n    PTOY_ETH,\n    QRL_BTC,\n    QRL_ETH,\n    QTUM_BTC,\n    QTUM_ETH,\n    QWARK_BTC,\n    RADS_BTC,\n    RBY_BTC,\n    RDD_BTC,\n    REP_BTC,\n    REP_ETH,\n    REP_EUR,\n    REP_USDT,\n    RIC_BTC,\n    RISE_BTC,\n    RLC_BTC,\n    RLC_ETH,\n    SAFEX_BTC,\n    SALT_BTC,\n    SALT_ETH,\n    SBD_BTC,\n    SC_BTC,\n    SC_ETH,\n    SEQ_BTC,\n    SHIFT_BTC,\n    SIB_BTC,\n    SJCX_BTC,\n    SLR_BTC,\n    SLS_BTC,\n    SNGLS_BTC,\n    SNGLS_ETH,\n    SNRG_BTC,\n    SNT_BTC,\n    SNT_ETH,\n    SPHR_BTC,\n    SPR_BTC,\n    START_BTC,\n    STEEM_BTC,\n    STEEM_ETH,\n    STORJ_BTC,\n    STORJ_ETH,\n    STRAT_BTC,\n    STRAT_ETH,\n    STR_BTC,\n    STR_USDT,\n    SWIFT_BTC,\n    SWT_BTC,\n    SYNX_BTC,\n    SYS_BTC,\n    THC_BTC,\n    TIME_BTC,\n    TIME_ETH,\n    TIX_BTC,\n    TIX_ETH,\n    TKN_BTC,\n    TKN_ETH,\n    TKS_BTC,\n    TRIG_BTC,\n    TRST_BTC,\n    TRST_ETH,\n    TRUST_BTC,\n    TX_BTC,\n    UBQ_BTC,\n    UNB_BTC,\n    USDT_USD,\n    VIA_BTC,\n    VOX_BTC,\n    VRC_BTC,\n    VRM_BTC,\n    VTC_BTC,\n    VTR_BTC,\n    WAVES_BTC,\n    WAVES_ETH,\n    WINGS_BTC,\n    WINGS_ETH,\n    XAUR_BTC,\n    XBC_BTC,\n    XCP_BTC,\n    XDG_BTC,\n    XDN_BTC,\n    XEL_BTC,\n    XEM_BTC,\n    XEM_ETH,\n    XLM_BTC,\n    XLM_ETH,\n    XMG_BTC,\n    XMR_BTC,\n    XMR_ETH,\n    XMR_EUR,\n    XMR_USD,\n    XMR_USDT,\n    XMY_BTC,\n    XPM_BTC,\n    XRP_BTC,\n    XRP_ETH,\n    XRP_EUR,\n    XRP_USD,\n    XRP_USDT,\n    XST_BTC,\n    XVC_BTC,\n    XVG_BTC,\n    XWC_BTC,\n    XZC_BTC,\n    ZCL_BTC,\n    ZEC_BTC,\n    ZEC_ETH,\n    ZEC_EUR,\n    ZEC_USD,\n    ZEC_USDT,\n    ZEC_XMR,\n    ZEN_BTC,\n    ZRX_BTC,\n    ZRX_ETH,\n}\n"
  },
  {
    "path": "tests/bitstamp.rs",
    "content": "#[cfg(test)]\nmod bitstamp_tests {\n    extern crate coinnect;\n    extern crate bigdecimal;\n\n    use self::bigdecimal::BigDecimal;\n    use std::str::FromStr;\n\n    use self::coinnect::bitstamp::utils;\n    use self::coinnect::bitstamp::{BitstampApi, BitstampCreds};\n    use self::coinnect::kraken::KrakenCreds;\n\n    use self::coinnect::exchange::ExchangeApi;\n    use self::coinnect::types::Pair;\n\n    #[test]\n    fn build_url_should_return_the_a_url() {\n        assert_eq!(utils::build_url(\"ticker\", \"btcusd\"),\n                   \"https://www.bitstamp.net/api/v2/ticker/btcusd/\");\n    }\n    #[test]\n    fn build_url_should_return_the_url_for_transactions_for_btc_usd() {\n        assert_eq!(utils::build_url(\"transactions\", \"btcusd\"),\n                   \"https://www.bitstamp.net/api/v2/transactions/btcusd/\");\n    }\n\n    #[test]\n    fn fail_with_invalid_creds() {\n        let creds = KrakenCreds::new(\"\", \"\", \"\");\n        let res = BitstampApi::new(creds);\n        assert_eq!(res.unwrap_err().to_string(),\n                   \"Invalid config: \\nExpected: Bitstamp\\nFind: Kraken\");\n    }\n\n\n    #[test]\n    fn can_get_real_bitstamp_tick() {\n        let creds = BitstampCreds::new(\"\", \"\", \"\", \"\");\n        let mut api = BitstampApi::new(creds).unwrap();\n        api.ticker(Pair::BTC_USD).unwrap();\n    }\n\n    #[test]\n    fn ticker_should_have_the_correct_last() {\n        let creds = BitstampCreds::new(\"\", \"\", \"\", \"\");\n        let mut api = BitstampApi::new(creds).unwrap();\n        let result = api.ticker(Pair::BTC_USD);\n        assert_ne!(result.unwrap().last_trade_price,\n                   BigDecimal::from_str(\"0.0\").unwrap());\n    }\n    #[test]\n    fn ticker_should_have_the_correct_high() {\n        let creds = BitstampCreds::new(\"\", \"\", \"\", \"\");\n        let mut api = BitstampApi::new(creds).unwrap();\n        let result = api.ticker(Pair::BTC_USD);\n        assert_ne!(result.unwrap().highest_bid,\n                   BigDecimal::from_str(\"0.0\").unwrap());\n    }\n    #[test]\n    fn ticker_should_have_the_correct_low() {\n        let creds = BitstampCreds::new(\"\", \"\", \"\", \"\");\n        let mut api = BitstampApi::new(creds).unwrap();\n        let result = api.ticker(Pair::BTC_USD);\n        assert_ne!(result.unwrap().lowest_ask,\n                   BigDecimal::from_str(\"0.0\").unwrap());\n    }\n    #[test]\n    fn ticker_should_have_the_correct_volume() {\n        let creds = BitstampCreds::new(\"\", \"\", \"\", \"\");\n        let mut api = BitstampApi::new(creds).unwrap();\n        let result = api.ticker(Pair::BTC_USD);\n        assert_ne!(result.unwrap().volume.unwrap(),\n                   BigDecimal::from_str(\"0.0\").unwrap());\n    }\n\n    #[test]\n    fn should_return_an_order_book() {\n        let creds = BitstampCreds::new(\"\", \"\", \"\", \"\");\n        let mut api = BitstampApi::new(creds).unwrap();\n        let result = api.return_order_book(Pair::BTC_USD);\n        assert_eq!(result.is_ok(), true);\n    }\n\n    #[test]\n    fn order_book_should_have_a_timestamp() {\n        let creds = BitstampCreds::new(\"\", \"\", \"\", \"\");\n        let mut api = BitstampApi::new(creds).unwrap();\n        let result = api.return_order_book(Pair::BTC_USD);\n        assert!(result.unwrap().contains_key(\"timestamp\"));\n    }\n    #[test]\n    fn order_book_should_have_bids() {\n        let creds = BitstampCreds::new(\"\", \"\", \"\", \"\");\n        let mut api = BitstampApi::new(creds).unwrap();\n        let result = api.return_order_book(Pair::BTC_USD);\n        assert!(result.unwrap().contains_key(\"bids\"));\n    }\n    #[test]\n    fn order_book_should_have_asks() {\n        let creds = BitstampCreds::new(\"\", \"\", \"\", \"\");\n        let mut api = BitstampApi::new(creds).unwrap();\n        let result = api.return_order_book(Pair::BTC_USD);\n        assert!(result.unwrap().contains_key(\"bids\"));\n    }\n\n    #[test]\n    fn order_book_should_have_asks_for_btcusd() {\n        let creds = BitstampCreds::new(\"\", \"\", \"\", \"\");\n        let mut api = BitstampApi::new(creds).unwrap();\n        assert!(api.return_order_book(Pair::BTC_USD)\n                    .unwrap()\n                    .contains_key(\"asks\"));\n    }\n    #[test]\n    fn order_book_should_have_asks_for_btceur() {\n        let creds = BitstampCreds::new(\"\", \"\", \"\", \"\");\n        let mut api = BitstampApi::new(creds).unwrap();\n        assert!(api.return_order_book(Pair::BTC_USD)\n                    .unwrap()\n                    .contains_key(\"asks\"));\n    }\n\n    #[test]\n    fn should_create_a_fixed_nonce_when_requested() {\n        assert_eq!(utils::generate_nonce(Some(\"1\".to_string())), \"1\");\n    }\n    #[test]\n    fn should_create_a_nonce_bigger_than_2017() {\n        assert!(utils::generate_nonce(None).parse::<i64>().unwrap() > 1483228800);\n    }\n    #[test]\n    fn should_create_a_correct_signature() {\n        let nonce = \"1483228800\";\n        let customer_id = \"123456\";\n        let api_key = \"1234567890ABCDEF1234567890ABCDEF\";\n        let api_secret = \"1234567890ABCDEF1234567890ABCDEF\";\n        let expected_signature = \"7D7C4168D49CBC2620A45EF00EAA228C1287561F1C1F94172272E1231A8ADF6B\"\n            .to_string();\n        assert_eq!(utils::build_signature(nonce, customer_id, api_key, api_secret).unwrap(),\n                   expected_signature);\n    }\n\n    #[test]\n    fn should_return_the_trade_history_for_btc_usd() {\n        let creds = BitstampCreds::new(\"\", \"\", \"\", \"\");\n        let mut api = BitstampApi::new(creds).unwrap();\n        let result = api.return_trade_history(Pair::BTC_USD);\n\n        assert_eq!(result.is_ok(), false);\n    }\n\n    // IMPORTANT: Real keys are needed in order to retrieve the balance\n    #[test]\n    #[cfg_attr(not(feature = \"bitstamp_private_tests\"), ignore)]\n    fn balance_should_have_usd_and_btc_balance() {\n        use std::path::PathBuf;\n        let path = PathBuf::from(\"./keys_real.json\");\n        let creds = BitstampCreds::new_from_file(\"account_bitstamp\", path).unwrap();\n        let mut api = BitstampApi::new(creds).unwrap();\n        let result = api.return_balances().unwrap();\n        let result_looking_for_usd = result.clone();\n        let result_looking_for_btc = result.clone();\n\n        assert!(result_looking_for_usd.contains_key(\"usd_balance\"));\n        assert!(result_looking_for_btc.contains_key(\"btc_balance\"));\n    }\n}\n"
  },
  {
    "path": "tests/bittrex.rs",
    "content": "#[cfg(test)]\nmod bittrex_tests {\n    extern crate coinnect;\n\n    use self::coinnect::bittrex::{BittrexApi, BittrexCreds};\n\n    #[test]\n    fn get_markets_should_return_a_result() {\n        let creds = BittrexCreds::new(\"bittrex\", \"\", \"\");\n        let mut api = BittrexApi::new(creds).unwrap();\n\n        let result = api.get_markets().unwrap();\n\n        assert!(result.contains_key(\"success\"))\n    }\n\n    #[test]\n    fn get_ticker_should_return_a_ticker() {\n        let creds = BittrexCreds::new(\"bittrex\", \"\", \"\");\n        let mut api = BittrexApi::new(creds).unwrap();\n\n        let result = api.get_ticker(\"BTC-LTC\").unwrap();\n\n        let last_price = result.get(\"result\").unwrap().as_object().unwrap().get(\"Last\");\n        assert!(last_price.is_some())\n    }\n\n    /// IMPORTANT: Real keys are needed in order to retrieve the balances\n    #[test]\n    #[cfg_attr(not(feature = \"bittrex_private_tests\"), ignore)]\n    fn balances_should_return_a_result() {\n        use std::path::PathBuf;\n        let path = PathBuf::from(\"./keys_real.json\");\n        let creds = BittrexCreds::new_from_file(\"account_bittrex\", path).unwrap();\n        let mut api = BittrexApi::new(creds).unwrap();\n\n        let result = api.get_balances().unwrap();\n\n        assert!(result.get(\"result\").is_some())\n    }\n}\n"
  },
  {
    "path": "tests/coinnect.rs",
    "content": "#[cfg(test)]\nmod coinnect_tests {\n    extern crate coinnect;\n    extern crate bigdecimal;\n\n    use self::bigdecimal::BigDecimal;\n    use std::str::FromStr;\n    use std::path::PathBuf;\n\n    use self::coinnect::coinnect::Coinnect;\n    use self::coinnect::exchange::{Exchange, ExchangeApi};\n    use self::coinnect::kraken::KrakenCreds;\n    use self::coinnect::bitstamp::BitstampCreds;\n    use self::coinnect::poloniex::PoloniexCreds;\n    use self::coinnect::bittrex::BittrexCreds;\n    use self::coinnect::error::*;\n    use self::coinnect::types::*;\n\n    #[test]\n    fn can_create_new_api_connection_to_bitstamp() {\n        let creds = BitstampCreds::new(\"test\", \"bs_api_key\", \"bs_api_secret\", \"bs_cust_id\");\n        let api: Box<ExchangeApi> = Coinnect::new(Exchange::Bitstamp, creds).unwrap();\n\n        assert_eq!(format!(\"{:?}\", api),\n                   \"BitstampApi { last_request: 0, api_key: \\\"bs_api_key\\\", api_secret: \\\n        \\\"bs_api_secret\\\", customer_id: \\\"bs_cust_id\\\", http_client: Client { \\\n                    redirect_policy: FollowAll, read_timeout: None, write_timeout: None, proxy: \\\n                    None }, burst: false }\");\n    }\n    #[test]\n    fn can_create_new_api_connection_to_kraken() {\n        //        let api = Coinnect::new(Exchange::Kraken, \"\", \"\", \"\");\n        //        assert_eq!(api, Exchange::Kraken);\n    }\n    #[test]\n    fn can_create_new_api_connection_to_poloniex() {\n        //        let api = Coinnect::new(Exchange::Poloniex, \"\", \"\", \"\");\n        //        assert_eq!(api, Exchange::Poloniex);\n    }\n\n    #[test]\n    fn coinnect_can_get_a_ticker_from_bitstamp() {\n        let creds = BitstampCreds::new(\"test\", \"bs_api_key\", \"bs_api_secret\", \"bs_cust_id\");\n        let mut api = Coinnect::new(Exchange::Bitstamp, creds).unwrap();\n        let ticker = api.ticker(Pair::BTC_USD);\n\n        assert_ne!(ticker.unwrap().last_trade_price, BigDecimal::from_str(\"0.0\").unwrap());\n    }\n\n    #[test]\n    fn coinnect_can_get_a_ticker_from_kraken() {\n        let creds = KrakenCreds::new(\"test\", \"api_key\", \"api_secret\");\n        let mut api = Coinnect::new(Exchange::Kraken, creds).unwrap();\n        let ticker = api.ticker(Pair::BTC_EUR);\n\n        assert_ne!(ticker.unwrap().last_trade_price, BigDecimal::from_str(\"0.0\").unwrap());\n    }\n\n    #[test]\n    fn coinnect_can_get_a_ticker_from_poloniex() {\n        let creds = PoloniexCreds::new(\"test\", \"api_key\", \"api_secret\");\n        let mut api = Coinnect::new(Exchange::Poloniex, creds).unwrap();\n        let ticker = api.ticker(Pair::ETH_BTC);\n\n        assert_ne!(ticker.unwrap().last_trade_price, BigDecimal::from_str(\"0.0\").unwrap());\n    }\n\n    #[test]\n    fn coinnect_can_get_a_ticker_from_bittrex() {\n        let creds = BittrexCreds::new(\"test\", \"api_key\", \"api_secret\");\n        let mut api = Coinnect::new(Exchange::Bittrex, creds).unwrap();\n        let ticker = api.ticker(Pair::ETH_BTC);\n\n        assert_ne!(ticker.unwrap().last_trade_price, BigDecimal::from_str(\"0.0\").unwrap());\n    }\n\n    #[test]\n    fn coinnect_can_get_an_orderbook_from_bitstamp() {\n        let creds = BitstampCreds::new(\"test\", \"api_key\", \"api_secret\", \"customer_id\");\n        let mut api = Coinnect::new(Exchange::Bitstamp, creds).unwrap();\n        let orderbook = api.orderbook(Pair::BTC_EUR);\n\n        assert_ne!(orderbook.unwrap().avg_price().unwrap(), BigDecimal::from_str(\"0.0\").unwrap())\n    }\n\n    #[test]\n    fn coinnect_can_get_an_orderbook_from_kraken() {\n        let creds = KrakenCreds::new(\"test\", \"api_key\", \"api_secret\");\n        let mut api = Coinnect::new(Exchange::Kraken, creds).unwrap();\n        let orderbook = api.orderbook(Pair::BTC_EUR);\n\n        assert_ne!(orderbook.unwrap().avg_price().unwrap(), BigDecimal::from_str(\"0.0\").unwrap())\n    }\n\n    #[test]\n    fn coinnect_can_get_an_orderbook_from_poloniex() {\n        let creds = PoloniexCreds::new(\"test\", \"api_key\", \"api_secret\");\n        let mut api = Coinnect::new(Exchange::Poloniex, creds).unwrap();\n        let orderbook = api.orderbook(Pair::ETH_BTC);\n\n        assert_ne!(orderbook.unwrap().avg_price().unwrap(), BigDecimal::from_str(\"0.0\").unwrap())\n    }\n\n    #[test]\n    fn coinnect_can_get_an_orderbook_from_bittrex() {\n        let creds = BittrexCreds::new(\"test\", \"api_key\", \"api_secret\");\n        let mut api = Coinnect::new(Exchange::Bittrex, creds).unwrap();\n        let orderbook = api.orderbook(Pair::ETH_BTC);\n\n        assert_ne!(orderbook.unwrap().avg_price().unwrap(), BigDecimal::from_str(\"0.0\").unwrap())\n    }\n\n    #[test]\n    #[cfg_attr(not(feature = \"bitstamp_private_tests\"), ignore)]\n    fn coinnect_can_get_the_balances_from_bitstamp() {\n        let path = PathBuf::from(\"./keys_real.json\");\n        let mut api = Coinnect::new_from_file(Exchange::Bitstamp, \"account_bitstamp\", path)\n            .unwrap();\n        let balances: Balances = api.balances().unwrap();\n\n        assert!(balances.len() > 0)\n    }\n\n    #[test]\n    #[cfg_attr(not(feature = \"poloniex_private_tests\"), ignore)]\n    fn coinnect_can_get_the_balances_from_poloniex() {\n        let path = PathBuf::from(\"./keys_real.json\");\n        let mut api = Coinnect::new_from_file(Exchange::Poloniex, \"account_poloniex\", path)\n            .unwrap();\n        let balances: Balances = api.balances().unwrap();\n\n        assert!(balances.len() > 0)\n    }\n\n    #[test]\n    #[cfg_attr(not(feature = \"bitstamp_private_tests\"), ignore)]\n    fn coinnect_can_get_at_least_a_positive_balance_from_bitstamp() {\n        let path = PathBuf::from(\"./keys_real.json\");\n        let mut api = Coinnect::new_from_file(Exchange::Bitstamp, \"account_bitstamp\", path)\n            .unwrap();\n        let balances: Balances = api.balances().unwrap();\n\n        assert!(balances.get(&Currency::BTC).unwrap() >= &BigDecimal::from_str(\"0.0\").unwrap());\n        assert!(balances.get(&Currency::EUR).unwrap() >= &BigDecimal::from_str(\"0.0\").unwrap());\n        assert!(balances.get(&Currency::USD).unwrap() >= &BigDecimal::from_str(\"0.0\").unwrap());\n        assert!(balances.get(&Currency::XRP).unwrap() >= &BigDecimal::from_str(\"0.0\").unwrap())\n    }\n\n    #[test]\n    #[cfg_attr(not(feature = \"kraken_private_tests\"), ignore)]\n    fn coinnect_can_get_the_balances_from_kraken() {\n        let path = PathBuf::from(\"./keys_real.json\");\n        let mut api = Coinnect::new_from_file(Exchange::Kraken, \"account_kraken\", path)\n            .unwrap();\n        let balances: Balances = api.balances().unwrap();\n\n        assert!(balances.len() > 0);\n        assert!(balances.get(&Currency::BTC).unwrap() >= &BigDecimal::from_str(\"0.0\").unwrap())\n    }\n\n    #[test]\n    #[cfg_attr(not(feature = \"poloniex_private_tests\"), ignore)]\n    fn coinnect_can_get_at_least_a_positive_balance_from_poloniex() {\n        let path = PathBuf::from(\"./keys_real.json\");\n        let mut api = Coinnect::new_from_file(Exchange::Poloniex, \"account_poloniex\", path)\n            .unwrap();\n        let balances: Balances = api.balances().unwrap();\n        let mut is_positive = false;\n        for (_, balance) in &balances {\n            if balance > &BigDecimal::from_str(\"0.0\").unwrap() {\n                is_positive = true;\n                break;\n            }\n        }\n        assert!(is_positive)\n    }\n\n    #[test]\n    #[cfg_attr(not(feature = \"bittrex_private_tests\"), ignore)]\n    fn coinnect_can_get_at_least_a_positive_balance_from_bittrex() {\n        let path = PathBuf::from(\"./keys_real.json\");\n        let mut api = Coinnect::new_from_file(Exchange::Bittrex, \"account_bittrex\", path)\n            .unwrap();\n        let balances: Balances = api.balances().unwrap();\n        let mut is_positive = false;\n        for (_, balance) in &balances {\n            if balance >= &BigDecimal::from_str(\"0.0\").unwrap() {\n                is_positive = true;\n                break;\n            }\n        }\n        assert!(is_positive)\n    }\n\n    #[test]\n    #[cfg_attr(not(feature = \"kraken_private_tests\"), ignore)]\n    fn coinnect_can_add_order_from_kraken() {\n        let path = PathBuf::from(\"./keys_real.json\");\n        let creds = KrakenCreds::new_from_file(\"account_kraken\", path).unwrap();\n        let mut api = Coinnect::new(Exchange::Kraken, creds).unwrap();\n        // following request should return an error since Kraken minimum order size is BigDecimal::from_str(\"0.01\")?\n        let orderinfo = api.add_order(OrderType::BuyLimit,\n                                      Pair::BTC_EUR,\n                                      BigDecimal::from_str(\"0.00001\").unwrap(),\n                                      Some(BigDecimal::from_str(\"1000.58\").unwrap()));\n\n        assert_eq!(orderinfo.unwrap_err().to_string(),\n                   ErrorKind::InsufficientOrderSize.to_string())\n    }\n\n    #[test]\n    #[cfg_attr(not(feature = \"poloniex_private_tests\"), ignore)]\n    fn coinnect_can_add_order_from_poloniex() {\n        let path = PathBuf::from(\"./keys_real.json\");\n        let creds = PoloniexCreds::new_from_file(\"account_poloniex\", path).unwrap();\n        let mut api = Coinnect::new(Exchange::Poloniex, creds).unwrap();\n        // following request should return an error\n        let orderinfo = api.add_order(OrderType::BuyLimit,\n                                      Pair::ETH_BTC,\n                                      BigDecimal::from_str(\"0.00001\").unwrap(),\n                                      Some(BigDecimal::from_str(\"1000.58\").unwrap()));\n\n        assert_eq!(orderinfo.unwrap_err().to_string(),\n                   ErrorKind::InsufficientOrderSize.to_string())\n    }\n\n    #[test]\n    #[cfg_attr(not(feature = \"bitstamp_private_tests\"), ignore)]\n    fn coinnect_can_add_order_from_bitstamp() {\n        let path = PathBuf::from(\"./keys_real.json\");\n        let creds = BitstampCreds::new_from_file(\"account_bitstamp\", path).unwrap();\n        let mut api = Coinnect::new(Exchange::Bitstamp, creds).unwrap();\n        // following request should return an error\n        let orderinfo = api.add_order(OrderType::BuyLimit,\n                                      Pair::EUR_USD,\n                                      BigDecimal::from_str(\"0.00001\").unwrap(),\n                                      Some(BigDecimal::from_str(\"1000.58\").unwrap()));\n\n        assert_eq!(orderinfo.unwrap_err().to_string(),\n                   ErrorKind::InsufficientOrderSize.to_string())\n    }\n\n    #[test]\n    #[cfg_attr(not(feature = \"bittrex_private_tests\"), ignore)]\n    fn coinnect_can_add_order_from_bittrex() {\n        let path = PathBuf::from(\"./keys_real.json\");\n        let creds = BittrexCreds::new_from_file(\"account_bittrex\", path).unwrap();\n        let mut api = Coinnect::new(Exchange::Bittrex, creds).unwrap();\n        // following request should return an error\n        let orderinfo = api.add_order(OrderType::BuyLimit,\n                                      Pair::ETH_BTC,\n                                      BigDecimal::from_str(\"0.000000000001\").unwrap(),\n                                      Some(BigDecimal::from_str(\"1000.58\").unwrap()));\n\n        assert_eq!(orderinfo.unwrap_err().to_string(),\n                   ErrorKind::InsufficientOrderSize.to_string())\n    }\n}\n"
  },
  {
    "path": "tests/exchange.rs",
    "content": "#[cfg(test)]\nmod exchange_tests {\n    extern crate coinnect;\n\n    #[test]\n    fn tests_work() {\n        //        use self::coinnect::Exchange::ExchangeApi;\n        assert!(true);\n    }\n}\n"
  },
  {
    "path": "tests/gdax.rs",
    "content": "#[cfg(test)]\nmod gdax_tests {\n    extern crate coinnect;\n    extern crate bigdecimal;\n\n    use self::bigdecimal::BigDecimal;\n    use std::str::FromStr;\n\n    use self::coinnect::gdax::utils;\n    use self::coinnect::gdax::{GdaxApi, GdaxCreds};\n    use self::coinnect::bitstamp::BitstampCreds;\n\n    use self::coinnect::exchange::ExchangeApi;\n    use self::coinnect::types::Pair;\n\n    #[test]\n    fn build_url_should_return_the_a_url() {\n        assert_eq!(utils::build_url(\"ticker\", \"btc-usd\"),\n                   \"https://api.gdax.com/products/btc-usd/ticker\");\n    }\n    #[test]\n    fn build_url_should_return_the_url_for_transactions_for_btc_usd() {\n        assert_eq!(utils::build_url(\"transactions\", \"btc-usd\"),\n                   \"https://api.gdax.com/accounts/btc-usd/ledger\");\n    }\n\n    #[test]\n    fn fail_with_invalid_creds() {\n        let creds = BitstampCreds::new(\"\", \"\", \"\", \"\");\n        let res = GdaxApi::new(creds);\n        assert_eq!(res.unwrap_err().to_string(),\n                   \"Invalid config: \\nExpected: Gdax\\nFind: Bitstamp\");\n    }\n\n\n    #[test]\n    fn can_get_real_gdax_tick() {\n        let creds = GdaxCreds::new(\"\", \"\", \"\", \"\");\n        let mut api = GdaxApi::new(creds).unwrap();\n        api.ticker(Pair::BTC_USD).unwrap();\n    }\n\n    #[test]\n    fn ticker_should_have_the_correct_last() {\n        let creds = GdaxCreds::new(\"\", \"\", \"\", \"\");\n        let mut api = GdaxApi::new(creds).unwrap();\n        let result = api.ticker(Pair::BTC_USD);\n        assert_ne!(result.unwrap().last_trade_price,\n                   BigDecimal::from_str(\"0.0\").unwrap());\n    }\n    #[test]\n    fn ticker_should_have_the_correct_high() {\n        let creds = GdaxCreds::new(\"\", \"\", \"\", \"\");\n        let mut api = GdaxApi::new(creds).unwrap();\n        let result = api.ticker(Pair::BTC_USD);\n        assert_ne!(result.unwrap().highest_bid,\n                   BigDecimal::from_str(\"0.0\").unwrap());\n    }\n    #[test]\n    fn ticker_should_have_the_correct_low() {\n        let creds = GdaxCreds::new(\"\", \"\", \"\", \"\");\n        let mut api = GdaxApi::new(creds).unwrap();\n        let result = api.ticker(Pair::BTC_USD);\n        assert_ne!(result.unwrap().lowest_ask,\n                   BigDecimal::from_str(\"0.0\").unwrap());\n    }\n    #[test]\n    fn ticker_should_have_the_correct_volume() {\n        let creds = GdaxCreds::new(\"\", \"\", \"\", \"\");\n        let mut api = GdaxApi::new(creds).unwrap();\n        let result = api.ticker(Pair::BTC_USD);\n        assert_ne!(result.unwrap().volume.unwrap(),\n                   BigDecimal::from_str(\"0.0\").unwrap());\n    }\n\n    #[test]\n    fn should_return_an_order_book() {\n        let creds = GdaxCreds::new(\"\", \"\", \"\", \"\");\n        let mut api = GdaxApi::new(creds).unwrap();\n        let result = api.return_order_book(Pair::BTC_USD);\n        assert_eq!(result.is_ok(), true);\n    }\n\n    #[test]\n    fn order_book_should_have_a_timestamp() {\n        let creds = GdaxCreds::new(\"\", \"\", \"\", \"\");\n        let mut api = GdaxApi::new(creds).unwrap();\n        let result = api.return_order_book(Pair::BTC_USD);\n        assert!(result.unwrap().contains_key(\"sequence\"));\n    }\n    #[test]\n    fn order_book_should_have_asks_for_btcusd() {\n        let creds = GdaxCreds::new(\"\", \"\", \"\", \"\");\n        let mut api = GdaxApi::new(creds).unwrap();\n        assert!(api.return_order_book(Pair::BTC_USD)\n                    .unwrap()\n                    .contains_key(\"asks\"));\n    }\n    #[test]\n    fn order_book_should_have_asks_for_btceur() {\n        let creds = GdaxCreds::new(\"\", \"\", \"\", \"\");\n        let mut api = GdaxApi::new(creds).unwrap();\n        assert!(api.return_order_book(Pair::BTC_USD)\n                    .unwrap()\n                    .contains_key(\"asks\"));\n    }\n\n    #[test]\n    fn should_create_a_fixed_nonce_when_requested() {\n        assert_eq!(utils::generate_nonce(Some(\"1\".to_string())), \"1\");\n    }\n    #[test]\n    fn should_create_a_nonce_bigger_than_2017() {\n        assert!(utils::generate_nonce(None).parse::<i64>().unwrap() > 1483228800);\n    }\n//    #[test]\n//    fn should_create_a_correct_signature() {\n//        let nonce = \"1483228800\";\n//        let passphrase = \"123456\";\n//        let api_key = \"1234567890ABCDEF1234567890ABCDEF\";\n//        let api_secret = \"1234567890ABCDEF1234567890ABCDEF\";\n//        let expected_signature = \"7D7C4168D49CBC2620A45EF00EAA228C1287561F1C1F94172272E1231A8ADF6B\"\n//            .to_string();\n//        assert_eq!(utils::build_signature(nonce, passphrase, api_key, api_secret).unwrap(),\n//                   expected_signature);\n//    }\n//\n//    #[test]\n//    fn should_return_the_trade_history_for_btc_usd() {\n//        let creds = GdaxCreds::new(\"\", \"\", \"\", \"\");\n//        let mut api = GdaxApi::new(creds).unwrap();\n//        let result = api.return_trade_history(Pair::BTC_USD);\n//\n//        assert_eq!(result.is_ok(), false);\n//    }\n//\n//    // IMPORTANT: Real keys are needed in order to retrieve the balance\n//    #[test]\n////    #[cfg_attr(not(feature = \"gdax_private_tests\"), ignore)]\n//    fn balance_should_have_usd_and_btc_balance() {\n//        use std::path::PathBuf;\n//        let path = PathBuf::from(\"./keys_real.json\");\n//        let creds = GdaxCreds::new_from_file(\"account_gdax\", path).unwrap();\n//        let mut api = GdaxApi::new(creds).unwrap();\n//        let result = api.return_balances().unwrap();\n//        let result_looking_for_usd = result.clone();\n//        let result_looking_for_btc = result.clone();\n//\n//        assert!(result_looking_for_usd.contains_key(\"usd_balance\"));\n//        assert!(result_looking_for_btc.contains_key(\"btc_balance\"));\n//    }\n}\n"
  },
  {
    "path": "tests/kraken.rs",
    "content": "#[cfg(test)]\nmod kraken_tests {\n    extern crate coinnect;\n\n    use self::coinnect::bitstamp::BitstampCreds;\n    use self::coinnect::kraken::{KrakenApi, KrakenCreds};\n\n    #[test]\n    fn fail_with_invalid_creds() {\n        let creds = BitstampCreds::new(\"\", \"\", \"\", \"\");\n        let res = KrakenApi::new(creds);\n        assert_eq!(\n            res.unwrap_err().to_string(),\n            \"Invalid config: \\nExpected: Kraken\\nFind: Bitstamp\"\n        );\n    }\n\n    /// IMPORTANT: Real keys are needed in order to retrieve the balance\n    #[test]\n    #[cfg_attr(not(feature = \"kraken_private_tests\"), ignore)]\n    fn balance_should_return_a_result() {\n        use std::path::PathBuf;\n        let path = PathBuf::from(\"./keys_real.json\");\n        let creds = KrakenCreds::new_from_file(\"account_kraken\", path).unwrap();\n        let mut api = KrakenApi::new(creds).unwrap();\n\n        let result = api.get_account_balance().unwrap();\n\n        println!(\"{:?}\", result);\n        assert!(result.contains_key(\"result\"))\n    }\n\n    /// IMPORTANT: Real keys are needed in order to retrieve a token\n    #[test]\n    #[cfg_attr(not(feature = \"kraken_private_tests\"), ignore)]\n    fn get_websockets_token_should_return_a_token() {\n        use std::path::PathBuf;\n        let path = PathBuf::from(\"./keys_real.json\");\n        let creds = KrakenCreds::new_from_file(\"account_kraken\", path).unwrap();\n        let mut api = KrakenApi::new(creds).unwrap();\n\n        let result = api.get_websockets_token().unwrap();\n\n        println!(\"{:?}\", result);\n        assert!(result.contains_key(\"result\"));\n        assert!(result[\"result\"].is_object());\n        assert!(result[\"result\"][\"expires\"].is_number());\n        assert!(result[\"result\"][\"token\"].is_string())\n    }\n}\n"
  },
  {
    "path": "tests/poloniex.rs",
    "content": "#[cfg(test)]\nmod poloniex_tests {\n    extern crate coinnect;\n\n    use self::coinnect::poloniex::{PoloniexApi, PoloniexCreds};\n    use self::coinnect::bitstamp::BitstampCreds;\n\n    #[test]\n    fn fail_with_invalid_creds() {\n        let creds = BitstampCreds::new(\"\", \"\", \"\", \"\");\n        let res = PoloniexApi::new(creds);\n        assert_eq!(res.unwrap_err().to_string(),\n                   \"Invalid config: \\nExpected: Poloniex\\nFind: Bitstamp\");\n    }\n\n    /// IMPORTANT: Real keys are needed in order to retrieve the balance\n    #[test]\n    #[cfg_attr(not(feature = \"poloniex_private_tests\"), ignore)]\n    fn balance_has_btc_key() {\n        use std::path::PathBuf;\n        let path = PathBuf::from(\"./keys_real.json\");\n        let creds = PoloniexCreds::new_from_file(\"account_poloniex\", path).unwrap();\n        let mut api = PoloniexApi::new(creds).unwrap();\n        let result = api.return_balances();\n\n        assert!(result.unwrap().contains_key(\"BTC\"));\n    }\n}\n"
  }
]