diff --git a/.gitignore b/.gitignore
index c7fc172..9ac6f20 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
config.toml
commit_msg
+.DS_STORE
target/
configs/
diff --git a/Cargo.lock b/Cargo.lock
index fb56a4f..9ce13e8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,18 +4,18 @@ version = 3
[[package]]
name = "addr2line"
-version = "0.21.0"
+version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
+checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
[[package]]
-name = "adler"
-version = "1.0.2"
+name = "adler2"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "android-tzdata"
@@ -33,54 +33,75 @@ dependencies = [
]
[[package]]
-name = "async-recursion"
-version = "1.0.5"
+name = "assert-json-diff"
+version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0"
+checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "async-stream"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
+dependencies = [
+ "async-stream-impl",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-stream-impl"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
[[package]]
name = "autocfg"
-version = "1.1.0"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "backtrace"
-version = "0.3.69"
+version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [
"addr2line",
- "cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
+ "windows-targets",
]
[[package]]
name = "base64"
-version = "0.21.7"
+version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
-version = "1.3.2"
+version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
-
-[[package]]
-name = "bitflags"
-version = "2.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "block-buffer"
@@ -93,9 +114,9 @@ dependencies = [
[[package]]
name = "bumpalo"
-version = "3.14.0"
+version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "byteorder"
@@ -105,15 +126,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
-version = "1.5.0"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
+checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
[[package]]
name = "cbadv"
-version = "1.4.0"
+version = "2.0.0"
dependencies = [
- "async-recursion",
+ "assert-json-diff",
"base64",
"chrono",
"futures",
@@ -127,8 +148,10 @@ dependencies = [
"ring",
"serde",
"serde_json",
+ "serde_with",
"sha2",
"tokio",
+ "tokio-test",
"tokio-tungstenite",
"toml",
"uuid",
@@ -136,11 +159,11 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.0.83"
+version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc"
dependencies = [
- "libc",
+ "shlex",
]
[[package]]
@@ -151,16 +174,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
-version = "0.4.33"
+version = "0.4.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb"
+checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
+ "serde",
"wasm-bindgen",
- "windows-targets 0.52.0",
+ "windows-targets",
]
[[package]]
@@ -175,15 +199,15 @@ dependencies = [
[[package]]
name = "core-foundation-sys"
-version = "0.8.6"
+version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
-version = "0.2.12"
+version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
+checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
dependencies = [
"libc",
]
@@ -198,11 +222,56 @@ dependencies = [
"typenum",
]
+[[package]]
+name = "darling"
+version = "0.20.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.20.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.20.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "data-encoding"
-version = "2.5.0"
+version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
+checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
+
+[[package]]
+name = "deranged"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
+dependencies = [
+ "powerfmt",
+ "serde",
+]
[[package]]
name = "digest"
@@ -215,11 +284,22 @@ dependencies = [
"subtle",
]
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "encoding_rs"
-version = "0.8.33"
+version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
@@ -232,19 +312,19 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
-version = "0.3.8"
+version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
+checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
name = "fastrand"
-version = "2.0.1"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
+checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
[[package]]
name = "fnv"
@@ -278,9 +358,9 @@ dependencies = [
[[package]]
name = "futures"
-version = "0.3.30"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
+checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [
"futures-channel",
"futures-core",
@@ -293,9 +373,9 @@ dependencies = [
[[package]]
name = "futures-channel"
-version = "0.3.30"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
+checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
"futures-sink",
@@ -303,15 +383,15 @@ dependencies = [
[[package]]
name = "futures-core"
-version = "0.3.30"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-executor"
-version = "0.3.30"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
+checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
dependencies = [
"futures-core",
"futures-task",
@@ -320,15 +400,15 @@ dependencies = [
[[package]]
name = "futures-io"
-version = "0.3.30"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
+checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-macro"
-version = "0.3.30"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
+checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
@@ -337,21 +417,21 @@ dependencies = [
[[package]]
name = "futures-sink"
-version = "0.3.30"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
+checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]]
name = "futures-task"
-version = "0.3.30"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
+checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-util"
-version = "0.3.30"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
+checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-channel",
"futures-core",
@@ -377,9 +457,9 @@ dependencies = [
[[package]]
name = "getrandom"
-version = "0.2.12"
+version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
+checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
@@ -388,23 +468,23 @@ dependencies = [
[[package]]
name = "gimli"
-version = "0.28.1"
+version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
+checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "h2"
-version = "0.3.24"
+version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
+checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e"
dependencies = [
+ "atomic-waker",
"bytes",
"fnv",
"futures-core",
"futures-sink",
- "futures-util",
"http",
- "indexmap",
+ "indexmap 2.7.0",
"slab",
"tokio",
"tokio-util",
@@ -413,15 +493,15 @@ dependencies = [
[[package]]
name = "hashbrown"
-version = "0.14.3"
+version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
-name = "hermit-abi"
-version = "0.3.4"
+name = "hashbrown"
+version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f"
+checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "hex"
@@ -440,9 +520,9 @@ dependencies = [
[[package]]
name = "http"
-version = "0.2.11"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
+checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
dependencies = [
"bytes",
"fnv",
@@ -451,69 +531,110 @@ dependencies = [
[[package]]
name = "http-body"
-version = "0.4.6"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
"bytes",
"http",
- "pin-project-lite",
]
[[package]]
-name = "httparse"
-version = "1.8.0"
+name = "http-body-util"
+version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
+dependencies = [
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
[[package]]
-name = "httpdate"
-version = "1.0.3"
+name = "httparse"
+version = "1.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
[[package]]
name = "hyper"
-version = "0.14.28"
+version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
+checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f"
dependencies = [
"bytes",
"futures-channel",
- "futures-core",
"futures-util",
"h2",
"http",
"http-body",
"httparse",
- "httpdate",
"itoa",
"pin-project-lite",
- "socket2",
+ "smallvec",
"tokio",
- "tower-service",
- "tracing",
"want",
]
+[[package]]
+name = "hyper-rustls"
+version = "0.27.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
+dependencies = [
+ "futures-util",
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+]
+
[[package]]
name = "hyper-tls"
-version = "0.5.0"
+version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
+checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
+ "http-body-util",
"hyper",
+ "hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
]
[[package]]
name = "iana-time-zone"
-version = "0.1.59"
+version = "0.1.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539"
+checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
dependencies = [
"android_system_properties",
"core-foundation-sys",
@@ -532,70 +653,218 @@ dependencies = [
"cc",
]
+[[package]]
+name = "icu_collections"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid_transform"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
+dependencies = [
+ "displaydoc",
+ "icu_locid",
+ "icu_locid_transform_data",
+ "icu_provider",
+ "tinystr",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid_transform_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
+
+[[package]]
+name = "icu_normalizer"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "utf16_iter",
+ "utf8_iter",
+ "write16",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
+
+[[package]]
+name = "icu_properties"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_locid_transform",
+ "icu_properties_data",
+ "icu_provider",
+ "tinystr",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
+
+[[package]]
+name = "icu_provider"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
+dependencies = [
+ "displaydoc",
+ "icu_locid",
+ "icu_provider_macros",
+ "stable_deref_trait",
+ "tinystr",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_provider_macros"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
[[package]]
name = "idna"
-version = "0.5.0"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
- "unicode-bidi",
- "unicode-normalization",
+ "autocfg",
+ "hashbrown 0.12.3",
+ "serde",
]
[[package]]
name = "indexmap"
-version = "2.2.1"
+version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b"
+checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
dependencies = [
"equivalent",
- "hashbrown",
+ "hashbrown 0.15.2",
+ "serde",
]
[[package]]
name = "ipnet"
-version = "2.9.0"
+version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
+checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708"
[[package]]
name = "itoa"
-version = "1.0.10"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
+checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "js-sys"
-version = "0.3.67"
+version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1"
+checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705"
dependencies = [
+ "once_cell",
"wasm-bindgen",
]
[[package]]
-name = "lazy_static"
-version = "1.4.0"
+name = "libc"
+version = "0.2.167"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc"
[[package]]
-name = "libc"
-version = "0.2.152"
+name = "linux-raw-sys"
+version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
+checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
-name = "linux-raw-sys"
-version = "0.4.13"
+name = "litemap"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
+checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
[[package]]
name = "lock_api"
-version = "0.4.11"
+version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
+checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
@@ -603,15 +872,15 @@ dependencies = [
[[package]]
name = "log"
-version = "0.4.20"
+version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "memchr"
-version = "2.7.1"
+version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "mime"
@@ -621,31 +890,30 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "miniz_oxide"
-version = "0.7.1"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
dependencies = [
- "adler",
+ "adler2",
]
[[package]]
name = "mio"
-version = "0.8.10"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
+checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [
"libc",
"wasi",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
]
[[package]]
name = "native-tls"
-version = "0.2.11"
+version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
+checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
dependencies = [
- "lazy_static",
"libc",
"log",
"openssl",
@@ -658,46 +926,42 @@ dependencies = [
]
[[package]]
-name = "num-traits"
-version = "0.2.17"
+name = "num-conv"
+version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
-dependencies = [
- "autocfg",
-]
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
-name = "num_cpus"
-version = "1.16.0"
+name = "num-traits"
+version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
- "hermit-abi",
- "libc",
+ "autocfg",
]
[[package]]
name = "object"
-version = "0.32.2"
+version = "0.36.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
+checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
-version = "1.19.0"
+version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "openssl"
-version = "0.10.63"
+version = "0.10.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8"
+checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
dependencies = [
- "bitflags 2.4.2",
+ "bitflags",
"cfg-if",
"foreign-types",
"libc",
@@ -725,9 +989,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
-version = "0.9.99"
+version = "0.9.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae"
+checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
dependencies = [
"cc",
"libc",
@@ -737,9 +1001,9 @@ dependencies = [
[[package]]
name = "parking_lot"
-version = "0.12.1"
+version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core",
@@ -747,15 +1011,15 @@ dependencies = [
[[package]]
name = "parking_lot_core"
-version = "0.9.9"
+version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
+checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
- "windows-targets 0.48.5",
+ "windows-targets",
]
[[package]]
@@ -766,9 +1030,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pin-project-lite"
-version = "0.2.13"
+version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
[[package]]
name = "pin-utils"
@@ -778,30 +1042,39 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
-version = "0.3.29"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
-version = "0.2.17"
+version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
+dependencies = [
+ "zerocopy",
+]
[[package]]
name = "proc-macro2"
-version = "1.0.78"
+version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
+checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
-version = "1.0.35"
+version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
@@ -838,18 +1111,18 @@ dependencies = [
[[package]]
name = "redox_syscall"
-version = "0.4.1"
+version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
]
[[package]]
name = "reqwest"
-version = "0.11.23"
+version = "0.12.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41"
+checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f"
dependencies = [
"base64",
"bytes",
@@ -859,8 +1132,11 @@ dependencies = [
"h2",
"http",
"http-body",
+ "http-body-util",
"hyper",
+ "hyper-rustls",
"hyper-tls",
+ "hyper-util",
"ipnet",
"js-sys",
"log",
@@ -869,9 +1145,11 @@ dependencies = [
"once_cell",
"percent-encoding",
"pin-project-lite",
+ "rustls-pemfile",
"serde",
"serde_json",
"serde_urlencoded",
+ "sync_wrapper",
"system-configuration",
"tokio",
"tokio-native-tls",
@@ -880,55 +1158,95 @@ dependencies = [
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
- "winreg",
+ "windows-registry",
]
[[package]]
name = "ring"
-version = "0.17.7"
+version = "0.17.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74"
+checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
dependencies = [
"cc",
+ "cfg-if",
"getrandom",
"libc",
"spin",
"untrusted",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
]
[[package]]
name = "rustc-demangle"
-version = "0.1.23"
+version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustix"
-version = "0.38.30"
+version = "0.38.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
+checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6"
dependencies = [
- "bitflags 2.4.2",
+ "bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
]
+[[package]]
+name = "rustls"
+version = "0.23.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1"
+dependencies = [
+ "once_cell",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b"
+
+[[package]]
+name = "rustls-webpki"
+version = "0.102.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
[[package]]
name = "ryu"
-version = "1.0.16"
+version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
+checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "schannel"
-version = "0.1.23"
+version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
+checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
dependencies = [
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -939,11 +1257,11 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
-version = "2.9.2"
+version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
@@ -952,9 +1270,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
-version = "2.9.1"
+version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
+checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2"
dependencies = [
"core-foundation-sys",
"libc",
@@ -962,18 +1280,18 @@ dependencies = [
[[package]]
name = "serde"
-version = "1.0.196"
+version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
+checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.196"
+version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
+checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
dependencies = [
"proc-macro2",
"quote",
@@ -982,20 +1300,21 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.113"
+version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
+checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
dependencies = [
"itoa",
+ "memchr",
"ryu",
"serde",
]
[[package]]
name = "serde_spanned"
-version = "0.6.5"
+version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
+checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
dependencies = [
"serde",
]
@@ -1012,6 +1331,36 @@ dependencies = [
"serde",
]
+[[package]]
+name = "serde_with"
+version = "3.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817"
+dependencies = [
+ "base64",
+ "chrono",
+ "hex",
+ "indexmap 1.9.3",
+ "indexmap 2.7.0",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "serde_with_macros",
+ "time",
+]
+
+[[package]]
+name = "serde_with_macros"
+version = "3.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "sha1"
version = "0.10.6"
@@ -1034,11 +1383,17 @@ dependencies = [
"digest",
]
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
[[package]]
name = "signal-hook-registry"
-version = "1.4.1"
+version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
dependencies = [
"libc",
]
@@ -1054,18 +1409,18 @@ dependencies = [
[[package]]
name = "smallvec"
-version = "1.13.1"
+version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "socket2"
-version = "0.5.5"
+version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
+checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
dependencies = [
"libc",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
]
[[package]]
@@ -1074,39 +1429,71 @@ version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
[[package]]
name = "subtle"
-version = "2.5.0"
+version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
-version = "2.0.48"
+version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
+checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
+[[package]]
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "system-configuration"
-version = "0.5.1"
+version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
+checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
dependencies = [
- "bitflags 1.3.2",
+ "bitflags",
"core-foundation",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
-version = "0.5.0"
+version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
+checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
dependencies = [
"core-foundation-sys",
"libc",
@@ -1114,31 +1501,31 @@ dependencies = [
[[package]]
name = "tempfile"
-version = "3.9.0"
+version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
+checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
dependencies = [
"cfg-if",
"fastrand",
- "redox_syscall",
+ "once_cell",
"rustix",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
name = "thiserror"
-version = "1.0.56"
+version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.56"
+version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
@@ -1146,44 +1533,69 @@ dependencies = [
]
[[package]]
-name = "tinyvec"
-version = "1.6.0"
+name = "time"
+version = "0.3.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
dependencies = [
- "tinyvec_macros",
+ "deranged",
+ "itoa",
+ "num-conv",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
]
[[package]]
-name = "tinyvec_macros"
-version = "0.1.1"
+name = "time-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+
+[[package]]
+name = "time-macros"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
[[package]]
name = "tokio"
-version = "1.35.1"
+version = "1.41.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
+checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
- "num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
]
[[package]]
name = "tokio-macros"
-version = "2.2.0"
+version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
+checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
@@ -1200,11 +1612,46 @@ dependencies = [
"tokio",
]
+[[package]]
+name = "tokio-rustls"
+version = "0.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
+dependencies = [
+ "rustls",
+ "rustls-pki-types",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-test"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7"
+dependencies = [
+ "async-stream",
+ "bytes",
+ "futures-core",
+ "tokio",
+ "tokio-stream",
+]
+
[[package]]
name = "tokio-tungstenite"
-version = "0.19.0"
+version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec509ac96e9a0c43427c74f003127d953a265737636129424288d27cb5c4b12c"
+checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9"
dependencies = [
"futures-util",
"log",
@@ -1216,23 +1663,22 @@ dependencies = [
[[package]]
name = "tokio-util"
-version = "0.7.10"
+version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
+checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
- "tracing",
]
[[package]]
name = "toml"
-version = "0.7.8"
+version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
+checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
dependencies = [
"serde",
"serde_spanned",
@@ -1242,20 +1688,20 @@ dependencies = [
[[package]]
name = "toml_datetime"
-version = "0.6.5"
+version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
+checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
-version = "0.19.15"
+version = "0.22.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
+checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
dependencies = [
- "indexmap",
+ "indexmap 2.7.0",
"serde",
"serde_spanned",
"toml_datetime",
@@ -1264,15 +1710,15 @@ dependencies = [
[[package]]
name = "tower-service"
-version = "0.3.2"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
-version = "0.1.40"
+version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"pin-project-lite",
"tracing-core",
@@ -1280,9 +1726,9 @@ dependencies = [
[[package]]
name = "tracing-core"
-version = "0.1.32"
+version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
dependencies = [
"once_cell",
]
@@ -1295,9 +1741,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "tungstenite"
-version = "0.19.0"
+version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67"
+checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a"
dependencies = [
"byteorder",
"bytes",
@@ -1309,7 +1755,6 @@ dependencies = [
"rand",
"sha1",
"thiserror",
- "url",
"utf-8",
]
@@ -1319,26 +1764,11 @@ version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
-[[package]]
-name = "unicode-bidi"
-version = "0.3.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
-
[[package]]
name = "unicode-ident"
-version = "1.0.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
-
-[[package]]
-name = "unicode-normalization"
-version = "0.1.22"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
-dependencies = [
- "tinyvec",
-]
+checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "untrusted"
@@ -1348,9 +1778,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
-version = "2.5.0"
+version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
+checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
"form_urlencoded",
"idna",
@@ -1363,11 +1793,23 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+[[package]]
+name = "utf16_iter"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
[[package]]
name = "uuid"
-version = "1.7.0"
+version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
+checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
dependencies = [
"getrandom",
"rand",
@@ -1376,9 +1818,9 @@ dependencies = [
[[package]]
name = "uuid-macro-internal"
-version = "1.7.0"
+version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7abb14ae1a50dad63eaa768a458ef43d298cd1bd44951677bd10b732a9ba2a2d"
+checksum = "6b91f57fe13a38d0ce9e28a03463d8d3c2468ed03d75375110ec71d93b449a08"
dependencies = [
"proc-macro2",
"quote",
@@ -1393,9 +1835,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
-version = "0.9.4"
+version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "want"
@@ -1414,19 +1856,20 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
-version = "0.2.90"
+version = "0.2.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406"
+checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c"
dependencies = [
"cfg-if",
+ "once_cell",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.90"
+version = "0.2.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd"
+checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd"
dependencies = [
"bumpalo",
"log",
@@ -1439,21 +1882,22 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
-version = "0.4.40"
+version = "0.4.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461"
+checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d"
dependencies = [
"cfg-if",
"js-sys",
+ "once_cell",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.90"
+version = "0.2.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999"
+checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1461,9 +1905,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.90"
+version = "0.2.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7"
+checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d"
dependencies = [
"proc-macro2",
"quote",
@@ -1474,15 +1918,15 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.90"
+version = "0.2.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
+checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49"
[[package]]
name = "web-sys"
-version = "0.3.67"
+version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed"
+checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -1494,16 +1938,37 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
- "windows-targets 0.52.0",
+ "windows-targets",
]
[[package]]
-name = "windows-sys"
-version = "0.48.0"
+name = "windows-registry"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0"
+dependencies = [
+ "windows-result",
+ "windows-strings",
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
dependencies = [
- "windows-targets 0.48.5",
+ "windows-result",
+ "windows-targets",
]
[[package]]
@@ -1512,138 +1977,193 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
- "windows-targets 0.52.0",
+ "windows-targets",
]
[[package]]
-name = "windows-targets"
-version = "0.48.5"
+name = "windows-sys"
+version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
- "windows_aarch64_gnullvm 0.48.5",
- "windows_aarch64_msvc 0.48.5",
- "windows_i686_gnu 0.48.5",
- "windows_i686_msvc 0.48.5",
- "windows_x86_64_gnu 0.48.5",
- "windows_x86_64_gnullvm 0.48.5",
- "windows_x86_64_msvc 0.48.5",
+ "windows-targets",
]
[[package]]
name = "windows-targets"
-version = "0.52.0"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
- "windows_aarch64_gnullvm 0.52.0",
- "windows_aarch64_msvc 0.52.0",
- "windows_i686_gnu 0.52.0",
- "windows_i686_msvc 0.52.0",
- "windows_x86_64_gnu 0.52.0",
- "windows_x86_64_gnullvm 0.52.0",
- "windows_x86_64_msvc 0.52.0",
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
-version = "0.48.5"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.52.0"
+name = "windows_aarch64_msvc"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
-name = "windows_aarch64_msvc"
-version = "0.48.5"
+name = "windows_i686_gnu"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
-name = "windows_aarch64_msvc"
-version = "0.52.0"
+name = "windows_i686_gnullvm"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
-name = "windows_i686_gnu"
-version = "0.48.5"
+name = "windows_i686_msvc"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
-name = "windows_i686_gnu"
-version = "0.52.0"
+name = "windows_x86_64_gnu"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
-name = "windows_i686_msvc"
-version = "0.48.5"
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
-name = "windows_i686_msvc"
-version = "0.52.0"
+name = "windows_x86_64_msvc"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
-name = "windows_x86_64_gnu"
-version = "0.48.5"
+name = "winnow"
+version = "0.6.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "write16"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
[[package]]
-name = "windows_x86_64_gnu"
-version = "0.52.0"
+name = "writeable"
+version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.48.5"
+name = "yoke"
+version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
+dependencies = [
+ "serde",
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.52.0"
+name = "yoke-derive"
+version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
[[package]]
-name = "windows_x86_64_msvc"
-version = "0.48.5"
+name = "zerocopy"
+version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+dependencies = [
+ "byteorder",
+ "zerocopy-derive",
+]
[[package]]
-name = "windows_x86_64_msvc"
-version = "0.52.0"
+name = "zerocopy-derive"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
+checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e"
+dependencies = [
+ "zerofrom-derive",
+]
[[package]]
-name = "winnow"
-version = "0.5.35"
+name = "zerofrom-derive"
+version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1931d78a9c73861da0134f453bb1f790ce49b2e30eba8410b4b79bac72b46a2d"
+checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
dependencies = [
- "memchr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
]
[[package]]
-name = "winreg"
-version = "0.50.0"
+name = "zeroize"
+version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+
+[[package]]
+name = "zerovec"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
dependencies = [
- "cfg-if",
- "windows-sys 0.48.0",
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
]
diff --git a/Cargo.toml b/Cargo.toml
index ff17aa9..95573ca 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,25 +1,70 @@
[package]
name = "cbadv"
-license = "MIT"
-version = "1.4.0"
+version = "2.0.0"
edition = "2021"
description = "Asynchronous Coinbase Advanced REST and WebSocket API"
+license = "MIT"
readme = "README.md"
homepage = "https://github.com/Ohkthx/cbadv-rs"
repository = "https://github.com/Ohkthx/cbadv-rs"
keywords = ["trading", "coinbase", "coinbasepro", "coinbaseadvanced", "crypto"]
+categories = ["api-bindings", "cryptocurrency"]
include = ["*/**.rs"]
[features]
-default = []
+default = ["config"]
full = ["config"]
config = ["dep:toml"]
+[dependencies]
+# Core dependencies
+reqwest = { version = "0.12.9", features = ["json"] }
+futures = "0.3.31"
+tokio = { version = "1.41.1", features = ["full"] }
+
+# Cryptography and signing
+hmac = "0.12.1"
+sha2 = "0.10.8"
+hex = "0.4.3"
+
+# Serialization and configuration
+serde = { version = "1.0.215", features = ["derive"] }
+serde_json = "1.0.133"
+serde_with = "3.11.0"
+toml = { version = "0.8.19", optional = true }
+
+# WebSocket support
+tokio-tungstenite = { version = "0.24.0", features = ["native-tls"] }
+futures-util = "0.3.31"
+
+# Utilities
+uuid = { version = "1.11.0", features = [
+ "v4",
+ "fast-rng",
+ "macro-diagnostics",
+] }
+chrono = "0.4.38"
+num-traits = "0.2.19"
+base64 = "0.22.1"
+ring = "0.17.8"
+rand = "0.8.5"
+openssl = "0.10.68"
+
[[example]]
name = "account_api"
path = "examples/account_api.rs"
required-features = ["config"]
+[[example]]
+name = "convert_api"
+path = "examples/convert_api.rs"
+required-features = ["config"]
+
+[[example]]
+name = "payment_api"
+path = "examples/payment_api.rs"
+required-features = ["config"]
+
[[example]]
name = "product_api"
path = "examples/product_api.rs"
@@ -36,8 +81,22 @@ path = "examples/order_api.rs"
required-features = ["config"]
[[example]]
-name = "util_api"
-path = "examples/util_api.rs"
+name = "public_api"
+path = "examples/public_api.rs"
+
+[[example]]
+name = "sandbox_api"
+path = "examples/sandbox_api.rs"
+required-features = ["config"]
+
+[[example]]
+name = "portfolio_api"
+path = "examples/portfolio_api.rs"
+required-features = ["config"]
+
+[[example]]
+name = "data_api"
+path = "examples/data_api.rs"
required-features = ["config"]
[[example]]
@@ -45,10 +104,14 @@ name = "websocket"
path = "examples/websocket.rs"
required-features = ["config"]
+[[example]]
+name = "websocket_user"
+path = "examples/websocket_user.rs"
+required-features = ["config"]
+
[[example]]
name = "watch_candles"
path = "examples/watch_candles.rs"
-required-features = ["config"]
[[example]]
name = "custom_config"
@@ -61,23 +124,10 @@ lto = "fat"
codegen-units = 1
opt-level = 3
-[dependencies]
-reqwest = { version = "0.11", features = ["json"] } # Making HTTP requests.
-futures = { version = "0.3" } # Async / await blocks
-tokio = { version = "1.12.0", features = ["full"] } # Async runtime
-hmac = { version = "0.12.1" } # Signing requests with a signature.
-sha2 = { version = "0.10.6" } # Signing requests with a signature.
-hex = { version = "0.4.3" } # Convert signature for HTTP headers.
-serde_json = { version = "1.0.96" } # Converting Configuration file and Objects from API.
-serde = { version = "1.0.163", features = ["derive"] } # Converting Configuration file and Objects from API.
-toml = { version = "0.7.3", optional = true } # Creating Configuration file.
-uuid = { version = "1.3.4", features = ["v4", "fast-rng", "macro-diagnostics"] } # Create Client ID for orders.
-async-recursion = { version = "1.0.4" } # Recursive async functions require this.
-tokio-tungstenite = { version = "0.19.0", features = ["native-tls"] } # WebSocket requirement.
-futures-util = { version = "0.3.28" } # Required for the WebSocket client.
-chrono = { version = "0.4.31" } # Used to pass current candle timestamp to candle watcher.
-num-traits = "0.2.17"
-openssl = "0.10.63"
-base64 = "0.21.7"
-ring = "0.17.7"
-rand = "0.8.5"
+[dev-dependencies]
+tokio-test = "0.4.4"
+assert-json-diff = "2.0.2"
+
+[badges]
+travis-ci = { repository = "ohkthx/cbadv-rs", branch = "main" }
+maintenance = { status = "actively-developed" }
diff --git a/README.md b/README.md
index 478a7a4..aa7d6f2 100644
--- a/README.md
+++ b/README.md
@@ -17,133 +17,166 @@
alt="crates.io downloads">
+---
+
# Asynchronous CoinBase Advanced API
-The objective of this crate is to grant highly performant asynchronous access to the **CoinBase Advanced** REST and WebSocket API. Included with the crate are ways to organize your API Keys and Secrets inside of a configuration file.
+The **cbadv-rs** crate provides high-performance, asynchronous access to the Coinbase Advanced REST and WebSocket APIs. This project includes features to securely configure API keys and secrets, making it suitable for developers seeking robust API integration.
-This project is current a work-in-progress. Changes between versions can vary greatly as this API becomes more refined and adapts to CoinBase Advances changing state. I ask you to understand that I am not liable for any issues you may encounter while this project is in this state and encourage you to verify and test before committing to using this yourself in a serious manner such as in production.
+This project is currently a work-in-progress. While the crate is usable, API changes or updates may occur as Coinbase Advanced evolves. Please thoroughly test before using in production.
-Contributions are encouraged! The API reference can be seen at [CoinBase Advanced API](https://docs.cloud.coinbase.com/advanced-trade-api/reference). If you wish to add this to your project, either use `cargo add cbadv` or add the following line to your dependencies section in **Cargo.toml**:
+To get started, add this crate to your project using `cargo add cbadv` or manually add the following to your `Cargo.toml`:
```toml
[dependencies]
cbadv = { git = "https://github.com/ohkthx/cbadv-rs", branch = "main" }
```
+---
+
+## Table of Contents
+
+- [Features](#features)
+- [Documentation](#documentation)
+- [Configuration](#configuration)
+- [Examples](#examples)
+- [API Coverage](#api-coverage)
+ - [WebSocket API](#websocket-api)
+ - [REST API](#rest-api)
+- [TODO](#todo)
+- [Contributing](#contributing)
+- [Tips Appreciated!](#tips-appreciated)
+
+---
+
## Features
-- Asynchronous.
-- Easy-to-use REST and WebSocket clients.
-- Configuration file to hold API Key and API Secret. `features = ["config"]`
-- Covers all REST endpoints currently accessible (as of 20231206).
-- Covers all WebSocket endpoints currently accessible (as of 20231206).
-- Lots of examples! Check them out to get started.
+- Asynchronous API access with support for REST and WebSocket protocols.
+- Authenticated and Public REST Endpoints.
+- Builders to create REST and WebSocket Clients.
+- Convenient configuration file support for API keys (`features = ["config"]`).
+- Comprehensive coverage of all accessible REST and WebSocket endpoints (as of **20231206**).
+- Numerous examples for seamless integration and testing.
+
+---
## Documentation
-Most of the documentation can be accessed by clicking the following link: [docs.rs](https://docs.rs/cbadv/latest/cbadv/). That documentation is automatically generated and also accessible from [crates.io](https://crates.io/crates/cbadv).
-
-### Covered API requests
-
-#### WebSocket API
-
-Client: `use cbadv::WebSocketClient`
-
-- **Authentication** [client.connect]
-- **Subscribe** [client.subscribe / client.sub]
-- **Unsubscribe** [client.unsubscribe / client.unsub]
-- **Channels Supported**
- - Status [Channel::STATUS]
- - Candles [Channel::CANDLES]
- - Ticker [Channel::TICKER]
- - Ticker Batch [Channel::TICKER_BATCH]
- - Level2 [Channel::LEVEL2]
- - User [Channel::USER]
- - Market Trades [Channel::MARKET_TRADES]
-
-#### REST API
-
-Client: `use cbadv::RestClient`
-
-- **Accounts [client.account]**
- - List Accounts [client.account.get_bulk]
- - Get Account [client.account.get]
-- **Products [client.product]**
- - Get Best Bid / Ask [client.product.best_bid_ask]
- - Get Product Book [client.product.product_book]
- - List Products [client.product.get_bulk]
- - Get Product [client.product.get]
- - Get Product Candles [client.product.candles]
- - Get Market Trades (Ticker) [client.product.ticker]
-- **Orders [client.order]**
- - Create Order
- - Market IOC (untested) [client.order.create_market]
- - Limit GTC [client.order.create_limit_gtc]
- - Limit GTD (untested) [client.order.create_limit_gtd]
- - Stop Limit GTC (untested) [client.order.create_stop_limit_gtc]
- - Stop Limit GTD (untested) [client.order.create_stop_limit_gtd]
- - Edit Orders [client.order.edit]
- - Edit Orders Preview [client.order.preview_edit]
- - Cancel Orders [client.order.cancel]
- - List Orders [client.order.get_bulk]
- - List Fills (untested) [client.order.fills]
- - Get Order [client.order.get]
-- **Fees [client.fee]**
- - Get Transaction Summary [client.fee.get]
-- **Converts [client.convert]**
- - Create Quote (untested) [client.convert.create_quote]
- - Get Convert (untested) [client.convert.get]
- - Commit Convert (untested) [client.convert.commit]
-- **Utils [client.util]**
- - Get API Unix Time [client.util.unixtime]
-
-### Added Requests and Features
-
-These functions were created to cover common functionality but not initially part of the CoinBase Advanced API. They may require several API requests to accomplish their results.
-
-- **REST: Accounts** [client.account]
- - Get Account by ID [client.account.get_by_id] - Gets an account by the ID (ex BTC or ETH)
- - Get All [client.account.get_all] - Gets all accounts.
-- **REST: Products** [client.product]
- - Get Candles (Extended) [client.product.candles_ext] - Obtains more than the limit (300) candles.
-- **REST: Orders** [client.order]
- - Get All Orders [client.order.get_all] - Obtains all orders for a product.
- - Cancel All Orders [client.order.cancel_all] - Cancels all OPEN orders for a product.
-- **WebSocket: Watch Candles** [client.watch_candles]
- - Watches candles for for updates, produces completed candles for a series.
- - Candles have 5 minute granularities, this cannot be changed in the current API.
-
-### TODO
-
-Test all endpoints that are currently untested.
-
-## Configuration Feature
-
-Configuration requires you to add the 'config' feature (`features = ["config"]`) to your `Cargo.toml`. The default configuration is unusable due to the API requiring a Key and Secret. You can create, modify, and delete API Keys and Secrets with this [link](https://www.coinbase.com/settings/api).
-
-Copy the `config.toml.sample` to `config.toml` and add in your API information. The `config.toml` file will automatically be read on launch to access your accounts API information. Unlike the depreciated CoinBase Pro API, there's no longer access to Public API endpoints. All access requires authentication. The key and secret is authentication requirements for HTTP requests to be properly [signed](https://docs.cloud.coinbase.com/advanced-trade-api/docs/rest-api-auth) and accepted by CoinBase.
-
-\***\*Custom configurations\*\*** can be created with additional sections beyond just `[coinbase]`. See [custom_config.toml.sample](https://github.com/Ohkthx/cbadv-rs/tree/main/custom_config.toml.sample) for an example of the configuration file. An example of how to implement and create a custom configuration file can be seen in [custom_config.rs](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/custom_config.rs).
-
-Example of enabled `config` feature in `Cargo.toml`.
+Full API documentation is available at [docs.rs](https://docs.rs/cbadv/latest/cbadv/). You can also find helpful information on [crates.io](https://crates.io/crates/cbadv).
+
+---
+
+## API Coverage
+
+### WebSocket API
+
+Client: `use cbadv::{WebSocketClient, WebSocketClientBuilder}`
+
+- **Authentication**: `client.connect`
+- **Subscribe**: `client.subscribe` or `client.sub`
+- **Unsubscribe**: `client.unsubscribe` or `client.unsub`
+- **Channels Supported**:
+ - `Channel::STATUS`: Status
+ - `Channel::CANDLES`: Candles
+ - `Channel::TICKER`: Ticker
+ - `Channel::TICKER_BATCH`: Ticker Batch
+ - `Channel::LEVEL2`: Level 2 Market Data
+ - `Channel::USER`: User-Specific Updates
+ - `Channel::MARKET_TRADES`: Market Trades
+ - `Channel::HEARTBEATS`: Hearbeat (maintains connection.)
+ - `Channel::FUTURES_BALANCE_SUMMARY`: Balance Summary for Futures.
+
+### REST API
+
+Client: `use cbadv::{RestClient, RestClientBuilder}`
+
+- **Accounts (`client.account`)**:
+ - List Accounts: `client.account.get_bulk`
+ - Get Account: `client.account.get`
+- **Products (`client.product`)**:
+ - Get Best Bid/Ask: `client.product.best_bid_ask`
+ - Get Product Book: `client.product.product_book`
+ - List Products: `client.product.get_bulk`
+ - Get Product Details: `client.product.get`
+ - Get Product Candles: `client.product.candles`
+ - Get Market Trades (Ticker): `client.product.ticker`
+- **Orders (`client.order`)**:
+ - Create Order: `client.order.create`
+ - Edit Order: `client.order.edit`
+ - Preview Order Edit: `client.order.preview_edit`
+ - Preview Order Create: `client.order.preview_create`
+ - Cancel Order: `client.order.cancel`
+ - List Orders: `client.order.get_bulk`
+ - List Fills: `client.order.fills`
+ - Get Order: `client.order.get`
+ - Close Position (untested): `client.order.close_position`
+- **Fees (`client.fee`)**:
+ - Get Transaction Summary: `client.fee.get`
+- **Converts (`client.convert`)**:
+ - Create Quote: `client.convert.create_quote`
+ - Get Convert: `client.convert.get`
+ - Commit Convert (untested): `client.convert.commit`
+- **Portfolios (`client.portfolio`)**:
+ - Create Portfolio: `client.portfolio.create`
+ - List Portfolios: `client.portfolio.get_all`
+ - Get Portfolio Breakdown: `client.portfolio.get`
+ - Edit Portfolio: `client.portfolio.edit`
+ - Delete Portfolio: `client.portfolio.delete`
+ - Move Funds (untested): `client.portfolio.move_funds`
+- **Payments (`client.payment`)**
+ - List Payments: `client.payment.get_all`
+ - Get Payment: `client.payment.get`
+- **Data (`client.data`)**
+ - API Key Permissions: `client.data.key_permissions`
+- **Public (`client.public`)**:
+ - Get API Unix Server Time: `client.public.time`
+ - Get Product Book: `client.public.product_book`
+ - List Products: `client.public.products`
+ - Get Product: `client.public.product`
+ - Get Product Candles: `client.public.candles`
+ - Get Product Ticker: `client.public.ticker`
+
+---
+
+## Configuration
+
+To enable the configuration feature, include it in your `Cargo.toml`:
```toml
[dependencies]
cbadv = { version = "*", features = ["config"] }
```
+Set up `config.toml` with your API credentials. A sample file can be found at `config.toml.sample`. See the [custom configuration example](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/custom_config.rs) for advanced setups.
+
+---
+
## Examples
-Check above in the **Covered API requests** section for possibly covered examples. All examples are located at [cbadv-rs/examples](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/) directory.
+Explore the [examples directory](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/) for usage scenarios.
-## Tips Appreciated!
+---
-Wallet addresses are provided below, or click the badges above!
+## TODO
-```
-Ethereum (ETH): 0x7d75f6a9c021fcc70691fec73368198823fb0f60
-Bitcoin (BTC): bc1q75w3cgutug8qdxw3jlmqnkjlv9alt3jr7ftha0
-Binance (BNB): 0x7d75f6a9c021fcc70691fec73368198823fb0f60
-```
+- Test unverified endpoints.
+- Expand examples to cover more advanced cases.
+
+---
+
+## Contributing
+
+Contributions are welcome! Fork the repository, create a feature branch, and submit a pull request.
+
+---
+
+## Tips Appreciated
+
+Support this project via cryptocurrency donations:
+
+**Ethereum (ETH):** 0x7d75f6a9c021fcc70691fec73368198823fb0f60
+**Bitcoin (BTC):** bc1q75w3cgutug8qdxw3jlmqnkjlv9alt3jr7ftha0
+**Binance (BNB):** 0x7d75f6a9c021fcc70691fec73368198823fb0f60
diff --git a/config.toml.sample b/config.toml.sample
index 94cbf81..a5d7b37 100644
--- a/config.toml.sample
+++ b/config.toml.sample
@@ -5,3 +5,4 @@ version = 1
api_key = "YOUR_COINBASE_API_KEY_HERE"
api_secret = "YOUR_COINBASE_API_SECRET_HERE"
debug = false
+use_sandbox = true
diff --git a/custom_config.toml.sample b/custom_config.toml.sample
index b4357ba..9802f5d 100644
--- a/custom_config.toml.sample
+++ b/custom_config.toml.sample
@@ -9,3 +9,4 @@ version = 1
api_key = "YOUR_COINBASE_API_KEY_HERE"
api_secret = "YOUR_COINBASE_API_SECRET_HERE"
debug = false
+use_sandbox = true
diff --git a/examples/README.md b/examples/README.md
index bc43ba2..b8907ab 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -1,70 +1,215 @@
-
+
-
+
-
+
-# Examples
+# cbadv-rs: Coinbase Advanced Trading API Wrapper
-The following examples are for testing and demonstrating the use of the crate. Please review the examples before running them to fully understand what is happening and how they are used. If you have any suggestions, feel free to let me know!
+Welcome to **cbadv-rs**, a Rust crate for interacting with the Coinbase Advanced Trading API. This library provides easy-to-use interfaces for various Coinbase APIs such as Account, Product, Fee, Order, Portfolio, Public, Sandbox, and WebSocket.
-## Account API
+## Table of Contents
-Demonstrates how to use the Account API, accessbile at: [account_api.rs](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/account_api.rs)
+- [Examples](#examples)
+ - [Account API](#account-api)
+ - [Product API](#product-api)
+ - [Fee API](#fee-api)
+ - [Order API](#order-api)
+ - [Portfolio API](#portfolio-api)
+ - [Payment API](#payment-api)
+ - [Convert API](#convert-api)
+ - [Data API](#data-api)
+ - [Public API](#public-api)
+ - [Sandbox API](#sandbox-api)
+ - [WebSocket API](#websocket-api)
+ - [User Orders (WebSocket API)](#user-orders-websocket-api)
+ - [Watch Candles (WebSocket API)](#watch-candles-websocket-api)
+ - [Custom Configurations](#custom-configurations)
+- [Contributing](#contributing)
+- [License](#license)
-**Command**:
+---
-- `cargo run --example account_api --features="config"`
+## Examples
-## Product API
+This section showcases example usage of the crate. Each example demonstrates a different API or functionality. Before running these examples, review the corresponding source code to understand how they work. If you have any suggestions, feel free to open an issue or submit a pull request!
-Demonstrates how to use the Product API, accessbile at: [product_api.rs](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/product_api.rs)
+### Account API
-**Command**:
+Learn how to use the Account API. Example source: [account_api.rs](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/account_api.rs)
-- `cargo run --example product_api --features="config"`
+**Run the example**:
-## Fee API
+```bash
+cargo run --example account_api --features="config"
+```
-Demonstrates how to use the Fee API, accessbile at: [fee_api.rs](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/fee_api.rs)
+---
-**Command**:
+### Product API
-- `cargo run --example fee_api --features="config"`
+Learn how to use the Product API. Example source: [product_api.rs](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/product_api.rs)
-## Order API
+**Run the example**:
-Demonstrates how to use the Order API, accessbile at: [order_api.rs](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/order_api.rs)
+```bash
+cargo run --example product_api --features="config"
+```
-**Command**:
+---
-- `cargo run --example order_api --features="config"`
+### Fee API
-## WebSocket API
+Learn how to use the Fee API. Example source: [fee_api.rs](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/fee_api.rs)
-Demonstrates how to use the WebSocket API, accessbile at: [websocket.rs](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/websocket.rs)
+**Run the example**:
-**Command**:
+```bash
+cargo run --example fee_api --features="config"
+```
-- `cargo run --example websocket --features="config"`
+---
-### WebSocket API - Watch Candles
+### Order API
-Demonstrates how to use the Watch Candles via the WebSocket API, accessbile at: [watch_candles.rs](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/watch_candles.rs) These candles are limited to 5 minute granularity and cannot be currently changed (as of 20231019).
+Learn how to use the Order API. Example source: [order_api.rs](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/order_api.rs)
-**Command**:
+**Run the example**:
-- `cargo run --example watch_candles --features="config"`
+```bash
+cargo run --example order_api --features="config"
+```
-## Custom Configurations
+---
-Demonstrates how to create a custom configuration file to meet your needs in integration, accessbile at: [custom_config.rs](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/custom_config.rs)
+### Portfolio API
-**Command**:
+Learn how to use the Portfolio API. Example source: [portfolio_api.rs](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/portfolio_api.rs)
-- `cargo run --example custom_config --features="config"`
+**Run the example**:
+
+```bash
+cargo run --example portfolio_api --features="config"
+```
+
+---
+
+### Convert API
+
+Learn how to use the Convert API. Example source: [convert_api.rs](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/convert_api.rs)
+
+**Run the example**:
+
+```bash
+cargo run --example convert_api --features="config"
+```
+
+---
+
+### Payment API
+
+Learn how to use the Payment API. Example source: [payment_api.rs](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/payment_api.rs)
+
+**Run the example**:
+
+```bash
+cargo run --example payment_api --features="config"
+```
+
+---
+
+### Data API
+
+Learn how to use the Data API. Example source: [data_api.rs](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/data_api.rs)
+
+**Run the example**:
+
+```bash
+cargo run --example data_api --features="config"
+```
+
+---
+
+### Public API
+
+Learn how to use the Public API. Example source: [public_api.rs](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/public_api.rs)
+
+**Run the example**:
+
+```bash
+cargo run --example public_api --features="config"
+```
+
+---
+
+### Sandbox API
+
+Learn how to use the Sandbox API for testing without affecting real accounts. Example source: [sandbox_api.rs](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/sandbox_api.rs)
+
+**Run the example**:
+
+```bash
+cargo run --example sandbox_api --features="config"
+```
+
+---
+
+### WebSocket API
+
+Learn how to use the WebSocket API for real-time data. Example source: [websocket.rs](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/websocket.rs)
+
+**Run the example**:
+
+```bash
+cargo run --example websocket --features="config"
+```
+
+#### User Orders (WebSocket API)
+
+Learn how to watch user data via the WebSocket API. Example source: [websocket_user.rs](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/websocket_user.rs)
+
+**Run the example**:
+
+```bash
+cargo run --example websocket_user --features="config"
+```
+
+---
+
+#### Watch Candles (WebSocket API)
+
+Learn how to watch candlestick data via the WebSocket API. Currently, only 5-minute granularity is supported (as of 2023-10-19). Example source: [watch_candles.rs](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/watch_candles.rs)
+
+**Run the example**:
+
+```bash
+cargo run --example watch_candles --features="config"
+```
+
+---
+
+### Custom Configurations
+
+Learn how to create custom configuration files tailored to your integration needs. Example source: [custom_config.rs](https://github.com/Ohkthx/cbadv-rs/tree/main/examples/custom_config.rs)
+
+**Run the example**:
+
+```bash
+cargo run --example custom_config --features="config"
+```
+
+---
+
+## Contributing
+
+Contributions are welcome! Feel free to open issues or submit pull requests to improve this crate. For major changes, please open an issue first to discuss what you would like to change.
+
+## License
+
+This project is licensed under the [MIT License](https://opensource.org/licenses/MIT). See the [LICENSE](LICENSE) file for details.
diff --git a/examples/account_api.rs b/examples/account_api.rs
index a8e2126..029bb66 100644
--- a/examples/account_api.rs
+++ b/examples/account_api.rs
@@ -7,9 +7,9 @@
use std::process::exit;
-use cbadv::account::ListAccountsQuery;
+use cbadv::account::AccountListQuery;
use cbadv::config::{self, BaseConfig};
-use cbadv::RestClient;
+use cbadv::RestClientBuilder;
#[tokio::main]
async fn main() {
@@ -33,7 +33,7 @@ async fn main() {
};
// Create a client to interact with the API.
- let mut client = match RestClient::from_config(&config) {
+ let mut client = match RestClientBuilder::new().with_config(&config).build() {
Ok(c) => c,
Err(why) => {
eprintln!("!ERROR! {}", why);
@@ -43,7 +43,11 @@ async fn main() {
// Pull accounts by ID.
println!("Obtaining account by ID (non-standard).");
- match client.account.get_by_id(product_name, None).await {
+ match client
+ .account
+ .get_by_id(product_name, &AccountListQuery::new())
+ .await
+ {
Ok(account) => println!("{:#?}", account),
Err(error) => println!("Unable to get account: {}", error),
}
@@ -51,7 +55,7 @@ async fn main() {
// Pull accounts by ID.
let mut account_uuid = "".to_string();
println!("\n\nObtaining ALL accounts (non-standard).");
- match client.account.get_all(None).await {
+ match client.account.get_all(&AccountListQuery::new()).await {
Ok(accounts) => {
println!("Obtained {:#?} accounts.", accounts.len());
@@ -68,10 +72,7 @@ async fn main() {
}
// Parameters to send to the API.
- let query = ListAccountsQuery {
- // limit: Some(250),
- ..Default::default()
- };
+ let query = AccountListQuery::new();
// Pull all accounts.
println!("\n\nObtaining Bulk Accounts.");
diff --git a/examples/convert_api.rs b/examples/convert_api.rs
new file mode 100644
index 0000000..79bcd18
--- /dev/null
+++ b/examples/convert_api.rs
@@ -0,0 +1,71 @@
+//! # Convert API Example, check out the Convert API for all functionality.
+//!
+//! Shows how to:
+//! - Create a convert quote.
+//! - Obtain a convert quote.
+
+use std::process::exit;
+
+use cbadv::config::{self, BaseConfig};
+use cbadv::convert::{ConvertQuery, ConvertQuoteRequest};
+use cbadv::RestClientBuilder;
+
+#[tokio::main]
+async fn main() {
+ let from_product: &str = "USDC";
+ let to_product: &str = "USD";
+ let amount: f64 = 0.05;
+
+ // Load the configuration file.
+ let config: BaseConfig = match config::load("config.toml") {
+ Ok(c) => c,
+ Err(err) => {
+ println!("Could not load configuration file.");
+ if config::exists("config.toml") {
+ println!("File exists, {}", err);
+ exit(1);
+ }
+
+ // Create a new configuration file.
+ config::create_base_config("config.toml").unwrap();
+ println!("Empty configuration file created, please update it.");
+ exit(1);
+ }
+ };
+
+ // Create a client to interact with the API.
+ let mut client = match RestClientBuilder::new().with_config(&config).build() {
+ Ok(c) => c,
+ Err(why) => {
+ eprintln!("!ERROR! {}", why);
+ exit(1)
+ }
+ };
+
+ // Create a quote to convert USDC to USD.
+ println!(
+ "Creating a quote to convert {} {} to {}.",
+ amount, from_product, to_product
+ );
+ let request = ConvertQuoteRequest::new(from_product, to_product, amount);
+ let quote = match client.convert.create_quote(&request).await {
+ Ok(q) => q,
+ Err(why) => {
+ eprintln!("!ERROR! {}", why);
+ exit(1)
+ }
+ };
+
+ println!("Quote created: {:#?}", quote);
+ println!("\n\nObtain the quote with the quote_id: {}", quote.id);
+ let query = ConvertQuery::new(from_product, to_product);
+ match client.convert.get("e.id, &query).await {
+ Ok(q) => {
+ println!("Quote obtained: {:#?}", q);
+ }
+ Err(why) => {
+ eprintln!("!ERROR! {}", why);
+ exit(1)
+ }
+ };
+}
diff --git a/examples/custom_config.rs b/examples/custom_config.rs
index 4993fd3..683db6c 100644
--- a/examples/custom_config.rs
+++ b/examples/custom_config.rs
@@ -8,7 +8,7 @@ use std::process::exit;
use serde::{Deserialize, Serialize};
use cbadv::config::{self, ApiConfig, ConfigFile};
-use cbadv::RestClient;
+use cbadv::RestClientBuilder;
/// `[general]` section in the configuration file.
#[derive(Serialize, Deserialize, Debug, Clone)]
@@ -66,7 +66,7 @@ async fn main() {
};
// Create a client to interact with the API.
- let mut client = match RestClient::from_config(&config) {
+ let mut client = match RestClientBuilder::new().with_config(&config).build() {
Ok(c) => c,
Err(why) => {
eprintln!("!ERROR! {}", why);
diff --git a/examples/util_api.rs b/examples/data_api.rs
similarity index 64%
rename from examples/util_api.rs
rename to examples/data_api.rs
index e7214bf..6b60ece 100644
--- a/examples/util_api.rs
+++ b/examples/data_api.rs
@@ -1,12 +1,12 @@
-//! # Util API Example, check out the Util API for all functionality.
+//! # Data API Example, check out the Data API for all functionality.
//!
//! Shows how to:
-//! - Obtain the API Unix time.
+//! - Obtain API Key Permissions.
use std::process::exit;
use cbadv::config::{self, BaseConfig};
-use cbadv::RestClient;
+use cbadv::RestClientBuilder;
#[tokio::main]
async fn main() {
@@ -28,7 +28,7 @@ async fn main() {
};
// Create a client to interact with the API.
- let mut client = match RestClient::from_config(&config) {
+ let mut client = match RestClientBuilder::new().with_config(&config).build() {
Ok(c) => c,
Err(why) => {
eprintln!("!ERROR! {}", why);
@@ -36,10 +36,10 @@ async fn main() {
}
};
- // Get API Unix time.
- println!("Obtaining API Unix time");
- match client.util.unixtime().await {
- Ok(time) => println!("{:#?}", time),
- Err(error) => println!("Unable to get the Unix time: {}", error),
+ // Get the API key permissions.
+ println!("Obtaining Key Permissions for the API key.");
+ match client.data.key_permissions().await {
+ Ok(perm) => println!("{:#?}", perm),
+ Err(error) => println!("Unable to get the API key permissions: {}", error),
}
}
diff --git a/examples/fee_api.rs b/examples/fee_api.rs
index da4c29c..ca26e8c 100644
--- a/examples/fee_api.rs
+++ b/examples/fee_api.rs
@@ -6,8 +6,9 @@
use std::process::exit;
use cbadv::config::{self, BaseConfig};
-use cbadv::fee::TransactionSummaryQuery;
-use cbadv::RestClient;
+use cbadv::fee::FeeTransactionSummaryQuery;
+use cbadv::product::ProductType;
+use cbadv::RestClientBuilder;
#[tokio::main]
async fn main() {
@@ -29,7 +30,7 @@ async fn main() {
};
// Create a client to interact with the API.
- let mut client = match RestClient::from_config(&config) {
+ let mut client = match RestClientBuilder::new().with_config(&config).build() {
Ok(c) => c,
Err(why) => {
eprintln!("!ERROR! {}", why);
@@ -38,7 +39,7 @@ async fn main() {
};
// Parameters to send to the API.
- let params = TransactionSummaryQuery::default();
+ let params = FeeTransactionSummaryQuery::new().product_type(ProductType::Spot);
// Get fee transaction summary.
println!("Obtaining Transaction Fee Summary");
diff --git a/examples/order_api.rs b/examples/order_api.rs
index 510e38c..686da51 100644
--- a/examples/order_api.rs
+++ b/examples/order_api.rs
@@ -9,21 +9,38 @@
//! - Obtain specific order by ID.
use std::process::exit;
+use std::thread;
use cbadv::config::{self, BaseConfig};
-use cbadv::order::{ListOrdersQuery, OrderSide};
-use cbadv::RestClient;
+use cbadv::order::{
+ OrderCancelRequest, OrderCreateBuilder, OrderEditRequest, OrderListQuery, OrderSide,
+ OrderStatus, OrderType, TimeInForce,
+};
+use cbadv::RestClientBuilder;
+use chrono::Duration;
#[tokio::main]
async fn main() {
- let create_trade: bool = false;
- let cancel_open_orders: bool = false;
- let edit_open_order_id: Option = None;
- let product_pair: &str = "DOGE-USD";
- let total_size: f64 = 300.0;
- let price: f64 = 100.00;
- let edit_price: f64 = 50.00;
- let side: &str = "SELL";
+ let create_new: bool = false;
+ let edit_created: bool = true;
+ let cancel_created: bool = true;
+ let cancel_all: bool = false;
+ let product_id: &str = "ETH-USDC";
+ let mut created_order_id: Option = None;
+ let new_order = match OrderCreateBuilder::new(product_id, &OrderSide::Buy)
+ .base_size(0.005)
+ .limit_price(100.0)
+ .post_only(true)
+ .order_type(OrderType::Limit)
+ .time_in_force(TimeInForce::GoodUntilCancelled)
+ .build()
+ {
+ Ok(order) => order,
+ Err(error) => {
+ println!("Unable to build order: {}", error);
+ exit(1);
+ }
+ };
// Load the configuration file.
let config: BaseConfig = match config::load("config.toml") {
@@ -43,7 +60,7 @@ async fn main() {
};
// Create a client to interact with the API.
- let mut client = match RestClient::from_config(&config) {
+ let mut client = match RestClientBuilder::new().with_config(&config).build() {
Ok(c) => c,
Err(why) => {
eprintln!("!ERROR! {}", why);
@@ -51,49 +68,76 @@ async fn main() {
}
};
- if create_trade {
- println!("Creating Order for {}.", product_pair);
- match client
- .order
- .create_limit_gtc(product_pair, side, &total_size, &price, true)
- .await
- {
- Ok(summary) => println!("Order creation result: {:#?}", summary),
+ if create_new {
+ println!(
+ "Creating Order with Client ID: {}",
+ new_order.client_order_id
+ );
+ match client.order.create(&new_order).await {
+ Ok(summary) => {
+ if let Some(success) = &summary.success_response {
+ created_order_id = Some(success.order_id.clone());
+ }
+ println!("Order creation result: {:#?}", summary);
+ }
Err(error) => println!("Unable to create order: {}", error),
}
}
- if let Some(order_id) = edit_open_order_id {
- println!("\n\nEditing order for {}.", order_id);
- match client.order.edit(&order_id, total_size, edit_price).await {
- Ok(result) => println!("{:#?}", result),
- Err(error) => println!("Unable to edit order: {}", error),
+ if let Some(order_id) = &created_order_id {
+ if create_new && edit_created {
+ thread::sleep(Duration::seconds(1).to_std().unwrap());
+ let edit_order = OrderEditRequest::new(order_id, 50.0, 0.006);
+ println!("\n\nEditing order for {}.", order_id);
+ match client.order.edit(&edit_order).await {
+ Ok(result) => println!("{:#?}", result),
+ Err(error) => println!("Unable to edit order: {}", error),
+ }
+ }
+ }
+
+ if let Some(order_id) = &created_order_id {
+ if create_new && cancel_created {
+ println!("\n\nCancelling Order with ID: {}", order_id);
+ match client
+ .order
+ .cancel(&OrderCancelRequest::new(&[order_id.clone()]))
+ .await
+ {
+ Ok(summary) => println!("Order cancel result: {:#?}", summary),
+ Err(error) => println!("Unable to cancel order: {}", error),
+ }
}
}
- if cancel_open_orders {
- println!("\n\nCancelling all OPEN orders for {}.", product_pair);
- match client.order.cancel_all(product_pair).await {
+ // Cancels all OPEN orders.
+ if cancel_all {
+ println!("\n\nCancelling all OPEN orders for {}.", product_id);
+ match client.order.cancel_all(product_id).await {
Ok(result) => println!("{:#?}", result),
Err(error) => println!("Unable to cancel orders: {}", error),
}
}
- println!("\n\nGetting all orders for {}.", product_pair);
- match client.order.get_all(product_pair, None).await {
+ println!("\n\nGetting all orders for {} (get_all).", product_id);
+ match client
+ .order
+ .get_all(product_id, &OrderListQuery::new())
+ .await
+ {
Ok(orders) => println!("Orders obtained: {:#?}", orders.len()),
Err(error) => println!("Unable to obtain all orders: {}", error),
}
- // Get all SELLING orders.
+ // Get all BUYING orders.
let mut order_id = "".to_string();
- let query = ListOrdersQuery {
- product_id: Some(product_pair.to_string()),
- order_side: Some(OrderSide::Sell),
+ let query = OrderListQuery {
+ product_ids: Some(vec![product_id.to_string()]),
+ order_side: Some(OrderSide::Buy),
..Default::default()
};
- println!("\n\nObtaining Orders.");
+ println!("\n\nObtaining Orders (bulk).");
match client.order.get_bulk(&query).await {
Ok(orders) => {
println!("Orders obtained: {:#?}", orders.orders.len());
@@ -108,15 +152,19 @@ async fn main() {
// Build list of orders to cancel.
let mut order_ids: Vec = vec![];
for order in orders.orders {
- if order.status == "OPEN" {
+ if order.status == OrderStatus::Open {
order_ids.push(order.order_id);
}
}
// Cancel the orders.
- if cancel_open_orders && !order_ids.is_empty() {
+ if cancel_all && !order_ids.is_empty() {
println!("\n\nCancelling open orders.");
- match client.order.cancel(&order_ids).await {
+ match client
+ .order
+ .cancel(&OrderCancelRequest::new(&order_ids))
+ .await
+ {
Ok(summary) => println!("Order cancel result: {:#?}", summary),
Err(error) => println!("Unable to cancel order: {}", error),
}
diff --git a/examples/payment_api.rs b/examples/payment_api.rs
new file mode 100644
index 0000000..0b74eba
--- /dev/null
+++ b/examples/payment_api.rs
@@ -0,0 +1,63 @@
+//! # Payment API Example, check out the Payment API for all functionality.
+//!
+//! Shows how to:
+//! - Get all payment methods.
+//! - Get a single payment method.
+
+use std::process::exit;
+
+use cbadv::config::{self, BaseConfig};
+use cbadv::RestClientBuilder;
+
+#[tokio::main]
+async fn main() {
+ // Load the configuration file.
+ let config: BaseConfig = match config::load("config.toml") {
+ Ok(c) => c,
+ Err(err) => {
+ println!("Could not load configuration file.");
+ if config::exists("config.toml") {
+ println!("File exists, {}", err);
+ exit(1);
+ }
+
+ // Create a new configuration file.
+ config::create_base_config("config.toml").unwrap();
+ println!("Empty configuration file created, please update it.");
+ exit(1);
+ }
+ };
+
+ // Create a client to interact with the API.
+ let mut client = match RestClientBuilder::new().with_config(&config).build() {
+ Ok(c) => c,
+ Err(why) => {
+ eprintln!("!ERROR! {}", why);
+ exit(1)
+ }
+ };
+
+ let mut payment_method_id = None;
+
+ // Get payment methods.
+ println!("Obtaining all payment methods.");
+ match client.payment.get_all().await {
+ Ok(methods) => {
+ println!("{:#?}", methods);
+ if let Some(method) = methods.first() {
+ payment_method_id = Some(method.id.clone());
+ }
+ }
+ Err(error) => println!("Unable to get the Payment Methods: {}", error),
+ }
+
+ // Obtain a single payment method.
+ if let Some(payment_method_id) = payment_method_id {
+ // Get a single payment method.
+ println!("\n\nObtaining a single payment method.");
+ match client.payment.get(&payment_method_id).await {
+ Ok(method) => println!("{:#?}", method),
+ Err(error) => println!("Unable to get the Payment Method: {}", error),
+ }
+ }
+}
diff --git a/examples/portfolio_api.rs b/examples/portfolio_api.rs
new file mode 100644
index 0000000..10b90a0
--- /dev/null
+++ b/examples/portfolio_api.rs
@@ -0,0 +1,113 @@
+//! # Portfolio API Example, check out the Portfolio API for all functionality.
+//!
+//! Shows how to:
+//! - Create a new portfolio.
+//! - Edit an existing portfolio.
+//! - Delete an existing portfolio.
+//! - Obtain a list of portfolios.
+//! - Obtain the breakdown of a portfolio.
+
+use std::process::exit;
+
+use cbadv::config::{self, BaseConfig};
+use cbadv::portfolio::{PortfolioBreakdownQuery, PortfolioListQuery, PortfolioModifyRequest};
+use cbadv::RestClientBuilder;
+
+#[tokio::main]
+async fn main() {
+ // Set to None to not create.
+ let create_portfolio_name = None;
+ // let create_portfolio_name = Some("New Portfolio");
+
+ // Set to None to not edit.
+ let edit_portfolio_uuid = None;
+ // let edit_portfolio_uuid = Some("AAAAAAAA-BBBB-CCCC-DDDDDDDDDDDD");
+ let edit_portfolio_name = "DeleteMe";
+
+ // Set to None to not delete.
+ let delete_portfolio_uuid = None;
+ // let delete_portfolio_uuid = Some("AAAAAAAA-BBBB-CCCC-DDDDDDDDDDDD");
+
+ // Load the configuration file.
+ let config: BaseConfig = match config::load("config.toml") {
+ Ok(c) => c,
+ Err(err) => {
+ println!("Could not load configuration file.");
+ if config::exists("config.toml") {
+ println!("File exists, {}", err);
+ exit(1);
+ }
+
+ // Create a new configuration file.
+ config::create_base_config("config.toml").unwrap();
+ println!("Empty configuration file created, please update it.");
+ exit(1);
+ }
+ };
+
+ // Create a client to interact with the API.
+ let mut client = match RestClientBuilder::new().with_config(&config).build() {
+ Ok(c) => c,
+ Err(why) => {
+ eprintln!("!ERROR! {}", why);
+ exit(1)
+ }
+ };
+
+ // Create a new portfolio.
+ if let Some(name) = create_portfolio_name {
+ println!("Creating Portfolio.");
+ match client.portfolio.create(name).await {
+ Ok(portfolio) => println!("{:#?}", portfolio),
+ Err(error) => println!("Unable to create the portfolio: {}", error),
+ }
+ }
+
+ // Edit an existing portfolio.
+ if let Some(uuid) = edit_portfolio_uuid {
+ println!("Editing Portfolio.");
+ let request = PortfolioModifyRequest::new(edit_portfolio_name);
+ match client.portfolio.edit(uuid, &request).await {
+ Ok(portfolio) => println!("{:#?}", portfolio),
+ Err(error) => println!("Unable to edit the portfolio: {}", error),
+ }
+ }
+
+ // Delete an existing portfolio.
+ if let Some(uuid) = delete_portfolio_uuid {
+ println!("Deleting Portfolio.");
+ match client.portfolio.delete(uuid).await {
+ Ok(_) => println!("Portfolio deleted!"),
+ Err(error) => println!("Unable to delete the portfolio: {}", error),
+ }
+ }
+
+ // Parameters to send to the API.
+ let query = PortfolioListQuery::new();
+
+ // Get listed portfolios..
+ println!("Obtaining Portfolios");
+ let breakdown_uuid = match client.portfolio.get_all(&query).await {
+ Ok(portfolios) => {
+ println!("{:#?}", portfolios);
+ Some(portfolios.first().unwrap().uuid.clone())
+ }
+ Err(error) => {
+ println!("Unable to get the portfolios: {}", error);
+ None
+ }
+ };
+
+ // Get the breakdown for the first portfolio.
+ if let Some(uuid) = breakdown_uuid {
+ println!("Obtaining Portfolio Breakdown for {}.", uuid);
+ match client
+ .portfolio
+ .get(&uuid, &PortfolioBreakdownQuery::new())
+ .await
+ {
+ Ok(breakdown) => println!("{:#?}", breakdown),
+ Err(error) => println!("Unable to get the breakdown: {}", error),
+ }
+ }
+}
diff --git a/examples/product_api.rs b/examples/product_api.rs
index 11fc1a1..e97cebc 100644
--- a/examples/product_api.rs
+++ b/examples/product_api.rs
@@ -10,8 +10,11 @@
use std::process::exit;
use cbadv::config::{self, BaseConfig};
-use cbadv::product::{ListProductsQuery, TickerQuery};
-use cbadv::{time, RestClient};
+use cbadv::product::{
+ ProductBidAskQuery, ProductCandleQuery, ProductListQuery, ProductTickerQuery,
+};
+use cbadv::time::Granularity;
+use cbadv::{time, RestClientBuilder};
#[tokio::main]
async fn main() {
@@ -35,7 +38,7 @@ async fn main() {
};
// Create a client to interact with the API.
- let mut client = match RestClient::from_config(&config) {
+ let mut client = match RestClientBuilder::new().with_config(&config).build() {
Ok(c) => c,
Err(why) => {
eprintln!("!ERROR! {}", why);
@@ -49,32 +52,21 @@ async fn main() {
println!("{:#?}\n\n", product);
println!("Getting best bids and asks.");
- match client
- .product
- .best_bid_ask(vec!["BTC-USD".to_string()])
- .await
- {
+ let query = ProductBidAskQuery::new().product_ids(&["BTC-USD".to_string()]);
+ match client.product.best_bid_ask(&query).await {
Ok(bidasks) => println!("{:#?}", bidasks),
Err(error) => println!("Unable to get best bids and asks: {}", error),
}
// NOTE: Commented out due to large amounts of output.
// println!("\n\nGetting product book.");
- // match client
- // .product
- // .product_book(product_pair.clone(), None)
- // .await
- // {
+ // match client.product.product_book(product_pair, None).await {
// Ok(book) => println!("{:#?}", book),
// Err(error) => println!("Unable to get product book: {}", error),
// }
println!("\n\nGetting multiple products.");
- let query = ListProductsQuery {
- // limit: Some(500),
- // product_ids: Some(vec!["BTC-USD".to_string(), "ETH-USD".to_string()]),
- ..Default::default()
- };
+ let query = ProductListQuery::new();
// Pull multiple products from the Product API.
match client.product.get_bulk(&query).await {
@@ -83,14 +75,16 @@ async fn main() {
}
// Pull candles.
- println!("\n\nGetting candles for: {}.", product_pair);
- let granularity = time::Granularity::OneDay;
- let interval = time::Granularity::to_secs(&granularity) as u64;
let end = time::now();
- let start = time::before(end, interval * 730);
- let time_span = time::Span::new(start, end, &granularity);
- println!("Intervals collecting: {}", time_span.count());
- match client.product.candles_ext(product_pair, &time_span).await {
+ let interval = Granularity::to_secs(&Granularity::OneDay) as u64;
+ println!("\n\nGetting candles for: {}.", product_pair);
+ let query = ProductCandleQuery::new(
+ time::before(end, interval * 365),
+ end,
+ time::Granularity::OneDay,
+ );
+
+ match client.product.candles_ext(product_pair, &query).await {
Ok(candles) => {
println!("Obtained {} candles.", candles.len());
match candles.first() {
@@ -103,7 +97,7 @@ async fn main() {
// Pull ticker.
println!("\n\nGetting ticker for: {}.", product_pair);
- let query = TickerQuery { limit: 200 };
+ let query = ProductTickerQuery::new(200);
match client.product.ticker(product_pair, &query).await {
Ok(ticker) => {
println!(
diff --git a/examples/public_api.rs b/examples/public_api.rs
new file mode 100644
index 0000000..4a6ac3d
--- /dev/null
+++ b/examples/public_api.rs
@@ -0,0 +1,97 @@
+//! # Public API Example, check out the Public API for all functionality.
+//!
+//! Shows how to:
+//! - Obtain the API Unix time.
+//! - Obtain the Product Book for a product.
+//! - Obtain multiple products.
+//! - Obtain candles for a product.
+//! - Obtain the ticker for a product.
+
+use std::process::exit;
+
+use cbadv::product::{ProductCandleQuery, ProductListQuery, ProductTickerQuery};
+use cbadv::time::Granularity;
+use cbadv::{time, RestClientBuilder};
+
+#[tokio::main]
+async fn main() {
+ let product_pair: &str = "BTC-USD";
+
+ // Create a client to interact with the API.
+ let mut client = match RestClientBuilder::new().build() {
+ Ok(c) => c,
+ Err(why) => {
+ eprintln!("!ERROR! {}", why);
+ exit(1)
+ }
+ };
+
+ // Get API Unix time.
+ println!("Obtaining API Unix time");
+ match client.public.time().await {
+ Ok(time) => println!("{:#?}", time),
+ Err(error) => println!("Unable to get the Unix time: {}", error),
+ }
+
+ // NOTE: Commented out due to large amounts of output.
+ // Get the Product Book for BTC-USD.
+ // println!("\n\nObtain the Product Book for {product_pair}.");
+ // match client.public.product_book(product_pair, None).await {
+ // Ok(book) => println!("{:#?}", book),
+ // Err(error) => println!("Unable to get the Product Book: {}", error),
+ // }
+
+ println!("\n\nGetting multiple products.");
+ let query = ProductListQuery {
+ // limit: Some(500),
+ // product_ids: Some(vec!["BTC-USD".to_string(), "ETH-USD".to_string()]),
+ // get_all_products: Some(true),
+ ..Default::default()
+ };
+
+ // Pull multiple products from the Product API.
+ match client.public.products(&query).await {
+ Ok(products) => println!("Obtained {:#?} products", products.len()),
+ Err(error) => println!("Unable to get products: {}", error),
+ }
+
+ // Pull candles.
+ let end = time::now();
+ let interval = Granularity::to_secs(&Granularity::OneDay) as u64;
+ println!("\n\nGetting candles for: {}.", product_pair);
+ let query = ProductCandleQuery::new(
+ time::before(end, interval * 365),
+ end,
+ time::Granularity::OneDay,
+ );
+
+ match client.public.candles_ext(product_pair, &query).await {
+ Ok(candles) => {
+ println!("Obtained {} candles.", candles.len());
+ match candles.first() {
+ Some(candle) => println!("{:#?}", candle),
+ None => println!("Out of bounds, no candles obtained."),
+ }
+ }
+ Err(error) => println!("Unable to get candles: {}", error),
+ }
+
+ // Pull ticker.
+ println!("\n\nGetting ticker for: {}.", product_pair);
+ let query = ProductTickerQuery::new(200);
+ match client.public.ticker(product_pair, &query).await {
+ Ok(ticker) => {
+ println!(
+ "best bid: {:#?}\nbest ask: {:#?}\ntrades: {:#?}",
+ ticker.best_bid,
+ ticker.best_ask,
+ ticker.trades.len()
+ );
+ match ticker.trades.first() {
+ Some(trade) => println!("{:#?}", trade),
+ None => println!("Out of bounds, no trades available."),
+ }
+ }
+ Err(error) => println!("Unable to get ticker: {}", error),
+ }
+}
diff --git a/examples/sandbox_api.rs b/examples/sandbox_api.rs
new file mode 100644
index 0000000..99f0650
--- /dev/null
+++ b/examples/sandbox_api.rs
@@ -0,0 +1,90 @@
+//! # Sandbox API Example
+//!
+//! Shows how to:
+//! - Create an order.
+//! - Edit an order.
+//! - Cancel all OPEN orders.
+//! - Obtain ALL orders.
+//! - Obtain multiple orders.
+//! - Obtain specific order by ID.
+
+use std::process::exit;
+
+use cbadv::config::{self, BaseConfig};
+use cbadv::order::{OrderCreateBuilder, OrderEditRequest, OrderSide, OrderType, TimeInForce};
+use cbadv::RestClientBuilder;
+
+#[tokio::main]
+async fn main() {
+ let product_pair: &str = "BTC-USD";
+ let total_size: f64 = 0.005;
+ let price: f64 = 100.00;
+ let side: OrderSide = OrderSide::Buy;
+
+ // Load the configuration file.
+ let config: BaseConfig = match config::load("config.toml") {
+ Ok(c) => c,
+ Err(err) => {
+ println!("Could not load configuration file.");
+ if config::exists("config.toml") {
+ println!("File exists, {}", err);
+ exit(1);
+ }
+
+ // Create a new configuration file.
+ config::create_base_config("config.toml").unwrap();
+ println!("Empty configuration file created, please update it.");
+ exit(1);
+ }
+ };
+
+ // Create a client to interact with the API.
+ let mut client = match RestClientBuilder::new()
+ .with_config(&config)
+ .use_sandbox(true)
+ .build()
+ {
+ Ok(c) => c,
+ Err(why) => {
+ eprintln!("!ERROR! {}", why);
+ exit(1)
+ }
+ };
+
+ // Create an order request using the `OrderCreateBuilder`.
+ // This example creates a Limit Order that is Good-Til-Cancelled (GTC) and post-only.
+ let order = match OrderCreateBuilder::new(product_pair, &side)
+ .base_size(total_size)
+ .limit_price(price)
+ .post_only(true)
+ .order_type(OrderType::Limit)
+ .time_in_force(TimeInForce::GoodUntilCancelled)
+ .preview(true)
+ .build()
+ {
+ Ok(order) => order,
+ Err(error) => {
+ println!("Unable to build order: {}", error);
+ exit(1);
+ }
+ };
+
+ println!("Creating Order for {}.", product_pair);
+ match client.order.create(&order).await {
+ Ok(summary) => println!("Order creation result: {:#?}", summary),
+ Err(error) => println!("Unable to create order: {}", error),
+ }
+
+ println!("\n\nPreviewing an order creation.");
+ match client.order.preview_create(&order).await {
+ Ok(summary) => println!("Order preview result: {:#?}", summary),
+ Err(error) => println!("Unable to preview order: {}", error),
+ }
+
+ println!("\n\nPreviewing an order edit.");
+ let edit_preview = OrderEditRequest::new("order_id", 100.00, 0.005);
+ match client.order.preview_edit(&edit_preview).await {
+ Ok(summary) => println!("Order edit preview result: {:#?}", summary),
+ Err(error) => println!("Unable to preview order edit: {}", error),
+ }
+}
diff --git a/examples/watch_candles.rs b/examples/watch_candles.rs
index db89cbe..470b709 100644
--- a/examples/watch_candles.rs
+++ b/examples/watch_candles.rs
@@ -8,10 +8,9 @@
use std::process::exit;
-use cbadv::config::{self, BaseConfig};
-use cbadv::product::{Candle, ListProductsQuery};
+use cbadv::product::{Candle, ProductListQuery};
use cbadv::traits::CandleCallback;
-use cbadv::{RestClient, WebSocketClient};
+use cbadv::{RestClient, RestClientBuilder, WebSocketClientBuilder};
/// Example of user-defined struct to pass to the candle watcher.
pub struct UserStruct {
@@ -30,7 +29,7 @@ impl CandleCallback for UserStruct {
// Processed | Product_Id | Candle Start | Current
println!(
- "{:<5} {:>11} ({}): finished candle {}",
+ "{:<5} {:>14} ({}): finished candle {}",
self.processed, product_id, candle.start, is_same
);
}
@@ -38,22 +37,20 @@ impl CandleCallback for UserStruct {
/// Obtain product names of candles to be obtained.
async fn get_products(client: &mut RestClient) -> Vec {
- println!("Getting '*-USD' products.");
- let query = ListProductsQuery {
- ..Default::default()
- };
+ println!("Getting '*-USDC' products.");
// Holds all of the product names.
let mut product_names: Vec = vec![];
+ let query = ProductListQuery::new();
// Pull multiple products from the Product API.
- match client.product.get_bulk(&query).await {
+ match client.public.products(&query).await {
Ok(products) => {
product_names = products
.iter()
- // Filter products to only containing *-USD pairs.
+ // Filter products to only containing *-USDC pairs.
.filter_map(|p| match p.quote_currency_id.as_str() {
- "USD" => Some(p.product_id.clone()),
+ "USDC" => Some(p.product_id.clone()),
_ => None,
})
.collect();
@@ -66,25 +63,8 @@ async fn get_products(client: &mut RestClient) -> Vec {
#[tokio::main]
async fn main() -> Result<(), Box> {
- // Load the configuration file.
- let config: BaseConfig = match config::load("config.toml") {
- Ok(c) => c,
- Err(err) => {
- println!("Could not load configuration file.");
- if config::exists("config.toml") {
- println!("File exists, {}", err);
- exit(1);
- }
-
- // Create a new configuration file with defaults.
- config::create_base_config("config.toml").unwrap();
- println!("Empty configuration file created, please update it.");
- exit(1);
- }
- };
-
// Create a client to interact with the API.
- let mut rclient = match RestClient::from_config(&config) {
+ let mut rclient = match RestClientBuilder::new().build() {
Ok(c) => c,
Err(why) => {
eprintln!("!ERROR! {}", why);
@@ -93,7 +73,11 @@ async fn main() -> Result<(), Box> {
};
// Create a client to interact with the API.
- let mut wsclient = match WebSocketClient::from_config(&config) {
+ let wsclient = match WebSocketClientBuilder::new()
+ .auto_reconnect(true)
+ .max_retries(20)
+ .build()
+ {
Ok(c) => c,
Err(why) => {
eprintln!("!ERROR! {}", why);
diff --git a/examples/websocket.rs b/examples/websocket.rs
index 5f4d588..c59824f 100644
--- a/examples/websocket.rs
+++ b/examples/websocket.rs
@@ -11,8 +11,8 @@ use std::process::exit;
use cbadv::config::{self, BaseConfig};
use cbadv::traits::MessageCallback;
use cbadv::types::CbResult;
-use cbadv::ws::{Channel, Message};
-use cbadv::WebSocketClient;
+use cbadv::ws::{Channel, EndpointType, Message};
+use cbadv::WebSocketClientBuilder;
/// Example of an object with an attached callback function for messages.
struct CallbackObject {
@@ -25,21 +25,11 @@ impl MessageCallback for CallbackObject {
/// the stream.
fn message_callback(&mut self, msg: CbResult) {
let rcvd = match msg {
- Ok(value) => match value {
- Message::Status(v) => format!("{:?}", v),
- Message::Candles(v) => format!("{:?}", v),
- Message::Ticker(v) => format!("{:?}", v),
- Message::TickerBatch(v) => format!("{:?}", v),
- Message::Level2(v) => format!("{:?}", v),
- Message::User(v) => format!("{:?}", v),
- Message::MarketTrades(v) => format!("{:?}", v),
- Message::Heartbeats(v) => format!("{:?}", v),
- Message::Subscribe(v) => format!("{:?}", v),
- },
- Err(error) => format!("{}", error),
+ Ok(message) => format!("{:?}", message), // Leverage Debug for all Message variants
+ Err(error) => format!("Error: {}", error), // Handle WebSocket errors
};
- // Using the callback objects properties.
+ // Update the callback object's properties and log the message.
self.total_processed += 1;
println!("{:<5}> {}\n", self.total_processed, rcvd);
}
@@ -64,37 +54,51 @@ async fn main() {
}
};
- // Create a client to interact with the API.
- let mut client = match WebSocketClient::from_config(&config) {
- Ok(c) => c,
- Err(why) => {
- eprintln!("!ERROR! {}", why);
- exit(1)
- }
- };
+ let mut client = WebSocketClientBuilder::new()
+ .with_config(&config)
+ .auto_reconnect(true)
+ .max_retries(20)
+ .build()
+ .map_err(|e| {
+ eprintln!("!ERROR! {}", e);
+ exit(1);
+ })
+ .unwrap();
- // Callback Object
- let cb_obj: CallbackObject = CallbackObject { total_processed: 0 };
+ // Callback Object.
+ let cb_obj = CallbackObject { total_processed: 0 };
// Connect to the websocket, a subscription needs to be sent within 5 seconds.
// If a subscription is not sent, Coinbase will close the connection.
- let reader = client.connect().await.unwrap();
- let listener = tokio::spawn(WebSocketClient::listener_with(reader, cb_obj));
+ let mut readers = client
+ .connect()
+ .await
+ .expect("Could not connect to WebSocket");
+
+ let public = readers
+ .take_endpoint(&EndpointType::Public)
+ .expect("Could not get public reader");
+
+ let listened_client = client.clone();
+ let listener = tokio::spawn(async move {
+ let mut listened_client = listened_client;
+ listened_client.listen_trait(public, cb_obj).await;
+ });
// Products of interest.
let products = vec!["BTC-USD".to_string(), "ETH-USD".to_string()];
// Heartbeats is a great way to keep a connection alive and not timeout.
- client.sub(Channel::Heartbeats, &[]).await.unwrap();
+ client.sub(&Channel::Heartbeats, &[]).await.unwrap();
// Subscribe to user orders.
- client.sub(Channel::User, &products).await.unwrap();
+ client.sub(&Channel::User, &products).await.unwrap();
// Get updates (subscribe) on products and currencies.
- client.sub(Channel::Candles, &products).await.unwrap();
+ client.sub(&Channel::Candles, &products).await.unwrap();
// Stop obtaining (unsubscribe) updates on products and currencies.
- client.unsub(Channel::Status, &products).await.unwrap();
+ client.unsub(&Channel::Status, &products).await.unwrap();
// Passes the parser callback and listens for messages.
listener.await.unwrap();
diff --git a/examples/websocket_user.rs b/examples/websocket_user.rs
new file mode 100644
index 0000000..ae9faae
--- /dev/null
+++ b/examples/websocket_user.rs
@@ -0,0 +1,96 @@
+//! # WebSocket User API Example, check out the WebSocket API for all functionality.
+//!
+//! Shows how to:
+//! - Connect WebSocket Client.
+//! - Setup Listener and parse messages.
+//! - Subscribe to channels.
+//! - Unsubscribe to channels.
+
+use std::process::exit;
+
+use cbadv::config::{self, BaseConfig};
+use cbadv::traits::MessageCallback;
+use cbadv::types::CbResult;
+use cbadv::ws::{Channel, EndpointType, Message};
+use cbadv::WebSocketClientBuilder;
+
+/// Example of an object with an attached callback function for messages.
+struct CallbackObject {
+ /// Total amount of messages processed.
+ total_processed: usize,
+}
+
+impl MessageCallback for CallbackObject {
+ /// This is used to parse messages. It is passed to the `listen` function to pull Messages out of
+ /// the stream.
+ fn message_callback(&mut self, msg: CbResult) {
+ let rcvd = match msg {
+ Ok(message) => format!("{:?}", message), // Leverage Debug for all Message variants
+ Err(error) => format!("Error: {}", error), // Handle WebSocket errors
+ };
+
+ // Update the callback object's properties and log the message.
+ self.total_processed += 1;
+ println!("{:<5}> {}\n", self.total_processed, rcvd);
+ }
+}
+
+#[tokio::main]
+async fn main() {
+ // Load the configuration file.
+ let config: BaseConfig = match config::load("config.toml") {
+ Ok(c) => c,
+ Err(err) => {
+ println!("Could not load configuration file.");
+ if config::exists("config.toml") {
+ println!("File exists, {}", err);
+ exit(1);
+ }
+
+ // Create a new configuration file.
+ config::create_base_config("config.toml").unwrap();
+ println!("Empty configuration file created, please update it.");
+ exit(1);
+ }
+ };
+
+ let mut client = WebSocketClientBuilder::new()
+ .with_config(&config)
+ .auto_reconnect(true)
+ .max_retries(20)
+ .build()
+ .map_err(|e| {
+ eprintln!("!ERROR! {}", e);
+ exit(1);
+ })
+ .unwrap();
+
+ // Callback Object.
+ let cb_obj = CallbackObject { total_processed: 0 };
+
+ // Connect to the websocket, a subscription needs to be sent within 5 seconds.
+ // If a subscription is not sent, Coinbase will close the connection.
+ let mut readers = client
+ .connect()
+ .await
+ .expect("Could not connect to WebSocket.");
+
+ let user = readers
+ .take_endpoint(&EndpointType::User)
+ .expect("Could not get secure user reader.");
+
+ let listened_client = client.clone();
+ let listener = tokio::spawn(async move {
+ let mut listened_client = listened_client;
+ listened_client.listen_trait(user, cb_obj).await;
+ });
+
+ // Heartbeats is a great way to keep a connection alive and not timeout.
+ client.sub(&Channel::Heartbeats, &[]).await.unwrap();
+
+ // Subscribe to user orders.
+ client.sub(&Channel::User, &[]).await.unwrap();
+
+ // Passes the parser callback and listens for messages.
+ listener.await.unwrap();
+}
diff --git a/src/apis/account.rs b/src/apis/account.rs
index 9ab431b..fcc224e 100644
--- a/src/apis/account.rs
+++ b/src/apis/account.rs
@@ -3,19 +3,17 @@
//! `account` gives access to the Account API and the various endpoints associated with it.
//! This allows you to obtain account information either by account UUID or in bulk (all accounts).
-use async_recursion::async_recursion;
-
-use crate::account::{Account, AccountResponse, ListAccountsQuery, ListedAccounts};
-use crate::constants::accounts::RESOURCE_ENDPOINT;
-use crate::errors::CbAdvError;
-use crate::signer::Signer;
-use crate::traits::NoQuery;
+use crate::account::{Account, AccountListQuery, AccountWrapper, PaginatedAccounts};
+use crate::constants::accounts::{LIST_ACCOUNT_MAXIMUM, RESOURCE_ENDPOINT};
+use crate::errors::CbError;
+use crate::http_agent::SecureHttpAgent;
+use crate::traits::{HttpAgent, NoQuery};
use crate::types::CbResult;
/// Provides access to the Account API for the service.
pub struct AccountApi {
/// Object used to sign requests made to the API.
- signer: Signer,
+ agent: Option,
}
impl AccountApi {
@@ -23,10 +21,9 @@ impl AccountApi {
///
/// # Arguments
///
- /// * `signer` - A Signer that include the API Key & Secret along with a client to make
- /// requests.
- pub(crate) fn new(signer: Signer) -> Self {
- Self { signer }
+ /// * `signer` - A Signer that include the API Key & Secret along with a client to make requests.
+ pub(crate) fn new(agent: Option) -> Self {
+ Self { agent }
}
/// Obtains a single account based on the Account UUID (ex. "XXXX-YYYY-ZZZZ"). This is the most
@@ -43,18 +40,18 @@ impl AccountApi {
///
///
pub async fn get(&mut self, account_uuid: &str) -> CbResult {
+ let agent = get_auth!(self.agent, "get account");
let resource = format!("{}/{}", RESOURCE_ENDPOINT, account_uuid);
- match self.signer.get(&resource, &NoQuery).await {
- Ok(value) => match value.json::().await {
- Ok(resp) => Ok(resp.account),
- Err(_) => Err(CbAdvError::BadParse("account object".to_string())),
- },
- Err(error) => Err(error),
- }
+ let response = agent.get(&resource, &NoQuery).await?;
+ let data: AccountWrapper = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data.into())
}
/// Obtains a single account based on the Account ID (ex. "BTC").
- /// This wraps `get_bulk` and recursively makes several additional requests until either the
+ /// This wraps `get_bulk` and iteratively makes several additional requests until either the
/// account is found or there are not more accounts. This is a more expensive call, but more
/// convient than `get` which requires knowing the UUID already.
///
@@ -64,69 +61,67 @@ impl AccountApi {
/// # Arguments
///
/// * `id` - Identifier for the account, such as BTC or ETH.
- /// * `query` - Optional parameters, should default to None unless you want additional control.
- #[async_recursion]
- pub async fn get_by_id(
- &mut self,
- id: &str,
- query: Option,
- ) -> CbResult {
- let mut query = match query {
- Some(p) => p,
- None => ListAccountsQuery::default(),
- };
-
- match self.get_bulk(&query).await {
- Ok(mut listed) => {
- // Find the index.
- match listed.accounts.iter().position(|r| r.currency == id) {
- Some(index) => Ok(listed.accounts.swap_remove(index)),
- None => {
- // Prevent further requests if no more can be made.
- if !listed.has_next {
- return Err(CbAdvError::NotFound("no matching ids".to_string()));
- }
-
- // Make another request to the API for the account.
- query.cursor = Some(listed.cursor);
- self.get_by_id(id, Some(query)).await
- }
- }
+ /// * `query` - Parameters to control the query, such as limit.
+ pub async fn get_by_id(&mut self, id: &str, query: &AccountListQuery) -> CbResult {
+ is_auth!(self.agent, "get account by ID");
+
+ let mut query = query.clone().limit(LIST_ACCOUNT_MAXIMUM);
+
+ loop {
+ // Fetch accounts with the current query, propagating any errors.
+ let mut listed = self.get_bulk(&query).await?;
+
+ // Check if the desired account is in the current batch.
+ if let Some(index) = listed.accounts.iter().position(|r| r.currency == id) {
+ return Ok(listed.accounts.swap_remove(index));
+ }
+
+ // If no more pages to fetch, return a "not found" error with context.
+ if !listed.has_next {
+ return Err(CbError::NotFound(format!(
+ "No account found with ID '{}'.",
+ id
+ )));
}
- Err(error) => Err(error),
+
+ // Update the cursor for the next API call.
+ query.cursor = Some(listed.cursor);
}
}
/// Obtains all accounts available to the API Key. Use a larger limit in the query to decrease
- /// the amount of API calls. Recursively makes calls to obtain all accounts.
+ /// the amount of API calls. Iteratively makes calls to obtain all accounts.
///
/// NOTE: NOT A STANDARD API FUNCTION. QoL function that may require additional API requests than
/// normal.
///
/// # Arguments
///
- /// * `query` - Optional parameters, should default to None unless you want additional control.
- #[async_recursion]
- pub async fn get_all(&mut self, query: Option) -> CbResult> {
- let mut query = match query {
- Some(p) => p,
- None => ListAccountsQuery::default(),
- };
-
- // Obtain until there are not anymore accounts.
- match self.get_bulk(&query).await {
- Ok(mut listed) => {
- if listed.has_next {
- query.cursor = Some(listed.cursor);
- match self.get_all(Some(query)).await {
- Ok(mut accounts) => listed.accounts.append(&mut accounts),
- Err(error) => return Err(error),
- }
- }
- Ok(listed.accounts)
+ /// * `query` - Parameters to control the query, such as limit.
+ pub async fn get_all(&mut self, query: &AccountListQuery) -> CbResult> {
+ is_auth!(self.agent, "get all accounts");
+
+ let mut query = query.clone().limit(LIST_ACCOUNT_MAXIMUM);
+ let mut all_accounts = Vec::new();
+
+ loop {
+ // Fetch accounts with the current query, propagating any errors.
+ let mut listed = self.get_bulk(&query).await?;
+
+ // Append fetched accounts to the result list.
+ all_accounts.append(&mut listed.accounts);
+
+ // Check if there's more data to fetch.
+ if listed.has_next {
+ // Update the cursor for the next request.
+ query.cursor = Some(listed.cursor);
+ } else {
+ // No more data to fetch.
+ break;
}
- Err(error) => return Err(error),
}
+
+ Ok(all_accounts)
}
/// Obtains various accounts from the API.
@@ -137,13 +132,13 @@ impl AccountApi {
/// https://api.coinbase.com/api/v3/brokerage/accounts
///
///
- pub async fn get_bulk(&mut self, query: &ListAccountsQuery) -> CbResult {
- match self.signer.get(RESOURCE_ENDPOINT, query).await {
- Ok(value) => match value.json::().await {
- Ok(resp) => Ok(resp),
- Err(_) => Err(CbAdvError::BadParse("accounts vector".to_string())),
- },
- Err(error) => Err(error),
- }
+ pub async fn get_bulk(&mut self, query: &AccountListQuery) -> CbResult {
+ let agent = get_auth!(self.agent, "get bulk accounts");
+ let response = agent.get(RESOURCE_ENDPOINT, query).await?;
+ let data: PaginatedAccounts = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data)
}
}
diff --git a/src/apis/convert.rs b/src/apis/convert.rs
index b119d4c..ee79c4e 100644
--- a/src/apis/convert.rs
+++ b/src/apis/convert.rs
@@ -3,19 +3,17 @@
//! `convert` gives access to the Convert API and the various endpoints associated with it.
//! This allows for the conversion between two currencies.
-use crate::constants::convert::{QUOTE_ENDPOINT, RESOURCE_ENDPOINT};
-use crate::convert::{
- ConvertQuery, ConvertQuoteQuery, ConvertResponse, Trade, TradeIncentiveMetadata,
-};
-use crate::errors::CbAdvError;
-use crate::signer::Signer;
-use crate::traits::NoQuery;
+use crate::constants::convert::{QUOTE_ENDPOINT, TRADE_ENDPOINT};
+use crate::convert::{ConvertQuery, ConvertQuoteRequest, Trade, TradeWrapper};
+use crate::errors::CbError;
+use crate::http_agent::SecureHttpAgent;
+use crate::traits::{HttpAgent, NoQuery};
use crate::types::CbResult;
/// Provides access to the Convert API for the service.
pub struct ConvertApi {
/// Object used to sign requests made to the API.
- signer: Signer,
+ agent: Option,
}
impl ConvertApi {
@@ -23,10 +21,9 @@ impl ConvertApi {
///
/// # Arguments
///
- /// * `signer` - A Signer that include the API Key & Secret along with a client to make
- /// requests.
- pub(crate) fn new(signer: Signer) -> Self {
- Self { signer }
+ /// * `agent` - A agent that include the API Key & Secret along with a client to make requests.
+ pub(crate) fn new(agent: Option) -> Self {
+ Self { agent }
}
/// Create a convert quote with a specified source currency, target currency, and amount.
@@ -36,81 +33,71 @@ impl ConvertApi {
///
/// Trades are valid for 10 minutes after the quote is created.
///
- /// https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_createconvertquote
- pub async fn create_quote(
- &mut self,
- from_account: &str,
- to_account: &str,
- amount: f64,
- metadata: Option,
- ) -> CbResult {
- let query = ConvertQuoteQuery {
- from_account: from_account.to_string(),
- to_account: to_account.to_string(),
- amount: amount.to_string(),
- trade_incentive_metadata: metadata,
- };
-
- match self.signer.post(QUOTE_ENDPOINT, &NoQuery, &query).await {
- Ok(value) => match value.json::().await {
- Ok(resp) => Ok(resp.trade),
- Err(_) => Err(CbAdvError::BadParse(
- "convert quote response object".to_string(),
- )),
- },
- Err(error) => Err(error),
- }
+ /// # Arguments
+ ///
+ /// * `request` - The request to create a quote.
+ ///
+ /// # Endpoint / Reference
+ ///
+ #[allow(rustdoc::bare_urls)]
+ /// https://api.coinbase.com/api/v3/brokerage/convert/quote
+ ///
+ ///
+ pub async fn create_quote(&mut self, request: &ConvertQuoteRequest) -> CbResult {
+ let agent = get_auth!(self.agent, "create convert quote");
+ let response = agent.post(QUOTE_ENDPOINT, &NoQuery, request).await?;
+ let data = response
+ .json::()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data.into())
}
/// Gets a list of information about a convert trade with a specified trade ID, source currency, and target currency.
///
- /// https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_getconverttrade
- pub async fn get(
- &mut self,
- trade_id: &str,
- from_account: &str,
- to_account: &str,
- ) -> CbResult {
- let resource = format!("{}/trade/{}", RESOURCE_ENDPOINT, trade_id);
- let query = ConvertQuery {
- from_account: from_account.to_string(),
- to_account: to_account.to_string(),
- };
-
- match self.signer.get(&resource, &query).await {
- Ok(value) => match value.json::().await {
- Ok(resp) => Ok(resp.trade),
- Err(_) => Err(CbAdvError::BadParse(
- "get convert response object".to_string(),
- )),
- },
- Err(error) => Err(error),
- }
+ /// # Arguments
+ ///
+ /// * `trade_id` - The trade ID to get information about.
+ /// * `query` - The query to obtain the trade.
+ ///
+ /// # Endpoint / Reference
+ ///
+ #[allow(rustdoc::bare_urls)]
+ /// https://api.coinbase.com/api/v3/brokerage/convert/trade
+ ///
+ ///
+ pub async fn get(&mut self, trade_id: &str, query: &ConvertQuery) -> CbResult {
+ let agent = get_auth!(self.agent, "get convert trade");
+ let resource = format!("{}/{}", TRADE_ENDPOINT, trade_id);
+ let response = agent.get(&resource, query).await?;
+ let data: TradeWrapper = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data.into())
}
/// Commits a convert trade with a specified trade ID, source currency, and target currency.
///
- /// https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_commitconverttrade
- pub async fn commit(
- &mut self,
- trade_id: &str,
- from_account: &str,
- to_account: &str,
- ) -> CbResult {
- let resource = format!("{}/trade/{}", RESOURCE_ENDPOINT, trade_id);
- let query = ConvertQuery {
- from_account: from_account.to_string(),
- to_account: to_account.to_string(),
- };
-
- match self.signer.post(&resource, &NoQuery, &query).await {
- Ok(value) => match value.json::().await {
- Ok(resp) => Ok(resp.trade),
- Err(_) => Err(CbAdvError::BadParse(
- "convert commit response object".to_string(),
- )),
- },
- Err(error) => Err(error),
- }
+ /// # Arguments
+ ///
+ /// * `trade_id` - The trade ID to get information about.
+ /// * `query` - The query to commit the trade.
+ ///
+ /// # Endpoint / Reference
+ ///
+ #[allow(rustdoc::bare_urls)]
+ /// https://api.coinbase.com/api/v3/brokerage/convert/trade
+ ///
+ ///
+ pub async fn commit(&mut self, trade_id: &str, query: &ConvertQuery) -> CbResult {
+ let agent = get_auth!(self.agent, "commit convert quote");
+ let resource = format!("{}/{}", TRADE_ENDPOINT, trade_id);
+ let response = agent.post(&resource, &NoQuery, query).await?;
+ let data: TradeWrapper = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data.into())
}
}
diff --git a/src/apis/data.rs b/src/apis/data.rs
new file mode 100644
index 0000000..94549f2
--- /dev/null
+++ b/src/apis/data.rs
@@ -0,0 +1,45 @@
+//! # Coinbase Advanced Data API
+//!
+//! `data` gives access to the Data API and the various endpoints associated with it.
+
+use crate::constants::data::KEY_PERMISSIONS_ENDPOINT;
+use crate::errors::CbError;
+use crate::http_agent::SecureHttpAgent;
+use crate::models::data::KeyPermissions;
+use crate::traits::{HttpAgent, NoQuery};
+use crate::types::CbResult;
+
+/// Provides access to the Data API for the service.
+pub struct DataApi {
+ /// Object used to sign requests made to the API.
+ agent: Option,
+}
+
+impl DataApi {
+ /// Creates a new instance of the Data API. This grants access to various data information.
+ ///
+ /// # Arguments
+ ///
+ /// * `agent` - A agent that include the API Key & Secret along with a client to make requests.
+ pub(crate) fn new(agent: Option) -> Self {
+ Self { agent }
+ }
+
+ /// Get information about your CDP API key permissions.
+ ///
+ /// # Endpoint / Reference
+ ///
+ #[allow(rustdoc::bare_urls)]
+ /// https://api.coinbase.com/api/v3/brokerage/key_permissions
+ ///
+ ///
+ pub async fn key_permissions(&mut self) -> CbResult {
+ let agent = get_auth!(self.agent, "get key permissions");
+ let response = agent.get(KEY_PERMISSIONS_ENDPOINT, &NoQuery).await?;
+ let data: KeyPermissions = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data)
+ }
+}
diff --git a/src/apis/fee.rs b/src/apis/fee.rs
index 7dcccb9..6957deb 100644
--- a/src/apis/fee.rs
+++ b/src/apis/fee.rs
@@ -4,15 +4,16 @@
//! Currently the only endpoint available is the Transaction Summary endpoint.
use crate::constants::fees::RESOURCE_ENDPOINT;
-use crate::errors::CbAdvError;
-use crate::fee::{TransactionSummary, TransactionSummaryQuery};
-use crate::signer::Signer;
+use crate::errors::CbError;
+use crate::fee::{FeeTransactionSummaryQuery, TransactionSummary};
+use crate::http_agent::SecureHttpAgent;
+use crate::traits::HttpAgent;
use crate::types::CbResult;
/// Provides access to the Fee API for the service.
pub struct FeeApi {
/// Object used to sign requests made to the API.
- signer: Signer,
+ agent: Option,
}
impl FeeApi {
@@ -20,18 +21,16 @@ impl FeeApi {
///
/// # Arguments
///
- /// * `signer` - A Signer that include the API Key & Secret along with a client to make
- /// requests.
- pub(crate) fn new(signer: Signer) -> Self {
- Self { signer }
+ /// * `agent` - A agent that include the API Key & Secret along with a client to make requests.
+ pub(crate) fn new(agent: Option) -> Self {
+ Self { agent }
}
/// Obtains fee transaction summary from the API.
///
/// # Arguments
///
- /// * `query` - Optional paramaters used to modify the resulting scope of the
- /// summary.
+ /// * `query` - Paramaters used to modify the resulting scope of the summary.
///
/// # Endpoint / Reference
///
@@ -39,13 +38,16 @@ impl FeeApi {
/// https://api.coinbase.com/api/v3/brokerage/transaction_summary
///
///
- pub async fn get(&mut self, query: &TransactionSummaryQuery) -> CbResult {
- match self.signer.get(RESOURCE_ENDPOINT, query).await {
- Ok(value) => match value.json::().await {
- Ok(resp) => Ok(resp),
- Err(_) => Err(CbAdvError::BadParse("fee summary object".to_string())),
- },
- Err(error) => Err(error),
- }
+ pub async fn get(
+ &mut self,
+ query: &FeeTransactionSummaryQuery,
+ ) -> CbResult {
+ let agent = get_auth!(self.agent, "get fee transaction summary");
+ let response = agent.get(RESOURCE_ENDPOINT, query).await?;
+ let data: TransactionSummary = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data)
}
}
diff --git a/src/apis/mod.rs b/src/apis/mod.rs
index bfac1b1..d8b2b5a 100644
--- a/src/apis/mod.rs
+++ b/src/apis/mod.rs
@@ -1,13 +1,19 @@
mod account;
mod convert;
+mod data;
mod fee;
mod order;
+mod payment;
+mod portfolio;
mod product;
-mod util;
+mod public;
pub(crate) use account::AccountApi;
pub(crate) use convert::ConvertApi;
+pub(crate) use data::DataApi;
pub(crate) use fee::FeeApi;
pub(crate) use order::OrderApi;
+pub(crate) use payment::PaymentApi;
+pub(crate) use portfolio::PortfolioApi;
pub(crate) use product::ProductApi;
-pub(crate) use util::UtilApi;
+pub(crate) use public::PublicApi;
diff --git a/src/apis/order.rs b/src/apis/order.rs
index eef4346..03ea3ab 100644
--- a/src/apis/order.rs
+++ b/src/apis/order.rs
@@ -3,27 +3,25 @@
//! `order` gives access to the Order API and the various endpoints associated with it.
//! These allow you to obtain past created orders, create new orders, and cancel orders.
-use uuid::Uuid;
-
use crate::constants::orders::{
- BATCH_ENDPOINT, CANCEL_BATCH_ENDPOINT, EDIT_ENDPOINT, EDIT_PREVIEW_ENDPOINT, FILLS_ENDPOINT,
- RESOURCE_ENDPOINT,
+ BATCH_ENDPOINT, CANCEL_BATCH_ENDPOINT, CLOSE_POSITION_ENDPOINT, CREATE_PREVIEW_ENDPOINT,
+ EDIT_ENDPOINT, EDIT_PREVIEW_ENDPOINT, FILLS_ENDPOINT, RESOURCE_ENDPOINT,
};
-use crate::errors::CbAdvError;
+use crate::errors::CbError;
+use crate::http_agent::SecureHttpAgent;
use crate::order::{
- CancelOrders, CancelOrdersResponse, CreateOrder, EditOrder, EditOrderResponse, LimitGtc,
- LimitGtd, ListFillsQuery, ListOrdersQuery, ListedFills, ListedOrders, MarketIoc, Order,
- OrderConfiguration, OrderResponse, OrderStatus, OrderStatusResponse, PreviewEditOrderResponse,
- StopLimitGtc, StopLimitGtd,
+ Order, OrderCancelRequest, OrderCancelResponse, OrderCancelWrapper, OrderClosePositionRequest,
+ OrderCreatePreview, OrderCreateRequest, OrderCreateResponse, OrderEditPreview,
+ OrderEditRequest, OrderEditResponse, OrderListFillsQuery, OrderListQuery, OrderStatus,
+ OrderWrapper, PaginatedFills, PaginatedOrders,
};
-use crate::signer::Signer;
-use crate::traits::NoQuery;
+use crate::traits::{HttpAgent, NoQuery};
use crate::types::CbResult;
/// Provides access to the Order API for the service.
pub struct OrderApi {
/// Object used to sign requests made to the API.
- signer: Signer,
+ agent: Option,
}
impl OrderApi {
@@ -31,17 +29,16 @@ impl OrderApi {
///
/// # Arguments
///
- /// * `signer` - A Signer that include the API Key & Secret along with a client to make
- /// requests.
- pub(crate) fn new(signer: Signer) -> Self {
- Self { signer }
+ /// * `agent` - A agent that include the API Key & Secret along with a client to make requests.
+ pub(crate) fn new(agent: Option) -> Self {
+ Self { agent }
}
/// Cancel orders.
///
/// # Arguments
///
- /// * `order_ids` - A vector of strings that represents order IDs to cancel.
+ /// * `request` - A struct containing what orders to cancel.
///
/// # Endpoint / Reference
///
@@ -49,22 +46,17 @@ impl OrderApi {
/// https://api.coinbase.com/api/v3/brokerage/orders/batch_cancel
///
///
- pub async fn cancel(&mut self, order_ids: &[String]) -> CbResult> {
- let body = CancelOrders {
- order_ids: order_ids.to_vec(),
- };
-
- match self
- .signer
- .post(CANCEL_BATCH_ENDPOINT, &NoQuery, body)
+ pub async fn cancel(
+ &mut self,
+ request: &OrderCancelRequest,
+ ) -> CbResult> {
+ let agent = get_auth!(self.agent, "cancel orders");
+ let response = agent.post(CANCEL_BATCH_ENDPOINT, &NoQuery, request).await?;
+ let data: OrderCancelWrapper = response
+ .json()
.await
- {
- Ok(value) => match value.json::().await {
- Ok(resp) => Ok(resp.results),
- Err(_) => Err(CbAdvError::BadParse("cancel order object".to_string())),
- },
- Err(error) => Err(error),
- }
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data.into())
}
/// Cancel all OPEN orders for a specific product ID.
@@ -75,31 +67,33 @@ impl OrderApi {
/// # Arguments
///
/// * `product_id` - Product to cancel all OPEN orders for.
- pub async fn cancel_all(&mut self, product_id: &str) -> CbResult> {
- let query = ListOrdersQuery {
- product_id: Some(product_id.to_string()),
+ pub async fn cancel_all(&mut self, product_id: &str) -> CbResult> {
+ is_auth!(self.agent, "cancel all orders");
+
+ let query = OrderListQuery {
+ product_ids: Some(vec![product_id.to_string()]),
order_status: Some(vec![OrderStatus::Open]),
..Default::default()
};
- // Obtain all open orders.
- match self.get_all(product_id, Some(query)).await {
- Ok(orders) => {
- // Build list of orders to cancel.
- let order_ids: Vec = orders.iter().map(|o| o.order_id.clone()).collect();
+ // Obtain all open orders for the given product.
+ let open_orders = self.get_all(product_id, &query).await?;
- // Do nothing since no orders found.
- if order_ids.is_empty() {
- return Err(CbAdvError::NothingToDo(
- "no orders found to cancel".to_string(),
- ));
- }
+ // Collect the IDs of orders to cancel.
+ let request = OrderCancelRequest::new(
+ &open_orders
+ .iter()
+ .map(|order| order.order_id.clone())
+ .collect::>(),
+ );
- // Cancel the order list.
- self.cancel(&order_ids).await
- }
- Err(error) => Err(error),
+ // No orders to cancel.
+ if request.order_ids.is_empty() {
+ return Ok(vec![]);
}
+
+ // Cancel the orders and return the response.
+ self.cancel(&request).await
}
/// Edit an order with a specified new size, or new price. Only limit order types, with time
@@ -109,9 +103,7 @@ impl OrderApi {
///
/// # Arguments
///
- /// * `order_id` - ID of the order to edit.
- /// * `size` - New size of the order.
- /// * `price` - New price of the order.
+ /// * `request` - A struct containing the order ID, new size, and new price.
///
/// # Endpoint / Reference
///
@@ -119,278 +111,71 @@ impl OrderApi {
/// https://api.coinbase.com/api/v3/brokerage/orders/edit
///
/// https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_editorder
- pub async fn edit(
- &mut self,
- order_id: &str,
- size: f64,
- price: f64,
- ) -> CbResult {
- let body = EditOrder {
- order_id: order_id.to_string(),
- size: size.to_string(),
- price: price.to_string(),
- };
-
- match self.signer.post(EDIT_ENDPOINT, &NoQuery, body).await {
- Ok(value) => match value.json::().await {
- Ok(edits) => Ok(edits),
- Err(_) => Err(CbAdvError::BadParse(
- "could not parse edit order object".to_string(),
- )),
- },
- Err(error) => Err(error),
- }
- }
-
- /// Simulate an edit order request with a specified new size, or new price, to preview the result of an edit. Only
- /// limit order types, with time in force type of good-till-cancelled can be edited.
- ///
- /// # Arguments
- ///
- /// * `order_id` - ID of the order to edit.
- /// * `size` - New size of the order.
- /// * `price` - New price of the order.
- ///
- /// # Endpoint / Reference
- ///
- #[allow(rustdoc::bare_urls)]
- /// https://api.coinbase.com/api/v3/brokerage/orders/edit_preivew
- ///
- /// https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_previeweditorder
- pub async fn preview_edit(
- &mut self,
- order_id: &str,
- size: f64,
- price: f64,
- ) -> CbResult {
- let body = EditOrder {
- order_id: order_id.to_string(),
- size: size.to_string(),
- price: price.to_string(),
- };
-
- match self
- .signer
- .post(EDIT_PREVIEW_ENDPOINT, &NoQuery, body)
+ pub async fn edit(&mut self, request: &OrderEditRequest) -> CbResult {
+ let agent = get_auth!(self.agent, "edit order");
+ let response = agent.post(EDIT_ENDPOINT, &NoQuery, request).await?;
+ let data: OrderEditResponse = response
+ .json()
.await
- {
- Ok(value) => match value.json::().await {
- Ok(response) => Ok(response),
- Err(_) => Err(CbAdvError::BadParse(
- "could not parse preview edit order response".to_string(),
- )),
- },
- Err(error) => Err(error),
- }
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data)
}
- /// Create an order.
+ /// Preview creating an order.
///
/// # Arguments
///
- /// * `product_id` - A string that represents the product's ID.
- /// * `side` - A string that represents the side: BUY or SELL
- /// * `configuration` - A OrderConfiguration containing details on type of order.
+ /// * `request` - A struct containing the order details to preview.
///
/// # Endpoint / Reference
///
#[allow(rustdoc::bare_urls)]
- /// https://api.coinbase.com/api/v3/brokerage/orders
+ /// https://api.coinbase.com/api/v3/brokerage/orders/preview
///
- ///
- async fn create(
+ ///
+ pub async fn preview_create(
&mut self,
- product_id: &str,
- side: &str,
- configuration: OrderConfiguration,
- ) -> CbResult {
- let body = CreateOrder {
- client_order_id: Uuid::new_v4().to_string(),
- product_id: product_id.to_string(),
- side: side.to_string(),
- order_configuration: configuration,
- };
-
- match self.signer.post(RESOURCE_ENDPOINT, &NoQuery, body).await {
- Ok(value) => match value.json::().await {
- Ok(resp) => Ok(resp),
- Err(_) => Err(CbAdvError::BadParse("created order object".to_string())),
- },
- Err(error) => Err(error),
- }
- }
-
- /// Create a market order.
- ///
- /// # Arguments
- ///
- /// * `product_id` - A string that represents the product's ID.
- /// * `side` - A string that represents the side: BUY or SELL
- /// * `size` - A 64-bit float that represents the size to buy or sell.
- ///
- /// # Endpoint / Reference
- ///
- #[allow(rustdoc::bare_urls)]
- /// https://api.coinbase.com/api/v3/brokerage/orders
- ///
- ///
- pub async fn create_market(
- &mut self,
- product_id: &str,
- side: &str,
- size: &f64,
- ) -> CbResult {
- let market = if side == "BUY" {
- MarketIoc {
- quote_size: Some(size.to_string()),
- base_size: None,
- }
- } else {
- MarketIoc {
- quote_size: None,
- base_size: Some(size.to_string()),
- }
- };
-
- let config = OrderConfiguration {
- market_market_ioc: Some(market),
- ..Default::default()
- };
-
- self.create(product_id, side, config).await
- }
-
- /// Create a Good til Cancelled Limit order.
- ///
- /// # Arguments
- ///
- /// * `product_id` - A string that represents the product's ID.
- /// * `side` - A string that represents the side: BUY or SELL
- /// * `size` - A 64-bit float that represents the size to buy or sell.
- /// * `price` - A 64-bit float that represents the price to buy or sell.
- /// * `post_only` - A boolean that represents MAKER or TAKER.
- ///
- /// # Endpoint / Reference
- ///
- #[allow(rustdoc::bare_urls)]
- /// https://api.coinbase.com/api/v3/brokerage/orders
- ///
- ///
- pub async fn create_limit_gtc(
- &mut self,
- product_id: &str,
- side: &str,
- size: &f64,
- price: &f64,
- post_only: bool,
- ) -> CbResult {
- let limit = LimitGtc {
- base_size: size.to_string(),
- limit_price: price.to_string(),
- post_only,
- };
-
- let config = OrderConfiguration {
- limit_limit_gtc: Some(limit),
- ..Default::default()
- };
-
- self.create(product_id, side, config).await
- }
-
- /// Create a Good til Time (Date) Limit order.
- ///
- /// # Arguments
- ///
- /// * `product_id` - A string that represents the product's ID.
- /// * `side` - A string that represents the side: BUY or SELL
- /// * `size` - A 64-bit float that represents the size to buy or sell.
- /// * `price` - A 64-bit float that represents the price to buy or sell.
- /// * `end_time` - A string that represents the time to kill the order.
- /// * `post_only` - A boolean that represents MAKER or TAKER.
- ///
- /// # Endpoint / Reference
- ///
- #[allow(rustdoc::bare_urls)]
- /// https://api.coinbase.com/api/v3/brokerage/orders
- ///
- ///
- pub async fn create_limit_gtd(
- &mut self,
- product_id: &str,
- side: &str,
- size: &f64,
- price: &f64,
- end_time: &str,
- post_only: bool,
- ) -> CbResult {
- let limit = LimitGtd {
- base_size: size.to_string(),
- limit_price: price.to_string(),
- end_time: end_time.to_string(),
- post_only,
- };
-
- let config = OrderConfiguration {
- limit_limit_gtd: Some(limit),
- ..Default::default()
- };
-
- self.create(product_id, side, config).await
+ request: &OrderCreateRequest,
+ ) -> CbResult {
+ let agent = get_auth!(self.agent, "preview create order");
+ let response = agent
+ .post(CREATE_PREVIEW_ENDPOINT, &NoQuery, request)
+ .await?;
+ let data: OrderCreatePreview = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data)
}
- /// Create a Good til Cancelled Stop Limit order.
+ /// Simulate an edit order request with a specified new size, or new price, to preview the result of an edit. Only
+ /// limit order types, with time in force type of good-till-cancelled can be edited.
///
/// # Arguments
///
- /// * `product_id` - A string that represents the product's ID.
- /// * `side` - A string that represents the side: BUY or SELL
- /// * `size` - A 64-bit float that represents the size to buy or sell.
- /// * `limit_price` - Ceiling price for which the order should get filled.
- /// * `stop_price` - Price at which the order should trigger - if stop direction is Up, then the order will trigger when the last trade price goes above this, otherwise order will trigger when last trade price goes below this price.
- /// * `stop_direction` - Possible values: [UNKNOWN_STOP_DIRECTION, STOP_DIRECTION_STOP_UP, STOP_DIRECTION_STOP_DOWN]
+ /// * `request` - A struct containing the order ID, new size, and new price.
///
/// # Endpoint / Reference
///
#[allow(rustdoc::bare_urls)]
- /// https://api.coinbase.com/api/v3/brokerage/orders
+ /// https://api.coinbase.com/api/v3/brokerage/orders/edit_preivew
///
- ///
- pub async fn create_stop_limit_gtc(
- &mut self,
- product_id: &str,
- side: &str,
- size: &f64,
- limit_price: &f64,
- stop_price: &f64,
- stop_direction: &str,
- ) -> CbResult {
- let stoplimit = StopLimitGtc {
- base_size: size.to_string(),
- limit_price: limit_price.to_string(),
- stop_price: stop_price.to_string(),
- stop_direction: stop_direction.to_string(),
- };
-
- let config = OrderConfiguration {
- stop_limit_stop_limit_gtc: Some(stoplimit),
- ..Default::default()
- };
-
- self.create(product_id, side, config).await
+ /// https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_previeweditorder
+ pub async fn preview_edit(&mut self, request: &OrderEditRequest) -> CbResult {
+ let agent = get_auth!(self.agent, "preview edit order");
+ let response = agent.post(EDIT_PREVIEW_ENDPOINT, &NoQuery, request).await?;
+ let data: OrderEditPreview = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data)
}
- /// Create a Good til Time (Date) Stop Limit order.
+ /// Create an order.
///
/// # Arguments
///
- /// * `product_id` - A string that represents the product's ID.
- /// * `side` - A string that represents the side: BUY or SELL
- /// * `size` - A 64-bit float that represents the size to buy or sell.
- /// * `limit_price` - Ceiling price for which the order should get filled.
- /// * `stop_price` - Price at which the order should trigger - if stop direction is Up, then the order will trigger when the last trade price goes above this, otherwise order will trigger when last trade price goes below this price.
- /// * `stop_direction` - Possible values: [UNKNOWN_STOP_DIRECTION, STOP_DIRECTION_STOP_UP, STOP_DIRECTION_STOP_DOWN]
- /// * `end_time` - Time at which the order should be cancelled if it's not filled.
+ /// * `request` - A struct containing the order details to create.
///
/// # Endpoint / Reference
///
@@ -398,31 +183,14 @@ impl OrderApi {
/// https://api.coinbase.com/api/v3/brokerage/orders
///
///
- #[allow(clippy::too_many_arguments)]
- pub async fn create_stop_limit_gtd(
- &mut self,
- product_id: &str,
- side: &str,
- size: &f64,
- limit_price: &f64,
- stop_price: &f64,
- stop_direction: &str,
- end_time: &str,
- ) -> CbResult {
- let stoplimit = StopLimitGtd {
- base_size: size.to_string(),
- limit_price: limit_price.to_string(),
- stop_price: stop_price.to_string(),
- end_time: end_time.to_string(),
- stop_direction: stop_direction.to_string(),
- };
-
- let config = OrderConfiguration {
- stop_limit_stop_limit_gtd: Some(stoplimit),
- ..Default::default()
- };
-
- self.create(product_id, side, config).await
+ pub async fn create(&mut self, request: &OrderCreateRequest) -> CbResult {
+ let agent = get_auth!(self.agent, "create order");
+ let response = agent.post(RESOURCE_ENDPOINT, &NoQuery, request).await?;
+ let data: OrderCreateResponse = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data)
}
/// Obtains a single order based on the Order ID (ex. "XXXX-YYYY-ZZZZ").
@@ -438,20 +206,20 @@ impl OrderApi {
///
///
pub async fn get(&mut self, order_id: &str) -> CbResult {
+ let agent = get_auth!(self.agent, "get order");
let resource = format!("{}/historical/{}", RESOURCE_ENDPOINT, order_id);
- match self.signer.get(&resource, &NoQuery).await {
- Ok(value) => match value.json::().await {
- Ok(resp) => Ok(resp.order),
- Err(_) => Err(CbAdvError::BadParse(
- "could not parse order object".to_string(),
- )),
- },
- Err(error) => Err(error),
- }
+ let response = agent.get(&resource, &NoQuery).await?;
+ let data: OrderWrapper = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data.into())
}
/// Obtains various orders from the API.
///
+ /// # Arguments
+ ///
/// * `query` - A Parameters to modify what is returned by the API.
///
/// # Endpoint / Reference
@@ -460,61 +228,56 @@ impl OrderApi {
/// https://api.coinbase.com/api/v3/brokerage/orders/historical
///
///
- pub async fn get_bulk(&mut self, query: &ListOrdersQuery) -> CbResult {
- match self.signer.get(BATCH_ENDPOINT, query).await {
- Ok(value) => match value.json::().await {
- Ok(resp) => Ok(resp),
- Err(_) => Err(CbAdvError::BadParse(
- "could not parse orders vector".to_string(),
- )),
- },
- Err(error) => Err(error),
- }
+ pub async fn get_bulk(&mut self, query: &OrderListQuery) -> CbResult {
+ let agent = get_auth!(self.agent, "get bulk orders");
+ let response = agent.get(BATCH_ENDPOINT, query).await?;
+ let data: PaginatedOrders = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data)
}
/// Obtains all orders for a product based on the product ID. (ex. "BTC-USD").
/// This wraps `get_bulk` and makes several additional requests until there are no
/// additional orders.
///
- /// NOTE: NOT A STANDARD API FUNCTION. QoL function that may require additional API requests than
- /// normal.
+ /// NOTE: NOT A STANDARD API FUNCTION. QoL function that may require additional API requests than normal.
///
/// # Arguments
///
/// * `product_id` - Identifier for the account, such as BTC-USD or ETH-USD.
- /// * `query` - Optional parameters, should default to None unless you want additional control.
+ /// * `query` - A Parameters to modify what is returned by the API.
pub async fn get_all(
&mut self,
product_id: &str,
- query: Option,
+ query: &OrderListQuery,
) -> CbResult> {
- let mut query = match query {
- Some(p) => p,
- None => ListOrdersQuery::default(),
- };
+ is_auth!(self.agent, "get all orders");
+
+ // Set the product ID for the query.
+ let mut query = query.clone().product_ids(&[product_id.to_string()]);
+ let mut all_orders: Vec = vec![];
- // Override product ID.
- query.product_id = Some(product_id.to_string());
- let mut orders: Vec = vec![];
- let mut has_next: bool = true;
+ // Fetch orders until no more pages are available.
+ loop {
+ let listed_orders = self.get_bulk(&query).await?;
+ all_orders.extend(listed_orders.orders);
- // Get the orders until there is not a next.
- while has_next {
- match self.get_bulk(&query).await {
- Ok(listed) => {
- has_next = listed.has_next;
- query.cursor = Some(listed.cursor);
- orders.extend(listed.orders);
- }
- Err(error) => return Err(error),
+ if listed_orders.has_next {
+ query.cursor = Some(listed_orders.cursor);
+ } else {
+ break;
}
}
- Ok(orders)
+ Ok(all_orders)
}
/// Obtains fills from the API.
///
+ /// # Arguments
+ ///
/// * `query` - A Parameters to modify what is returned by the API.
///
/// # Endpoint / Reference
@@ -523,15 +286,40 @@ impl OrderApi {
/// https://api.coinbase.com/api/v3/brokerage/orders/historical/fills
///
///
- pub async fn fills(&mut self, query: &ListFillsQuery) -> CbResult {
- match self.signer.get(FILLS_ENDPOINT, query).await {
- Ok(value) => match value.json::().await {
- Ok(resp) => Ok(resp),
- Err(_) => Err(CbAdvError::BadParse(
- "could not parse fills vector".to_string(),
- )),
- },
- Err(error) => Err(error),
- }
+ pub async fn fills(&mut self, query: &OrderListFillsQuery) -> CbResult {
+ let agent = get_auth!(self.agent, "get fills");
+ let response = agent.get(FILLS_ENDPOINT, query).await?;
+ let data: PaginatedFills = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data)
+ }
+
+ /// Places an order to close any open positions for a specified product_id.
+ ///
+ /// # Arguments
+ ///
+ /// * `request` - A request as to what position to close.
+ ///
+ /// # Endpoint / Reference
+ ///
+ #[allow(rustdoc::bare_urls)]
+ /// https://api.coinbase.com/api/v3/brokerage/orders/close_position
+ ///
+ ///
+ pub async fn close_position(
+ &mut self,
+ request: &OrderClosePositionRequest,
+ ) -> CbResult {
+ let agent = get_auth!(self.agent, "close position");
+ let response = agent
+ .post(CLOSE_POSITION_ENDPOINT, &NoQuery, request)
+ .await?;
+ let data: OrderCreateResponse = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data)
}
}
diff --git a/src/apis/payment.rs b/src/apis/payment.rs
new file mode 100644
index 0000000..624341a
--- /dev/null
+++ b/src/apis/payment.rs
@@ -0,0 +1,68 @@
+//! # Coinbase Advanced Payment API
+//!
+//! `payment` gives access to the Payment API and the various endpoints associated with it.
+
+use crate::constants::payments::RESOURCE_ENDPOINT;
+use crate::errors::CbError;
+use crate::http_agent::SecureHttpAgent;
+use crate::models::payment::{PaymentMethod, PaymentMethodWrapper, PaymentMethodsWrapper};
+use crate::traits::{HttpAgent, NoQuery};
+use crate::types::CbResult;
+
+/// Provides access to the Payment API for the service.
+pub struct PaymentApi {
+ /// Object used to sign requests made to the API.
+ agent: Option,
+}
+
+impl PaymentApi {
+ /// Creates a new instance of the Payment API. This grants access to payment information.
+ ///
+ /// # Arguments
+ ///
+ /// * `agent` - A agent that include the API Key & Secret along with a client to make requests.
+ pub(crate) fn new(agent: Option) -> Self {
+ Self { agent }
+ }
+
+ /// Obtains a list of payment methods for the current user from the API.
+ ///
+ /// # Endpoint / Reference
+ ///
+ #[allow(rustdoc::bare_urls)]
+ /// https://api.coinbase.com/api/v3/brokerage/payment_methods
+ ///
+ ///
+ pub async fn get_all(&mut self) -> CbResult> {
+ let agent = get_auth!(self.agent, "get all payment methods");
+ let response = agent.get(RESOURCE_ENDPOINT, &NoQuery).await?;
+ let data: PaymentMethodsWrapper = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data.into())
+ }
+
+ /// Obtains a single payment method by its unique identifier.
+ ///
+ /// # Arguments
+ ///
+ /// * `payment_method_id` - The unique identifier for the payment method.
+ ///
+ /// # Endpoint / Reference
+ ///
+ #[allow(rustdoc::bare_urls)]
+ /// https://api.coinbase.com/api/v3/brokerage/payment_methods
+ ///
+ ///
+ pub async fn get(&mut self, payment_method_id: &str) -> CbResult {
+ let agent = get_auth!(self.agent, "get payment method");
+ let resource = format!("{}/{}", RESOURCE_ENDPOINT, payment_method_id);
+ let response = agent.get(&resource, &NoQuery).await?;
+ let data: PaymentMethodWrapper = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data.into())
+ }
+}
diff --git a/src/apis/portfolio.rs b/src/apis/portfolio.rs
new file mode 100644
index 0000000..ebe95b4
--- /dev/null
+++ b/src/apis/portfolio.rs
@@ -0,0 +1,169 @@
+//! # Coinbase Advanced Portfolio API
+//!
+//! `portfolio` gives access to the Portfolio API and the various endpoints associated with it.
+//! This allows for the management of individual portfolios.
+
+use crate::constants::portfolios::{MOVE_FUNDS_ENDPOINT, RESOURCE_ENDPOINT};
+use crate::errors::CbError;
+use crate::http_agent::SecureHttpAgent;
+use crate::models::portfolio::{Portfolio, PortfolioListQuery, PortfoliosWrapper};
+use crate::portfolio::{
+ PortfolioBreakdown, PortfolioBreakdownQuery, PortfolioBreakdownWrapper, PortfolioModifyRequest,
+ PortfolioMoveFundsRequest, PortfolioWrapper,
+};
+use crate::traits::{HttpAgent, NoQuery};
+use crate::types::CbResult;
+
+/// Provides access to the Portfolio API for the service.
+pub struct PortfolioApi {
+ /// Object used to sign requests made to the API.
+ agent: Option,
+}
+
+impl PortfolioApi {
+ /// Creates a new instance of the Portfolio API. This grants access to product information.
+ ///
+ /// # Arguments
+ ///
+ /// * `agent` - A agent that include the API Key & Secret along with a client to make requests.
+ pub(crate) fn new(agent: Option) -> Self {
+ Self { agent }
+ }
+
+ /// Obtains various portfolios from the API.
+ ///
+ /// # Arguments
+ ///
+ /// * `query` - The query parameters to filter the results.
+ ///
+ /// # Endpoint / Reference
+ ///
+ #[allow(rustdoc::bare_urls)]
+ /// https://api.coinbase.com/api/v3/brokerage/portfolios
+ ///
+ ///
+ pub async fn get_all(&mut self, query: &PortfolioListQuery) -> CbResult> {
+ let agent = get_auth!(self.agent, "get all portfolios");
+ let response = agent.get(RESOURCE_ENDPOINT, query).await?;
+ let data: PortfoliosWrapper = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data.into())
+ }
+
+ /// Creates a new portfolio.
+ ///
+ /// # Arguments
+ ///
+ /// * `request` - The request to create a new portfolio.
+ ///
+ /// # Endpoint / Reference
+ ///
+ #[allow(rustdoc::bare_urls)]
+ /// https://api.coinbase.com/api/v3/brokerage/portfolios
+ ///
+ ///
+ pub async fn create(&mut self, request: &PortfolioModifyRequest) -> CbResult {
+ let agent = get_auth!(self.agent, "create portfolio");
+ let response = agent.post(RESOURCE_ENDPOINT, &NoQuery, request).await?;
+ let data: PortfolioWrapper = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data.into())
+ }
+
+ /// Edits an existing portfolio.
+ ///
+ /// # Arguments
+ ///
+ /// * `portfolio_uuid` - The UUID of the portfolio to edit.
+ /// * `request` - The request to edit the portfolio.
+ ///
+ /// # Endpoint / Reference
+ ///
+ #[allow(rustdoc::bare_urls)]
+ /// https://api.coinbase.com/api/v3/brokerage/portfolios
+ ///
+ ///
+ pub async fn edit(
+ &mut self,
+ portfolio_uuid: &str,
+ request: &PortfolioModifyRequest,
+ ) -> CbResult {
+ let agent = get_auth!(self.agent, "edit portfolio");
+ let resource = format!("{}/{}", RESOURCE_ENDPOINT, portfolio_uuid);
+ let response = agent.put(&resource, &NoQuery, request).await?;
+ let data: PortfolioWrapper = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data.into())
+ }
+
+ /// Edits an existing portfolio.
+ ///
+ /// # Arguments
+ ///
+ /// * `portfolio_uuid` - The UUID of the portfolio to delete.
+ ///
+ /// # Endpoint / Reference
+ ///
+ #[allow(rustdoc::bare_urls)]
+ /// https://api.coinbase.com/api/v3/brokerage/portfolios
+ ///
+ ///
+ pub async fn delete(&mut self, portfolio_uuid: &str) -> CbResult<()> {
+ let agent = get_auth!(self.agent, "delete portfolio");
+ let resource = format!("{}/{}", RESOURCE_ENDPOINT, portfolio_uuid);
+ agent.delete(&resource, &NoQuery).await?;
+ Ok(())
+ }
+
+ /// Move funds from a source portfolio to a target portfolio.
+ ///
+ /// # Arguments
+ ///
+ /// * `request` - The request to move funds.
+ ///
+ /// # Endpoint / Reference
+ ///
+ #[allow(rustdoc::bare_urls)]
+ /// https://api.coinbase.com/api/v3/brokerage/portfolios/move_funds
+ ///
+ ///
+ pub async fn move_funds(&mut self, request: &PortfolioMoveFundsRequest) -> CbResult<()> {
+ let agent = get_auth!(self.agent, "move funds");
+ agent.post(MOVE_FUNDS_ENDPOINT, &NoQuery, request).await?;
+ Ok(())
+ }
+
+ /// Obtains a breakdown of a specific portfolio.
+ ///
+ /// # Arguments
+ ///
+ /// * `portfolio_uuid` - The UUID of the portfolio to obtain a breakdown for.
+ /// * `query` - The query parameters to filter the results.
+ ///
+ /// # Endpoint / Reference
+ ///
+ #[allow(rustdoc::bare_urls)]
+ /// https://api.coinbase.com/api/v3/brokerage/portfolios
+ ///
+ ///
+ pub async fn get(
+ &mut self,
+ portfolio_uuid: &str,
+ query: &PortfolioBreakdownQuery,
+ ) -> CbResult {
+ let agent = get_auth!(self.agent, "get portfolio breakdown");
+ let resource = format!("{}/{}", RESOURCE_ENDPOINT, portfolio_uuid);
+ let response = agent.get(&resource, query).await?;
+ let data: PortfolioBreakdownWrapper = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data.into())
+ }
+}
diff --git a/src/apis/product.rs b/src/apis/product.rs
index 9d6bd14..82d01fd 100644
--- a/src/apis/product.rs
+++ b/src/apis/product.rs
@@ -7,21 +7,21 @@
use crate::constants::products::{
BID_ASK_ENDPOINT, CANDLE_MAXIMUM, PRODUCT_BOOK_ENDPOINT, RESOURCE_ENDPOINT,
};
-use crate::errors::CbAdvError;
+use crate::errors::CbError;
+use crate::http_agent::SecureHttpAgent;
use crate::models::product::{
- BidAskResponse, Candle, CandleResponse, ListProductsQuery, ListProductsResponse, Product,
- ProductBook, ProductBookResponse, Ticker, TickerQuery,
+ Candle, CandlesWrapper, Product, ProductBook, ProductBookWrapper, ProductBooksWrapper,
+ ProductListQuery, ProductTickerQuery, ProductsWrapper, Ticker,
};
-use crate::product::{BidAskQuery, ProductBookQuery};
-use crate::signer::Signer;
-use crate::time;
-use crate::traits::NoQuery;
+use crate::product::{ProductBidAskQuery, ProductBookQuery, ProductCandleQuery};
+use crate::time::{self, Granularity};
+use crate::traits::{HttpAgent, NoQuery, Query};
use crate::types::CbResult;
/// Provides access to the Product API for the service.
pub struct ProductApi {
/// Object used to sign requests made to the API.
- signer: Signer,
+ agent: Option,
}
impl ProductApi {
@@ -29,18 +29,16 @@ impl ProductApi {
///
/// # Arguments
///
- /// * `signer` - A Signer that include the API Key & Secret along with a client to make
- /// requests.
- pub(crate) fn new(signer: Signer) -> Self {
- Self { signer }
+ /// * `agent` - A agent that include the API Key & Secret along with a client to make requests.
+ pub(crate) fn new(agent: Option) -> Self {
+ Self { agent }
}
/// Obtains best bids and asks for a vector of product IDs..
///
/// # Arguments
///
- /// * `product_ids` - A vector of strings the represents the product IDs of product books to
- /// obtain.
+ /// * `query` - A query to obtain the best bid/ask for multiple products.
///
/// # Endpoint / Reference
///
@@ -48,24 +46,21 @@ impl ProductApi {
/// https://api.coinbase.com/api/v3/brokerage/best_bid_ask
///
///
- pub async fn best_bid_ask(&mut self, product_ids: Vec) -> CbResult> {
- let query = BidAskQuery { product_ids };
-
- match self.signer.get(BID_ASK_ENDPOINT, &query).await {
- Ok(value) => match value.json::().await {
- Ok(bidasks) => Ok(bidasks.pricebooks),
- Err(_) => Err(CbAdvError::BadParse("bid asks object".to_string())),
- },
- Err(error) => Err(error),
- }
+ pub async fn best_bid_ask(&mut self, query: &ProductBidAskQuery) -> CbResult> {
+ let agent = get_auth!(self.agent, "get best bid/ask");
+ let response = agent.get(BID_ASK_ENDPOINT, query).await?;
+ let data: ProductBooksWrapper = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data.into())
}
/// Obtains the product book (bids and asks) for the product ID provided.
///
/// # Arguments
///
- /// * `product_id` - A string the represents the product's ID.
- /// * `limit` - An integer the represents the amount to obtain, defaults to 250.
+ /// * `query` - A query to obtain the product book.
///
/// # Endpoint / Reference
///
@@ -73,23 +68,14 @@ impl ProductApi {
/// https://api.coinbase.com/api/v3/brokerage/product_book
///
///
- pub async fn product_book(
- &mut self,
- product_id: &str,
- limit: Option,
- ) -> CbResult {
- let query = ProductBookQuery {
- product_id: product_id.to_string(),
- limit,
- };
-
- match self.signer.get(PRODUCT_BOOK_ENDPOINT, &query).await {
- Ok(value) => match value.json::().await {
- Ok(book) => Ok(book.pricebook),
- Err(_) => Err(CbAdvError::BadParse("product book object".to_string())),
- },
- Err(error) => Err(error),
- }
+ pub async fn product_book(&mut self, query: &ProductBookQuery) -> CbResult {
+ let agent = get_auth!(self.agent, "get product book");
+ let response = agent.get(PRODUCT_BOOK_ENDPOINT, query).await?;
+ let data: ProductBookWrapper = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data.into())
}
/// Obtains a single product based on the Product ID (ex. "BTC-USD").
@@ -105,14 +91,14 @@ impl ProductApi {
///
///
pub async fn get(&mut self, product_id: &str) -> CbResult {
+ let agent = get_auth!(self.agent, "get product");
let resource = format!("{}/{}", RESOURCE_ENDPOINT, product_id);
- match self.signer.get(&resource, &NoQuery).await {
- Ok(value) => match value.json::().await {
- Ok(product) => Ok(product),
- Err(_) => Err(CbAdvError::BadParse("product object".to_string())),
- },
- Err(error) => Err(error),
- }
+ let response = agent.get(&resource, &NoQuery).await?;
+ let data: Product = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data)
}
/// Obtains bulk products from the API.
@@ -127,14 +113,14 @@ impl ProductApi {
/// https://api.coinbase.com/api/v3/brokerage/products
///
///
- pub async fn get_bulk(&mut self, query: &ListProductsQuery) -> CbResult> {
- match self.signer.get(RESOURCE_ENDPOINT, query).await {
- Ok(value) => match value.json::().await {
- Ok(resp) => Ok(resp.products),
- Err(_) => Err(CbAdvError::BadParse("products vector".to_string())),
- },
- Err(error) => Err(error),
- }
+ pub async fn get_bulk(&mut self, query: &ProductListQuery) -> CbResult> {
+ let agent = get_auth!(self.agent, "get bulk products");
+ let response = agent.get(RESOURCE_ENDPOINT, query).await?;
+ let data: ProductsWrapper = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data.into())
}
/// Obtains candles for a specific product.
@@ -142,7 +128,7 @@ impl ProductApi {
/// # Arguments
///
/// * `product_id` - A string the represents the product's ID.
- /// * `query` - Span of time to obtain.
+ /// * `query` - A query to obtain candles within a span of time.
///
/// # Endpoint / Reference
///
@@ -150,15 +136,19 @@ impl ProductApi {
/// https://api.coinbase.com/api/v3/brokerage/products/{product_id}/candles
///
///
- pub async fn candles(&mut self, product_id: &str, query: &time::Span) -> CbResult> {
+ pub async fn candles(
+ &mut self,
+ product_id: &str,
+ query: &ProductCandleQuery,
+ ) -> CbResult> {
+ let agent = get_auth!(self.agent, "get candles");
let resource = format!("{}/{}/candles", RESOURCE_ENDPOINT, product_id);
- match self.signer.get(&resource, query).await {
- Ok(value) => match value.json::().await {
- Ok(resp) => Ok(resp.candles),
- Err(_) => Err(CbAdvError::BadParse("candle object".to_string())),
- },
- Err(error) => Err(error),
- }
+ let response = agent.get(&resource, query).await?;
+ let data: CandlesWrapper = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data.into())
}
/// Obtains candles for a specific product extended. This will exceed the 300 limit threshold
@@ -174,47 +164,44 @@ impl ProductApi {
pub async fn candles_ext(
&mut self,
product_id: &str,
- query: &time::Span,
+ query: &ProductCandleQuery,
) -> CbResult> {
- let resource = format!("{}/{}/candles", RESOURCE_ENDPOINT, product_id);
-
- // Make a copy of the query.
- let end = query.end;
- let interval = query.granularity as u64;
- let maximum = CANDLE_MAXIMUM;
- let granularity = &time::Granularity::from_secs(query.granularity);
-
- // Create new span.
- let mut span = time::Span::new(query.start, end, granularity);
- span.end = time::after(query.start, interval * maximum);
- if span.end > end {
- span.end = end;
+ is_auth!(self.agent, "get candles extended");
+ query.check()?;
+
+ // Extract query parameters.
+ let end_time = query.end;
+ let granularity = query.granularity.clone();
+ let interval_seconds = Granularity::to_secs(&granularity) as u64;
+ let maximum_candles = CANDLE_MAXIMUM as u64;
+
+ // Initialize the span.
+ let mut current_start = query.start;
+ let mut all_candles: Vec = Vec::new();
+
+ while current_start < end_time {
+ // Calculate the end time for the current batch.
+ let current_end = std::cmp::min(
+ time::after(current_start, interval_seconds * maximum_candles),
+ end_time,
+ );
+
+ // Create a new span for the current batch and fetch candles.
+ let query = ProductCandleQuery {
+ start: current_start,
+ end: current_end,
+ granularity: granularity.clone(),
+ limit: CANDLE_MAXIMUM,
+ };
+
+ let mut candles = self.candles(product_id, &query).await?;
+ all_candles.append(&mut candles);
+
+ // Update the start time for the next batch.
+ current_start = current_end;
}
- let mut candles: Vec = vec![];
- while span.count() > 0 {
- match self.signer.get(&resource, &span).await {
- Ok(value) => match value.json::().await {
- Ok(resp) => candles.extend(resp.candles),
- Err(_) => return Err(CbAdvError::BadParse("candle object".to_string())),
- },
- Err(error) => return Err(error),
- }
-
- // Update to get additional candles.
- span.start = span.end;
- span.end = time::after(span.start, interval * CANDLE_MAXIMUM);
- if span.end > end {
- span.end = end;
- }
-
- // Stop condition.
- if span.start > span.end {
- span.start = span.end;
- }
- }
-
- Ok(candles)
+ Ok(all_candles)
}
/// Obtains product ticker from the API.
@@ -230,14 +217,18 @@ impl ProductApi {
/// https://api.coinbase.com/api/v3/brokerage/products/{product_id}/ticker
///
///
- pub async fn ticker(&mut self, product_id: &str, query: &TickerQuery) -> CbResult {
+ pub async fn ticker(
+ &mut self,
+ product_id: &str,
+ query: &ProductTickerQuery,
+ ) -> CbResult {
+ let agent = get_auth!(self.agent, "get ticker");
let resource = format!("{}/{}/ticker", RESOURCE_ENDPOINT, product_id);
- match self.signer.get(&resource, query).await {
- Ok(value) => match value.json::().await {
- Ok(resp) => Ok(resp),
- Err(_) => Err(CbAdvError::BadParse("ticker object".to_string())),
- },
- Err(error) => Err(error),
- }
+ let response = agent.get(&resource, query).await?;
+ let data: Ticker = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data)
}
}
diff --git a/src/apis/public.rs b/src/apis/public.rs
new file mode 100644
index 0000000..6b274d8
--- /dev/null
+++ b/src/apis/public.rs
@@ -0,0 +1,222 @@
+//! # Coinbase Advanced Public API
+//!
+//! `public` gives access to the Public API and the various endpoints associated with it.
+//! Some of the features include getting the API current time in ISO format.
+
+use crate::constants::products::CANDLE_MAXIMUM;
+use crate::constants::public::{PRODUCT_BOOK_ENDPOINT, RESOURCE_ENDPOINT, SERVERTIME_ENDPOINT};
+use crate::errors::CbError;
+use crate::http_agent::PublicHttpAgent;
+use crate::models::product::{
+ Candle, CandlesWrapper, Product, ProductBook, ProductBookWrapper, ProductListQuery,
+ ProductTickerQuery, ProductsWrapper, Ticker,
+};
+use crate::models::public::ServerTime;
+use crate::product::{ProductBookQuery, ProductCandleQuery};
+use crate::time::{self, Granularity};
+use crate::traits::{HttpAgent, NoQuery, Query};
+use crate::types::CbResult;
+
+/// Provides access to the Public API for the service.
+pub struct PublicApi {
+ /// Object used to sign requests made to the API.
+ agent: PublicHttpAgent,
+}
+
+impl PublicApi {
+ /// Creates a new instance of the Public API. This grants access to public information that requires no authentication.
+ ///
+ /// # Arguments
+ ///
+ /// * `agent` - A agent that include the API Key & Secret along with a client to make requests.
+ pub(crate) fn new(agent: PublicHttpAgent) -> Self {
+ Self { agent }
+ }
+
+ /// Get the current time from the Coinbase Advanced API.
+ ///
+ /// # Endpoint / Reference
+ ///
+ #[allow(rustdoc::bare_urls)]
+ /// https://api.coinbase.com/api/v3/brokerage/time
+ ///
+ ///
+ pub async fn time(&mut self) -> CbResult {
+ let response = self.agent.get(SERVERTIME_ENDPOINT, &NoQuery).await?;
+ let data: ServerTime = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data)
+ }
+
+ /// Obtains the product book (bids and asks) for the product ID provided.
+ ///
+ /// # Arguments
+ ///
+ /// * `query` - Query used to obtain the product book.
+ ///
+ /// # Endpoint / Reference
+ ///
+ #[allow(rustdoc::bare_urls)]
+ /// https://api.coinbase.com/api/v3/brokerage/market/product_book
+ ///
+ ///
+ pub async fn product_book(&mut self, query: &ProductBookQuery) -> CbResult {
+ let response = self.agent.get(PRODUCT_BOOK_ENDPOINT, query).await?;
+ let data: ProductBookWrapper = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data.into())
+ }
+
+ /// Obtains a single product based on the Product ID (ex. "BTC-USD").
+ ///
+ /// # Arguments
+ ///
+ /// * `product_id` - A string the represents the product's ID.
+ ///
+ /// # Endpoint / Reference
+ ///
+ #[allow(rustdoc::bare_urls)]
+ /// https://api.coinbase.com/api/v3/brokerage/products/{product_id}
+ ///
+ ///
+ pub async fn product(&mut self, product_id: &str) -> CbResult {
+ let resource = format!("{}/{}", RESOURCE_ENDPOINT, product_id);
+ let response = self.agent.get(&resource, &NoQuery).await?;
+ let data: Product = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data)
+ }
+
+ /// Obtains bulk products from the API.
+ ///
+ /// # Arguments
+ ///
+ /// * `query` - Query used to obtain products.
+ ///
+ /// # Endpoint / Reference
+ ///
+ #[allow(rustdoc::bare_urls)]
+ /// https://api.coinbase.com/api/v3/brokerage/products
+ ///
+ ///
+ pub async fn products(&mut self, query: &ProductListQuery) -> CbResult> {
+ let response = self.agent.get(RESOURCE_ENDPOINT, query).await?;
+ let data: ProductsWrapper = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data.into())
+ }
+
+ /// Obtains candles for a specific product.
+ ///
+ /// # Arguments
+ ///
+ /// * `product_id` - A string the represents the product's ID.
+ /// * `query` - Span of time to obtain.
+ ///
+ /// # Endpoint / Reference
+ ///
+ #[allow(rustdoc::bare_urls)]
+ /// https://api.coinbase.com/api/v3/brokerage/products/{product_id}/candles
+ ///
+ ///
+ pub async fn candles(
+ &mut self,
+ product_id: &str,
+ query: &ProductCandleQuery,
+ ) -> CbResult> {
+ let resource = format!("{}/{}/candles", RESOURCE_ENDPOINT, product_id);
+ let response = self.agent.get(&resource, query).await?;
+ let data: CandlesWrapper = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data.into())
+ }
+
+ /// Obtains candles for a specific product extended. This will exceed the 300 limit threshold
+ /// and try to obtain the amount specified.
+ ///
+ /// NOTE: NOT A STANDARD API FUNCTION. QoL function that may require additional API requests than
+ /// normal.
+ ///
+ /// # Arguments
+ ///
+ /// * `product_id` - A string the represents the product's ID.
+ /// * `query` - Span of time to obtain.
+ pub async fn candles_ext(
+ &mut self,
+ product_id: &str,
+ query: &ProductCandleQuery,
+ ) -> CbResult> {
+ query.check()?;
+
+ // Extract query parameters.
+ let end_time = query.end;
+ let granularity = query.granularity.clone();
+ let interval_seconds = Granularity::to_secs(&granularity) as u64;
+ let maximum_candles = CANDLE_MAXIMUM as u64;
+
+ // Initialize the span.
+ let mut current_start = query.start;
+ let mut all_candles: Vec = Vec::new();
+
+ while current_start < end_time {
+ // Calculate the end time for the current batch.
+ let current_end = std::cmp::min(
+ time::after(current_start, interval_seconds * maximum_candles),
+ end_time,
+ );
+
+ // Create a new span for the current batch and fetch candles.
+ let query = ProductCandleQuery {
+ start: current_start,
+ end: current_end,
+ granularity: granularity.clone(),
+ limit: CANDLE_MAXIMUM,
+ };
+
+ let mut candles = self.candles(product_id, &query).await?;
+ all_candles.append(&mut candles);
+
+ // Update the start time for the next batch.
+ current_start = current_end;
+ }
+
+ Ok(all_candles)
+ }
+
+ /// Obtains product ticker from the API.
+ ///
+ /// # Arguments
+ ///
+ /// * `product_id` - A string the represents the product's ID.
+ /// * `query` - Amount of products to get.
+ ///
+ /// # Endpoint / Reference
+ ///
+ #[allow(rustdoc::bare_urls)]
+ /// https://api.coinbase.com/api/v3/brokerage/products/{product_id}/ticker
+ ///
+ ///
+ pub async fn ticker(
+ &mut self,
+ product_id: &str,
+ query: &ProductTickerQuery,
+ ) -> CbResult {
+ let resource = format!("{}/{}/ticker", RESOURCE_ENDPOINT, product_id);
+ let response = self.agent.get(&resource, query).await?;
+ let data: Ticker = response
+ .json()
+ .await
+ .map_err(|e| CbError::JsonError(e.to_string()))?;
+ Ok(data)
+ }
+}
diff --git a/src/apis/util.rs b/src/apis/util.rs
deleted file mode 100644
index e4f44a6..0000000
--- a/src/apis/util.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-//! # Coinbase Advanced Utils API
-//!
-//! `util` gives access to the Utils API and the various endpoints associated with it.
-//! Some of the features include getting the API current time in ISO format.
-
-use crate::constants::utils::UNIXTIME_ENDPOINT;
-use crate::errors::CbAdvError;
-use crate::signer::Signer;
-use crate::traits::NoQuery;
-use crate::types::CbResult;
-use crate::util::UnixTime;
-
-/// Provides access to the Utils API for the service.
-pub struct UtilApi {
- /// Object used to sign requests made to the API.
- signer: Signer,
-}
-
-impl UtilApi {
- /// Creates a new instance of the Utils API. This grants access to product information.
- ///
- /// # Arguments
- ///
- /// * `signer` - A Signer that include the API Key & Secret along with a client to make
- /// requests.
- pub(crate) fn new(signer: Signer) -> Self {
- Self { signer }
- }
-
- /// Get the current time from the Coinbase Advanced API.
- ///
- /// # Endpoint / Reference
- ///
- #[allow(rustdoc::bare_urls)]
- /// https://api.coinbase.com/api/v3/brokerage/time
- ///
- /// https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_getunixtime
- pub async fn unixtime(&mut self) -> CbResult {
- match self.signer.get(UNIXTIME_ENDPOINT, &NoQuery).await {
- Ok(value) => match value.json::().await {
- Ok(resp) => Ok(resp),
- Err(_) => Err(CbAdvError::BadParse("util unixtime object".to_string())),
- },
- Err(error) => Err(error),
- }
- }
-}
diff --git a/src/candle_watcher.rs b/src/candle_watcher.rs
new file mode 100644
index 0000000..16c37a4
--- /dev/null
+++ b/src/candle_watcher.rs
@@ -0,0 +1,164 @@
+//! Candle Watcher is the underlying object used to track candle updates.
+
+use std::cmp::Ord;
+use std::collections::HashMap;
+
+use chrono::Utc;
+
+use crate::constants::websocket::GRANULARITY;
+use crate::models::product::Candle;
+use crate::models::websocket::{CandleUpdate, Channel, Event, Message};
+use crate::traits::{CandleCallback, MessageCallback};
+use crate::types::CbResult;
+use crate::ws::Endpoint;
+use crate::WebSocketClient;
+
+/// Tracks the candle watcher task.
+pub(crate) struct CandleWatcher
+where
+ T: CandleCallback,
+{
+ /// Holds the most recent candle processed for each product. [key: Product Id, value: Candle]
+ candles: HashMap,
+ /// User-defined object that implements `CandleCallback`, triggered on completed candles.
+ user_watcher: T,
+}
+
+impl CandleWatcher
+where
+ T: CandleCallback,
+{
+ /// Starts the task that tracks candles for completion.
+ ///
+ /// # Arguments
+ ///
+ /// * `reader` - WebSocket reader to receive updates.
+ /// * `user_obj` - User object that implements `CandleCallback` to receive completed candles.
+ pub(crate) async fn start(mut client: WebSocketClient, endpoint: Endpoint, user_obj: T)
+ where
+ T: CandleCallback + Send + Sync + 'static,
+ {
+ let tracker = Self {
+ candles: HashMap::new(),
+ user_watcher: user_obj,
+ };
+
+ // Start the listener.
+ client.listen_trait(endpoint, tracker).await;
+ }
+
+ /// Returns a completed candle if a newer candle is received.
+ ///
+ /// # Arguments
+ ///
+ /// * `product_id` - The ID of the product this candle belongs to.
+ /// * `new_candle` - The new candle update received from the WebSocket.
+ fn check_candle(&mut self, product_id: &str, new_candle: Candle) -> Option {
+ // Retrieve the current candle for the product.
+ match self.candles.get(product_id) {
+ Some(existing_candle) => {
+ if existing_candle.start < new_candle.start {
+ // A newer candle has been received; replace the existing candle.
+ let completed_candle = self.candles.remove(product_id).unwrap();
+ self.candles.insert(product_id.to_string(), new_candle);
+ Some(completed_candle) // Return the completed candle.
+ } else {
+ // Update the existing candle without considering it complete.
+ self.candles.insert(product_id.to_string(), new_candle);
+ None
+ }
+ }
+ None => {
+ // No existing candle; add the new candle as the initial one.
+ self.candles.insert(product_id.to_string(), new_candle);
+ None
+ }
+ }
+ }
+
+ /// Extracts candle updates from a WebSocket message.
+ ///
+ /// # Arguments
+ ///
+ /// * `message` - The WebSocket message to extract updates from.
+ ///
+ /// # Returns
+ ///
+ /// A vector of `CandleUpdate` sorted by timestamp (newest first).
+ fn extract_candle_updates(&self, message: &Message) -> Vec {
+ let mut updates: Vec = message
+ .events
+ .iter()
+ .filter_map(|event| {
+ if let Event::Candles(candles_event) = event {
+ Some(candles_event.candles.clone())
+ } else {
+ None
+ }
+ })
+ .flatten()
+ .collect();
+
+ // Sort updates by timestamp (newest first).
+ updates.sort_by(|a, b| b.data.start.cmp(&a.data.start));
+ updates
+ }
+
+ /// Processes a vector of candle updates.
+ ///
+ /// # Arguments
+ ///
+ /// * `updates` - The sorted vector of `CandleUpdate` to process.
+ fn process_candle_updates(&mut self, mut updates: Vec) {
+ if let Some(update) = updates.pop() {
+ let product_id = update.product_id.clone();
+ let new_candle = update.data;
+
+ if let Some(completed_candle) = self.check_candle(&product_id, new_candle) {
+ self.trigger_user_callback(product_id, completed_candle);
+ }
+ }
+ }
+
+ /// Triggers the user's callback with a completed candle.
+ ///
+ /// # Arguments
+ ///
+ /// * `product_id` - The ID of the product associated with the candle.
+ /// * `completed_candle` - The completed candle to send to the callback.
+ fn trigger_user_callback(&mut self, product_id: String, completed_candle: Candle) {
+ let now = Utc::now().timestamp() as u64;
+ let start_time = now - (now % (GRANULARITY * 2));
+
+ self.user_watcher
+ .candle_callback(start_time, product_id, completed_candle);
+ }
+}
+
+impl MessageCallback for CandleWatcher
+where
+ T: CandleCallback + Send + Sync,
+{
+ /// Handles incoming messages and processes candle updates.
+ fn message_callback(&mut self, msg: CbResult) {
+ match msg {
+ Ok(message) => {
+ if message.channel != Channel::Candles {
+ return; // Ignore non-candle messages.
+ }
+
+ // Extract candle updates and process them.
+ let updates = self.extract_candle_updates(&message);
+ if updates.is_empty() {
+ return; // No updates to process.
+ }
+
+ // Process the most recent update and handle completed candles.
+ self.process_candle_updates(updates);
+ }
+ Err(err) => {
+ eprintln!("!WEBSOCKET ERROR! {}", err);
+ }
+ }
+ }
+}
diff --git a/src/config.rs b/src/config.rs
index 9d8e4c6..b6d7257 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -7,7 +7,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::fs;
use toml;
-const CURRENT_CONFIG_VERSION: u8 = 1;
+const CURRENT_CONFIG_VERSION: u8 = 2;
/// Generic configuration file with the minimum requirements for API access.
/// This is used to implement on custom configurations and to be passed when
@@ -29,6 +29,8 @@ pub struct ApiConfig {
pub api_secret: String,
/// Enable debug messages or not.
pub debug: bool,
+ /// Use sandbox or not.
+ pub use_sandbox: bool,
}
impl ApiConfig {
@@ -45,6 +47,7 @@ impl Default for ApiConfig {
api_key: "YOUR_COINBASE_API_KEY_HERE".to_string(),
api_secret: "YOUR_COINBASE_API_SECRET_HERE".to_string(),
debug: false,
+ use_sandbox: false,
}
}
}
diff --git a/src/constants.rs b/src/constants.rs
index bd0ffcc..bdadbc2 100644
--- a/src/constants.rs
+++ b/src/constants.rs
@@ -2,16 +2,19 @@
/// Root resource for the API
pub(crate) const API_ROOT_URI: &str = "api.coinbase.com";
+pub(crate) const API_SANDBOX_ROOT_URI: &str = "api-sandbox.coinbase.com";
+pub(crate) const CRATE_USER_AGENT: &str = "cbadv/Rust";
/// Accounts API constants
pub(crate) mod accounts {
pub(crate) const RESOURCE_ENDPOINT: &str = "/api/v3/brokerage/accounts";
+ pub(crate) const LIST_ACCOUNT_MAXIMUM: u32 = 250;
}
/// Convert API constants
pub(crate) mod convert {
- pub(crate) const RESOURCE_ENDPOINT: &str = "/api/v3/brokerage/convert";
pub(crate) const QUOTE_ENDPOINT: &str = "/api/v3/brokerage/convert/quote";
+ pub(crate) const TRADE_ENDPOINT: &str = "/api/v3/brokerage/convert/trade";
}
/// Fees API constants
@@ -24,41 +27,58 @@ pub(crate) mod orders {
pub(crate) const RESOURCE_ENDPOINT: &str = "/api/v3/brokerage/orders";
pub(crate) const CANCEL_BATCH_ENDPOINT: &str = "/api/v3/brokerage/orders/batch_cancel";
pub(crate) const EDIT_ENDPOINT: &str = "/api/v3/brokerage/orders/edit";
+ pub(crate) const CREATE_PREVIEW_ENDPOINT: &str = "/api/v3/brokerage/orders/preview";
pub(crate) const EDIT_PREVIEW_ENDPOINT: &str = "/api/v3/brokerage/orders/edit_preview";
pub(crate) const BATCH_ENDPOINT: &str = "/api/v3/brokerage/orders/historical/batch";
pub(crate) const FILLS_ENDPOINT: &str = "/api/v3/brokerage/orders/historical/fills";
+ pub(crate) const CLOSE_POSITION_ENDPOINT: &str = "/api/v3/brokerage/orders/close_position";
+}
+
+/// Portfolios API constants
+pub(crate) mod portfolios {
+ pub(crate) const RESOURCE_ENDPOINT: &str = "/api/v3/brokerage/portfolios";
+ pub(crate) const MOVE_FUNDS_ENDPOINT: &str = "/api/v3/brokerage/portfolios/move_funds";
}
/// Products API constants
pub(crate) mod products {
- pub(crate) const CANDLE_MAXIMUM: u64 = 300;
+ pub(crate) const CANDLE_MAXIMUM: u32 = 350;
pub(crate) const RESOURCE_ENDPOINT: &str = "/api/v3/brokerage/products";
pub(crate) const BID_ASK_ENDPOINT: &str = "/api/v3/brokerage/best_bid_ask";
pub(crate) const PRODUCT_BOOK_ENDPOINT: &str = "/api/v3/brokerage/product_book";
}
-/// Utils API constants
-pub(crate) mod utils {
- pub(crate) const UNIXTIME_ENDPOINT: &str = "/api/v3/brokerage/time";
+/// Payment API constants
+pub(crate) mod payments {
+ pub(crate) const RESOURCE_ENDPOINT: &str = "/api/v3/brokerage/payment_methods";
+}
+
+/// Data API constants
+pub(crate) mod data {
+ pub(crate) const KEY_PERMISSIONS_ENDPOINT: &str = "/api/v3/brokerage/key_permissions";
}
-/// REST API constants
-pub(crate) mod rest {
- pub(crate) const SERVICE: &str = "retail_rest_api_proxy";
+/// Public API constants
+pub(crate) mod public {
+ pub(crate) const SERVERTIME_ENDPOINT: &str = "/api/v3/brokerage/time";
+ pub(crate) const PRODUCT_BOOK_ENDPOINT: &str = "/api/v3/brokerage/market/product_book";
+ pub(crate) const RESOURCE_ENDPOINT: &str = "/api/v3/brokerage/market/products";
}
/// Websocket API constants
pub(crate) mod websocket {
- pub(crate) const RESOURCE_ENDPOINT: &str = "wss://advanced-trade-ws.coinbase.com";
+ pub(crate) const PUBLIC_ENDPOINT: &str = "wss://advanced-trade-ws.coinbase.com";
+ pub(crate) const SECURE_ENDPOINT: &str = "wss://advanced-trade-ws-user.coinbase.com";
/// Granularity of Candles from the WebSocket Candle subscription.
/// NOTE: This is a restriction by CoinBase and cannot be currently changed (20240125)
pub(crate) const GRANULARITY: u64 = 300;
- pub(crate) const SERVICE: &str = "public_websocket_api";
}
/// Amount of tokens per second refilled.
pub(crate) mod ratelimits {
- pub(crate) const REST_REFRESH_RATE: f64 = 30.0;
- pub(crate) const WEBSOCKET_REFRESH_RATE: f64 = 750.0;
+ pub(crate) const SECURE_REST_REFRESH_RATE: f64 = 30.0;
+ pub(crate) const PUBLIC_REST_REFRESH_RATE: f64 = 10.0;
+ pub(crate) const SECURE_WEBSOCKET_REFRESH_RATE: f64 = 750.0;
+ pub(crate) const PUBLIC_WEBSOCKET_REFRESH_RATE: f64 = 8.0;
}
diff --git a/src/errors.rs b/src/errors.rs
index f7f3c09..f5ddb00 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -1,14 +1,18 @@
//! Contains all errors produced by the crate.
+use std::error::Error;
use std::fmt;
/// Types of errors that can occur.
#[derive(Debug)]
-pub enum CbAdvError {
- /// Unable to parse JSON successfully.
+pub enum CbError {
+ /// Unable to parse JSON or Builders successfully.
BadParse(String),
/// Non-200 status code received.
- BadStatus(String),
+ BadStatus {
+ code: reqwest::StatusCode,
+ body: String,
+ },
/// Could not connect to the service.
BadConnection(String),
/// Nothing to do.
@@ -20,23 +24,47 @@ pub enum CbAdvError {
/// Could not identify the API Secret key type.
BadPrivateKey(String),
/// Could not serialize the body of a message.
- BadSerialization,
+ BadSerialization(String),
/// General unknown error.
Unknown(String),
+ /// HTTP request error.
+ RequestError(String),
+ /// URL parse error.
+ UrlParseError(String),
+ /// JSON deserialization error.
+ JsonError(String),
+ /// Authentication error.
+ AuthenticationError(String),
+ /// An invalid query.
+ BadQuery(String),
+ /// An invalid request.
+ BadRequest(String),
}
-impl fmt::Display for CbAdvError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+impl fmt::Display for CbError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
- CbAdvError::Unknown(value) => write!(f, "unknown error occured: {}", value),
- CbAdvError::BadSignature(value) => write!(f, "could not create signature: {}", value),
- CbAdvError::BadSerialization => write!(f, "could not serialize the message body"),
- CbAdvError::BadPrivateKey(value) => write!(f, "invalid private key: {}", value),
- CbAdvError::BadParse(value) => write!(f, "could not parse: {}", value),
- CbAdvError::NothingToDo(value) => write!(f, "nothing to do: {}", value),
- CbAdvError::NotFound(value) => write!(f, "could not find: {}", value),
- CbAdvError::BadStatus(value) => write!(f, "non-zero status occurred: {}", value),
- CbAdvError::BadConnection(value) => write!(f, "could not connect: {}", value),
+ CbError::Unknown(value) => write!(f, "unknown error occurred: {}", value),
+ CbError::BadSignature(value) => write!(f, "could not create signature: {}", value),
+ CbError::BadSerialization(value) => {
+ write!(f, "could not serialize the message body: {}", value)
+ }
+ CbError::BadPrivateKey(value) => write!(f, "invalid private key: {}", value),
+ CbError::BadParse(value) => write!(f, "could not parse: {}", value),
+ CbError::NothingToDo(value) => write!(f, "nothing to do: {}", value),
+ CbError::NotFound(value) => write!(f, "could not find: {}", value),
+ CbError::BadStatus { code, body } => {
+ write!(f, "HTTP error {}: {}", code.as_u16(), body)
+ }
+ CbError::BadConnection(value) => write!(f, "could not connect: {}", value),
+ CbError::RequestError(value) => write!(f, "HTTP request error: {}", value),
+ CbError::UrlParseError(value) => write!(f, "URL parse error: {}", value),
+ CbError::JsonError(value) => write!(f, "JSON deserialization error: {}", value),
+ CbError::AuthenticationError(value) => write!(f, "authentication error: {}", value),
+ CbError::BadQuery(value) => write!(f, "invalid query: {}", value),
+ CbError::BadRequest(value) => write!(f, "invalid request: {}", value),
}
}
}
+
+impl Error for CbError {}
diff --git a/src/http_agent.rs b/src/http_agent.rs
new file mode 100644
index 0000000..af958e2
--- /dev/null
+++ b/src/http_agent.rs
@@ -0,0 +1,329 @@
+//! # Authentication and signing messages.
+//!
+//! `http_agent` contains the backbone of the API requests in the form of the SecureHttpAgent and PublicHttpAgent struct. This signs
+//! all requests to the API for ensure proper authentication. The HttpAgents are also responsible for handling
+//! the GET and POST requests.
+
+use std::sync::Arc;
+
+use futures::lock::Mutex;
+use reqwest::header::{CONTENT_TYPE, USER_AGENT};
+use reqwest::{Method, Response, Url};
+use serde::Serialize;
+
+use crate::constants::{API_ROOT_URI, API_SANDBOX_ROOT_URI, CRATE_USER_AGENT};
+use crate::errors::CbError;
+use crate::jwt::Jwt;
+use crate::token_bucket::TokenBucket;
+use crate::traits::{HttpAgent, Query, Request};
+use crate::types::CbResult;
+
+/// Base HTTP Agent that is responsible for making requests and token bucket.
+#[derive(Debug, Clone)]
+pub(crate) struct HttpAgentBase {
+ /// Wrapped client that is responsible for making the requests.
+ client: reqwest::Client,
+ /// Token bucket, used for rate limiting.
+ bucket: Arc>,
+ /// Root URI for the API.
+ root_uri: &'static str,
+}
+
+impl HttpAgentBase {
+ /// Creates a new instance of SecureHttpAgent.
+ ///
+ /// # Arguments
+ ///
+ /// * `use_sandbox` - A boolean that determines if the sandbox should be used.
+ /// * `shared_bucket` - Shared token bucket for all APIs.
+ pub(crate) fn new(use_sandbox: bool, shared_bucket: Arc>) -> CbResult {
+ let root_uri = if use_sandbox {
+ API_SANDBOX_ROOT_URI
+ } else {
+ API_ROOT_URI
+ };
+
+ let client = reqwest::Client::builder()
+ .timeout(std::time::Duration::from_secs(10))
+ .build()
+ .map_err(|e| CbError::RequestError(e.to_string()))?;
+
+ Ok(Self {
+ client,
+ bucket: shared_bucket,
+ root_uri,
+ })
+ }
+
+ /// Constructs a URL for the request being made.
+ ///
+ /// # Arguments
+ ///
+ /// * `resource` - A string representing the resource that is being accessed.
+ /// * `query` - A string containing options / parameters for the URL.
+ fn build_url(&self, resource: &str, query: &impl Query) -> CbResult {
+ // Ensure the query is valid.
+ query.check()?;
+
+ let base_url = Url::parse(&format!("https://{}", self.root_uri))
+ .map_err(|e| CbError::UrlParseError(e.to_string()))?;
+ let mut url = base_url
+ .join(resource)
+ .map_err(|e| CbError::UrlParseError(e.to_string()))?;
+ url.set_query(Some(&query.to_query()));
+ Ok(url)
+ }
+
+ /// Converts the request to a JSON string.
+ ///
+ /// # Arguments
+ ///
+ /// * `request` - The request to convert to a JSON string.
+ fn convert_request<'a, T>(&self, request: &'a T) -> CbResult
+ where
+ T: Request + Serialize + 'a,
+ {
+ request.check()?;
+ let data = serde_json::to_string(&request)
+ .map_err(|e| CbError::BadSerialization(e.to_string()))?;
+ Ok(data)
+ }
+
+ /// Handles the response from the API.
+ ///
+ /// # Arguments
+ ///
+ /// * `response` - The response from the API.
+ async fn handle_response(&self, response: Response) -> CbResult {
+ if response.status().is_success() {
+ Ok(response)
+ } else {
+ let status = response.status();
+ let body = response
+ .text()
+ .await
+ .unwrap_or_else(|_| "Could not parse error message".to_string());
+ Err(CbError::BadStatus { code: status, body })
+ }
+ }
+
+ /// Executes the request to the API.
+ ///
+ /// # Arguments
+ ///
+ /// * `method` - The method of the request, GET, POST, etc.
+ /// * `url` - The URL to make the request to.
+ /// * `body` - The body of the request, if any.
+ /// * `token` - The token to authenticate the request.
+ pub(crate) async fn execute_request(
+ &mut self,
+ method: Method,
+ url: Url,
+ body: Option,
+ token: Option,
+ ) -> CbResult {
+ {
+ let mut locked_bucket = self.bucket.lock().await;
+ locked_bucket.wait_on().await;
+ }
+
+ let mut request = self
+ .client
+ .request(method, url)
+ .header(CONTENT_TYPE, "application/json")
+ .header(USER_AGENT, CRATE_USER_AGENT);
+
+ if let Some(token) = token {
+ request = request.bearer_auth(token);
+ }
+
+ if let Some(body) = body {
+ request = request.body(body);
+ }
+
+ let response = request
+ .send()
+ .await
+ .map_err(|e| CbError::RequestError(e.to_string()))?;
+
+ self.handle_response(response).await
+ }
+}
+
+/// Unsigned HTTP Agent that is responsible for making requests without authentication.
+#[derive(Debug, Clone)]
+pub(crate) struct PublicHttpAgent {
+ /// Base client that is responsible for making the requests.
+ pub(crate) base: HttpAgentBase,
+}
+
+impl PublicHttpAgent {
+ /// Creates a new instance of PublicHttpAgent.
+ ///
+ /// # Arguments
+ ///
+ /// * `use_sandbox` - A boolean that determines if the sandbox should be used.
+ /// * `shared_bucket` - Shared token bucket for all APIs.
+ pub(crate) fn new(use_sandbox: bool, shared_bucket: Arc>) -> CbResult {
+ Ok(Self {
+ base: HttpAgentBase::new(use_sandbox, shared_bucket)?,
+ })
+ }
+}
+
+impl HttpAgent for PublicHttpAgent {
+ async fn get(&mut self, resource: &str, query: &impl Query) -> CbResult {
+ let url = self.base.build_url(resource, query)?;
+ self.base
+ .execute_request(Method::GET, url, None, None)
+ .await
+ }
+
+ async fn post<'a, T>(
+ &mut self,
+ resource: &str,
+ query: &impl Query,
+ body: &'a T,
+ ) -> CbResult
+ where
+ T: Request + Serialize + 'a,
+ {
+ let url = self.base.build_url(resource, query)?;
+ let data = self.base.convert_request(body)?;
+ self.base
+ .execute_request(Method::POST, url, Some(data), None)
+ .await
+ }
+
+ async fn put<'a, T>(
+ &mut self,
+ resource: &str,
+ query: &impl Query,
+ body: &'a T,
+ ) -> CbResult
+ where
+ T: Request + Serialize + 'a,
+ {
+ let url = self.base.build_url(resource, query)?;
+ let data = self.base.convert_request(body)?;
+ self.base
+ .execute_request(Method::PUT, url, Some(data), None)
+ .await
+ }
+
+ async fn delete(&mut self, resource: &str, query: &impl Query) -> CbResult {
+ let url = self.base.build_url(resource, query)?;
+ self.base
+ .execute_request(Method::DELETE, url, None, None)
+ .await
+ }
+}
+
+/// Creates and signs HTTP Requests to the API.
+#[derive(Debug, Clone)]
+pub(crate) struct SecureHttpAgent {
+ /// JSON Webtoken Generator, disabled in sandbox mode.
+ jwt: Option,
+ /// Base client that is responsible for making the requests.
+ base: HttpAgentBase,
+}
+
+/// Responsible for signing and sending HTTP requests.
+impl SecureHttpAgent {
+ /// Creates a new instance of SecureHttpAgent.
+ ///
+ /// # Arguments
+ ///
+ /// * `api_key` - A string that holds the key for the API service.
+ /// * `api_secret` - A string that holds the secret for the API service.
+ /// * `use_sandbox` - A boolean that determines if the sandbox should be used.
+ /// * `shared_bucket` - Shared token bucket for all APIs.
+ pub(crate) fn new(
+ api_key: &str,
+ api_secret: &str,
+ use_sandbox: bool,
+ shared_bucket: Arc>,
+ ) -> CbResult {
+ let jwt = if use_sandbox {
+ // Do not generate JWT in sandbox mode.
+ None
+ } else {
+ Some(
+ Jwt::new(api_key, api_secret)
+ .map_err(|e| CbError::Unknown(format!("Error creating JWT: {}", e)))?,
+ )
+ };
+
+ Ok(Self {
+ jwt,
+ base: HttpAgentBase::new(use_sandbox, shared_bucket)?,
+ })
+ }
+
+ /// Builds a token for the request. If JWT is not enabled, returns None.
+ ///
+ /// # Arguments
+ ///
+ /// * `method` - The method of the request, GET, POST, etc.
+ /// * `resource` - The resource being accessed.
+ fn build_token(&self, method: Method, resource: &str) -> CbResult> {
+ if let Some(jwt) = &self.jwt {
+ let uri = Jwt::build_uri(method.as_str(), self.base.root_uri, resource);
+ Ok(Some(jwt.encode(Some(&uri))?))
+ } else {
+ Ok(None)
+ }
+ }
+}
+
+impl HttpAgent for SecureHttpAgent {
+ async fn get(&mut self, resource: &str, query: &impl Query) -> CbResult {
+ let url = self.base.build_url(resource, query)?;
+ let token = self.build_token(Method::GET, resource)?;
+ self.base
+ .execute_request(Method::GET, url, None, token)
+ .await
+ }
+
+ async fn post<'a, T>(
+ &mut self,
+ resource: &str,
+ query: &impl Query,
+ body: &'a T,
+ ) -> CbResult
+ where
+ T: Request + Serialize + 'a,
+ {
+ let url = self.base.build_url(resource, query)?;
+ let data = self.base.convert_request(body)?;
+ let token = self.build_token(Method::POST, resource)?;
+ self.base
+ .execute_request(Method::POST, url, Some(data), token)
+ .await
+ }
+
+ async fn put<'a, T>(
+ &mut self,
+ resource: &str,
+ query: &impl Query,
+ body: &'a T,
+ ) -> CbResult
+ where
+ T: Request + Serialize + 'a,
+ {
+ let url = self.base.build_url(resource, query)?;
+ let data = self.base.convert_request(body)?;
+ let token = self.build_token(Method::PUT, resource)?;
+ self.base
+ .execute_request(Method::PUT, url, Some(data), token)
+ .await
+ }
+
+ async fn delete(&mut self, resource: &str, query: &impl Query) -> CbResult {
+ let url = self.base.build_url(resource, query)?;
+ let token = self.build_token(Method::DELETE, resource)?;
+ self.base
+ .execute_request(Method::DELETE, url, None, token)
+ .await
+ }
+}
diff --git a/src/jwt.rs b/src/jwt.rs
index a3dbec1..3a3b27d 100644
--- a/src/jwt.rs
+++ b/src/jwt.rs
@@ -10,7 +10,7 @@ use ring::rand::SystemRandom;
use ring::signature::{self};
use serde::Serialize;
-use crate::errors::CbAdvError;
+use crate::errors::CbError;
use crate::time;
use crate::types::CbResult;
@@ -19,7 +19,6 @@ struct Header {
alg: String,
kid: String,
nonce: String,
- typ: String,
}
#[derive(Serialize)]
@@ -28,7 +27,6 @@ struct Payload {
iss: String,
nbf: u64,
exp: u64,
- aud: Vec,
#[serde(skip_serializing_if = "Option::is_none")]
uri: Option,
}
@@ -73,19 +71,17 @@ impl Jwt {
alg: "ES256".to_string(),
kid: self.api_key.clone(),
nonce,
- typ: "JWT".to_string(),
}
}
/// Creates the payload for the message.
- fn build_payload(&self, service: &str, uri: Option<&str>) -> Payload {
+ fn build_payload(&self, uri: Option<&str>) -> Payload {
let now = time::now();
Payload {
sub: self.api_key.clone(),
iss: "coinbase-cloud".to_string(),
nbf: now,
exp: now + 120,
- aud: [service.to_string()].to_vec(),
uri: uri.map(|u| u.to_string()),
}
}
@@ -99,7 +95,7 @@ impl Jwt {
/// Encodes a serializable type.
fn base64_encode(input: &T) -> CbResult {
let raw =
- serde_json::to_vec(input).map_err(|why| CbAdvError::BadSignature(why.to_string()))?;
+ serde_json::to_vec(input).map_err(|why| CbError::BadSignature(why.to_string()))?;
Ok(Self::to_base64(&raw))
}
@@ -134,13 +130,13 @@ impl Jwt {
// Not in pkcs8 format, attempt conversion.
let ec_key = EcKey::private_key_from_pem(key)
- .map_err(|why| CbAdvError::BadPrivateKey(why.to_string()))?;
+ .map_err(|why| CbError::BadPrivateKey(why.to_string()))?;
let pkey =
- PKey::from_ec_key(ec_key).map_err(|why| CbAdvError::BadPrivateKey(why.to_string()))?;
+ PKey::from_ec_key(ec_key).map_err(|why| CbError::BadPrivateKey(why.to_string()))?;
let new_key = pkey
.private_key_to_pem_pkcs8()
- .map_err(|why| CbAdvError::BadPrivateKey(why.to_string()))?;
+ .map_err(|why| CbError::BadPrivateKey(why.to_string()))?;
Self::parse_key(&new_key)
}
@@ -158,22 +154,22 @@ impl Jwt {
/// # Returns
///
/// A `CbResult>` which is Ok containing the decoded binary key data if successful,
- /// or an Err with a `CbAdvError::BadPrivateKey` containing the error message if any error occurs.
+ /// or an Err with a `CbError::BadPrivateKey` containing the error message if any error occurs.
fn parse_key(api_secret: &[u8]) -> CbResult> {
let pem_str =
- str::from_utf8(api_secret).map_err(|why| CbAdvError::BadPrivateKey(why.to_string()))?;
+ str::from_utf8(api_secret).map_err(|why| CbError::BadPrivateKey(why.to_string()))?;
// Checks for the headers and footers to remove it.
let base64_encoded = if pem_str.starts_with("-----BEGIN") && pem_str.contains("-----END") {
let start = pem_str
.find("-----BEGIN")
.and_then(|s| pem_str[s..].find('\n'))
- .ok_or_else(|| CbAdvError::BadPrivateKey("No BEGIN delimiter".to_string()))?
+ .ok_or_else(|| CbError::BadPrivateKey("No BEGIN delimiter".to_string()))?
+ 1;
let end = pem_str
.find("-----END")
- .ok_or_else(|| CbAdvError::BadPrivateKey("No END delimiter".to_string()))?;
+ .ok_or_else(|| CbError::BadPrivateKey("No END delimiter".to_string()))?;
// Get the data between the header and footer.
pem_str[start..end]
@@ -187,7 +183,7 @@ impl Jwt {
// Decode the key.
STANDARD_NO_PAD
.decode(base64_encoded)
- .map_err(|why| CbAdvError::BadPrivateKey(why.to_string()))
+ .map_err(|why| CbError::BadPrivateKey(why.to_string()))
}
/// Signs a message using ECDSA with the specified private key.
@@ -203,10 +199,10 @@ impl Jwt {
fn sign_message(&self, message: &[u8]) -> CbResult {
let signing_key =
signature::EcdsaKeyPair::from_pkcs8(Self::get_alg(), &self.api_secret, &self.rng)
- .map_err(|why| CbAdvError::BadSignature(why.to_string()))?;
+ .map_err(|why| CbError::BadSignature(why.to_string()))?;
let signature = signing_key
.sign(&self.rng, message)
- .map_err(|why| CbAdvError::BadSignature(why.to_string()))?;
+ .map_err(|why| CbError::BadSignature(why.to_string()))?;
Ok(Self::to_base64(signature.as_ref()))
}
@@ -221,20 +217,20 @@ impl Jwt {
/// # Returns
///
/// A `CbResult` with the JWT token if successful; otherwise, an error.
- pub(crate) fn encode(&self, service: &str, uri: Option<&str>) -> CbResult {
+ pub(crate) fn encode(&self, uri: Option<&str>) -> CbResult {
// Conver the header and payload into base64.
let header = Self::base64_encode(&self.build_header())?;
- let payload = Self::base64_encode(&self.build_payload(service, uri))?;
+ let payload = Self::base64_encode(&self.build_payload(uri))?;
// Create the message w/ header and payload.
let mut message = String::with_capacity(header.len() + payload.len() + 128);
write!(message, "{}.{}", header, payload)
- .map_err(|why| CbAdvError::BadSignature(why.to_string()))?;
+ .map_err(|why| CbError::BadSignature(why.to_string()))?;
// Sign the message.
let signature = self.sign_message(message.as_bytes())?;
write!(message, ".{}", signature)
- .map_err(|why| CbAdvError::BadSignature(why.to_string()))?;
+ .map_err(|why| CbError::BadSignature(why.to_string()))?;
Ok(message)
}
diff --git a/src/lib.rs b/src/lib.rs
index 9e065b6..4dc4fe4 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -14,9 +14,12 @@
#[cfg(feature = "config")]
pub mod config;
+#[macro_use]
+pub(crate) mod macros;
+
+mod candle_watcher;
+pub(crate) mod http_agent;
pub(crate) mod jwt;
-pub(crate) mod signer;
-mod task_tracker;
mod token_bucket;
pub(crate) mod constants;
@@ -28,9 +31,11 @@ pub(crate) mod utils;
pub(crate) mod apis;
pub(crate) mod models;
-pub use models::{account, convert, fee, order, product, util, websocket as ws};
+pub use models::{
+ account, convert, fee, order, portfolio, product, public, shared, websocket as ws,
+};
mod rest;
mod websocket;
-pub use rest::RestClient;
-pub use websocket::WebSocketClient;
+pub use rest::{RestClient, RestClientBuilder};
+pub use websocket::{WebSocketClient, WebSocketClientBuilder};
diff --git a/src/macros.rs b/src/macros.rs
new file mode 100644
index 0000000..ce721af
--- /dev/null
+++ b/src/macros.rs
@@ -0,0 +1,24 @@
+macro_rules! get_auth {
+ ($agent:expr, $method_name:expr) => {
+ match $agent {
+ Some(ref mut agent) => agent,
+ None => {
+ return Err(CbError::AuthenticationError(format!(
+ "Authentication required for '{}'.",
+ $method_name
+ )));
+ }
+ }
+ };
+}
+
+macro_rules! is_auth {
+ ($agent:expr, $method_name:expr) => {
+ if $agent.is_none() {
+ return Err(CbError::AuthenticationError(format!(
+ "Authentication required for '{}'.",
+ $method_name
+ )));
+ }
+ };
+}
diff --git a/src/models/account.rs b/src/models/account.rs
index 7140842..35254ac 100644
--- a/src/models/account.rs
+++ b/src/models/account.rs
@@ -5,17 +5,41 @@
use serde::{Deserialize, Serialize};
+use crate::constants::accounts::LIST_ACCOUNT_MAXIMUM;
+use crate::errors::CbError;
use crate::traits::Query;
-use crate::utils::{deserialize_numeric, QueryBuilder};
+use crate::types::CbResult;
+use crate::utils::QueryBuilder;
-/// Represents a Balance for either Available or Held funds.
-#[derive(Serialize, Deserialize, Debug, Clone)]
-pub struct Balance {
- /// Value for the currency available or held.
- #[serde(deserialize_with = "deserialize_numeric")]
- pub value: f64,
- /// Denomination of the currency.
- pub currency: String,
+use super::shared::Balance;
+
+/// Platform that the account is associated with.
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
+pub enum Platform {
+ /// Spot account.
+ #[serde(rename = "ACCOUNT_PLATFORM_CONSUMER")]
+ Consumer,
+ /// US Derivatives account.
+ #[serde(rename = "ACCOUNT_PLATFORM_CFM_CONSUMER")]
+ CfmConsumer,
+ /// International Exchange account.
+ #[serde(rename = "ACCOUNT_PLATFORM_INTX")]
+ Intx,
+}
+
+/// Possible values for the account type.
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
+pub enum AccountType {
+ #[serde(rename = "ACCOUNT_TYPE_UNSPECIFIED")]
+ Unspecified,
+ #[serde(rename = "ACCOUNT_TYPE_CRYPTO")]
+ Crypto,
+ #[serde(rename = "ACCOUNT_TYPE_FIAT")]
+ Fiat,
+ #[serde(rename = "ACCOUNT_TYPE_VAULT")]
+ Vault,
+ #[serde(rename = "ACCOUNT_TYPE_PERP_FUTURES")]
+ PerpFutures,
}
/// Represents an Account received from the API.
@@ -40,16 +64,18 @@ pub struct Account {
/// Time at which this account was deleted.
pub deleted_at: Option,
/// Possible values: [ACCOUNT_TYPE_UNSPECIFIED, ACCOUNT_TYPE_CRYPTO, ACCOUNT_TYPE_FIAT, ACCOUNT_TYPE_VAULT]
- pub r#type: String,
+ pub r#type: AccountType,
/// Whether or not this account is ready to trade.
pub ready: bool,
/// Current balance on hold.
pub hold: Balance,
+ /// Platform that the account is associated with.
+ pub platform: Platform,
}
-/// Represents a list of accounts received from the API.
+/// Response from the API that wraps a list of accounts.
#[derive(Deserialize, Debug)]
-pub struct ListedAccounts {
+pub struct PaginatedAccounts {
/// Accounts returned from the API.
pub accounts: Vec,
/// Whether there are additional pages for this query.
@@ -60,28 +86,71 @@ pub struct ListedAccounts {
pub size: u32,
}
-/// Represents an account response from the API.
-#[derive(Deserialize, Debug)]
-pub(crate) struct AccountResponse {
- /// Account returned from the API.
- pub(crate) account: Account,
-}
-
/// Represents parameters that are optional for List Account API request.
-#[derive(Serialize, Default, Debug, Clone)]
-pub struct ListAccountsQuery {
+#[derive(Serialize, Debug, Clone)]
+pub struct AccountListQuery {
/// Amount to obtain, default 49 maximum is 250.
- pub limit: Option,
+ pub limit: u32,
/// Returns accounts after the cursor provided.
pub cursor: Option,
}
-impl Query for ListAccountsQuery {
- /// Converts the object into HTTP request parameters.
+impl Query for AccountListQuery {
+ fn check(&self) -> CbResult<()> {
+ if self.limit == 0 || self.limit > LIST_ACCOUNT_MAXIMUM {
+ return Err(CbError::BadQuery(format!(
+ "Limit must be greater than 0 with a maximum of {}",
+ LIST_ACCOUNT_MAXIMUM
+ )));
+ }
+ Ok(())
+ }
+
fn to_query(&self) -> String {
QueryBuilder::new()
- .push_u32_optional("limit", self.limit)
+ .push("limit", self.limit)
.push_optional("cursor", &self.cursor)
.build()
}
}
+
+impl Default for AccountListQuery {
+ fn default() -> Self {
+ Self {
+ limit: 49,
+ cursor: None,
+ }
+ }
+}
+
+impl AccountListQuery {
+ /// Creates a new AccountListQuery with default values.
+ pub fn new() -> Self {
+ Default::default()
+ }
+
+ /// Sets the limit for the query. Default is 49 and maximum is 250.
+ pub fn limit(mut self, limit: u32) -> Self {
+ self.limit = limit;
+ self
+ }
+
+ /// Sets the cursor for the query.
+ pub fn cursor(mut self, cursor: String) -> Self {
+ self.cursor = Some(cursor);
+ self
+ }
+}
+
+/// Response from the API that wraps a single account.
+#[derive(Deserialize, Debug)]
+pub(crate) struct AccountWrapper {
+ /// Account returned from the API.
+ pub(crate) account: Account,
+}
+
+impl From for Account {
+ fn from(wrapper: AccountWrapper) -> Self {
+ wrapper.account
+ }
+}
diff --git a/src/models/convert.rs b/src/models/convert.rs
index e313f48..a9bb278 100644
--- a/src/models/convert.rs
+++ b/src/models/convert.rs
@@ -4,97 +4,102 @@
//! This allows for the conversion between two currencies.
use serde::{Deserialize, Serialize};
+use serde_with::{serde_as, DisplayFromStr};
-use crate::{traits::Query, utils::QueryBuilder};
+use crate::errors::CbError;
+use crate::traits::{Query, Request};
+use crate::types::CbResult;
+use crate::utils::QueryBuilder;
-use super::account::Balance;
+use super::shared::Balance;
-#[derive(Deserialize, Debug)]
-pub(crate) struct ConvertResponse {
- pub(crate) trade: Trade,
+/// Possible values for the trade status.
+#[derive(Deserialize, Serialize, Clone, PartialEq, Debug)]
+pub enum TradeStatus {
+ /// Unspecified trade status.
+ #[serde(rename = "TRADE_STATUS_UNSPECIFIED")]
+ Unspecified,
+ /// Trade has been created.
+ #[serde(rename = "TRADE_STATUS_CREATED")]
+ Created,
+ /// Trade has started.
+ #[serde(rename = "TRADE_STATUS_STARTED")]
+ Started,
+ /// Trade has been completed.
+ #[serde(rename = "TRADE_STATUS_COMPLETED")]
+ Completed,
+ /// Trade has been canceled.
+ #[serde(rename = "TRADE_STATUS_CANCELED")]
+ Canceled,
}
-#[derive(Deserialize, Debug)]
+#[derive(Deserialize, Serialize, Debug)]
pub struct Trade {
/// The trade id, used to get and commit the trade
pub id: String,
/// Possible values: [TRADE_STATUS_UNSPECIFIED, TRADE_STATUS_CREATED, TRADE_STATUS_STARTED, TRADE_STATUS_COMPLETED, TRADE_STATUS_CANCELED]
- pub status: String,
+ pub status: TradeStatus,
pub user_entered_amount: Balance,
pub amount: Balance,
pub subtotal: Balance,
pub total: Balance,
- /// List of fees associated with the trade
+ // List of fees associated with the trade
pub fees: Vec,
- pub total_fee: FeeDetail,
+ pub total_fee: Fee,
pub source: AccountDetail,
pub target: AccountDetail,
- pub unit_price: UnitPrice,
pub user_warnings: Vec,
pub user_reference: String,
- /// The currency of the source account
+ // The currency of the source account
pub source_currency: String,
- /// The currency of the target account
+ // The currency of the target account
pub target_currency: String,
- pub cancellation_reason: CancellationReason,
- /// The id of the source account
+ // The id of the source account
pub source_id: String,
- /// The id of the target account
+ // The id of the target account
pub target_id: String,
pub exchange_rate: Balance,
- /// Tax details for the trade
+ // Tax details for the trade
pub tax_details: Vec,
- pub trade_incentive_info: TradeIncentiveInfo,
- pub total_fee_without_tax: FeeDetail,
- pub fiat_denoted_total: Balance,
}
-#[derive(Deserialize, Debug)]
+#[derive(Deserialize, Serialize, Debug)]
pub struct Fee {
pub title: String,
pub description: String,
pub amount: Balance,
pub label: String,
- pub disclosure: Disclosure,
+ pub disclosure: Option,
}
-#[derive(Deserialize, Debug)]
+#[derive(Deserialize, Serialize, Debug)]
pub struct Disclosure {
pub title: String,
pub description: String,
pub link: Link,
}
-#[derive(Deserialize, Debug)]
+#[derive(Deserialize, Serialize, Debug)]
pub struct Link {
pub text: String,
pub url: String,
}
-#[derive(Deserialize, Debug)]
-pub struct FeeDetail {
- pub title: String,
- pub description: String,
- pub amount: Balance,
- pub label: String,
- pub disclosure: Disclosure,
-}
-
-#[derive(Deserialize, Debug)]
+#[derive(Deserialize, Serialize, Debug)]
pub struct AccountDetail {
pub r#type: String,
pub network: String,
pub ledger_account: LedgerAccount,
}
-#[derive(Deserialize, Debug)]
+#[derive(Deserialize, Serialize, Debug)]
pub struct LedgerAccount {
pub account_id: String,
pub currency: String,
pub owner: Owner,
}
-#[derive(Deserialize, Debug)]
+#[derive(Deserialize, Serialize, Debug)]
pub struct Owner {
pub id: String,
pub uuid: String,
@@ -102,20 +107,20 @@ pub struct Owner {
pub r#type: String,
}
-#[derive(Deserialize, Debug)]
+#[derive(Deserialize, Serialize, Debug)]
pub struct UnitPrice {
pub target_to_fiat: PriceScale,
pub target_to_source: PriceScale,
pub source_to_fiat: PriceScale,
}
-#[derive(Deserialize, Debug)]
+#[derive(Deserialize, Serialize, Debug)]
pub struct PriceScale {
pub amount: Balance,
pub scale: i32,
}
-#[derive(Deserialize, Debug)]
+#[derive(Deserialize, Serialize, Debug)]
pub struct UserWarning {
pub id: String,
pub link: Link,
@@ -124,14 +129,14 @@ pub struct UserWarning {
pub message: String,
}
-#[derive(Deserialize, Debug)]
+#[derive(Deserialize, Serialize, Debug)]
pub struct WarningContext {
pub details: Vec,
pub title: String,
pub link_text: String,
}
-#[derive(Deserialize, Debug)]
+#[derive(Deserialize, Serialize, Debug)]
pub struct CancellationReason {
pub message: String,
pub code: String,
@@ -139,13 +144,13 @@ pub struct CancellationReason {
pub error_cta: String,
}
-#[derive(Deserialize, Debug)]
+#[derive(Deserialize, Serialize, Debug)]
pub struct TaxDetail {
pub name: String,
pub amount: Balance,
}
-#[derive(Deserialize, Debug)]
+#[derive(Deserialize, Serialize, Debug)]
pub struct TradeIncentiveInfo {
pub applied_incentive: bool,
pub user_incentive_id: String,
@@ -155,35 +160,104 @@ pub struct TradeIncentiveInfo {
pub redeemed: bool,
}
+/// Trade incentive to waive trade fees.
#[derive(Serialize, Deserialize, Debug)]
pub struct TradeIncentiveMetadata {
+ /// The user incentive id.
pub user_incentive_id: Option,
+ /// A promo code for waiving fees.
pub code_val: Option,
}
-#[derive(Serialize, Deserialize, Debug)]
-pub(crate) struct ConvertQuoteQuery {
+/// Represents a request to create a convert quote.
+#[serde_as]
+#[derive(Serialize, Debug, Default)]
+pub struct ConvertQuoteRequest {
/// The currency of the account to convert from, e.g. USD
- pub(crate) from_account: String,
+ pub from_account: String,
/// The currency of the account to convert to, e.g. USDC
- pub(crate) to_account: String,
- /// The amount to convert in the currency of the from_account
- pub(crate) amount: String,
+ pub to_account: String,
+ /// The amount to convert in the currency of the from_account.
+ #[serde_as(as = "DisplayFromStr")]
+ pub amount: f64,
+ /// Trade incentive to waive trade fees.
#[serde(skip_serializing_if = "Option::is_none")]
- pub(crate) trade_incentive_metadata: Option,
+ pub trade_incentive_metadata: Option,
+}
+
+impl Request for ConvertQuoteRequest {
+ fn check(&self) -> CbResult<()> {
+ if self.from_account.is_empty() {
+ return Err(CbError::BadRequest("from_account is required".to_string()));
+ } else if self.to_account.is_empty() {
+ return Err(CbError::BadRequest("to_account is required".to_string()));
+ } else if self.amount <= 0.0 {
+ return Err(CbError::BadRequest(
+ "amount must be greater than 0".to_string(),
+ ));
+ }
+ Ok(())
+ }
+}
+
+impl ConvertQuoteRequest {
+ /// Creates a new instance of the ConvertQuoteRequest.
+ ///
+ /// # Arguments
+ ///
+ /// * `from_account` - The currency of the account to convert from, e.g. USD
+ /// * `to_account` - The currency of the account to convert to, e.g. USDC
+ /// * `amount` - The amount to convert in the currency of the from_account.
+ pub fn new(from_account: &str, to_account: &str, amount: f64) -> Self {
+ Self {
+ from_account: from_account.to_string(),
+ to_account: to_account.to_string(),
+ amount,
+ trade_incentive_metadata: None,
+ }
+ }
+
+ /// Sets the trade incentive to waive trade fees.
+ pub fn trade_incentive_metadata(mut self, metadata: TradeIncentiveMetadata) -> Self {
+ self.trade_incentive_metadata = Some(metadata);
+ self
+ }
}
/// Represents parameters to obtain a currency conversion.
+///
+/// # Required Parameters
+///
+/// * `from_account` - The currency of the account to convert from, e.g. USD
+/// * `to_account` - The currency of the account to convert to, e.g. USDC
#[derive(Serialize, Default, Debug)]
-pub(crate) struct ConvertQuery {
+pub struct ConvertQuery {
/// Originating account.
- pub(crate) from_account: String,
+ pub from_account: String,
/// Sending account.
- pub(crate) to_account: String,
+ pub to_account: String,
+}
+impl Request for ConvertQuery {
+ fn check(&self) -> CbResult<()> {
+ if self.from_account.is_empty() {
+ return Err(CbError::BadRequest("from_account is required".to_string()));
+ } else if self.to_account.is_empty() {
+ return Err(CbError::BadRequest("to_account is required".to_string()));
+ }
+ Ok(())
+ }
}
impl Query for ConvertQuery {
- /// Converts the object into HTTP request parameters.
+ fn check(&self) -> CbResult<()> {
+ if self.from_account.is_empty() {
+ return Err(CbError::BadQuery("from_account is required".to_string()));
+ } else if self.to_account.is_empty() {
+ return Err(CbError::BadQuery("to_account is required".to_string()));
+ }
+ Ok(())
+ }
+
fn to_query(&self) -> String {
QueryBuilder::new()
.push("from_account", &self.from_account)
@@ -191,3 +265,44 @@ impl Query for ConvertQuery {
.build()
}
}
+
+impl ConvertQuery {
+ /// Creates a new instance of the ConvertQuery.
+ ///
+ /// # Arguments
+ ///
+ /// * `from_account` - The currency of the account to convert from, e.g. USD
+ /// * `to_account` - The currency of the account to convert to, e.g. USDC
+ pub fn new(from_account: &str, to_account: &str) -> Self {
+ Self {
+ from_account: from_account.to_string(),
+ to_account: to_account.to_string(),
+ }
+ }
+
+ /// Sets the originating account.
+ /// Note: This is a required field.
+ pub fn from_account(mut self, from_account: &str) -> Self {
+ self.from_account = from_account.to_string();
+ self
+ }
+
+ /// Sets the sending account.
+ /// Note: This is a required field.
+ pub fn to_account(mut self, to_account: &str) -> Self {
+ self.to_account = to_account.to_string();
+ self
+ }
+}
+
+/// Response from the convert API endpoint.
+#[derive(Deserialize, Debug)]
+pub(crate) struct TradeWrapper {
+ pub(crate) trade: Trade,
+}
+
+impl From for Trade {
+ fn from(wrapper: TradeWrapper) -> Self {
+ wrapper.trade
+ }
+}
diff --git a/src/models/data.rs b/src/models/data.rs
new file mode 100644
index 0000000..37fe43c
--- /dev/null
+++ b/src/models/data.rs
@@ -0,0 +1,53 @@
+//! # Coinbase Advanced Data API
+//!
+//! `data` gives access to the Data API and the various endpoints associated with it.
+
+use core::fmt;
+
+use serde::{Deserialize, Serialize};
+
+/// Various types of portfolios.
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
+#[serde(rename_all = "UPPERCASE")]
+pub enum PortfolioType {
+ /// Undefined portfolio type.
+ Undefined,
+ /// Default portfolio type.
+ Default,
+ /// Consumer portfolio type.
+ Consumer,
+ /// Intx portfolio type.
+ Intx,
+}
+
+impl fmt::Display for PortfolioType {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.as_ref())
+ }
+}
+
+impl AsRef for PortfolioType {
+ fn as_ref(&self) -> &str {
+ match self {
+ PortfolioType::Undefined => "UNDEFINED",
+ PortfolioType::Default => "DEFAULT",
+ PortfolioType::Consumer => "CONSUMER",
+ PortfolioType::Intx => "INTX",
+ }
+ }
+}
+
+/// KeyPermissions represents the permissions associated with an API key.
+#[derive(Deserialize, Serialize, Debug, Clone)]
+pub struct KeyPermissions {
+ ///Indicates whether the API key has view permissions.
+ pub can_view: bool,
+ /// Indicates whether the API key has trade permissions.
+ pub can_trade: bool,
+ /// Indicates whether the API key has deposit/withdrawal permissions.
+ pub can_transfer: bool,
+ /// The portfolio ID associated with the API key.
+ pub portfolio_uuid: String,
+ /// The type of portfolio. Possible values: [UNDEFINED, DEFAULT, CONSUMER, INTX]
+ pub portfolio_type: PortfolioType,
+}
diff --git a/src/models/fee.rs b/src/models/fee.rs
index c134a2d..347e2f3 100644
--- a/src/models/fee.rs
+++ b/src/models/fee.rs
@@ -4,42 +4,50 @@
//! Currently the only endpoint available is the Transaction Summary endpoint.
use serde::{Deserialize, Serialize};
+use serde_with::{serde_as, DisplayFromStr};
+use crate::errors::CbError;
use crate::traits::Query;
-use crate::utils::{deserialize_numeric, QueryBuilder};
+use crate::types::CbResult;
+use crate::utils::QueryBuilder;
+
+use super::product::ProductType;
/// Pricing tier for user, determined by notional (USD) volume.
+#[serde_as]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FeeTier {
/// Current fee teir for the user.
pub pricing_tier: String,
/// Lower bound (inclusive) of pricing tier in notional volume.
- #[serde(deserialize_with = "deserialize_numeric")]
+ #[serde_as(as = "DisplayFromStr")]
pub usd_from: u32,
/// Upper bound (exclusive) of pricing tier in notional volume.
- #[serde(deserialize_with = "deserialize_numeric")]
+ #[serde_as(as = "DisplayFromStr")]
pub usd_to: u32,
/// Taker fee rate, applied if the order takes liquidity.
- #[serde(deserialize_with = "deserialize_numeric")]
+ #[serde_as(as = "DisplayFromStr")]
pub taker_fee_rate: f64,
/// Maker fee rate, applied if the order creates liquidity.
- #[serde(deserialize_with = "deserialize_numeric")]
+ #[serde_as(as = "DisplayFromStr")]
pub maker_fee_rate: f64,
}
/// Represents a decimal number with precision.
+#[serde_as]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MarginRate {
/// Value of the margin rate.
- #[serde(deserialize_with = "deserialize_numeric")]
+ #[serde_as(as = "DisplayFromStr")]
pub value: f64,
}
/// Represents a tax amount.
+#[serde_as]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Tax {
/// Amount of tax.
- #[serde(deserialize_with = "deserialize_numeric")]
+ #[serde_as(as = "DisplayFromStr")]
pub value: f64,
/// Type of tax. Possible values: [INCLUSIVE, EXCLUSIVE]
pub r#type: String,
@@ -70,25 +78,39 @@ pub struct TransactionSummary {
/// Represents parameters that are optional for transaction summary API request.
#[derive(Serialize, Default, Debug)]
-pub struct TransactionSummaryQuery {
- /// Start date for the summary.
- pub start_date: Option,
- /// End date for the summary.
- pub end_date: Option,
- /// String of the users native currency, default is USD.
- pub user_native_currency: Option,
+pub struct FeeTransactionSummaryQuery {
/// Type of products to return. Valid options: SPOT or FUTURE
- pub product_type: Option,
+ pub product_type: Option,
}
-impl Query for TransactionSummaryQuery {
- /// Converts the object into HTTP request parameters.
+impl Query for FeeTransactionSummaryQuery {
+ fn check(&self) -> CbResult<()> {
+ if let Some(product_type) = &self.product_type {
+ if *product_type == ProductType::Unknown {
+ return Err(CbError::BadQuery(
+ "product_type cannot be unknown".to_string(),
+ ));
+ }
+ }
+ Ok(())
+ }
+
fn to_query(&self) -> String {
QueryBuilder::new()
- .push_optional("start_date", &self.start_date)
- .push_optional("end_date", &self.end_date)
- .push_optional("user_native_currency", &self.user_native_currency)
.push_optional("product_type", &self.product_type)
.build()
}
}
+
+impl FeeTransactionSummaryQuery {
+ /// Creates a new instance of the query.
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Sets the product type for the query.
+ pub fn product_type(mut self, product_type: ProductType) -> Self {
+ self.product_type = Some(product_type);
+ self
+ }
+}
diff --git a/src/models/mod.rs b/src/models/mod.rs
index 6f51a16..379bce0 100644
--- a/src/models/mod.rs
+++ b/src/models/mod.rs
@@ -1,7 +1,11 @@
pub mod account;
pub mod convert;
+pub mod data;
pub mod fee;
pub mod order;
+pub mod payment;
+pub mod portfolio;
pub mod product;
-pub mod util;
+pub mod public;
+pub mod shared;
pub mod websocket;
diff --git a/src/models/order.rs b/src/models/order.rs
deleted file mode 100644
index 1341a46..0000000
--- a/src/models/order.rs
+++ /dev/null
@@ -1,525 +0,0 @@
-//! # Coinbase Advanced Order API
-//!
-//! `order` gives access to the Order API and the various endpoints associated with it.
-//! These allow you to obtain past created orders, create new orders, and cancel orders.
-
-use std::fmt;
-
-use serde::{Deserialize, Serialize};
-
-use crate::traits::Query;
-use crate::utils::{deserialize_numeric, QueryBuilder};
-
-/// Various order types.
-#[derive(Serialize, Deserialize, Debug, Clone)]
-pub enum OrderType {
- /// A Market order.
- Market,
- /// A Limit order.
- Limit,
- /// A stop order is an order that becomes a market order when triggered.
- Stop,
- /// A stop order is a limit order that doesn't go on the book until it hits the stop price.
- StopLimit,
-}
-
-impl fmt::Display for OrderType {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- OrderType::Market => write!(f, "MARKET"),
- OrderType::Limit => write!(f, "LIMIT"),
- OrderType::Stop => write!(f, "STOP"),
- OrderType::StopLimit => write!(f, "STOPLIMIT"),
- }
- }
-}
-
-impl AsRef for OrderType {
- fn as_ref(&self) -> &str {
- match self {
- OrderType::Market => "MARKET",
- OrderType::Limit => "LIMIT",
- OrderType::Stop => "STOP",
- OrderType::StopLimit => "STOPLIMIT",
- }
- }
-}
-
-/// Order side, BUY or SELL.
-#[derive(Serialize, Deserialize, Debug, Clone)]
-pub enum OrderSide {
- /// Buying a product.
- Buy,
- /// Selling a product.
- Sell,
-}
-
-impl AsRef for OrderSide {
- fn as_ref(&self) -> &str {
- match self {
- OrderSide::Buy => "BUY",
- OrderSide::Sell => "SELL",
- }
- }
-}
-
-impl fmt::Display for OrderSide {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- OrderSide::Buy => write!(f, "BUY"),
- OrderSide::Sell => write!(f, "SELL"),
- }
- }
-}
-
-/// Order status, OPEN, CANCELLED, and EXPIRED.
-#[derive(Serialize, Deserialize, Debug, Clone)]
-pub enum OrderStatus {
- /// Implies the order is still available and not closed.
- Open,
- /// Order was closed by cancellation.
- Cancelled,
- /// Order was closed by expiration.
- Expired,
-}
-
-impl fmt::Display for OrderStatus {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- OrderStatus::Open => write!(f, "OPEN"),
- OrderStatus::Cancelled => write!(f, "CANCELLED"),
- OrderStatus::Expired => write!(f, "EXPIRED"),
- }
- }
-}
-
-impl AsRef for OrderStatus {
- fn as_ref(&self) -> &str {
- match self {
- OrderStatus::Open => "OPEN",
- OrderStatus::Cancelled => "CANCELLED",
- OrderStatus::Expired => "EXPIRED",
- }
- }
-}
-
-/// Order updates for a user from a websocket.
-#[derive(Serialize, Deserialize, Debug, Clone)]
-pub struct OrderUpdate {
- /// Type of the update.
- pub r#type: String,
- /// Client Order ID (Normally a UUID)
- pub client_order_id: String,
- #[serde(deserialize_with = "deserialize_numeric")]
- pub cumulative_quantity: f64,
- #[serde(deserialize_with = "deserialize_numeric")]
- pub leaves_quantity: f64,
- /// Average price for the order.
- #[serde(deserialize_with = "deserialize_numeric")]
- pub avg_price: f64,
- /// Total fees for the order.
- #[serde(deserialize_with = "deserialize_numeric")]
- pub total_fees: f64,
- /// Status of the order.
- pub status: String,
- /// Product ID.
- pub product_id: String,
- /// Date-time when the order was created.
- pub creation_time: String,
- /// BUY or SELL.
- pub order_side: String,
- /// Type of the order.
- pub order_type: String,
-}
-
-/// Market Immediate or Cancel.
-#[derive(Serialize, Debug)]
-pub(crate) struct MarketIoc {
- /// Amount of quote currency to spend on order. Required for BUY orders.
- pub(crate) quote_size: Option,
- /// Amount of base currency to spend on order. Required for SELL orders.
- pub(crate) base_size: Option,
-}
-
-/// Limit Good til Cancelled.
-#[derive(Serialize, Debug)]
-pub(crate) struct LimitGtc {
- /// Amount of base currency to spend on order.
- pub(crate) base_size: String,
- /// Ceiling price for which the order should get filled.
- pub(crate) limit_price: String,
- /// Post only limit order.
- pub(crate) post_only: bool,
-}
-
-/// Limit Good til Time (Date).
-#[derive(Serialize, Debug)]
-pub(crate) struct LimitGtd {
- /// Amount of base currency to spend on order.
- pub(crate) base_size: String,
- /// Ceiling price for which the order should get filled.
- pub(crate) limit_price: String,
- /// Time at which the order should be cancelled if it's not filled.
- pub(crate) end_time: String,
- /// Post only limit order.
- pub(crate) post_only: bool,
-}
-
-/// Stop Limit Good til Cancelled.
-#[derive(Serialize, Debug)]
-pub(crate) struct StopLimitGtc {
- /// Amount of base currency to spend on order.
- pub(crate) base_size: String,
- /// Ceiling price for which the order should get filled.
- pub(crate) limit_price: String,
- /// Price at which the order should trigger - if stop direction is Up, then the order will trigger when the last trade price goes above this, otherwise order will trigger when last trade price goes below this price.
- pub(crate) stop_price: String,
- /// Possible values: [UNKNOWN_STOP_DIRECTION, STOP_DIRECTION_STOP_UP, STOP_DIRECTION_STOP_DOWN]
- pub(crate) stop_direction: String,
-}
-
-/// Stop Limit Good til Time (Date).
-#[derive(Serialize, Debug)]
-pub(crate) struct StopLimitGtd {
- /// Amount of base currency to spend on order.
- pub(crate) base_size: String,
- /// Ceiling price for which the order should get filled.
- pub(crate) limit_price: String,
- /// Price at which the order should trigger - if stop direction is Up, then the order will trigger when the last trade price goes above this, otherwise order will trigger when last trade price goes below this price.
- pub(crate) stop_price: String,
- /// Time at which the order should be cancelled if it's not filled.
- pub(crate) end_time: String,
- /// Possible values: [UNKNOWN_STOP_DIRECTION, STOP_DIRECTION_STOP_UP, STOP_DIRECTION_STOP_DOWN]
- pub(crate) stop_direction: String,
-}
-
-/// Create Order Configuration.
-#[derive(Serialize, Default, Debug)]
-pub(crate) struct OrderConfiguration {
- /// Market Order
- #[serde(skip_serializing_if = "Option::is_none")]
- pub(crate) market_market_ioc: Option,
- /// Limit Order, Good til Cancelled
- #[serde(skip_serializing_if = "Option::is_none")]
- pub(crate) limit_limit_gtc: Option,
- /// Limit Order, Good til Date (time)
- #[serde(skip_serializing_if = "Option::is_none")]
- pub(crate) limit_limit_gtd: Option,
- /// Stop Limit Order, Good til Cancelled
- #[serde(skip_serializing_if = "Option::is_none")]
- pub(crate) stop_limit_stop_limit_gtc: Option,
- /// Stop Limit Order, Good til Date (time)
- #[serde(skip_serializing_if = "Option::is_none")]
- pub(crate) stop_limit_stop_limit_gtd: Option,
-}
-
-/// Represents an order created to BUY or SELL.
-#[derive(Serialize, Debug)]
-pub(crate) struct CreateOrder {
- /// Client Order ID (UUID)
- pub(crate) client_order_id: String,
- /// Product ID (pair)
- pub(crate) product_id: String,
- /// Order Side: BUY or SELL.
- pub(crate) side: String,
- /// Configuration for the order.
- pub(crate) order_configuration: OrderConfiguration,
-}
-
-/// Represents an order to be edited.
-#[derive(Serialize, Debug)]
-pub(crate) struct EditOrder {
- /// ID of the order to edit.
- pub(crate) order_id: String,
- /// New price for order.
- pub(crate) price: String,
- /// New size for order.
- pub(crate) size: String,
-}
-
-/// Represents a vector of orders IDs to cancel.
-#[derive(Serialize, Debug)]
-pub(crate) struct CancelOrders {
- /// Vector of Order IDs to cancel.
- pub(crate) order_ids: Vec,
-}
-
-/// Represents a single edit entry in the edit history of an order.
-#[derive(Serialize, Deserialize, Debug, Clone)]
-pub struct EditHistory {
- /// The price associated with the edit.
- pub price: String,
- /// The size associated with the edit.
- pub size: String,
- /// The timestamp when the edit was accepted.
- pub replace_accept_timestamp: String,
-}
-
-/// Represents an Order received from the API.
-#[derive(Serialize, Deserialize, Debug, Clone)]
-pub struct Order {
- /// The unique id for this order.
- pub order_id: String,
- /// Client specified ID of order.
- pub client_order_id: String,
- /// The product this order was created for e.g. 'BTC-USD'
- pub product_id: String,
- /// The id of the User owning this Order.
- pub user_id: String,
- /// Possible values: [UNKNOWN_ORDER_SIDE, BUY, SELL]
- pub side: String,
- /// Possible values: [OPEN, FILLED, CANCELLED, EXPIRED, FAILED, UNKNOWN_ORDER_STATUS]
- pub status: String,
- /// Possible values: [UNKNOWN_TIME_IN_FORCE, GOOD_UNTIL_DATE_TIME, GOOD_UNTIL_CANCELLED, IMMEDIATE_OR_CANCEL, FILL_OR_KILL]
- pub time_in_force: String,
- /// Timestamp for when the order was created.
- pub created_time: String,
- /// The percent of total order amount that has been filled.
- #[serde(deserialize_with = "deserialize_numeric")]
- pub completion_percentage: f64,
- /// The portion (in base currency) of total order amount that has been filled.
- #[serde(deserialize_with = "deserialize_numeric")]
- pub filled_size: f64,
- /// The average of all prices of fills for this order.
- #[serde(deserialize_with = "deserialize_numeric")]
- pub average_filled_price: f64,
- /// Commission amount.
- #[serde(deserialize_with = "deserialize_numeric")]
- pub fee: f64,
- /// Number of fills that have been posted for this order.
- #[serde(deserialize_with = "deserialize_numeric")]
- pub number_of_fills: u32,
- /// The portion (in quote current) of total order amount that has been filled.
- #[serde(deserialize_with = "deserialize_numeric")]
- pub filled_value: f64,
- /// Whether a cancel request has been initiated for the order, and not yet completed.
- pub pending_cancel: bool,
- /// Whether the order was placed with quote currency/
- pub size_in_quote: bool,
- /// The total fees for the order.
- #[serde(deserialize_with = "deserialize_numeric")]
- pub total_fees: f64,
- /// Whether the order size includes fees.
- pub size_inclusive_of_fees: bool,
- /// Derived field: filled_value + total_fees for buy orders and filled_value - total_fees for sell orders.
- #[serde(deserialize_with = "deserialize_numeric")]
- pub total_value_after_fees: f64,
- /// Possible values: \[UNKNOWN_TRIGGER_STATUS, INVALID_ORDER_TYPE, STOP_PENDING, STOP_TRIGGERED\]
- pub trigger_status: String,
- /// Possible values: \[UNKNOWN_ORDER_TYPE, MARKET, LIMIT, STOP, STOP_LIMIT\]
- pub order_type: String,
- /// Possible values: \[REJECT_REASON_UNSPECIFIED\]
- pub reject_reason: String,
- /// True if the order is fully filled, false otherwise.
- pub settled: bool,
- /// Possible values: [SPOT, FUTURE]
- pub product_type: String,
- /// Message stating why the order was rejected.
- pub reject_message: String,
- /// Message stating why the order was canceled.
- pub cancel_message: String,
- /// An array of the latest 5 edits per order.
- pub edit_history: Vec,
-}
-
-/// Represents a fill received from the API.
-#[derive(Serialize, Deserialize, Debug, Clone)]
-pub struct Fill {
- /// Unique identifier for the fill.
- pub entry_id: String,
- /// Id of the fill -- unique for all `FILL` trade_types but not unique for adjusted fills.
- pub trade_id: String,
- /// Id of the order the fill belongs to.
- pub order_id: String,
- /// Time at which this fill was completed.
- pub trade_time: String,
- /// String denoting what type of fill this is. Regular fills have the value `FILL`.
- /// Adjusted fills have possible values `REVERSAL`, `CORRECTION`, `SYNTHETIC`.
- pub trade_type: String,
- /// Price the fill was posted at.
- #[serde(deserialize_with = "deserialize_numeric")]
- pub price: f64,
- /// Amount of order that was transacted at this fill.
- #[serde(deserialize_with = "deserialize_numeric")]
- pub size: f64,
- /// Fee amount for fill.
- #[serde(deserialize_with = "deserialize_numeric")]
- pub commission: f64,
- /// The product this order was created for.
- pub product_id: String,
- /// Time at which this fill was posted.
- pub sequence_timestamp: String,
- /// Possible values: [UNKNOWN_LIQUIDITY_INDICATOR, MAKER, TAKER]
- pub liquidity_indicator: String,
- /// Whether the order was placed with quote currency.
- pub size_in_quote: bool,
- /// User that placed the order the fill belongs to.
- pub user_id: String,
- /// Possible values: [UNKNOWN_ORDER_SIDE, BUY, SELL]
- pub side: String,
-}
-
-/// Represents a list of orders received from the API.
-#[derive(Deserialize, Debug)]
-pub struct ListedOrders {
- /// Vector of orders obtained.
- pub orders: Vec,
- /// If there are additional orders.
- pub has_next: bool,
- /// Cursor used to pull more orders.
- pub cursor: String,
-}
-
-/// Represents a list of fills received from the API.
-#[derive(Deserialize, Debug)]
-pub struct ListedFills {
- /// Vector of filled orders.
- pub orders: Vec,
- /// Cursor used to pull more fills.
- pub cursor: String,
-}
-
-/// Represents a create order response from the API.
-#[derive(Deserialize, Debug)]
-pub struct OrderResponse {
- /// Whether or not the order completed correctly.
- pub success: bool,
- /// Reason the order failed, if it did.
- pub failure_reason: String,
- /// Order Id of the order created.
- pub order_id: String,
-}
-
-/// Represents a cancel order response from the API.
-#[derive(Deserialize, Debug)]
-pub(crate) struct CancelOrdersResponse {
- /// Vector of orders cancelled.
- pub(crate) results: Vec,
-}
-
-/// Represents an order when obtaining a single order from the API.
-#[derive(Deserialize, Debug)]
-pub(crate) struct OrderStatusResponse {
- /// Order received.
- pub(crate) order: Order,
-}
-
-/// Represents an order when obtaining a single order from the API.
-#[derive(Deserialize, Debug)]
-pub struct EditOrderResponse {
- /// Whether or not the order edit succeeded.
- pub success: bool,
- /// Errors associated with the changes.
- pub errors: Vec,
-}
-
-/// Errors associated with the changes.
-#[derive(Deserialize, Debug)]
-pub struct EditOrderErrors {
- /// Reason the edit failed.
- pub edit_failure_reason: Option,
- /// Reason the preview failed.
- pub preview_failure_reason: Option,
-}
-
-/// Response from a preview edit order.
-#[derive(Deserialize, Debug)]
-pub struct PreviewEditOrderResponse {
- /// Contains reasons for failure in the edit or preview edit operation.
- pub errors: Vec,
- /// The amount of slippage in the order.
- #[serde(deserialize_with = "deserialize_numeric")]
- pub slippage: f64,
- /// The total value of the order.
- #[serde(deserialize_with = "deserialize_numeric")]
- pub order_total: f64,
- /// The total commission for the order.
- #[serde(deserialize_with = "deserialize_numeric")]
- pub commission_total: f64,
- /// The size of the quote currency in the order.
- #[serde(deserialize_with = "deserialize_numeric")]
- pub quote_size: f64,
- /// The size of the base currency in the order.
- #[serde(deserialize_with = "deserialize_numeric")]
- pub base_size: f64,
- /// The best bid price at the time of the order.
- #[serde(deserialize_with = "deserialize_numeric")]
- pub best_bid: f64,
- /// The best ask price at the time of the order.
- #[serde(deserialize_with = "deserialize_numeric")]
- pub best_ask: f64,
- /// The average price at which the order was filled.
- #[serde(deserialize_with = "deserialize_numeric")]
- pub average_filled_price: f64,
-}
-
-/// Represents parameters that are optional for List Orders API request.
-#[derive(Serialize, Default, Debug)]
-pub struct ListOrdersQuery {
- /// Optional string of the product ID. Defaults to null, or fetch for all products.
- pub product_id: Option,
- /// Note: Cannot pair OPEN orders with other order types.
- pub order_status: Option>,
- /// A pagination limit with no default set. If has_next is true, additional orders are available to be fetched with pagination; also the cursor value in the response can be passed as cursor parameter in the subsequent request.
- pub limit: Option,
- /// Start date to fetch orders from, inclusive.
- pub start_date: Option,
- /// An optional end date for the query window, exclusive. If provided only orders with creation time before this date will be returned.
- pub end_date: Option