├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── kucoin ├── client.rs ├── error.rs ├── margin.rs ├── market.rs ├── mod.rs ├── model │ ├── margin.rs │ ├── market.rs │ ├── mod.rs │ ├── trade.rs │ ├── user.rs │ └── websocket.rs ├── trade.rs ├── user.rs ├── utils.rs └── websocket.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "arc-swap" 7 | version = "0.4.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d7b8a9123b8027467bce0099fe556c628a53c8d83df0507084c31e9ba2e39aff" 10 | 11 | [[package]] 12 | name = "autocfg" 13 | version = "1.0.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 16 | 17 | [[package]] 18 | name = "backtrace" 19 | version = "0.3.42" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "b4b1549d804b6c73f4817df2ba073709e96e426f12987127c48e6745568c350b" 22 | dependencies = [ 23 | "backtrace-sys", 24 | "cfg-if 0.1.10", 25 | "libc", 26 | "rustc-demangle", 27 | ] 28 | 29 | [[package]] 30 | name = "backtrace-sys" 31 | version = "0.1.32" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" 34 | dependencies = [ 35 | "cc", 36 | "libc", 37 | ] 38 | 39 | [[package]] 40 | name = "base64" 41 | version = "0.12.3" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" 44 | 45 | [[package]] 46 | name = "base64" 47 | version = "0.13.0" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 50 | 51 | [[package]] 52 | name = "bitflags" 53 | version = "1.2.1" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 56 | 57 | [[package]] 58 | name = "block-buffer" 59 | version = "0.7.3" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" 62 | dependencies = [ 63 | "block-padding", 64 | "byte-tools", 65 | "byteorder", 66 | "generic-array 0.12.3", 67 | ] 68 | 69 | [[package]] 70 | name = "block-buffer" 71 | version = "0.9.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 74 | dependencies = [ 75 | "generic-array 0.14.4", 76 | ] 77 | 78 | [[package]] 79 | name = "block-padding" 80 | version = "0.1.5" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" 83 | dependencies = [ 84 | "byte-tools", 85 | ] 86 | 87 | [[package]] 88 | name = "bumpalo" 89 | version = "3.1.2" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "5fb8038c1ddc0a5f73787b130f4cc75151e96ed33e417fde765eb5a81e3532f4" 92 | 93 | [[package]] 94 | name = "byte-tools" 95 | version = "0.3.1" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" 98 | 99 | [[package]] 100 | name = "byteorder" 101 | version = "1.3.2" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" 104 | 105 | [[package]] 106 | name = "bytes" 107 | version = "0.5.4" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" 110 | 111 | [[package]] 112 | name = "bytes" 113 | version = "1.0.1" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" 116 | 117 | [[package]] 118 | name = "c2-chacha" 119 | version = "0.2.3" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" 122 | dependencies = [ 123 | "ppv-lite86", 124 | ] 125 | 126 | [[package]] 127 | name = "cc" 128 | version = "1.0.50" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" 131 | 132 | [[package]] 133 | name = "cfg-if" 134 | version = "0.1.10" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 137 | 138 | [[package]] 139 | name = "cfg-if" 140 | version = "1.0.0" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 143 | 144 | [[package]] 145 | name = "core-foundation" 146 | version = "0.9.1" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" 149 | dependencies = [ 150 | "core-foundation-sys", 151 | "libc", 152 | ] 153 | 154 | [[package]] 155 | name = "core-foundation-sys" 156 | version = "0.8.2" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" 159 | 160 | [[package]] 161 | name = "cpuid-bool" 162 | version = "0.1.2" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" 165 | 166 | [[package]] 167 | name = "crypto-mac" 168 | version = "0.7.0" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" 171 | dependencies = [ 172 | "generic-array 0.12.3", 173 | "subtle", 174 | ] 175 | 176 | [[package]] 177 | name = "digest" 178 | version = "0.8.1" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" 181 | dependencies = [ 182 | "generic-array 0.12.3", 183 | ] 184 | 185 | [[package]] 186 | name = "digest" 187 | version = "0.9.0" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 190 | dependencies = [ 191 | "generic-array 0.14.4", 192 | ] 193 | 194 | [[package]] 195 | name = "encoding_rs" 196 | version = "0.8.22" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "cd8d03faa7fe0c1431609dfad7bbe827af30f82e1e2ae6f7ee4fca6bd764bc28" 199 | dependencies = [ 200 | "cfg-if 0.1.10", 201 | ] 202 | 203 | [[package]] 204 | name = "failure" 205 | version = "0.1.8" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" 208 | dependencies = [ 209 | "backtrace", 210 | "failure_derive", 211 | ] 212 | 213 | [[package]] 214 | name = "failure_derive" 215 | version = "0.1.7" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "030a733c8287d6213886dd487564ff5c8f6aae10278b3588ed177f9d18f8d231" 218 | dependencies = [ 219 | "proc-macro2", 220 | "quote", 221 | "syn", 222 | "synstructure", 223 | ] 224 | 225 | [[package]] 226 | name = "fake-simd" 227 | version = "0.1.2" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" 230 | 231 | [[package]] 232 | name = "fnv" 233 | version = "1.0.6" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" 236 | 237 | [[package]] 238 | name = "foreign-types" 239 | version = "0.3.2" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 242 | dependencies = [ 243 | "foreign-types-shared", 244 | ] 245 | 246 | [[package]] 247 | name = "foreign-types-shared" 248 | version = "0.1.1" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 251 | 252 | [[package]] 253 | name = "form_urlencoded" 254 | version = "1.0.0" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00" 257 | dependencies = [ 258 | "matches", 259 | "percent-encoding", 260 | ] 261 | 262 | [[package]] 263 | name = "futures" 264 | version = "0.3.12" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "da9052a1a50244d8d5aa9bf55cbc2fb6f357c86cc52e46c62ed390a7180cf150" 267 | dependencies = [ 268 | "futures-channel", 269 | "futures-core", 270 | "futures-executor", 271 | "futures-io", 272 | "futures-sink", 273 | "futures-task", 274 | "futures-util", 275 | ] 276 | 277 | [[package]] 278 | name = "futures-channel" 279 | version = "0.3.12" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "f2d31b7ec7efab6eefc7c57233bb10b847986139d88cc2f5a02a1ae6871a1846" 282 | dependencies = [ 283 | "futures-core", 284 | "futures-sink", 285 | ] 286 | 287 | [[package]] 288 | name = "futures-core" 289 | version = "0.3.12" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "79e5145dde8da7d1b3892dad07a9c98fc04bc39892b1ecc9692cf53e2b780a65" 292 | 293 | [[package]] 294 | name = "futures-executor" 295 | version = "0.3.12" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "e9e59fdc009a4b3096bf94f740a0f2424c082521f20a9b08c5c07c48d90fd9b9" 298 | dependencies = [ 299 | "futures-core", 300 | "futures-task", 301 | "futures-util", 302 | ] 303 | 304 | [[package]] 305 | name = "futures-io" 306 | version = "0.3.12" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "28be053525281ad8259d47e4de5de657b25e7bac113458555bb4b70bc6870500" 309 | 310 | [[package]] 311 | name = "futures-macro" 312 | version = "0.3.12" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "c287d25add322d9f9abdcdc5927ca398917996600182178774032e9f8258fedd" 315 | dependencies = [ 316 | "proc-macro-hack", 317 | "proc-macro2", 318 | "quote", 319 | "syn", 320 | ] 321 | 322 | [[package]] 323 | name = "futures-sink" 324 | version = "0.3.12" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "caf5c69029bda2e743fddd0582d1083951d65cc9539aebf8812f36c3491342d6" 327 | 328 | [[package]] 329 | name = "futures-task" 330 | version = "0.3.12" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "13de07eb8ea81ae445aca7b69f5f7bf15d7bf4912d8ca37d6645c77ae8a58d86" 333 | dependencies = [ 334 | "once_cell", 335 | ] 336 | 337 | [[package]] 338 | name = "futures-util" 339 | version = "0.3.12" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "632a8cd0f2a4b3fdea1657f08bde063848c3bd00f9bbf6e256b8be78802e624b" 342 | dependencies = [ 343 | "futures-channel", 344 | "futures-core", 345 | "futures-io", 346 | "futures-macro", 347 | "futures-sink", 348 | "futures-task", 349 | "memchr", 350 | "pin-project-lite", 351 | "pin-utils", 352 | "proc-macro-hack", 353 | "proc-macro-nested", 354 | "slab", 355 | ] 356 | 357 | [[package]] 358 | name = "generic-array" 359 | version = "0.12.3" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" 362 | dependencies = [ 363 | "typenum", 364 | ] 365 | 366 | [[package]] 367 | name = "generic-array" 368 | version = "0.14.4" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 371 | dependencies = [ 372 | "typenum", 373 | "version_check", 374 | ] 375 | 376 | [[package]] 377 | name = "getrandom" 378 | version = "0.1.14" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 381 | dependencies = [ 382 | "cfg-if 0.1.10", 383 | "libc", 384 | "wasi 0.9.0+wasi-snapshot-preview1", 385 | ] 386 | 387 | [[package]] 388 | name = "getrandom" 389 | version = "0.2.2" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" 392 | dependencies = [ 393 | "cfg-if 1.0.0", 394 | "libc", 395 | "wasi 0.10.2+wasi-snapshot-preview1", 396 | ] 397 | 398 | [[package]] 399 | name = "h2" 400 | version = "0.3.0" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "6b67e66362108efccd8ac053abafc8b7a8d86a37e6e48fc4f6f7485eb5e9e6a5" 403 | dependencies = [ 404 | "bytes 1.0.1", 405 | "fnv", 406 | "futures-core", 407 | "futures-sink", 408 | "futures-util", 409 | "http", 410 | "indexmap", 411 | "slab", 412 | "tokio", 413 | "tokio-util", 414 | "tracing", 415 | "tracing-futures", 416 | ] 417 | 418 | [[package]] 419 | name = "hermit-abi" 420 | version = "0.1.6" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772" 423 | dependencies = [ 424 | "libc", 425 | ] 426 | 427 | [[package]] 428 | name = "hmac" 429 | version = "0.7.1" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" 432 | dependencies = [ 433 | "crypto-mac", 434 | "digest 0.8.1", 435 | ] 436 | 437 | [[package]] 438 | name = "http" 439 | version = "0.2.0" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "b708cc7f06493459026f53b9a61a7a121a5d1ec6238dee58ea4941132b30156b" 442 | dependencies = [ 443 | "bytes 0.5.4", 444 | "fnv", 445 | "itoa", 446 | ] 447 | 448 | [[package]] 449 | name = "http-body" 450 | version = "0.4.0" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "2861bd27ee074e5ee891e8b539837a9430012e249d7f0ca2d795650f579c1994" 453 | dependencies = [ 454 | "bytes 1.0.1", 455 | "http", 456 | ] 457 | 458 | [[package]] 459 | name = "httparse" 460 | version = "1.3.4" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" 463 | 464 | [[package]] 465 | name = "httpdate" 466 | version = "0.3.2" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" 469 | 470 | [[package]] 471 | name = "hyper" 472 | version = "0.14.4" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "e8e946c2b1349055e0b72ae281b238baf1a3ea7307c7e9f9d64673bdd9c26ac7" 475 | dependencies = [ 476 | "bytes 1.0.1", 477 | "futures-channel", 478 | "futures-core", 479 | "futures-util", 480 | "h2", 481 | "http", 482 | "http-body", 483 | "httparse", 484 | "httpdate", 485 | "itoa", 486 | "pin-project", 487 | "socket2", 488 | "tokio", 489 | "tower-service", 490 | "tracing", 491 | "want", 492 | ] 493 | 494 | [[package]] 495 | name = "hyper-rustls" 496 | version = "0.22.1" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" 499 | dependencies = [ 500 | "futures-util", 501 | "hyper", 502 | "log", 503 | "rustls", 504 | "tokio", 505 | "tokio-rustls", 506 | "webpki", 507 | ] 508 | 509 | [[package]] 510 | name = "hyper-tls" 511 | version = "0.5.0" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" 514 | dependencies = [ 515 | "bytes 1.0.1", 516 | "hyper", 517 | "native-tls", 518 | "tokio", 519 | "tokio-native-tls", 520 | ] 521 | 522 | [[package]] 523 | name = "idna" 524 | version = "0.2.0" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" 527 | dependencies = [ 528 | "matches", 529 | "unicode-bidi", 530 | "unicode-normalization", 531 | ] 532 | 533 | [[package]] 534 | name = "indexmap" 535 | version = "1.3.1" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "0b54058f0a6ff80b6803da8faf8997cde53872b38f4023728f6830b06cd3c0dc" 538 | dependencies = [ 539 | "autocfg", 540 | ] 541 | 542 | [[package]] 543 | name = "input_buffer" 544 | version = "0.4.0" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" 547 | dependencies = [ 548 | "bytes 1.0.1", 549 | ] 550 | 551 | [[package]] 552 | name = "instant" 553 | version = "0.1.9" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" 556 | dependencies = [ 557 | "cfg-if 1.0.0", 558 | ] 559 | 560 | [[package]] 561 | name = "ipnet" 562 | version = "2.3.0" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" 565 | 566 | [[package]] 567 | name = "itoa" 568 | version = "0.4.4" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" 571 | 572 | [[package]] 573 | name = "js-sys" 574 | version = "0.3.47" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "5cfb73131c35423a367daf8cbd24100af0d077668c8c2943f0e7dd775fef0f65" 577 | dependencies = [ 578 | "wasm-bindgen", 579 | ] 580 | 581 | [[package]] 582 | name = "kucoin_rs" 583 | version = "0.4.4" 584 | dependencies = [ 585 | "base64 0.12.3", 586 | "failure", 587 | "futures", 588 | "hmac", 589 | "pin-project", 590 | "reqwest", 591 | "serde", 592 | "serde_derive", 593 | "serde_json", 594 | "sha2", 595 | "streamunordered", 596 | "tokio", 597 | "tokio-native-tls", 598 | "tokio-tungstenite", 599 | "tungstenite 0.13.0", 600 | "url", 601 | ] 602 | 603 | [[package]] 604 | name = "lazy_static" 605 | version = "1.4.0" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 608 | 609 | [[package]] 610 | name = "libc" 611 | version = "0.2.86" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" 614 | 615 | [[package]] 616 | name = "lock_api" 617 | version = "0.4.2" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" 620 | dependencies = [ 621 | "scopeguard", 622 | ] 623 | 624 | [[package]] 625 | name = "log" 626 | version = "0.4.8" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 629 | dependencies = [ 630 | "cfg-if 0.1.10", 631 | ] 632 | 633 | [[package]] 634 | name = "matches" 635 | version = "0.1.8" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 638 | 639 | [[package]] 640 | name = "memchr" 641 | version = "2.3.0" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223" 644 | 645 | [[package]] 646 | name = "mime" 647 | version = "0.3.16" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 650 | 651 | [[package]] 652 | name = "mio" 653 | version = "0.7.8" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "dc250d6848c90d719ea2ce34546fb5df7af1d3fd189d10bf7bad80bfcebecd95" 656 | dependencies = [ 657 | "libc", 658 | "log", 659 | "miow", 660 | "ntapi", 661 | "winapi", 662 | ] 663 | 664 | [[package]] 665 | name = "miow" 666 | version = "0.3.6" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" 669 | dependencies = [ 670 | "socket2", 671 | "winapi", 672 | ] 673 | 674 | [[package]] 675 | name = "native-tls" 676 | version = "0.2.7" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" 679 | dependencies = [ 680 | "lazy_static", 681 | "libc", 682 | "log", 683 | "openssl", 684 | "openssl-probe", 685 | "openssl-sys", 686 | "schannel", 687 | "security-framework", 688 | "security-framework-sys", 689 | "tempfile", 690 | ] 691 | 692 | [[package]] 693 | name = "ntapi" 694 | version = "0.3.4" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "7a31937dea023539c72ddae0e3571deadc1414b300483fa7aaec176168cfa9d2" 697 | dependencies = [ 698 | "winapi", 699 | ] 700 | 701 | [[package]] 702 | name = "num_cpus" 703 | version = "1.12.0" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" 706 | dependencies = [ 707 | "hermit-abi", 708 | "libc", 709 | ] 710 | 711 | [[package]] 712 | name = "once_cell" 713 | version = "1.5.2" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" 716 | 717 | [[package]] 718 | name = "opaque-debug" 719 | version = "0.2.3" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" 722 | 723 | [[package]] 724 | name = "opaque-debug" 725 | version = "0.3.0" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 728 | 729 | [[package]] 730 | name = "openssl" 731 | version = "0.10.32" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "038d43985d1ddca7a9900630d8cd031b56e4794eecc2e9ea39dd17aa04399a70" 734 | dependencies = [ 735 | "bitflags", 736 | "cfg-if 1.0.0", 737 | "foreign-types", 738 | "lazy_static", 739 | "libc", 740 | "openssl-sys", 741 | ] 742 | 743 | [[package]] 744 | name = "openssl-probe" 745 | version = "0.1.2" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" 748 | 749 | [[package]] 750 | name = "openssl-sys" 751 | version = "0.9.60" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "921fc71883267538946025deffb622905ecad223c28efbfdef9bb59a0175f3e6" 754 | dependencies = [ 755 | "autocfg", 756 | "cc", 757 | "libc", 758 | "pkg-config", 759 | "vcpkg", 760 | ] 761 | 762 | [[package]] 763 | name = "parking_lot" 764 | version = "0.11.1" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" 767 | dependencies = [ 768 | "instant", 769 | "lock_api", 770 | "parking_lot_core", 771 | ] 772 | 773 | [[package]] 774 | name = "parking_lot_core" 775 | version = "0.8.3" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" 778 | dependencies = [ 779 | "cfg-if 1.0.0", 780 | "instant", 781 | "libc", 782 | "redox_syscall 0.2.5", 783 | "smallvec", 784 | "winapi", 785 | ] 786 | 787 | [[package]] 788 | name = "percent-encoding" 789 | version = "2.1.0" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 792 | 793 | [[package]] 794 | name = "pin-project" 795 | version = "1.0.5" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "96fa8ebb90271c4477f144354485b8068bd8f6b78b428b01ba892ca26caf0b63" 798 | dependencies = [ 799 | "pin-project-internal", 800 | ] 801 | 802 | [[package]] 803 | name = "pin-project-internal" 804 | version = "1.0.5" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "758669ae3558c6f74bd2a18b41f7ac0b5a195aea6639d6a9b5e5d1ad5ba24c0b" 807 | dependencies = [ 808 | "proc-macro2", 809 | "quote", 810 | "syn", 811 | ] 812 | 813 | [[package]] 814 | name = "pin-project-lite" 815 | version = "0.2.4" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827" 818 | 819 | [[package]] 820 | name = "pin-utils" 821 | version = "0.1.0" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 824 | 825 | [[package]] 826 | name = "pkg-config" 827 | version = "0.3.17" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" 830 | 831 | [[package]] 832 | name = "ppv-lite86" 833 | version = "0.2.10" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 836 | 837 | [[package]] 838 | name = "proc-macro-hack" 839 | version = "0.5.19" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 842 | 843 | [[package]] 844 | name = "proc-macro-nested" 845 | version = "0.1.3" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" 848 | 849 | [[package]] 850 | name = "proc-macro2" 851 | version = "1.0.24" 852 | source = "registry+https://github.com/rust-lang/crates.io-index" 853 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 854 | dependencies = [ 855 | "unicode-xid", 856 | ] 857 | 858 | [[package]] 859 | name = "quote" 860 | version = "1.0.2" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" 863 | dependencies = [ 864 | "proc-macro2", 865 | ] 866 | 867 | [[package]] 868 | name = "rand" 869 | version = "0.7.3" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 872 | dependencies = [ 873 | "getrandom 0.1.14", 874 | "libc", 875 | "rand_chacha 0.2.1", 876 | "rand_core 0.5.1", 877 | "rand_hc 0.2.0", 878 | ] 879 | 880 | [[package]] 881 | name = "rand" 882 | version = "0.8.3" 883 | source = "registry+https://github.com/rust-lang/crates.io-index" 884 | checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" 885 | dependencies = [ 886 | "libc", 887 | "rand_chacha 0.3.0", 888 | "rand_core 0.6.2", 889 | "rand_hc 0.3.0", 890 | ] 891 | 892 | [[package]] 893 | name = "rand_chacha" 894 | version = "0.2.1" 895 | source = "registry+https://github.com/rust-lang/crates.io-index" 896 | checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" 897 | dependencies = [ 898 | "c2-chacha", 899 | "rand_core 0.5.1", 900 | ] 901 | 902 | [[package]] 903 | name = "rand_chacha" 904 | version = "0.3.0" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" 907 | dependencies = [ 908 | "ppv-lite86", 909 | "rand_core 0.6.2", 910 | ] 911 | 912 | [[package]] 913 | name = "rand_core" 914 | version = "0.5.1" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 917 | dependencies = [ 918 | "getrandom 0.1.14", 919 | ] 920 | 921 | [[package]] 922 | name = "rand_core" 923 | version = "0.6.2" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" 926 | dependencies = [ 927 | "getrandom 0.2.2", 928 | ] 929 | 930 | [[package]] 931 | name = "rand_hc" 932 | version = "0.2.0" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 935 | dependencies = [ 936 | "rand_core 0.5.1", 937 | ] 938 | 939 | [[package]] 940 | name = "rand_hc" 941 | version = "0.3.0" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" 944 | dependencies = [ 945 | "rand_core 0.6.2", 946 | ] 947 | 948 | [[package]] 949 | name = "redox_syscall" 950 | version = "0.1.56" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 953 | 954 | [[package]] 955 | name = "redox_syscall" 956 | version = "0.2.5" 957 | source = "registry+https://github.com/rust-lang/crates.io-index" 958 | checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" 959 | dependencies = [ 960 | "bitflags", 961 | ] 962 | 963 | [[package]] 964 | name = "remove_dir_all" 965 | version = "0.5.2" 966 | source = "registry+https://github.com/rust-lang/crates.io-index" 967 | checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" 968 | dependencies = [ 969 | "winapi", 970 | ] 971 | 972 | [[package]] 973 | name = "reqwest" 974 | version = "0.11.1" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "0460542b551950620a3648c6aa23318ac6b3cd779114bd873209e6e8b5eb1c34" 977 | dependencies = [ 978 | "base64 0.13.0", 979 | "bytes 1.0.1", 980 | "encoding_rs", 981 | "futures-core", 982 | "futures-util", 983 | "http", 984 | "http-body", 985 | "hyper", 986 | "hyper-rustls", 987 | "hyper-tls", 988 | "ipnet", 989 | "js-sys", 990 | "lazy_static", 991 | "log", 992 | "mime", 993 | "native-tls", 994 | "percent-encoding", 995 | "pin-project-lite", 996 | "rustls", 997 | "serde", 998 | "serde_json", 999 | "serde_urlencoded", 1000 | "tokio", 1001 | "tokio-native-tls", 1002 | "tokio-rustls", 1003 | "url", 1004 | "wasm-bindgen", 1005 | "wasm-bindgen-futures", 1006 | "web-sys", 1007 | "webpki-roots", 1008 | "winreg", 1009 | ] 1010 | 1011 | [[package]] 1012 | name = "ring" 1013 | version = "0.16.12" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "1ba5a8ec64ee89a76c98c549af81ff14813df09c3e6dc4766c3856da48597a0c" 1016 | dependencies = [ 1017 | "cc", 1018 | "lazy_static", 1019 | "libc", 1020 | "spin", 1021 | "untrusted", 1022 | "web-sys", 1023 | "winapi", 1024 | ] 1025 | 1026 | [[package]] 1027 | name = "rustc-demangle" 1028 | version = "0.1.16" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" 1031 | 1032 | [[package]] 1033 | name = "rustls" 1034 | version = "0.19.0" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "064fd21ff87c6e87ed4506e68beb42459caa4a0e2eb144932e6776768556980b" 1037 | dependencies = [ 1038 | "base64 0.13.0", 1039 | "log", 1040 | "ring", 1041 | "sct", 1042 | "webpki", 1043 | ] 1044 | 1045 | [[package]] 1046 | name = "ryu" 1047 | version = "1.0.2" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" 1050 | 1051 | [[package]] 1052 | name = "schannel" 1053 | version = "0.1.16" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "87f550b06b6cba9c8b8be3ee73f391990116bf527450d2556e9b9ce263b9a021" 1056 | dependencies = [ 1057 | "lazy_static", 1058 | "winapi", 1059 | ] 1060 | 1061 | [[package]] 1062 | name = "scopeguard" 1063 | version = "1.1.0" 1064 | source = "registry+https://github.com/rust-lang/crates.io-index" 1065 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1066 | 1067 | [[package]] 1068 | name = "sct" 1069 | version = "0.6.0" 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" 1071 | checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" 1072 | dependencies = [ 1073 | "ring", 1074 | "untrusted", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "security-framework" 1079 | version = "2.0.0" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "c1759c2e3c8580017a484a7ac56d3abc5a6c1feadf88db2f3633f12ae4268c69" 1082 | dependencies = [ 1083 | "bitflags", 1084 | "core-foundation", 1085 | "core-foundation-sys", 1086 | "libc", 1087 | "security-framework-sys", 1088 | ] 1089 | 1090 | [[package]] 1091 | name = "security-framework-sys" 1092 | version = "2.0.0" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "f99b9d5e26d2a71633cc4f2ebae7cc9f874044e0c351a27e17892d76dce5678b" 1095 | dependencies = [ 1096 | "core-foundation-sys", 1097 | "libc", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "serde" 1102 | version = "1.0.117" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" 1105 | 1106 | [[package]] 1107 | name = "serde_derive" 1108 | version = "1.0.117" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" 1111 | dependencies = [ 1112 | "proc-macro2", 1113 | "quote", 1114 | "syn", 1115 | ] 1116 | 1117 | [[package]] 1118 | name = "serde_json" 1119 | version = "1.0.59" 1120 | source = "registry+https://github.com/rust-lang/crates.io-index" 1121 | checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" 1122 | dependencies = [ 1123 | "itoa", 1124 | "ryu", 1125 | "serde", 1126 | ] 1127 | 1128 | [[package]] 1129 | name = "serde_urlencoded" 1130 | version = "0.7.0" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" 1133 | dependencies = [ 1134 | "form_urlencoded", 1135 | "itoa", 1136 | "ryu", 1137 | "serde", 1138 | ] 1139 | 1140 | [[package]] 1141 | name = "sha-1" 1142 | version = "0.9.4" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "dfebf75d25bd900fd1e7d11501efab59bc846dbc76196839663e6637bba9f25f" 1145 | dependencies = [ 1146 | "block-buffer 0.9.0", 1147 | "cfg-if 1.0.0", 1148 | "cpuid-bool", 1149 | "digest 0.9.0", 1150 | "opaque-debug 0.3.0", 1151 | ] 1152 | 1153 | [[package]] 1154 | name = "sha2" 1155 | version = "0.8.2" 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" 1157 | checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" 1158 | dependencies = [ 1159 | "block-buffer 0.7.3", 1160 | "digest 0.8.1", 1161 | "fake-simd", 1162 | "opaque-debug 0.2.3", 1163 | ] 1164 | 1165 | [[package]] 1166 | name = "signal-hook-registry" 1167 | version = "1.2.0" 1168 | source = "registry+https://github.com/rust-lang/crates.io-index" 1169 | checksum = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" 1170 | dependencies = [ 1171 | "arc-swap", 1172 | "libc", 1173 | ] 1174 | 1175 | [[package]] 1176 | name = "slab" 1177 | version = "0.4.2" 1178 | source = "registry+https://github.com/rust-lang/crates.io-index" 1179 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 1180 | 1181 | [[package]] 1182 | name = "smallvec" 1183 | version = "1.6.1" 1184 | source = "registry+https://github.com/rust-lang/crates.io-index" 1185 | checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" 1186 | 1187 | [[package]] 1188 | name = "socket2" 1189 | version = "0.3.19" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" 1192 | dependencies = [ 1193 | "cfg-if 1.0.0", 1194 | "libc", 1195 | "winapi", 1196 | ] 1197 | 1198 | [[package]] 1199 | name = "spin" 1200 | version = "0.5.2" 1201 | source = "registry+https://github.com/rust-lang/crates.io-index" 1202 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 1203 | 1204 | [[package]] 1205 | name = "streamunordered" 1206 | version = "0.5.1" 1207 | source = "registry+https://github.com/rust-lang/crates.io-index" 1208 | checksum = "f9394ee1338fee8370bee649f8a7170b3a56917903a0956467ad192dcf8699ca" 1209 | dependencies = [ 1210 | "futures-core", 1211 | "futures-sink", 1212 | "futures-util", 1213 | "slab", 1214 | ] 1215 | 1216 | [[package]] 1217 | name = "subtle" 1218 | version = "1.0.0" 1219 | source = "registry+https://github.com/rust-lang/crates.io-index" 1220 | checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" 1221 | 1222 | [[package]] 1223 | name = "syn" 1224 | version = "1.0.60" 1225 | source = "registry+https://github.com/rust-lang/crates.io-index" 1226 | checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" 1227 | dependencies = [ 1228 | "proc-macro2", 1229 | "quote", 1230 | "unicode-xid", 1231 | ] 1232 | 1233 | [[package]] 1234 | name = "synstructure" 1235 | version = "0.12.3" 1236 | source = "registry+https://github.com/rust-lang/crates.io-index" 1237 | checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" 1238 | dependencies = [ 1239 | "proc-macro2", 1240 | "quote", 1241 | "syn", 1242 | "unicode-xid", 1243 | ] 1244 | 1245 | [[package]] 1246 | name = "tempfile" 1247 | version = "3.1.0" 1248 | source = "registry+https://github.com/rust-lang/crates.io-index" 1249 | checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" 1250 | dependencies = [ 1251 | "cfg-if 0.1.10", 1252 | "libc", 1253 | "rand 0.7.3", 1254 | "redox_syscall 0.1.56", 1255 | "remove_dir_all", 1256 | "winapi", 1257 | ] 1258 | 1259 | [[package]] 1260 | name = "thiserror" 1261 | version = "1.0.23" 1262 | source = "registry+https://github.com/rust-lang/crates.io-index" 1263 | checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146" 1264 | dependencies = [ 1265 | "thiserror-impl", 1266 | ] 1267 | 1268 | [[package]] 1269 | name = "thiserror-impl" 1270 | version = "1.0.23" 1271 | source = "registry+https://github.com/rust-lang/crates.io-index" 1272 | checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1" 1273 | dependencies = [ 1274 | "proc-macro2", 1275 | "quote", 1276 | "syn", 1277 | ] 1278 | 1279 | [[package]] 1280 | name = "tokio" 1281 | version = "1.2.0" 1282 | source = "registry+https://github.com/rust-lang/crates.io-index" 1283 | checksum = "e8190d04c665ea9e6b6a0dc45523ade572c088d2e6566244c1122671dbf4ae3a" 1284 | dependencies = [ 1285 | "autocfg", 1286 | "bytes 1.0.1", 1287 | "libc", 1288 | "memchr", 1289 | "mio", 1290 | "num_cpus", 1291 | "once_cell", 1292 | "parking_lot", 1293 | "pin-project-lite", 1294 | "signal-hook-registry", 1295 | "tokio-macros", 1296 | "winapi", 1297 | ] 1298 | 1299 | [[package]] 1300 | name = "tokio-macros" 1301 | version = "1.1.0" 1302 | source = "registry+https://github.com/rust-lang/crates.io-index" 1303 | checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57" 1304 | dependencies = [ 1305 | "proc-macro2", 1306 | "quote", 1307 | "syn", 1308 | ] 1309 | 1310 | [[package]] 1311 | name = "tokio-native-tls" 1312 | version = "0.3.0" 1313 | source = "registry+https://github.com/rust-lang/crates.io-index" 1314 | checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" 1315 | dependencies = [ 1316 | "native-tls", 1317 | "tokio", 1318 | ] 1319 | 1320 | [[package]] 1321 | name = "tokio-rustls" 1322 | version = "0.22.0" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" 1325 | dependencies = [ 1326 | "rustls", 1327 | "tokio", 1328 | "webpki", 1329 | ] 1330 | 1331 | [[package]] 1332 | name = "tokio-tungstenite" 1333 | version = "0.13.0" 1334 | source = "registry+https://github.com/rust-lang/crates.io-index" 1335 | checksum = "e1a5f475f1b9d077ea1017ecbc60890fda8e54942d680ca0b1d2b47cfa2d861b" 1336 | dependencies = [ 1337 | "futures-util", 1338 | "log", 1339 | "native-tls", 1340 | "pin-project", 1341 | "tokio", 1342 | "tokio-native-tls", 1343 | "tungstenite 0.12.0", 1344 | ] 1345 | 1346 | [[package]] 1347 | name = "tokio-util" 1348 | version = "0.6.3" 1349 | source = "registry+https://github.com/rust-lang/crates.io-index" 1350 | checksum = "ebb7cb2f00c5ae8df755b252306272cd1790d39728363936e01827e11f0b017b" 1351 | dependencies = [ 1352 | "bytes 1.0.1", 1353 | "futures-core", 1354 | "futures-sink", 1355 | "log", 1356 | "pin-project-lite", 1357 | "tokio", 1358 | ] 1359 | 1360 | [[package]] 1361 | name = "tower-service" 1362 | version = "0.3.0" 1363 | source = "registry+https://github.com/rust-lang/crates.io-index" 1364 | checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" 1365 | 1366 | [[package]] 1367 | name = "tracing" 1368 | version = "0.1.24" 1369 | source = "registry+https://github.com/rust-lang/crates.io-index" 1370 | checksum = "f77d3842f76ca899ff2dbcf231c5c65813dea431301d6eb686279c15c4464f12" 1371 | dependencies = [ 1372 | "cfg-if 1.0.0", 1373 | "pin-project-lite", 1374 | "tracing-core", 1375 | ] 1376 | 1377 | [[package]] 1378 | name = "tracing-core" 1379 | version = "0.1.17" 1380 | source = "registry+https://github.com/rust-lang/crates.io-index" 1381 | checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" 1382 | dependencies = [ 1383 | "lazy_static", 1384 | ] 1385 | 1386 | [[package]] 1387 | name = "tracing-futures" 1388 | version = "0.2.5" 1389 | source = "registry+https://github.com/rust-lang/crates.io-index" 1390 | checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" 1391 | dependencies = [ 1392 | "pin-project", 1393 | "tracing", 1394 | ] 1395 | 1396 | [[package]] 1397 | name = "try-lock" 1398 | version = "0.2.2" 1399 | source = "registry+https://github.com/rust-lang/crates.io-index" 1400 | checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" 1401 | 1402 | [[package]] 1403 | name = "tungstenite" 1404 | version = "0.12.0" 1405 | source = "registry+https://github.com/rust-lang/crates.io-index" 1406 | checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24" 1407 | dependencies = [ 1408 | "base64 0.13.0", 1409 | "byteorder", 1410 | "bytes 1.0.1", 1411 | "http", 1412 | "httparse", 1413 | "input_buffer", 1414 | "log", 1415 | "native-tls", 1416 | "rand 0.8.3", 1417 | "sha-1", 1418 | "url", 1419 | "utf-8", 1420 | ] 1421 | 1422 | [[package]] 1423 | name = "tungstenite" 1424 | version = "0.13.0" 1425 | source = "registry+https://github.com/rust-lang/crates.io-index" 1426 | checksum = "5fe8dada8c1a3aeca77d6b51a4f1314e0f4b8e438b7b1b71e3ddaca8080e4093" 1427 | dependencies = [ 1428 | "base64 0.13.0", 1429 | "byteorder", 1430 | "bytes 1.0.1", 1431 | "http", 1432 | "httparse", 1433 | "input_buffer", 1434 | "log", 1435 | "native-tls", 1436 | "rand 0.8.3", 1437 | "sha-1", 1438 | "thiserror", 1439 | "url", 1440 | "utf-8", 1441 | ] 1442 | 1443 | [[package]] 1444 | name = "typenum" 1445 | version = "1.12.0" 1446 | source = "registry+https://github.com/rust-lang/crates.io-index" 1447 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 1448 | 1449 | [[package]] 1450 | name = "unicode-bidi" 1451 | version = "0.3.4" 1452 | source = "registry+https://github.com/rust-lang/crates.io-index" 1453 | checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 1454 | dependencies = [ 1455 | "matches", 1456 | ] 1457 | 1458 | [[package]] 1459 | name = "unicode-normalization" 1460 | version = "0.1.12" 1461 | source = "registry+https://github.com/rust-lang/crates.io-index" 1462 | checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" 1463 | dependencies = [ 1464 | "smallvec", 1465 | ] 1466 | 1467 | [[package]] 1468 | name = "unicode-xid" 1469 | version = "0.2.0" 1470 | source = "registry+https://github.com/rust-lang/crates.io-index" 1471 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 1472 | 1473 | [[package]] 1474 | name = "untrusted" 1475 | version = "0.7.1" 1476 | source = "registry+https://github.com/rust-lang/crates.io-index" 1477 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 1478 | 1479 | [[package]] 1480 | name = "url" 1481 | version = "2.2.1" 1482 | source = "registry+https://github.com/rust-lang/crates.io-index" 1483 | checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" 1484 | dependencies = [ 1485 | "form_urlencoded", 1486 | "idna", 1487 | "matches", 1488 | "percent-encoding", 1489 | ] 1490 | 1491 | [[package]] 1492 | name = "utf-8" 1493 | version = "0.7.5" 1494 | source = "registry+https://github.com/rust-lang/crates.io-index" 1495 | checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" 1496 | 1497 | [[package]] 1498 | name = "vcpkg" 1499 | version = "0.2.8" 1500 | source = "registry+https://github.com/rust-lang/crates.io-index" 1501 | checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" 1502 | 1503 | [[package]] 1504 | name = "version_check" 1505 | version = "0.9.1" 1506 | source = "registry+https://github.com/rust-lang/crates.io-index" 1507 | checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" 1508 | 1509 | [[package]] 1510 | name = "want" 1511 | version = "0.3.0" 1512 | source = "registry+https://github.com/rust-lang/crates.io-index" 1513 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1514 | dependencies = [ 1515 | "log", 1516 | "try-lock", 1517 | ] 1518 | 1519 | [[package]] 1520 | name = "wasi" 1521 | version = "0.9.0+wasi-snapshot-preview1" 1522 | source = "registry+https://github.com/rust-lang/crates.io-index" 1523 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 1524 | 1525 | [[package]] 1526 | name = "wasi" 1527 | version = "0.10.2+wasi-snapshot-preview1" 1528 | source = "registry+https://github.com/rust-lang/crates.io-index" 1529 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 1530 | 1531 | [[package]] 1532 | name = "wasm-bindgen" 1533 | version = "0.2.70" 1534 | source = "registry+https://github.com/rust-lang/crates.io-index" 1535 | checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be" 1536 | dependencies = [ 1537 | "cfg-if 1.0.0", 1538 | "serde", 1539 | "serde_json", 1540 | "wasm-bindgen-macro", 1541 | ] 1542 | 1543 | [[package]] 1544 | name = "wasm-bindgen-backend" 1545 | version = "0.2.70" 1546 | source = "registry+https://github.com/rust-lang/crates.io-index" 1547 | checksum = "7bc45447f0d4573f3d65720f636bbcc3dd6ce920ed704670118650bcd47764c7" 1548 | dependencies = [ 1549 | "bumpalo", 1550 | "lazy_static", 1551 | "log", 1552 | "proc-macro2", 1553 | "quote", 1554 | "syn", 1555 | "wasm-bindgen-shared", 1556 | ] 1557 | 1558 | [[package]] 1559 | name = "wasm-bindgen-futures" 1560 | version = "0.4.20" 1561 | source = "registry+https://github.com/rust-lang/crates.io-index" 1562 | checksum = "3de431a2910c86679c34283a33f66f4e4abd7e0aec27b6669060148872aadf94" 1563 | dependencies = [ 1564 | "cfg-if 1.0.0", 1565 | "js-sys", 1566 | "wasm-bindgen", 1567 | "web-sys", 1568 | ] 1569 | 1570 | [[package]] 1571 | name = "wasm-bindgen-macro" 1572 | version = "0.2.70" 1573 | source = "registry+https://github.com/rust-lang/crates.io-index" 1574 | checksum = "3b8853882eef39593ad4174dd26fc9865a64e84026d223f63bb2c42affcbba2c" 1575 | dependencies = [ 1576 | "quote", 1577 | "wasm-bindgen-macro-support", 1578 | ] 1579 | 1580 | [[package]] 1581 | name = "wasm-bindgen-macro-support" 1582 | version = "0.2.70" 1583 | source = "registry+https://github.com/rust-lang/crates.io-index" 1584 | checksum = "4133b5e7f2a531fa413b3a1695e925038a05a71cf67e87dafa295cb645a01385" 1585 | dependencies = [ 1586 | "proc-macro2", 1587 | "quote", 1588 | "syn", 1589 | "wasm-bindgen-backend", 1590 | "wasm-bindgen-shared", 1591 | ] 1592 | 1593 | [[package]] 1594 | name = "wasm-bindgen-shared" 1595 | version = "0.2.70" 1596 | source = "registry+https://github.com/rust-lang/crates.io-index" 1597 | checksum = "dd4945e4943ae02d15c13962b38a5b1e81eadd4b71214eee75af64a4d6a4fd64" 1598 | 1599 | [[package]] 1600 | name = "web-sys" 1601 | version = "0.3.47" 1602 | source = "registry+https://github.com/rust-lang/crates.io-index" 1603 | checksum = "c40dc691fc48003eba817c38da7113c15698142da971298003cac3ef175680b3" 1604 | dependencies = [ 1605 | "js-sys", 1606 | "wasm-bindgen", 1607 | ] 1608 | 1609 | [[package]] 1610 | name = "webpki" 1611 | version = "0.21.2" 1612 | source = "registry+https://github.com/rust-lang/crates.io-index" 1613 | checksum = "f1f50e1972865d6b1adb54167d1c8ed48606004c2c9d0ea5f1eeb34d95e863ef" 1614 | dependencies = [ 1615 | "ring", 1616 | "untrusted", 1617 | ] 1618 | 1619 | [[package]] 1620 | name = "webpki-roots" 1621 | version = "0.21.0" 1622 | source = "registry+https://github.com/rust-lang/crates.io-index" 1623 | checksum = "82015b7e0b8bad8185994674a13a93306bea76cf5a16c5a181382fd3a5ec2376" 1624 | dependencies = [ 1625 | "webpki", 1626 | ] 1627 | 1628 | [[package]] 1629 | name = "winapi" 1630 | version = "0.3.8" 1631 | source = "registry+https://github.com/rust-lang/crates.io-index" 1632 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 1633 | dependencies = [ 1634 | "winapi-i686-pc-windows-gnu", 1635 | "winapi-x86_64-pc-windows-gnu", 1636 | ] 1637 | 1638 | [[package]] 1639 | name = "winapi-i686-pc-windows-gnu" 1640 | version = "0.4.0" 1641 | source = "registry+https://github.com/rust-lang/crates.io-index" 1642 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1643 | 1644 | [[package]] 1645 | name = "winapi-x86_64-pc-windows-gnu" 1646 | version = "0.4.0" 1647 | source = "registry+https://github.com/rust-lang/crates.io-index" 1648 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1649 | 1650 | [[package]] 1651 | name = "winreg" 1652 | version = "0.7.0" 1653 | source = "registry+https://github.com/rust-lang/crates.io-index" 1654 | checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" 1655 | dependencies = [ 1656 | "winapi", 1657 | ] 1658 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Eric Abrahams "] 3 | description = "Rust async library for the Kucoin API" 4 | documentation = "https://docs.rs/crate/kucoin_rs" 5 | edition = "2018" 6 | homepage = "https://github.com/esvwdev/kucoin_rs" 7 | keywords = ["kucoin", "cyptocurrency", "trading", "algotrading", "async"] 8 | license = "MIT" 9 | name = "kucoin_rs" 10 | readme = "README.md" 11 | repository = "https://github.com/escwdev/kucoin_rs" 12 | version = "0.4.4" 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | base64 = "0.12.0" 18 | failure = "0.1.7" 19 | futures = "0.3.9" 20 | hmac = "0.7.1" 21 | pin-project = "1.0.5" 22 | serde = "1.0.104" 23 | serde_derive = "1.0.104" 24 | serde_json = "1.0.48" 25 | sha2 = "0.8.1" 26 | streamunordered = "0.5" 27 | reqwest = { version = "0.11.1", features = ["json", "rustls-tls"] } 28 | tokio = { version = "1.0.1", features = ["full"]} 29 | tokio-native-tls = "0.3.0" 30 | tokio-tungstenite = { version = "0.13.0", features = ["tls"] } 31 | tungstenite = "0.13.0" 32 | url = "2.1.1" 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Eric 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kucoin_rs 2 | 3 | kucoin_rs is an open source library SDK/API wrapper for the 4 | [Kucoin Cryptocurrency Exchange](https://www.kucoin.com/). 5 | 6 | Trading cryptocurrencies is high risk and there are no guarentees towards the stability or effectiveness 7 | of this project. Comments, contributions, stars and donations are, however, all welcome. 8 | 9 | ## Description 10 | 11 | kucoin_rs supports all currently available Kucoin REST and Websocket endpoints. It is designed to be 12 | async and relies primarily on the tokio async runtime, reqwest for the HTTP layer and tokio_tungstenite 13 | for the Websocket layer. 14 | 15 | For the official API documentation visit [Kucoin Docs](https://docs.kucoin.com/). 16 | 17 | For the library specific documentation please visit [kucoin_rs](https://docs.rs/kucoin_rs) 18 | 19 | ## Getting Started 20 | 21 | The library can be used either directly through cloning the git repository and directly linking to your project or by utilizing cargo 22 | and installing the desired version. Once the library is accessible, bring the extern crate into your project. 23 | 24 | If you need information on particular endpoints please see the library specific documentation. If you clone the git you can run 25 | `cargo doc --open --no-deps` to view them locally. Alternatively, you can run `cargo doc --open` on your own projects that have added 26 | `kucoin_rs` as a depedency. Lastly you can visit [kucoin_rs](https://docs.rs/kucoin_rs). 27 | 28 | ## Authorization 29 | 30 | Authorization is required for many of the endpoints. The [`Kucoin Client`] handles all 31 | header construction but does require that the client is initialized with credentials to do so. To include credentials do the following: 32 | 33 | ```rust 34 | use kucoin_rs::kucoin::client::{Kucoin, Credentials, KucoinEnv}; 35 | 36 | let credentials = Credentials::new( 37 | "xxxxxxxxxxxxxXXXXXXxxx", // API KEY 38 | "XXxxxxx-xxxxxx-xXxxxx-xxxx", // SECRET KEY 39 | "xxxxxx" // PASSPHRASE 40 | ); 41 | 42 | let api = Kucoin::new(KucoinEnv::Live, Some(credentials)); 43 | ``` 44 | A non-authorized client can be used for accessing Public Endpoints by inputting a None: `Kucoin::new(KucoinEnv::Live, None);` 45 | 46 | ## Contribution 47 | 48 | Contributions are more than welcome for fixing bugs, writing further documentation, writing further tests, 49 | adding features or helping to improve performance. I'll do my best to review and implement pull requests. 50 | 51 | ## Donations 52 | 53 | Donations are always appreciated and help support development of more open source trading tools. 54 | 55 | BTC: 3KvTuAnv7o2VAf4LGgg1MiDURd2DgjFGaa 56 | 57 | ETH: 0x7713a223e0e86355ac02b1e0de77695e822071cf 58 | 59 | NEO: AWngpjmoXPHiJH6rtf81brPiyPomYAqe8j 60 | 61 | Contact me for any other specific cryptocurrencies you'd prefer to use. 62 | 63 | ## License 64 | 65 | This project is open source and uses the MIT license. Feel free to utilize it in whatever way you see fit. 66 | 67 | -------------------------------------------------------------------------------- /src/kucoin/client.rs: -------------------------------------------------------------------------------- 1 | use reqwest; 2 | use std::collections::HashMap; 3 | use std::time::Duration; 4 | 5 | use base64::encode; 6 | use failure; 7 | use hmac::{Hmac, Mac}; 8 | use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; 9 | use serde_json::json; 10 | use sha2::Sha256; 11 | 12 | use super::error::APIError; 13 | use super::model::Method; 14 | use super::utils::get_time; 15 | 16 | // Alias for HMAC-SHA256 17 | type HmacSha256 = Hmac; 18 | 19 | #[derive(Debug, Clone)] 20 | pub struct Credentials { 21 | api_key: String, 22 | secret_key: String, 23 | passphrase: String, 24 | } 25 | 26 | impl Credentials { 27 | pub fn new(api_key: &str, secret_key: &str, passphrase: &str) -> Self { 28 | Credentials { 29 | api_key: api_key.to_string(), 30 | secret_key: secret_key.to_string(), 31 | passphrase: passphrase.to_string(), 32 | } 33 | } 34 | } 35 | 36 | #[derive(Debug, Clone)] 37 | pub enum KucoinEnv { 38 | Live, 39 | Sandbox, 40 | } 41 | 42 | #[derive(Debug, Clone)] 43 | pub struct Kucoin { 44 | credentials: Option, 45 | environment: KucoinEnv, 46 | pub prefix: String, 47 | pub client: reqwest::Client, 48 | } 49 | 50 | impl Kucoin { 51 | pub fn new( 52 | environment: KucoinEnv, 53 | credentials: Option, 54 | ) -> Result { 55 | let client = reqwest::Client::builder() 56 | // .use_rustls_tls() 57 | .timeout(Duration::from_secs(60)) 58 | .build()?; 59 | let prefix = match environment { 60 | KucoinEnv::Live => String::from("https://api.kucoin.com"), 61 | KucoinEnv::Sandbox => String::from("https://openapi-sandbox.kucoin.com"), 62 | }; 63 | Ok(Kucoin { 64 | credentials, 65 | environment, 66 | prefix, 67 | client, 68 | }) 69 | } 70 | 71 | // Generic get request for internal library use. 72 | // Matches credentials for signed vs. unsigned API calls 73 | pub async fn get( 74 | &self, 75 | url: String, 76 | sign: Option, 77 | ) -> Result { 78 | let req_url = reqwest::Url::parse(&url).unwrap(); 79 | match sign { 80 | Some(sign) => { 81 | let resp = self.client.get(req_url).headers(sign).send().await?; 82 | if resp.status().is_success() { 83 | Ok(resp) 84 | } else { 85 | Ok(resp) 86 | } 87 | } 88 | None => { 89 | let resp = self.client.get(req_url).send().await?; 90 | if resp.status().is_success() { 91 | Ok(resp) 92 | } else { 93 | Ok(resp) 94 | } 95 | } 96 | } 97 | } 98 | 99 | pub async fn post( 100 | &self, 101 | url: String, 102 | sign: Option, 103 | params: Option>, 104 | ) -> Result { 105 | let req_url = reqwest::Url::parse(&url).unwrap(); 106 | if let Some(s) = sign { 107 | if let Some(p) = params { 108 | let resp = self 109 | .client 110 | .post(req_url) 111 | .headers(s) 112 | .json(&json!(p)) 113 | .send() 114 | .await?; 115 | if resp.status().is_success() { 116 | Ok(resp) 117 | } else { 118 | Ok(resp) 119 | } 120 | } else { 121 | let resp = self.client.post(req_url).headers(s).send().await?; 122 | if resp.status().is_success() { 123 | Ok(resp) 124 | } else if resp.status().is_server_error() { 125 | Ok(resp) 126 | } else { 127 | Ok(resp) 128 | } 129 | } 130 | } else { 131 | panic!("Unsigned POST request...") 132 | } 133 | } 134 | 135 | pub async fn delete( 136 | &self, 137 | url: String, 138 | sign: Option, 139 | ) -> Result { 140 | let req_url = reqwest::Url::parse(&url).unwrap(); 141 | if let Some(s) = sign { 142 | let resp = self.client.delete(req_url).headers(s).send().await?; 143 | if resp.status().is_success() { 144 | Ok(resp) 145 | } else if resp.status().is_server_error() { 146 | Ok(resp) 147 | } else { 148 | Ok(resp) 149 | } 150 | } else { 151 | panic!("Unsigned DELETE request...") 152 | } 153 | } 154 | 155 | pub fn sign_headers( 156 | &self, 157 | endpoint: String, 158 | params: Option<&HashMap>, 159 | query: Option, 160 | method: Method, 161 | ) -> Result { 162 | let mut headers = HeaderMap::new(); 163 | let nonce = get_time().to_string(); 164 | let mut api_key: &str = ""; 165 | let mut secret_key: &str = ""; 166 | let mut passphrase: &str = ""; 167 | let mut str_to_sign: String = String::new(); 168 | match &self.credentials { 169 | Some(c) => { 170 | api_key = &c.api_key; 171 | secret_key = &c.secret_key; 172 | passphrase = &c.passphrase; 173 | } 174 | None => (), 175 | } 176 | match method { 177 | Method::GET => { 178 | let meth = "GET"; 179 | if let Some(q) = query { 180 | // let query = format_query(&p); 181 | str_to_sign = format!("{}{}{}{}", nonce, meth, endpoint, q); 182 | } else { 183 | str_to_sign = format!("{}{}{}", nonce, meth, endpoint) 184 | } 185 | } 186 | Method::POST => { 187 | let meth = "POST"; 188 | if let Some(p) = params { 189 | let q = json!(&p); 190 | str_to_sign = format!("{}{}{}{}", nonce, meth, endpoint, q); 191 | } else { 192 | str_to_sign = format!("{}{}{}", nonce, meth, endpoint) 193 | } 194 | } 195 | Method::PUT => {} 196 | Method::DELETE => { 197 | let meth = "DELETE"; 198 | if let Some(q) = query { 199 | // let query = format_query(&p); 200 | str_to_sign = format!("{}{}{}{}", nonce, meth, endpoint, q); 201 | } else { 202 | str_to_sign = format!("{}{}{}", nonce, meth, endpoint) 203 | } 204 | } 205 | } 206 | let mut hmac_sign = HmacSha256::new_varkey(secret_key.as_bytes()).expect("HMAC can take key of any size"); 207 | hmac_sign.input(str_to_sign.as_bytes()); 208 | let sign_result = hmac_sign.result(); 209 | let sign_bytes = sign_result.code(); 210 | let sign_digest = encode(&sign_bytes); 211 | let mut hmac_passphrase = HmacSha256::new_varkey(secret_key.as_bytes()).expect("HMAC can take key of any size"); 212 | hmac_passphrase.input(passphrase.as_bytes()); 213 | let passphrase_result = hmac_passphrase.result(); 214 | let passphrase_bytes = passphrase_result.code(); 215 | let passphrase_digest = encode(&passphrase_bytes); 216 | headers.insert( 217 | HeaderName::from_static("kc-api-key"), 218 | HeaderValue::from_str(&api_key).unwrap(), 219 | ); 220 | headers.insert( 221 | HeaderName::from_static("kc-api-sign"), 222 | HeaderValue::from_str(&sign_digest).unwrap(), 223 | ); 224 | headers.insert( 225 | HeaderName::from_static("kc-api-timestamp"), 226 | HeaderValue::from_str(&nonce).unwrap(), 227 | ); 228 | headers.insert( 229 | HeaderName::from_static("kc-api-passphrase"), 230 | HeaderValue::from_str(&passphrase_digest).unwrap(), 231 | ); 232 | headers.insert( 233 | HeaderName::from_static("kc-api-key-version"), 234 | HeaderValue::from_str("2").unwrap(), 235 | ); 236 | Ok(headers) 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/kucoin/error.rs: -------------------------------------------------------------------------------- 1 | #[derive(Fail, Debug)] 2 | pub enum APIError { 3 | #[fail(display = "Serde issue parsing error {}", _0)] 4 | Serde(#[fail(cause)] serde_json::Error), 5 | #[fail(display = "Websocket error {}", _0)] 6 | Websocket(#[fail(cause)] tokio_tungstenite::tungstenite::Error), 7 | #[fail(display = "REST Call error {}", _0)] 8 | HTTP(#[fail(cause)] reqwest::Error), 9 | #[fail(display = "Other issue {}", _0)] 10 | Other(String), 11 | } 12 | 13 | impl APIError {} 14 | 15 | impl From for APIError { 16 | fn from(err: reqwest::Error) -> Self { 17 | APIError::HTTP(err) 18 | } 19 | } 20 | 21 | impl From for APIError { 22 | fn from(err: serde_json::Error) -> Self { 23 | APIError::Serde(err) 24 | } 25 | } 26 | 27 | impl From for APIError { 28 | fn from(err: tokio_tungstenite::tungstenite::Error) -> Self { 29 | APIError::Websocket(err) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/kucoin/margin.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use super::client::Kucoin; 4 | use super::error::APIError; 5 | use super::model::margin::{ 6 | BorrowOrder, BorrowOrderId, LendHistory, LendMarketData, LendOrder, LendRecord, MarginAccounts, 7 | MarginHistory, MarginInfo, MarginOrder, MarginOrderId, MarginTradeData, MarkPrice, RepayRecord, 8 | RepaymentRecord, 9 | }; 10 | use super::model::{APIData, APIDatum, Method, Pagination}; 11 | use super::utils::format_query; 12 | 13 | impl Kucoin { 14 | pub async fn get_mark_price(&self, symbol: &str) -> Result, APIError> { 15 | let endpoint = format!("/api/v1/mark-price/{}/current", symbol); 16 | let url = format!("{}{}", &self.prefix, endpoint); 17 | let resp = self.get(url, None).await?.json().await?; 18 | Ok(resp) 19 | } 20 | 21 | pub async fn get_margin_config_info(&self) -> Result, APIError> { 22 | let endpoint = String::from("/api/v1/margin/config"); 23 | let url = format!("{}{}", &self.prefix, endpoint); 24 | let resp = self.get(url, None).await?.json().await?; 25 | Ok(resp) 26 | } 27 | 28 | pub async fn get_margin_accounts(&self) -> Result, APIError> { 29 | let endpoint = String::from("/api/v1/margin/account"); 30 | let url = format!("{}{}", &self.prefix, endpoint); 31 | let headers = self 32 | .sign_headers(endpoint, None, None, Method::GET) 33 | .unwrap(); 34 | let resp = self.get(url, Some(headers)).await?.json().await?; 35 | Ok(resp) 36 | } 37 | 38 | /// Term param is comma delimited. Avaialble terms are 7,14,28 39 | pub async fn post_borrow_order( 40 | &self, 41 | currency: &str, 42 | trade_type: &str, 43 | size: f64, 44 | max_rate: Option, 45 | term: Option<&str>, 46 | ) -> Result, APIError> { 47 | let endpoint = String::from("/api/v1/margin/borrow"); 48 | let url = format!("{}{}", &self.prefix, endpoint); 49 | let mut params: HashMap = HashMap::new(); 50 | params.insert(String::from("currency"), currency.to_string()); 51 | params.insert(String::from("type"), trade_type.to_string()); 52 | params.insert(String::from("size"), size.to_string()); 53 | if let Some(m) = max_rate { 54 | params.insert(String::from("maxRate"), m.to_string()); 55 | } 56 | if let Some(t) = term { 57 | params.insert(String::from("term"), t.to_string()); 58 | } 59 | let headers = self 60 | .sign_headers(endpoint, Some(¶ms), None, Method::POST) 61 | .unwrap(); 62 | let resp = self 63 | .post(url, Some(headers), Some(params)) 64 | .await? 65 | .json() 66 | .await?; 67 | Ok(resp) 68 | } 69 | 70 | pub async fn get_borrow_order( 71 | &self, 72 | order_id: &str, 73 | ) -> Result, APIError> { 74 | let endpoint = format!("/api/v1/margin/borrow?orderId={}", order_id); 75 | let url = format!("{}{}", &self.prefix, endpoint); 76 | let headers = self 77 | .sign_headers(endpoint, None, None, Method::GET) 78 | .unwrap(); 79 | let resp = self.get(url, Some(headers)).await?.json().await?; 80 | Ok(resp) 81 | } 82 | 83 | pub async fn get_repay_record( 84 | &self, 85 | currency: Option<&str>, 86 | current_page: Option, 87 | page_size: Option, 88 | ) -> Result>, APIError> { 89 | let endpoint = String::from("/api/v1/margin/borrow/outstanding"); 90 | let mut params: HashMap = HashMap::new(); 91 | if let Some(c) = currency { 92 | params.insert(String::from("currency"), c.to_string()); 93 | } 94 | if let Some(c) = current_page { 95 | params.insert(String::from("currentPage"), c.to_string()); 96 | } 97 | if let Some(p) = page_size { 98 | params.insert(String::from("pageSize"), p.to_string()); 99 | } 100 | let query = format_query(¶ms); 101 | let url = format!("{}{}{}", &self.prefix, endpoint, query); 102 | let headers = self 103 | .sign_headers(endpoint, None, Some(query), Method::GET) 104 | .unwrap(); 105 | let resp = self.get(url, Some(headers)).await?.json().await?; 106 | Ok(resp) 107 | } 108 | 109 | pub async fn get_repayment_record( 110 | &self, 111 | currency: Option<&str>, 112 | current_page: Option, 113 | page_size: Option, 114 | ) -> Result>, APIError> { 115 | let endpoint = String::from("/api/v1/margin/borrow/repaid"); 116 | let mut params: HashMap = HashMap::new(); 117 | if let Some(c) = currency { 118 | params.insert(String::from("currency"), c.to_string()); 119 | } 120 | if let Some(c) = current_page { 121 | params.insert(String::from("currentPage"), c.to_string()); 122 | } 123 | if let Some(p) = page_size { 124 | params.insert(String::from("pageSize"), p.to_string()); 125 | } 126 | let query = format_query(¶ms); 127 | let url = format!("{}{}{}", &self.prefix, endpoint, query); 128 | let headers = self 129 | .sign_headers(endpoint, None, Some(query), Method::GET) 130 | .unwrap(); 131 | let resp = self.get(url, Some(headers)).await?.json().await?; 132 | Ok(resp) 133 | } 134 | 135 | pub async fn one_click_repayment( 136 | &self, 137 | currency: &str, 138 | sequence: &str, 139 | size: f64, 140 | ) -> Result, APIError> { 141 | let endpoint = String::from("/api/v1/margin/repay/all"); 142 | let url = format!("{}{}", &self.prefix, endpoint); 143 | let mut params: HashMap = HashMap::new(); 144 | params.insert(String::from("currency"), currency.to_string()); 145 | params.insert(String::from("sequence"), sequence.to_string()); 146 | params.insert(String::from("size"), size.to_string()); 147 | let headers = self 148 | .sign_headers(endpoint, Some(¶ms), None, Method::POST) 149 | .unwrap(); 150 | let resp = self 151 | .post(url, Some(headers), Some(params)) 152 | .await? 153 | .json() 154 | .await?; 155 | Ok(resp) 156 | } 157 | 158 | pub async fn repay_single_order( 159 | &self, 160 | currency: &str, 161 | trade_id: &str, 162 | size: f64, 163 | ) -> Result, APIError> { 164 | let endpoint = String::from("/api/v1/margin/repay/single"); 165 | let url = format!("{}{}", &self.prefix, endpoint); 166 | let mut params: HashMap = HashMap::new(); 167 | params.insert(String::from("currency"), currency.to_string()); 168 | params.insert(String::from("trade_id"), trade_id.to_string()); 169 | params.insert(String::from("size"), size.to_string()); 170 | let headers = self 171 | .sign_headers(endpoint, Some(¶ms), None, Method::POST) 172 | .unwrap(); 173 | let resp = self 174 | .post(url, Some(headers), Some(params)) 175 | .await? 176 | .json() 177 | .await?; 178 | Ok(resp) 179 | } 180 | 181 | pub async fn post_lend_order( 182 | &self, 183 | currency: &str, 184 | size: f32, 185 | daily_int_rate: f32, 186 | term: i32, 187 | ) -> Result, APIError> { 188 | let endpoint = String::from("/api/v1/margin/lend"); 189 | let url = format!("{}{}", &self.prefix, endpoint); 190 | let mut params: HashMap = HashMap::new(); 191 | params.insert(String::from("currency"), currency.to_string()); 192 | params.insert(String::from("size"), size.to_string()); 193 | params.insert(String::from("dailyIntRate"), daily_int_rate.to_string()); 194 | params.insert(String::from("term"), term.to_string()); 195 | let headers = self 196 | .sign_headers(endpoint, Some(¶ms), None, Method::POST) 197 | .unwrap(); 198 | let resp = self 199 | .post(url, Some(headers), Some(params)) 200 | .await? 201 | .json() 202 | .await?; 203 | Ok(resp) 204 | } 205 | 206 | pub async fn cancel_lend_order(&self, order_id: &str) -> Result, APIError> { 207 | let endpoint = format!("/api/v1/margin/lend/{}", order_id); 208 | let url = format!("{}{}", &self.prefix, endpoint); 209 | let headers = self 210 | .sign_headers(endpoint, None, None, Method::DELETE) 211 | .unwrap(); 212 | let resp = self.delete(url, Some(headers)).await?.json().await?; 213 | Ok(resp) 214 | } 215 | 216 | pub async fn set_auto_lend( 217 | &self, 218 | currency: &str, 219 | is_enable: bool, 220 | retain_size: Option, 221 | daily_int_rate: Option, 222 | term: Option, 223 | ) -> Result, APIError> { 224 | let endpoint = String::from("/api/v1/margin/toggle-auto-lend"); 225 | let url = format!("{}{}", &self.prefix, endpoint); 226 | let mut params: HashMap = HashMap::new(); 227 | params.insert(String::from("currency"), currency.to_string()); 228 | params.insert(String::from("isEnable"), is_enable.to_string()); 229 | if let Some(r) = retain_size { 230 | params.insert(String::from("retainSize"), r.to_string()); 231 | } 232 | if let Some(d) = daily_int_rate { 233 | params.insert(String::from("dailyIntRate"), d.to_string()); 234 | } 235 | if let Some(t) = term { 236 | params.insert(String::from("term"), t.to_string()); 237 | } 238 | let headers = self 239 | .sign_headers(endpoint, Some(¶ms), None, Method::POST) 240 | .unwrap(); 241 | let resp = self 242 | .post(url, Some(headers), Some(params)) 243 | .await? 244 | .json() 245 | .await?; 246 | Ok(resp) 247 | } 248 | 249 | pub async fn get_active_order( 250 | &self, 251 | currency: &str, 252 | current_page: Option, 253 | page_size: Option, 254 | ) -> Result>, APIError> { 255 | let endpoint = String::from("/api/v1/margin/lend/active"); 256 | let mut params: HashMap = HashMap::new(); 257 | params.insert(String::from("currency"), currency.to_string()); 258 | if let Some(c) = current_page { 259 | params.insert(String::from("currentPage"), c.to_string()); 260 | } 261 | if let Some(p) = page_size { 262 | params.insert(String::from("pageSize"), p.to_string()); 263 | } 264 | let query = format_query(¶ms); 265 | let url = format!("{}{}{}", &self.prefix, endpoint, query); 266 | let headers = self 267 | .sign_headers(endpoint, None, Some(query), Method::GET) 268 | .unwrap(); 269 | let resp = self.get(url, Some(headers)).await?.json().await?; 270 | Ok(resp) 271 | } 272 | 273 | pub async fn get_lend_history( 274 | &self, 275 | currency: Option<&str>, 276 | current_page: Option, 277 | page_size: Option, 278 | ) -> Result>, APIError> { 279 | let endpoint = String::from("/api/v1/margin/lend/done"); 280 | let mut params: HashMap = HashMap::new(); 281 | if let Some(c) = currency { 282 | params.insert(String::from("currency"), c.to_string()); 283 | } 284 | if let Some(c) = current_page { 285 | params.insert(String::from("currentPage"), c.to_string()); 286 | } 287 | if let Some(p) = page_size { 288 | params.insert(String::from("pageSize"), p.to_string()); 289 | } 290 | let query = format_query(¶ms); 291 | let url = format!("{}{}{}", &self.prefix, endpoint, query); 292 | let headers = self 293 | .sign_headers(endpoint, None, Some(query), Method::GET) 294 | .unwrap(); 295 | let resp = self.get(url, Some(headers)).await?.json().await?; 296 | Ok(resp) 297 | } 298 | 299 | pub async fn get_active_lend( 300 | &self, 301 | currency: Option<&str>, 302 | current_page: Option, 303 | page_size: Option, 304 | ) -> Result>, APIError> { 305 | let endpoint = String::from("/api/v1/margin/lend/trade/unsettled"); 306 | let mut params: HashMap = HashMap::new(); 307 | if let Some(c) = currency { 308 | params.insert(String::from("currency"), c.to_string()); 309 | } 310 | if let Some(c) = current_page { 311 | params.insert(String::from("currentPage"), c.to_string()); 312 | } 313 | if let Some(p) = page_size { 314 | params.insert(String::from("pageSize"), p.to_string()); 315 | } 316 | let query = format_query(¶ms); 317 | let url = format!("{}{}{}", &self.prefix, endpoint, query); 318 | let headers = self 319 | .sign_headers(endpoint, None, Some(query), Method::GET) 320 | .unwrap(); 321 | let resp = self.get(url, Some(headers)).await?.json().await?; 322 | Ok(resp) 323 | } 324 | 325 | pub async fn get_settled_lend( 326 | &self, 327 | currency: Option<&str>, 328 | current_page: Option, 329 | page_size: Option, 330 | ) -> Result>, APIError> { 331 | let endpoint = String::from("/api/v1/margin/lend/trade/settled"); 332 | let mut params: HashMap = HashMap::new(); 333 | if let Some(c) = currency { 334 | params.insert(String::from("currency"), c.to_string()); 335 | } 336 | if let Some(c) = current_page { 337 | params.insert(String::from("currentPage"), c.to_string()); 338 | } 339 | if let Some(p) = page_size { 340 | params.insert(String::from("pageSize"), p.to_string()); 341 | } 342 | let query = format_query(¶ms); 343 | let url = format!("{}{}{}", &self.prefix, endpoint, query); 344 | let headers = self 345 | .sign_headers(endpoint, None, Some(query), Method::GET) 346 | .unwrap(); 347 | let resp = self.get(url, Some(headers)).await?.json().await?; 348 | Ok(resp) 349 | } 350 | 351 | pub async fn get_lend_record( 352 | &self, 353 | currency: Option<&str>, 354 | ) -> Result, APIError> { 355 | let mut endpoint = String::from("/api/v1/margin/lend/assets"); 356 | if let Some(c) = currency { 357 | endpoint.push_str(&format!("?currency={}", c)); 358 | } 359 | let url = format!("{}{}", &self.prefix, endpoint); 360 | let headers = self 361 | .sign_headers(endpoint, None, None, Method::GET) 362 | .unwrap(); 363 | let resp = self.get(url, Some(headers)).await?.json().await?; 364 | Ok(resp) 365 | } 366 | 367 | pub async fn get_lend_market_data( 368 | &self, 369 | currency: &str, 370 | term: Option, 371 | ) -> Result, APIError> { 372 | let mut endpoint = format!("/api/v1/margin/market?currency={}", currency); 373 | if let Some(t) = term { 374 | endpoint.push_str(&format!("term={}", t)); 375 | } 376 | let url = format!("{}{}", &self.prefix, endpoint); 377 | let headers = self 378 | .sign_headers(endpoint, None, None, Method::GET) 379 | .unwrap(); 380 | let resp = self.get(url, Some(headers)).await?.json().await?; 381 | Ok(resp) 382 | } 383 | 384 | pub async fn get_margin_trade_data( 385 | &self, 386 | currency: &str, 387 | ) -> Result, APIError> { 388 | let endpoint = format!("/api/v1/margin/trade/last?currency={}", currency); 389 | let url = format!("{}{}", &self.prefix, endpoint); 390 | let headers = self 391 | .sign_headers(endpoint, None, None, Method::GET) 392 | .unwrap(); 393 | let resp = self.get(url, Some(headers)).await?.json().await?; 394 | Ok(resp) 395 | } 396 | } 397 | -------------------------------------------------------------------------------- /src/kucoin/market.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use reqwest::header; 4 | 5 | use super::client::Kucoin; 6 | use super::error::APIError; 7 | use super::model::market::{ 8 | AllTickers, Chain, Currency, DailyStats, Klines, OrderBook, AtomicOrderBook, OrderBookType, SymbolList, Ticker, 9 | TradeHistories 10 | }; 11 | use super::model::{APIData, APIDatum, Method}; 12 | use super::utils::format_query; 13 | 14 | impl Kucoin { 15 | pub async fn get_symbol_list( 16 | &self, 17 | market: Option<&str>, 18 | ) -> Result, APIError> { 19 | let endpoint = String::from("/api/v1/symbols"); 20 | let url = match market { 21 | Some(m) => format!("{}{}?market={}", &self.prefix, endpoint, m), 22 | None => format!("{}{}", &self.prefix, endpoint), 23 | }; 24 | let resp = self.get(url, None).await?.json().await?; 25 | Ok(resp) 26 | } 27 | 28 | pub async fn get_ticker(&self, symbol: &str) -> Result, APIError> { 29 | let endpoint = String::from("/api/v1/market/orderbook/level1"); 30 | let url = format!("{}{}?symbol={}", &self.prefix, endpoint, symbol); 31 | let resp = self.get(url, None).await?.json().await?; 32 | Ok(resp) 33 | } 34 | 35 | pub async fn get_all_tickers(&self) -> Result, APIError> { 36 | let endpoint = String::from("/api/v1/market/allTickers"); 37 | let url = format!("{}{}", &self.prefix, endpoint); 38 | let resp = self.get(url, None).await?.json().await?; 39 | Ok(resp) 40 | } 41 | 42 | pub async fn get_daily_stats(&self, symbol: &str) -> Result, APIError> { 43 | let endpoint = String::from("/api/v1/market/stats"); 44 | let url = format!("{}{}?symbol={}", &self.prefix, endpoint, symbol); 45 | let resp = self.get(url, None).await?.json().await?; 46 | Ok(resp) 47 | } 48 | 49 | pub async fn get_market_list(&self) -> Result, APIError> { 50 | let endpoint = String::from("/api/v1/markets"); 51 | let url = format!("{}{}", &self.prefix, endpoint); 52 | let resp = self.get(url, None).await?.json().await?; 53 | Ok(resp) 54 | } 55 | 56 | pub async fn get_orderbook( 57 | &self, 58 | symbol: &str, 59 | amount: OrderBookType, 60 | ) -> Result, APIError> { 61 | let endpoint = match amount { 62 | OrderBookType::L20 => format!("/api/v1/market/orderbook/level2_20?symbol={}", symbol), 63 | OrderBookType::L100 => format!("/api/v1/market/orderbook/level2_100?symbol={}", symbol), 64 | OrderBookType::Full => format!("/api/v3/market/orderbook/level2?symbol={}", symbol), 65 | }; 66 | match amount { 67 | OrderBookType::L20 | OrderBookType::L100 => { 68 | let url = format!("{}{}", &self.prefix, endpoint); 69 | let resp: APIDatum = self.get(url, None).await?.json().await?; 70 | return Ok(resp) 71 | }, 72 | OrderBookType::Full => { 73 | let url = format!("{}{}", &self.prefix, endpoint); 74 | let headers: header::HeaderMap = self 75 | .sign_headers(endpoint, None, None, Method::GET) 76 | .unwrap(); 77 | let resp = self.get(url, Some(headers)).await?.json().await?; 78 | return Ok(resp) 79 | }, 80 | } 81 | } 82 | 83 | pub async fn get_atomic_orderbook( 84 | &self, 85 | symbol: &str, 86 | ) -> Result, APIError> { 87 | let endpoint = format!("/api/v3/market/orderbook/level3?symbol={}", symbol); 88 | let url = format!("{}{}", &self.prefix, endpoint); 89 | let headers: header::HeaderMap = self 90 | .sign_headers(endpoint, None, None, Method::GET) 91 | .unwrap(); 92 | let resp = self.get(url, Some(headers)).await?.json().await?; 93 | Ok(resp) 94 | } 95 | 96 | pub async fn get_trade_histories( 97 | &self, 98 | symbol: &str, 99 | ) -> Result, APIError> { 100 | let endpoint = format!("/api/v1/market/histories?symbol={}", symbol); 101 | let url = format!("{}{}", &self.prefix, endpoint); 102 | let resp = self.get(url, None).await?.json().await?; 103 | Ok(resp) 104 | } 105 | 106 | pub async fn get_klines( 107 | &self, 108 | klines: Klines, 109 | symbol: &str, 110 | start_at: Option, 111 | end_at: Option, 112 | ) -> Result>, APIError> { 113 | let mut endpoint = String::from("/api/v1/market/candles?"); 114 | match klines { 115 | Klines::K1min => endpoint.push_str("type=1min"), 116 | Klines::K3min => endpoint.push_str("type=3min"), 117 | Klines::K5min => endpoint.push_str("type=5min"), 118 | Klines::K15min => endpoint.push_str("type=15min"), 119 | Klines::K30min => endpoint.push_str("type=30min"), 120 | Klines::K1hour => endpoint.push_str("type=1hour"), 121 | Klines::K2hour => endpoint.push_str("type=2hour"), 122 | Klines::K4hour => endpoint.push_str("type=4hour"), 123 | Klines::K6hour => endpoint.push_str("type=6hour"), 124 | Klines::K8hour => endpoint.push_str("type=8hour"), 125 | Klines::K12hour => endpoint.push_str("type=12hour"), 126 | Klines::K1day => endpoint.push_str("type=1day"), 127 | Klines::K1week => endpoint.push_str("type=1week"), 128 | } 129 | endpoint.push_str(&format!("&symbol={}", symbol)); 130 | if let Some(t) = start_at { 131 | endpoint.push_str(&format!("&startAt={}", t.to_string())); 132 | } 133 | if let Some(t) = end_at { 134 | endpoint.push_str(&format!("&endAt={}", t.to_string())); 135 | } 136 | let url = format!("{}{}", &self.prefix, endpoint); 137 | let resp = self.get(url, None).await?.json().await?; 138 | Ok(resp) 139 | } 140 | 141 | pub async fn get_currencies(&self) -> Result, APIError> { 142 | let endpoint = String::from("/api/v1/currencies"); 143 | let url = format!("{}{}", &self.prefix, endpoint); 144 | let resp = self.get(url, None).await?.json().await?; 145 | Ok(resp) 146 | } 147 | 148 | pub async fn get_currency( 149 | &self, 150 | currency: &str, 151 | chain: Option, 152 | ) -> Result, APIError> { 153 | let mut endpoint = format!("/api/v1/currencies/{}", currency); 154 | if let Some(c) = chain { 155 | match c { 156 | Chain::OMNI => endpoint.push_str("?chain=OMNI"), 157 | Chain::ERC20 => endpoint.push_str("?chain=ERC20"), 158 | Chain::TRC20 => endpoint.push_str("?chain=TRC20"), 159 | } 160 | } 161 | let url = format!("{}{}", &self.prefix, endpoint); 162 | let resp = self.get(url, None).await?.json().await?; 163 | Ok(resp) 164 | } 165 | 166 | pub async fn get_fiat_prices( 167 | &self, 168 | base: Option<&str>, 169 | currencies: Option<&str>, 170 | ) -> Result>, APIError> { 171 | let endpoint = String::from("/api/v1/prices"); 172 | let mut params: HashMap = HashMap::new(); 173 | let url: String; 174 | if let Some(b) = base { 175 | params.insert(String::from("base"), b.to_string()); 176 | } 177 | if let Some(c) = currencies { 178 | params.insert(String::from("currencies"), c.to_string()); 179 | } 180 | if !params.is_empty() { 181 | let query = format_query(¶ms); 182 | url = format!("{}{}{}", &self.prefix, endpoint, query); 183 | } else { 184 | url = format!("{}{}", &self.prefix, endpoint); 185 | } 186 | let resp = self.get(url, None).await?.json().await?; 187 | Ok(resp) 188 | } 189 | 190 | pub async fn get_server_time(&self) -> Result, APIError> { 191 | let endpoint = String::from("/api/v1/timestamp"); 192 | let url = format!("{}{}", &self.prefix, endpoint); 193 | let resp = self.get(url, None).await?.json().await?; 194 | Ok(resp) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/kucoin/mod.rs: -------------------------------------------------------------------------------- 1 | /// Main Kucoin API Client w/ All Endpoints 2 | pub mod client; 3 | pub mod error; 4 | pub mod margin; 5 | pub mod market; 6 | /// API Response Strucs 7 | pub mod model; 8 | pub mod trade; 9 | pub mod user; 10 | /// Utility Functions 11 | pub mod utils; 12 | pub mod websocket; 13 | -------------------------------------------------------------------------------- /src/kucoin/model/margin.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Deserialize, Serialize)] 2 | #[serde(rename_all = "camelCase")] 3 | pub struct MarkPrice { 4 | pub symbol: String, 5 | pub granularity: i32, 6 | pub time_point: i64, 7 | pub value: f32, 8 | } 9 | 10 | #[derive(Debug, Deserialize, Serialize)] 11 | #[serde(rename_all = "camelCase")] 12 | pub struct MarginInfo { 13 | pub currency_list: Vec, 14 | pub warning_debt_ratio: String, 15 | pub liq_debt_ratio: String, 16 | pub max_leverage: i32, 17 | } 18 | 19 | #[derive(Debug, Deserialize, Serialize)] 20 | #[serde(rename_all = "camelCase")] 21 | pub struct MarginAccounts { 22 | pub accounts: Vec, 23 | pub debt_ratio: String, 24 | } 25 | 26 | #[derive(Debug, Deserialize, Serialize)] 27 | #[serde(rename_all = "camelCase")] 28 | pub struct MarginAccount { 29 | pub available_balance: String, 30 | pub currency: String, 31 | pub hold_balance: String, 32 | pub liability: String, 33 | pub max_borrow_size: String, 34 | pub total_balance: String, 35 | } 36 | 37 | #[derive(Debug, Deserialize, Serialize)] 38 | #[serde(rename_all = "camelCase")] 39 | pub struct BorrowOrderId { 40 | pub order_id: String, 41 | pub currency: String, 42 | } 43 | 44 | #[derive(Debug, Deserialize, Serialize)] 45 | #[serde(rename_all = "camelCase")] 46 | pub struct BorrowOrder { 47 | pub currency: String, 48 | pub filled: String, 49 | pub match_list: Vec, 50 | pub order_id: String, 51 | pub size: String, 52 | pub status: String, 53 | } 54 | 55 | #[derive(Debug, Deserialize, Serialize)] 56 | #[serde(rename_all = "camelCase")] 57 | pub struct MatchList { 58 | pub currency: String, 59 | pub daily_int_rate: String, 60 | pub size: String, 61 | pub term: i32, 62 | pub timestamp: i64, 63 | pub trade_id: String, 64 | } 65 | 66 | #[derive(Debug, Deserialize, Serialize)] 67 | #[serde(rename_all = "camelCase")] 68 | pub struct RepayRecord { 69 | pub accrued_interest: String, 70 | pub created_at: i64, 71 | pub currency: String, 72 | pub daily_int_rate: String, 73 | pub liability: String, 74 | pub maturity_time: i64, 75 | pub principal: String, 76 | pub rapaid_size: Option, 77 | pub term: i32, 78 | pub trade_id: String, 79 | } 80 | 81 | #[derive(Debug, Deserialize, Serialize)] 82 | #[serde(rename_all = "camelCase")] 83 | pub struct RepaymentRecord { 84 | pub currency: String, 85 | pub daily_int_rate: String, 86 | pub interest: String, 87 | pub principal: String, 88 | pub rapaid_size: String, 89 | pub repay_time: String, 90 | pub term: i32, 91 | pub trade_id: String, 92 | } 93 | 94 | #[derive(Debug, Deserialize, Serialize)] 95 | #[serde(rename_all = "camelCase")] 96 | pub struct MarginOrderId { 97 | pub order_id: String, 98 | } 99 | 100 | #[derive(Debug, Deserialize, Serialize)] 101 | #[serde(rename_all = "camelCase")] 102 | pub struct MarginOrder { 103 | pub order_id: String, 104 | pub currency: String, 105 | pub size: String, 106 | pub filled_size: String, 107 | pub daily_int_rate: String, 108 | pub term: i32, 109 | pub created_at: i64, 110 | } 111 | 112 | #[derive(Debug, Deserialize, Serialize)] 113 | #[serde(rename_all = "camelCase")] 114 | pub struct MarginHistory { 115 | pub order_id: String, 116 | pub currency: String, 117 | pub size: String, 118 | pub filled_state: Option, 119 | pub daily_int_rate: String, 120 | pub term: i32, 121 | pub created_at: i64, 122 | pub status: String, 123 | } 124 | 125 | #[derive(Debug, Deserialize, Serialize)] 126 | #[serde(rename_all = "camelCase")] 127 | pub struct LendOrder { 128 | pub trade_id: String, 129 | pub currency: String, 130 | pub size: String, 131 | pub accrued_interest: String, 132 | pub repaid: String, 133 | pub daily_int_rate: String, 134 | pub term: i32, 135 | pub maturity_time: i64, 136 | } 137 | 138 | #[derive(Debug, Deserialize, Serialize)] 139 | #[serde(rename_all = "camelCase")] 140 | pub struct LendHistory { 141 | pub trade_id: String, 142 | pub currency: String, 143 | pub size: String, 144 | pub interest: String, 145 | pub repaid: String, 146 | pub daily_int_rate: String, 147 | pub term: i32, 148 | pub settled_at: i64, 149 | pub note: String, 150 | } 151 | 152 | #[derive(Debug, Deserialize, Serialize)] 153 | #[serde(rename_all = "camelCase")] 154 | pub struct LendRecord { 155 | pub currency: String, 156 | pub outstanding: String, 157 | pub filled_size: String, 158 | pub accrued_interest: String, 159 | pub realized_profit: String, 160 | pub is_auto_lend: bool, 161 | } 162 | 163 | #[derive(Debug, Deserialize, Serialize)] 164 | #[serde(rename_all = "camelCase")] 165 | pub struct LendMarketData { 166 | pub daily_int_rate: String, 167 | pub term: i32, 168 | pub size: String, 169 | } 170 | 171 | #[derive(Debug, Deserialize, Serialize)] 172 | #[serde(rename_all = "camelCase")] 173 | pub struct MarginTradeData { 174 | pub trade_id: String, 175 | pub currency: String, 176 | pub size: String, 177 | pub daily_int_rate: String, 178 | pub term: i32, 179 | pub timestamp: i64, 180 | } 181 | -------------------------------------------------------------------------------- /src/kucoin/model/market.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Deserialize, Serialize)] 2 | #[serde(rename_all = "camelCase")] 3 | pub struct SymbolList { 4 | pub symbol: String, 5 | pub name: String, 6 | pub base_currency: String, 7 | pub quote_currency: String, 8 | pub base_min_size: String, 9 | pub base_max_size: String, 10 | pub quote_max_size: String, 11 | pub base_increment: String, 12 | pub quote_increment: String, 13 | pub price_increment: String, 14 | pub fee_currency: String, 15 | pub enable_trading: bool, 16 | pub is_margin_enabled: bool, 17 | } 18 | 19 | #[derive(Debug, Deserialize, Serialize)] 20 | #[serde(rename_all = "camelCase")] 21 | pub struct Ticker { 22 | pub sequence: String, 23 | pub best_ask: String, 24 | pub size: String, 25 | pub price: String, 26 | pub best_bid_size: String, 27 | pub best_bid: String, 28 | pub best_ask_size: String, 29 | pub time: i64, 30 | } 31 | 32 | #[derive(Debug, Deserialize, Serialize)] 33 | #[serde(rename_all = "camelCase")] 34 | pub struct AllTickers { 35 | pub time: i64, 36 | pub ticker: Vec, 37 | } 38 | 39 | #[derive(Debug, Deserialize, Serialize)] 40 | #[serde(rename_all = "camelCase")] 41 | pub struct Tick { 42 | pub symbol: String, 43 | pub symbol_name: String, 44 | pub buy: String, 45 | pub sell: String, 46 | pub change_rate: Option, 47 | pub change_price: Option, 48 | pub high: Option, 49 | pub low: Option, 50 | pub vol: String, 51 | pub vol_value: String, 52 | pub last: String, 53 | } 54 | 55 | #[derive(Debug, Deserialize, Serialize)] 56 | #[serde(rename_all = "camelCase")] 57 | pub struct DailyStats { 58 | pub symbol: String, 59 | pub buy: String, 60 | pub sell: String, 61 | pub change_rate: Option, 62 | pub change_price: Option, 63 | pub high: Option, 64 | pub low: Option, 65 | pub vol: String, 66 | pub vol_value: String, 67 | pub last: String, 68 | } 69 | 70 | #[derive(Debug, Deserialize, Serialize)] 71 | #[serde(rename_all = "camelCase")] 72 | pub struct OrderBook { 73 | pub sequence: String, 74 | pub time: i64, 75 | pub bids: Vec>, 76 | pub asks: Vec>, 77 | } 78 | 79 | #[derive(Debug, Deserialize, Serialize)] 80 | #[serde(rename_all = "camelCase")] 81 | pub struct AtomicOrderBook { 82 | pub sequence: i64, 83 | pub time: i64, 84 | pub bids: Vec<(String, String, String, i64)>, 85 | pub asks: Vec<(String, String, String, i64)>, 86 | } 87 | 88 | pub enum OrderBookType { 89 | L20, 90 | L100, 91 | Full, 92 | } 93 | 94 | #[derive(Debug, Deserialize, Serialize)] 95 | #[serde(rename_all = "camelCase")] 96 | pub struct TradeHistories { 97 | pub sequence: String, 98 | pub price: String, 99 | pub size: String, 100 | pub side: String, 101 | pub time: i64, 102 | } 103 | 104 | pub enum Klines { 105 | K1min, 106 | K3min, 107 | K5min, 108 | K15min, 109 | K30min, 110 | K1hour, 111 | K2hour, 112 | K4hour, 113 | K6hour, 114 | K8hour, 115 | K12hour, 116 | K1day, 117 | K1week, 118 | } 119 | 120 | #[derive(Debug, Deserialize, Serialize)] 121 | #[serde(rename_all = "camelCase")] 122 | pub struct Currency { 123 | currency: String, 124 | name: String, 125 | full_name: String, 126 | precision: i32, 127 | withdrawal_min_size: String, 128 | withdrawal_min_fee: String, 129 | is_withdrawal_enabled: Option, 130 | is_deposit_enabled: bool, 131 | is_margin_enabled: bool, 132 | is_debit_enabled: bool, 133 | } 134 | 135 | pub enum Chain { 136 | OMNI, 137 | ERC20, 138 | TRC20, 139 | } 140 | 141 | pub enum Fiat { 142 | USD, 143 | EUR, 144 | CAD, 145 | CNY, 146 | AUD, 147 | KRW, 148 | JPY, 149 | GBP, 150 | INR, 151 | IDR, 152 | RUB, 153 | BRL, 154 | TRY, 155 | PLN, 156 | PHP, 157 | ZAR, 158 | THB, 159 | CHF, 160 | MYR, 161 | MXR, 162 | HRK, 163 | ARS, 164 | KZT, 165 | IRR, 166 | VND, 167 | ILS, 168 | BDT, 169 | HKD, 170 | TWD, 171 | COP, 172 | DKK, 173 | BGN, 174 | NOK, 175 | DZD, 176 | RON, 177 | SGD, 178 | NGN, 179 | CZK, 180 | PKR, 181 | SEK, 182 | NZD, 183 | UAH, 184 | } 185 | -------------------------------------------------------------------------------- /src/kucoin/model/mod.rs: -------------------------------------------------------------------------------- 1 | //! All Kucoin API endpoint response objects 2 | pub mod margin; 3 | pub mod market; 4 | pub mod trade; 5 | pub mod user; 6 | pub mod websocket; 7 | 8 | #[derive(Debug, Deserialize, Serialize)] 9 | #[serde(rename_all = "camelCase")] 10 | pub struct APIData { 11 | pub code: String, 12 | pub data: Option>, 13 | pub msg: Option, 14 | } 15 | 16 | #[derive(Debug, Deserialize, Serialize)] 17 | #[serde(rename_all = "camelCase")] 18 | pub struct APIDatum { 19 | pub code: String, 20 | pub data: Option, 21 | pub msg: Option, 22 | } 23 | 24 | #[derive(Debug, Deserialize, Serialize)] 25 | #[serde(rename_all = "camelCase")] 26 | pub enum Method { 27 | GET, 28 | POST, 29 | PUT, 30 | DELETE, 31 | } 32 | 33 | #[derive(Debug, Deserialize, Serialize)] 34 | #[serde(rename_all = "camelCase")] 35 | pub struct Pagination { 36 | pub current_page: i32, 37 | pub page_size: i32, 38 | pub total_num: i32, 39 | pub total_page: i32, 40 | pub items: Vec, 41 | } 42 | -------------------------------------------------------------------------------- /src/kucoin/model/trade.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Deserialize, Serialize)] 2 | #[serde(rename_all = "camelCase")] 3 | pub struct OrderResp { 4 | pub order_id: String, 5 | } 6 | 7 | #[derive(Debug, Deserialize, Serialize)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct CancelResp { 10 | pub cancelled_order_ids: Vec, 11 | } 12 | 13 | #[derive(Debug, Deserialize, Serialize)] 14 | #[serde(rename_all = "camelCase")] 15 | pub struct CancelByClientOidResp { 16 | pub cancelled_order_id: String, 17 | pub client_oid: String, 18 | } 19 | 20 | #[derive(Debug, Deserialize, Serialize)] 21 | #[serde(rename_all = "camelCase")] 22 | pub struct OrderInfo { 23 | pub id: String, 24 | pub symbol: String, 25 | pub op_type: String, 26 | pub r#type: String, 27 | pub side: String, 28 | pub price: String, 29 | pub size: String, 30 | pub funds: String, 31 | pub deal_funds: String, 32 | pub deal_size: String, 33 | pub fee: String, 34 | pub fee_currency: String, 35 | pub stp: String, 36 | pub stop: String, 37 | pub stop_triggered: bool, 38 | pub stop_price: String, 39 | pub time_in_force: String, 40 | pub post_only: bool, 41 | pub hidden: bool, 42 | pub iceberg: bool, 43 | pub visible_size: String, 44 | pub cancel_after: i64, 45 | pub channel: String, 46 | pub client_oid: String, 47 | pub remark: Option, 48 | pub tags: Option, 49 | pub is_active: Option, 50 | pub cancel_exist: bool, 51 | pub created_at: i64, 52 | pub trade_type: String, 53 | } 54 | 55 | #[derive(Debug, Deserialize, Serialize)] 56 | #[serde(rename_all = "camelCase")] 57 | pub struct HistoricalOrder { 58 | symbol: String, 59 | deal_price: Option, 60 | deal_value: Option, 61 | amount: Option, 62 | fee: String, 63 | side: String, 64 | created_at: i64, 65 | } 66 | 67 | #[derive(Debug, Deserialize, Serialize)] 68 | #[serde(rename_all = "camelCase")] 69 | pub struct FillsInfo { 70 | pub symbol: String, 71 | pub trade_id: String, 72 | pub order_id: String, 73 | pub counter_order_id: String, 74 | pub side: String, 75 | pub liquidity: String, 76 | pub force_taker: bool, 77 | pub price: String, 78 | pub size: String, 79 | pub funds: String, 80 | pub fee: String, 81 | pub fee_rate: String, 82 | pub fee_currency: String, 83 | pub stop: String, 84 | pub r#type: String, 85 | pub created_at: i64, 86 | pub trade_type: String, 87 | } 88 | -------------------------------------------------------------------------------- /src/kucoin/model/user.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Deserialize, Serialize)] 2 | #[serde(rename_all = "camelCase")] 3 | pub struct UserInfo { 4 | pub user_id: String, 5 | pub sub_name: String, 6 | pub remarks: String, 7 | } 8 | 9 | #[derive(Debug, Deserialize, Serialize)] 10 | #[serde(rename_all = "camelCase")] 11 | pub struct AccountId { 12 | pub id: String, 13 | } 14 | 15 | pub enum AccountType { 16 | Main, 17 | Trade, 18 | Margin, 19 | } 20 | 21 | #[derive(Debug, Deserialize, Serialize)] 22 | #[serde(rename_all = "camelCase")] 23 | pub struct Accounts { 24 | pub id: String, 25 | pub currency: String, 26 | pub r#type: String, 27 | pub balance: String, 28 | pub available: String, 29 | pub holds: String, 30 | } 31 | 32 | #[derive(Debug, Deserialize, Serialize)] 33 | #[serde(rename_all = "camelCase")] 34 | pub struct SingleAccount { 35 | pub currency: String, 36 | pub balance: String, 37 | pub available: String, 38 | pub holds: Option, 39 | } 40 | 41 | #[derive(Debug, Deserialize, Serialize)] 42 | #[serde(rename_all = "camelCase")] 43 | pub struct AccountInfo { 44 | pub currency: String, 45 | pub amount: String, 46 | pub fee: String, 47 | pub balance: String, 48 | pub biz_type: String, 49 | pub direction: String, 50 | pub created_at: i64, 51 | pub context: Option, 52 | } 53 | 54 | #[derive(Debug, Deserialize, Serialize)] 55 | #[serde(rename_all = "camelCase")] 56 | pub struct AccountHolds { 57 | pub currency: String, 58 | pub hold_amount: String, 59 | pub biz_type: String, 60 | pub order_id: String, 61 | pub created_at: i64, 62 | pub updated_at: i64, 63 | } 64 | 65 | #[derive(Debug, Deserialize, Serialize)] 66 | #[serde(rename_all = "camelCase")] 67 | pub struct SubAccountBalances { 68 | pub sub_user_id: String, 69 | pub sub_name: String, 70 | pub main_accounts: Vec, 71 | pub trade_accounts: Vec, 72 | pub margin_accounts: Vec, 73 | } 74 | 75 | #[derive(Debug, Deserialize, Serialize)] 76 | #[serde(rename_all = "camelCase")] 77 | pub struct SubAccountInfo { 78 | pub currency: String, 79 | pub balance: String, 80 | pub available: String, 81 | pub holds: String, 82 | pub base_currency: String, 83 | pub base_currency_price: String, 84 | pub base_amount: String, 85 | } 86 | 87 | #[derive(Debug, Deserialize, Serialize)] 88 | #[serde(rename_all = "camelCase")] 89 | pub struct TransferableBalance { 90 | pub currency: String, 91 | pub balance: String, 92 | pub available: String, 93 | pub holds: String, 94 | pub transferable: String, 95 | } 96 | 97 | #[derive(Debug, Deserialize, Serialize)] 98 | #[serde(rename_all = "camelCase")] 99 | pub struct OrderId { 100 | pub order_id: String, 101 | } 102 | 103 | #[derive(Debug, Deserialize, Serialize)] 104 | #[serde(rename_all = "camelCase")] 105 | pub struct DepositAddress { 106 | pub address: String, 107 | pub memo: String, 108 | pub chain: String, 109 | } 110 | 111 | #[derive(Debug, Deserialize, Serialize)] 112 | #[serde(rename_all = "camelCase")] 113 | pub struct DepositList { 114 | pub address: String, 115 | pub memo: String, 116 | pub amount: i32, 117 | pub fee: f32, 118 | pub currency: String, 119 | pub is_inner: bool, 120 | pub wallet_tx_id: String, 121 | pub status: String, 122 | pub remark: String, 123 | pub created_at: i64, 124 | pub updated_at: i64, 125 | } 126 | 127 | #[derive(Debug, Deserialize, Serialize)] 128 | #[serde(rename_all = "camelCase")] 129 | pub struct DepositListV1 { 130 | pub currency: String, 131 | pub created_at: i64, 132 | pub amount: String, 133 | pub wallet_tx_id: String, 134 | pub is_inner: bool, 135 | pub status: String, 136 | } 137 | 138 | #[derive(Debug, Deserialize, Serialize)] 139 | #[serde(rename_all = "camelCase")] 140 | pub struct WithdrawalList { 141 | pub id: String, 142 | pub address: String, 143 | pub memo: String, 144 | pub currency: String, 145 | pub amount: f32, 146 | pub fee: f32, 147 | pub wallet_tx_id: String, 148 | pub is_inner: bool, 149 | pub status: String, 150 | pub remark: String, 151 | pub created_at: i64, 152 | pub updated_at: i64, 153 | } 154 | 155 | #[derive(Debug, Deserialize, Serialize)] 156 | #[serde(rename_all = "camelCase")] 157 | pub struct WithdrawalListV1 { 158 | pub currency: String, 159 | pub created_at: i64, 160 | pub amount: String, 161 | pub address: String, 162 | pub wallet_tx_id: String, 163 | pub is_inner: bool, 164 | pub status: String, 165 | } 166 | 167 | #[allow(non_snake_case)] 168 | #[derive(Debug, Deserialize, Serialize)] 169 | #[serde(rename_all = "camelCase")] 170 | pub struct WithdrawalQuotas { 171 | pub currency: String, 172 | pub limit_BTC_amount: String, 173 | pub used_BTC_amount: String, 174 | pub limit_amount: String, 175 | pub remain_amount: String, 176 | pub available_amount: String, 177 | pub withdrawal_min_fee: String, 178 | pub inner_withdraw_min_fee: String, 179 | pub withdraw_min_size: String, 180 | pub is_withdraw_enabled: String, 181 | pub precision: i32, 182 | pub chain: String, 183 | } 184 | 185 | #[derive(Debug, Deserialize, Serialize)] 186 | #[serde(rename_all = "camelCase")] 187 | pub struct WithdrawalId { 188 | withdrawal_id: String, 189 | } 190 | -------------------------------------------------------------------------------- /src/kucoin/model/websocket.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | #[derive(Debug, Deserialize, Serialize)] 4 | #[serde(rename_all = "camelCase")] 5 | pub struct InstanceServers { 6 | pub instance_servers: Vec, 7 | pub token: String, 8 | } 9 | 10 | #[derive(Debug, Deserialize, Serialize)] 11 | #[serde(rename_all = "camelCase")] 12 | pub struct InstanceServer { 13 | pub ping_interval: i32, 14 | pub endpoint: String, 15 | pub protocol: String, 16 | pub encrypt: bool, 17 | pub ping_timeout: i32, 18 | } 19 | 20 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 21 | pub enum WSTopic { 22 | Ticker(Vec), 23 | AllTicker, 24 | Snapshot(String), 25 | OrderBook(Vec), 26 | OrderBookDepth5(Vec), 27 | OrderBookDepth50(Vec), 28 | Match(Vec), 29 | FullMatch(Vec), 30 | Level3Public(Vec), 31 | Level3Private(Vec), 32 | IndexPrice(Vec), 33 | MarketPrice(Vec), 34 | OrderBookChange(Vec), 35 | StopOrder(Vec), 36 | Balances, 37 | DebtRatio, 38 | PositionChange, 39 | MarginTradeOrder(String), 40 | TradeOrders, 41 | } 42 | 43 | pub enum WSType { 44 | Public, 45 | Private, 46 | } 47 | 48 | #[derive(Debug, Clone, Serialize, Deserialize)] 49 | #[allow(clippy::large_enum_variant)] 50 | pub enum KucoinWebsocketMsg { 51 | WelcomeMsg(DefaultMsg), 52 | SubscribeMsg(Subscribe), 53 | PingMsg(DefaultMsg), 54 | PongMsg(DefaultMsg), 55 | Ping, 56 | Pong, 57 | Binary(Vec), 58 | TickerMsg(WSResp), 59 | AllTickerMsg(WSResp), 60 | SnapshotMsg(WSResp), 61 | OrderBookMsg(WSResp), 62 | MatchMsg(WSResp), 63 | Level3ReceivedMsg(WSResp), 64 | Level3OpenMsg(WSResp), 65 | Level3MatchMsg(WSResp), 66 | Level3DoneMsg(WSResp), 67 | Level3ChangeMsg(WSResp), 68 | OrderBookDepthMsg(WSResp), 69 | FullMatchReceivedMsg(WSResp), 70 | FullMatchOpenMsg(WSResp), 71 | FullMatchDoneMsg(WSResp), 72 | FullMatchMatchMsg(WSResp), 73 | FullMatchChangeMsg(WSResp), 74 | IndexPriceMsg(WSResp), 75 | MarketPriceMsg(WSResp), 76 | OrderBookChangeMsg(WSResp), 77 | StopOrderMsg(WSResp), 78 | BalancesMsg(WSResp), 79 | DebtRatioMsg(WSResp), 80 | PositionChangeMsg(WSResp), 81 | MarginTradeOpenMsg(WSResp), 82 | MarginTradeUpdateMsg(WSResp), 83 | MarginTradeDoneMsg(WSResp), 84 | TradeOpenMsg(WSResp), 85 | TradeMatchMsg(WSResp), 86 | TradeFilledMsg(WSResp), 87 | TradeCanceledMsg(WSResp), 88 | TradeUpdateMsg(WSResp), 89 | Error(String), 90 | } 91 | 92 | #[derive(Debug, Clone, Deserialize, Serialize)] 93 | #[serde(rename_all = "camelCase")] 94 | pub struct WSResp { 95 | pub r#type: String, 96 | pub topic: String, 97 | pub subject: String, 98 | pub data: T, 99 | } 100 | 101 | #[derive(Debug, Clone, Deserialize, Serialize)] 102 | #[serde(rename_all = "camelCase")] 103 | pub struct DefaultMsg { 104 | pub id: String, 105 | pub r#type: String, 106 | } 107 | 108 | #[derive(Debug, Clone, Deserialize, Serialize)] 109 | #[serde(rename_all = "camelCase")] 110 | pub struct Subscribe { 111 | pub id: String, 112 | pub r#type: String, 113 | pub topic: String, 114 | pub private_channel: bool, 115 | pub response: bool, 116 | } 117 | 118 | #[derive(Debug, Clone, Deserialize, Serialize)] 119 | #[serde(rename_all = "camelCase")] 120 | pub struct SymbolTicker { 121 | pub sequence: String, 122 | pub best_ask: String, 123 | pub size: String, 124 | pub best_bid_size: String, 125 | pub price: String, 126 | pub best_ask_size: String, 127 | pub best_bid: String, 128 | } 129 | 130 | #[derive(Debug, Clone, Deserialize, Serialize)] 131 | #[serde(rename_all = "camelCase")] 132 | pub struct Snapshot { 133 | pub sequence: i64, 134 | pub data: SnapshotData, 135 | } 136 | 137 | #[derive(Debug, Clone, Deserialize, Serialize)] 138 | #[serde(rename_all = "camelCase")] 139 | pub struct SnapshotData { 140 | pub trading: bool, 141 | pub symbol: String, 142 | pub buy: f32, 143 | pub sell: f32, 144 | pub sort: i32, 145 | pub vol_value: f32, 146 | pub base_currency: String, 147 | pub market: String, 148 | pub quote_currency: String, 149 | pub symbol_code: String, 150 | pub datetime: i64, 151 | pub high: Option, 152 | pub vol: f32, 153 | pub low: Option, 154 | pub change_price: Option, 155 | pub change_rate: f32, 156 | pub last_traded_price: f32, 157 | pub board: i32, 158 | pub mark: i32, 159 | } 160 | 161 | #[derive(Debug, Clone, Deserialize, Serialize)] 162 | #[serde(rename_all = "camelCase")] 163 | pub struct Level2 { 164 | pub sequence_start: i64, 165 | pub sequence_end: i64, 166 | pub symbol: String, 167 | pub changes: Level2Changes, 168 | } 169 | 170 | #[derive(Debug, Clone, Deserialize, Serialize)] 171 | #[serde(rename_all = "camelCase")] 172 | pub struct Level2Depth { 173 | pub asks: Vec>, 174 | pub bids: Vec>, 175 | pub timestamp: u64, 176 | } 177 | 178 | #[derive(Debug, Clone, Deserialize, Serialize)] 179 | #[serde(rename_all = "camelCase")] 180 | pub struct Level2Changes { 181 | pub asks: Vec>, 182 | pub bids: Vec>, 183 | } 184 | 185 | #[derive(Debug, Clone, Deserialize, Serialize)] 186 | #[serde(rename_all = "camelCase")] 187 | pub struct Match { 188 | pub sequence: String, 189 | pub symbol: String, 190 | pub side: String, 191 | pub size: String, 192 | pub price: String, 193 | pub taker_order_id: String, 194 | pub time: String, 195 | pub r#type: String, 196 | pub maker_order_id: String, 197 | pub trade_id: String, 198 | } 199 | 200 | #[derive(Debug, Clone, Deserialize, Serialize)] 201 | #[serde(rename_all = "camelCase")] 202 | pub struct Level3Received { 203 | pub sequence: String, 204 | pub symbol: String, 205 | pub side: String, 206 | pub order_id: String, 207 | pub price: Option, 208 | pub time: String, 209 | pub client_oid: Option, 210 | pub r#type: String, 211 | pub order_type: String, 212 | } 213 | 214 | #[derive(Debug, Clone, Deserialize, Serialize)] 215 | #[serde(rename_all = "camelCase")] 216 | pub struct Level3Open { 217 | pub sequence: String, 218 | pub symbol: String, 219 | pub side: String, 220 | pub size: String, 221 | pub order_id: String, 222 | pub price: String, 223 | pub time: String, 224 | pub r#type: String, 225 | } 226 | 227 | #[derive(Debug, Clone, Deserialize, Serialize)] 228 | #[serde(rename_all = "camelCase")] 229 | pub struct Level3Done { 230 | pub sequence: String, 231 | pub symbol: String, 232 | pub reason: String, 233 | pub side: String, 234 | pub order_id: String, 235 | pub time: String, 236 | pub r#type: String, 237 | pub size: Option, 238 | } 239 | 240 | #[derive(Debug, Clone, Deserialize, Serialize)] 241 | #[serde(rename_all = "camelCase")] 242 | pub struct Level3Match { 243 | pub sequence: String, 244 | pub symbol: String, 245 | pub side: String, 246 | pub size: String, 247 | pub price: String, 248 | pub taker_order_id: String, 249 | pub time: String, 250 | pub r#type: String, 251 | pub maker_order_id: String, 252 | pub trade_id: String, 253 | } 254 | 255 | #[derive(Debug, Clone, Deserialize, Serialize)] 256 | #[serde(rename_all = "camelCase")] 257 | pub struct Level3Change { 258 | pub sequence: String, 259 | pub symbol: String, 260 | pub side: String, 261 | pub order_id: String, 262 | pub price: String, 263 | pub new_size: String, 264 | pub time: String, 265 | pub r#type: String, 266 | pub old_size: String, 267 | } 268 | 269 | #[derive(Debug, Clone, Deserialize, Serialize)] 270 | #[serde(rename_all = "camelCase")] 271 | pub struct FullMatchReceived { 272 | pub sequence: i64, 273 | pub symbol: String, 274 | pub order_id: String, 275 | pub client_oid: Option, 276 | pub ts: i64, 277 | } 278 | 279 | #[derive(Debug, Clone, Deserialize, Serialize)] 280 | #[serde(rename_all = "camelCase")] 281 | pub struct FullMatchOpen { 282 | pub sequence: i64, 283 | pub symbol: String, 284 | pub order_id: String, 285 | pub side: String, 286 | pub price: String, 287 | pub size: String, 288 | pub order_time: i64, 289 | pub ts: i64, 290 | } 291 | 292 | #[derive(Debug, Clone, Deserialize, Serialize)] 293 | #[serde(rename_all = "camelCase")] 294 | pub struct FullMatchDone { 295 | pub sequence: i64, 296 | pub symbol: String, 297 | pub order_id: String, 298 | pub reason: String, 299 | pub ts: i64, 300 | } 301 | 302 | #[derive(Debug, Clone, Deserialize, Serialize)] 303 | #[serde(rename_all = "camelCase")] 304 | pub struct FullMatchMatch { 305 | pub sequence: i64, 306 | pub symbol: String, 307 | pub side: String, 308 | pub price: String, 309 | pub remain_size: String, 310 | pub taker_order_id: String, 311 | pub maker_order_id: String, 312 | pub trade_id: String, 313 | pub ts: i64, 314 | } 315 | 316 | #[derive(Debug, Clone, Deserialize, Serialize)] 317 | #[serde(rename_all = "camelCase")] 318 | pub struct FullMatchChange { 319 | pub sequence: i64, 320 | pub symbol: String, 321 | pub size: String, 322 | pub order_id: String, 323 | pub ts: i64, 324 | } 325 | 326 | #[derive(Debug, Clone, Deserialize, Serialize)] 327 | #[serde(rename_all = "camelCase")] 328 | pub struct IndexPrice { 329 | pub symbol: String, 330 | pub granularity: i32, 331 | pub timestamp: i64, 332 | pub value: f32, 333 | } 334 | 335 | #[derive(Debug, Clone, Deserialize, Serialize)] 336 | #[serde(rename_all = "camelCase")] 337 | pub struct MarketPrice { 338 | pub symbol: String, 339 | pub granularity: i32, 340 | pub timestamp: i64, 341 | pub value: f32, 342 | } 343 | 344 | #[derive(Debug, Clone, Deserialize, Serialize)] 345 | #[serde(rename_all = "camelCase")] 346 | pub struct BookChange { 347 | pub sequence: i32, 348 | pub currency: String, 349 | pub daily_int_rate: f32, 350 | pub annual_int_rate: f32, 351 | pub term: i32, 352 | pub size: f32, 353 | pub side: String, 354 | pub ts: i64, 355 | } 356 | 357 | #[derive(Debug, Clone, Deserialize, Serialize)] 358 | #[serde(rename_all = "camelCase")] 359 | pub struct StopOrder { 360 | pub sequence: String, 361 | pub symbol: String, 362 | pub side: String, 363 | pub order_id: String, 364 | pub stop_entry: String, 365 | pub funds: String, 366 | pub time: String, 367 | pub r#type: String, 368 | pub reason: Option, 369 | } 370 | 371 | #[derive(Debug, Clone, Deserialize, Serialize)] 372 | #[serde(rename_all = "camelCase")] 373 | pub struct Balances { 374 | pub total: String, 375 | pub available: String, 376 | pub available_change: String, 377 | pub currency: String, 378 | pub hold: String, 379 | pub hold_change: String, 380 | pub relation_event: String, 381 | pub relation_event_id: String, 382 | pub time: String, 383 | pub account_id: String, 384 | } 385 | 386 | #[derive(Debug, Clone, Deserialize, Serialize)] 387 | #[serde(rename_all = "camelCase")] 388 | pub struct DebtRatio { 389 | pub debt_ratio: f32, 390 | pub total_debt: String, 391 | pub debt_list: HashMap, 392 | pub timestamp: i64, 393 | } 394 | 395 | #[derive(Debug, Clone, Deserialize, Serialize)] 396 | #[serde(rename_all = "camelCase")] 397 | pub struct PositionChange { 398 | pub r#type: String, 399 | pub timestamp: i64, 400 | } 401 | 402 | #[derive(Debug, Clone, Deserialize, Serialize)] 403 | #[serde(rename_all = "camelCase")] 404 | pub struct MarginTradeOpen { 405 | pub currency: String, 406 | pub order_id: String, 407 | pub daily_int_rate: f32, 408 | pub term: i32, 409 | pub size: i32, 410 | pub side: String, 411 | pub ts: i64, 412 | } 413 | 414 | #[derive(Debug, Clone, Deserialize, Serialize)] 415 | #[serde(rename_all = "camelCase")] 416 | pub struct MarginTradeUpdate { 417 | pub currency: String, 418 | pub order_id: String, 419 | pub daily_int_rate: f32, 420 | pub term: i32, 421 | pub size: i32, 422 | pub lent_size: f32, 423 | pub side: String, 424 | pub ts: i64, 425 | } 426 | 427 | #[derive(Debug, Clone, Deserialize, Serialize)] 428 | #[serde(rename_all = "camelCase")] 429 | pub struct MarginTradeDone { 430 | pub currency: String, 431 | pub order_id: String, 432 | pub reason: String, 433 | pub side: String, 434 | pub ts: i64, 435 | } 436 | 437 | #[derive(Debug, Clone, Deserialize, Serialize)] 438 | #[serde(rename_all = "camelCase")] 439 | pub struct TradeOpen { 440 | pub symbol: String, 441 | pub order_type: String, 442 | pub side: String, 443 | pub r#type: String, 444 | pub order_id: String, 445 | pub order_time: i64, 446 | pub size: String, 447 | pub filled_size: String, 448 | #[serde(default)] 449 | pub price: String, 450 | #[serde(default)] 451 | pub client_oid: String, 452 | pub remain_size: String, 453 | pub status: String, 454 | pub ts: i64, 455 | } 456 | 457 | #[derive(Debug, Clone, Deserialize, Serialize)] 458 | #[serde(rename_all = "camelCase")] 459 | pub struct TradeMatch { 460 | pub symbol: String, 461 | pub order_type: String, 462 | pub side: String, 463 | pub liquidity: String, 464 | pub r#type: String, 465 | pub order_id: String, 466 | pub order_time: i64, 467 | pub size: String, 468 | pub filled_size: String, 469 | #[serde(default)] 470 | pub price: String, 471 | pub match_price: String, 472 | pub match_size: String, 473 | pub trade_id: String, 474 | #[serde(default)] 475 | pub client_oid: String, 476 | pub remain_size: String, 477 | pub status: String, 478 | pub ts: i64, 479 | } 480 | 481 | #[derive(Debug, Clone, Deserialize, Serialize)] 482 | #[serde(rename_all = "camelCase")] 483 | pub struct TradeFilled { 484 | pub symbol: String, 485 | pub order_type: String, 486 | pub side: String, 487 | pub r#type: String, 488 | pub order_id: String, 489 | pub order_time: i64, 490 | pub size: String, 491 | pub filled_size: String, 492 | #[serde(default)] 493 | pub price: String, 494 | #[serde(default)] 495 | pub client_oid: String, 496 | pub remain_size: String, 497 | pub status: String, 498 | pub ts: i64, 499 | } 500 | 501 | #[derive(Debug, Clone, Deserialize, Serialize)] 502 | #[serde(rename_all = "camelCase")] 503 | pub struct TradeCanceled { 504 | pub symbol: String, 505 | pub order_type: String, 506 | pub side: String, 507 | pub r#type: String, 508 | pub order_id: String, 509 | pub order_time: i64, 510 | pub size: String, 511 | pub filled_size: String, 512 | #[serde(default)] 513 | pub price: String, 514 | #[serde(default)] 515 | pub client_oid: String, 516 | pub remain_size: String, 517 | pub status: String, 518 | pub ts: i64, 519 | } 520 | 521 | #[derive(Debug, Clone, Deserialize, Serialize)] 522 | #[serde(rename_all = "camelCase")] 523 | pub struct TradeUpdate { 524 | pub symbol: String, 525 | pub order_type: String, 526 | pub side: String, 527 | pub r#type: String, 528 | pub old_size: String, 529 | pub order_id: String, 530 | pub order_time: i64, 531 | pub size: String, 532 | pub filled_size: String, 533 | #[serde(default)] 534 | pub price: String, 535 | #[serde(default)] 536 | pub client_oid: String, 537 | pub remain_size: String, 538 | pub status: String, 539 | pub ts: i64, 540 | } 541 | -------------------------------------------------------------------------------- /src/kucoin/trade.rs: -------------------------------------------------------------------------------- 1 | use reqwest::header; 2 | use std::collections::HashMap; 3 | 4 | use super::client::Kucoin; 5 | use super::error::APIError; 6 | use super::model::trade::{ 7 | CancelByClientOidResp, CancelResp, FillsInfo, HistoricalOrder, OrderInfo, OrderResp, 8 | }; 9 | use super::model::{APIData, APIDatum, Method, Pagination}; 10 | use super::utils::format_query; 11 | 12 | impl Kucoin { 13 | /// Places a limit order. Takes required inputs directly and a Some type, or None for 14 | /// optional inputs. See OrderOptionals for build pattern usage to simplify generating optional params. 15 | pub async fn post_limit_order( 16 | &self, 17 | client_oid: &str, 18 | symbol: &str, 19 | side: &str, 20 | price: &str, 21 | size: &str, 22 | optionals: Option>, 23 | ) -> Result, APIError> { 24 | let endpoint = String::from("/api/v1/orders"); 25 | let url = format!("{}{}", &self.prefix, endpoint); 26 | let mut params: HashMap = HashMap::new(); 27 | params.insert(String::from("clientOid"), client_oid.to_string()); 28 | params.insert(String::from("symbol"), symbol.to_string()); 29 | params.insert(String::from("side"), side.to_string()); 30 | params.insert(String::from("price"), price.to_string()); 31 | params.insert(String::from("size"), size.to_string()); 32 | if let Some(opt) = optionals { 33 | let opts = parse_order(opt); 34 | params.extend(opts); 35 | }; 36 | let headers: header::HeaderMap = self 37 | .sign_headers(endpoint, Some(¶ms), None, Method::POST) 38 | .unwrap(); 39 | let resp = self 40 | .post(url, Some(headers), Some(params)) 41 | .await? 42 | .json() 43 | .await?; 44 | Ok(resp) 45 | } 46 | 47 | /// Places a market order. Takes required inputs directly and a Some type, or None for 48 | /// optional inputs. See OrderOptionals for build pattern usage to simplify generating optional params. 49 | /// 50 | /// Note that size is the amount in the base currency and funds is the amount in quote currency. Users 51 | /// should only use one or the other the order will fail. One of the two is a required parameter. 52 | pub async fn post_market_order( 53 | &self, 54 | client_oid: &str, 55 | symbol: &str, 56 | side: &str, 57 | size: Option, 58 | funds: Option, 59 | optionals: Option>, 60 | ) -> Result, APIError> { 61 | let endpoint = String::from("/api/v1/orders"); 62 | let url = format!("{}{}", &self.prefix, endpoint); 63 | let mut params: HashMap = HashMap::new(); 64 | params.insert(String::from("clientOid"), client_oid.to_string()); 65 | params.insert(String::from("symbol"), symbol.to_string()); 66 | params.insert(String::from("side"), side.to_string()); 67 | params.insert(String::from("type"), String::from("market")); 68 | if let Some(s) = size { 69 | params.insert(String::from("size"), s.to_string()); 70 | }; 71 | if let Some(f) = funds { 72 | params.insert(String::from("funds"), f.to_string()); 73 | }; 74 | if let Some(opt) = optionals { 75 | let opts = parse_order(opt); 76 | params.extend(opts); 77 | }; 78 | let headers: header::HeaderMap = self 79 | .sign_headers(endpoint, Some(¶ms), None, Method::POST) 80 | .unwrap(); 81 | let resp = self 82 | .post(url, Some(headers), Some(params)) 83 | .await? 84 | .json() 85 | .await?; 86 | Ok(resp) 87 | } 88 | 89 | /// Cancels an order based on the provided order id (required). 90 | pub async fn cancel_order(&self, order_id: &str) -> Result, APIError> { 91 | let endpoint = format!("/api/v1/orders/{}", order_id); 92 | let url = format!("{}{}", &self.prefix, endpoint); 93 | let headers: header::HeaderMap = self 94 | .sign_headers(endpoint, None, None, Method::DELETE) 95 | .unwrap(); 96 | let resp = self.delete(url, Some(headers)).await?.json().await?; 97 | Ok(resp) 98 | } 99 | 100 | /// Cancels an order based on the provided order id (required). 101 | pub async fn cancel_order_by_client_oid( 102 | &self, 103 | client_oid: &str, 104 | ) -> Result, APIError> { 105 | let endpoint = format!("/api/v1/order/client-order/{}", client_oid); 106 | let url = format!("{}{}", &self.prefix, endpoint); 107 | let headers: header::HeaderMap = self 108 | .sign_headers(endpoint, None, None, Method::DELETE) 109 | .unwrap(); 110 | let resp = self.delete(url, Some(headers)).await?.json().await?; 111 | Ok(resp) 112 | } 113 | 114 | // Cancels all orders of a given symbol (optional) or trade type (optional). 115 | pub async fn cancel_all_orders( 116 | &self, 117 | symbol: Option<&str>, 118 | trade_type: Option<&str>, 119 | ) -> Result, APIError> { 120 | let endpoint = String::from("/api/v1/orders"); 121 | let url: String; 122 | let headers: header::HeaderMap; 123 | let mut params: HashMap = HashMap::new(); 124 | if let Some(s) = symbol { 125 | params.insert(String::from("symbol"), s.to_owned()); 126 | }; 127 | if let Some(t) = trade_type { 128 | params.insert(String::from("tradeType"), t.to_owned()); 129 | }; 130 | if !params.is_empty() { 131 | let query = format_query(¶ms); 132 | url = format!("{}{}{}", &self.prefix, endpoint, query); 133 | headers = self 134 | .sign_headers(endpoint, Some(¶ms), None, Method::DELETE) 135 | .unwrap(); 136 | } else { 137 | url = format!("{}{}", &self.prefix, endpoint); 138 | headers = self 139 | .sign_headers(endpoint, None, None, Method::DELETE) 140 | .unwrap(); 141 | }; 142 | let resp = self.delete(url, Some(headers)).await?.json().await?; 143 | Ok(resp) 144 | } 145 | 146 | // Consider list orders 147 | pub async fn get_orders( 148 | &self, 149 | optionals: Option>, 150 | ) -> Result>, APIError> { 151 | let endpoint = String::from("/api/v1/orders"); 152 | let url: String; 153 | let headers: header::HeaderMap; 154 | let mut params: HashMap = HashMap::new(); 155 | if let Some(opts) = optionals { 156 | if let Some(o) = opts.status { 157 | params.insert("status".to_string(), o.to_string()); 158 | }; 159 | if let Some(o) = opts.symbol { 160 | params.insert("symbol".to_string(), o.to_string()); 161 | }; 162 | if let Some(o) = opts.side { 163 | params.insert("side".to_string(), o.to_string()); 164 | }; 165 | if let Some(o) = opts.r#type { 166 | params.insert("type".to_string(), o.to_string()); 167 | }; 168 | if let Some(o) = opts.trade_type { 169 | params.insert("tradeType".to_string(), o.to_string()); 170 | }; 171 | if let Some(o) = opts.start_at { 172 | params.insert("startAt".to_string(), o.to_string()); 173 | }; 174 | if let Some(o) = opts.end_at { 175 | params.insert("endAt".to_string(), o.to_string()); 176 | }; 177 | if let Some(o) = opts.current_page { 178 | params.insert("currentPage".to_string(), o.to_string()); 179 | }; 180 | if let Some(o) = opts.page_size { 181 | params.insert("pageSize".to_string(), o.to_string()); 182 | }; 183 | }; 184 | if !params.is_empty() { 185 | let query = format_query(¶ms); 186 | url = format!("{}{}{}", &self.prefix, endpoint, query); 187 | headers = self 188 | .sign_headers(endpoint, None, Some(query), Method::GET) 189 | .unwrap(); 190 | } else { 191 | url = format!("{}{}", &self.prefix, endpoint); 192 | headers = self 193 | .sign_headers(endpoint, None, None, Method::GET) 194 | .unwrap(); 195 | } 196 | let resp = self.get(url, Some(headers)).await?.json().await?; 197 | Ok(resp) 198 | } 199 | 200 | pub async fn get_v1_historical_orders( 201 | &self, 202 | symbol: Option<&str>, 203 | start_at: Option, 204 | end_at: Option, 205 | side: Option<&str>, 206 | current_page: Option, 207 | page_size: Option, 208 | ) -> Result>, APIError> { 209 | let endpoint = String::from("/api/v1/orders"); 210 | let url: String; 211 | let headers: header::HeaderMap; 212 | let mut params: HashMap = HashMap::new(); 213 | if let Some(o) = current_page { 214 | params.insert("current_page".to_string(), o.to_string()); 215 | }; 216 | if let Some(o) = page_size { 217 | params.insert("page_size".to_string(), o.to_string()); 218 | }; 219 | if let Some(o) = symbol { 220 | params.insert("symbol".to_string(), o.to_string()); 221 | }; 222 | if let Some(o) = start_at { 223 | params.insert("start_at".to_string(), o.to_string()); 224 | }; 225 | if let Some(o) = end_at { 226 | params.insert("end_at".to_string(), o.to_string()); 227 | }; 228 | if let Some(o) = side { 229 | params.insert("side".to_string(), o.to_string()); 230 | }; 231 | if !params.is_empty() { 232 | let query = format_query(¶ms); 233 | url = format!("{}{}{}", &self.prefix, endpoint, query); 234 | headers = self 235 | .sign_headers(endpoint, None, Some(query), Method::GET) 236 | .unwrap(); 237 | } else { 238 | url = format!("{}{}", &self.prefix, endpoint); 239 | headers = self 240 | .sign_headers(endpoint, None, None, Method::GET) 241 | .unwrap(); 242 | } 243 | let resp = self.get(url, Some(headers)).await?.json().await?; 244 | Ok(resp) 245 | } 246 | 247 | pub async fn get_recent_orders(&self) -> Result, APIError> { 248 | let endpoint = String::from("/api/v1/limit/orders"); 249 | let url = format!("{}{}", &self.prefix, endpoint); 250 | let headers: header::HeaderMap = self 251 | .sign_headers(endpoint, None, None, Method::GET) 252 | .unwrap(); 253 | let resp = self.get(url, Some(headers)).await?.json().await?; 254 | Ok(resp) 255 | } 256 | 257 | pub async fn get_order(&self, order_id: &str) -> Result, APIError> { 258 | let endpoint = format!("/api/v1/orders/{}", order_id); 259 | let url = format!("{}{}", &self.prefix, endpoint); 260 | let headers: header::HeaderMap = self 261 | .sign_headers(endpoint, None, None, Method::GET) 262 | .unwrap(); 263 | let resp = self.get(url, Some(headers)).await?.json().await?; 264 | Ok(resp) 265 | } 266 | 267 | pub async fn get_fills( 268 | &self, 269 | optionals: Option>, 270 | ) -> Result>, APIError> { 271 | let endpoint = String::from("/api/v1/fills"); 272 | let url: String; 273 | let headers: header::HeaderMap; 274 | let mut params: HashMap = HashMap::new(); 275 | if let Some(opts) = optionals { 276 | if let Some(o) = opts.order_id { 277 | params.insert("order_id".to_string(), o.to_string()); 278 | }; 279 | if let Some(o) = opts.symbol { 280 | params.insert("symbol".to_string(), o.to_string()); 281 | }; 282 | if let Some(o) = opts.side { 283 | params.insert("side".to_string(), o.to_string()); 284 | }; 285 | if let Some(o) = opts.r#type { 286 | params.insert("type".to_string(), o.to_string()); 287 | }; 288 | if let Some(o) = opts.start_at { 289 | params.insert("startAt".to_string(), o.to_string()); 290 | }; 291 | if let Some(o) = opts.end_at { 292 | params.insert("endAt".to_string(), o.to_string()); 293 | }; 294 | if let Some(o) = opts.trade_type { 295 | params.insert("tradeType".to_string(), o.to_string()); 296 | }; 297 | if let Some(o) = opts.current_page { 298 | params.insert("currentPage".to_string(), o.to_string()); 299 | }; 300 | if let Some(o) = opts.page_size { 301 | params.insert("pageSize".to_string(), o.to_string()); 302 | }; 303 | }; 304 | if !params.is_empty() { 305 | let query = format_query(¶ms); 306 | url = format!("{}{}{}", &self.prefix, endpoint, query); 307 | headers = self 308 | .sign_headers(endpoint, None, Some(query), Method::GET) 309 | .unwrap(); 310 | } else { 311 | url = format!("{}{}", &self.prefix, endpoint); 312 | headers = self 313 | .sign_headers(endpoint, None, None, Method::GET) 314 | .unwrap(); 315 | }; 316 | let resp = self.get(url, Some(headers)).await?.json().await?; 317 | Ok(resp) 318 | } 319 | 320 | pub async fn get_recent_fills(&self) -> Result, APIError> { 321 | let endpoint = String::from("/api/v1/limit/fills"); 322 | let url = format!("{}{}", &self.prefix, endpoint); 323 | let headers = self 324 | .sign_headers(endpoint, None, None, Method::GET) 325 | .unwrap(); 326 | let resp = self.get(url, Some(headers)).await?.json().await?; 327 | Ok(resp) 328 | } 329 | } 330 | 331 | fn parse_order(optionals: OrderOptionals) -> HashMap { 332 | let mut params: HashMap = HashMap::new(); 333 | 334 | if let Some(o) = optionals.remark { 335 | params.insert(String::from("remark"), o.to_string()); 336 | }; 337 | if let Some(o) = optionals.stop { 338 | params.insert(String::from("stop"), o.to_string()); 339 | }; 340 | if let Some(o) = optionals.stop_price { 341 | params.insert(String::from("stopPrice"), o.to_string()); 342 | }; 343 | if let Some(o) = optionals.time_in_force { 344 | params.insert(String::from("timeInForce"), o.to_string()); 345 | }; 346 | if let Some(o) = optionals.cancel_after { 347 | params.insert(String::from("cancelAfter"), o.to_string()); 348 | }; 349 | if let Some(o) = optionals.post_only { 350 | params.insert(String::from("postOnly"), o.to_string()); 351 | }; 352 | if let Some(o) = optionals.hidden { 353 | params.insert(String::from("hidden"), o.to_string()); 354 | }; 355 | if let Some(o) = optionals.iceberg { 356 | params.insert(String::from("iceberg"), o.to_string()); 357 | }; 358 | if let Some(o) = optionals.visible_size { 359 | params.insert(String::from("visibleSize"), o.to_string()); 360 | }; 361 | 362 | params 363 | } 364 | 365 | /// OrderOptionals contains a builder pattern that can be used to more easily take advantage of optional inputs. 366 | /// 367 | /// Example: 368 | /// ``` rust 369 | /// use kucoin_rs::kucoin::trade::OrderOptionals; 370 | /// 371 | /// let options = OrderOptionals::new() 372 | /// .remark("Example of OrderOptionals builder pattern") 373 | /// .stp("CO") 374 | /// .hidden(true) 375 | /// .build(); 376 | /// ``` 377 | /// 378 | /// See the Kucoin documentation for full list of options relative to market and limit orders. 379 | #[derive(Debug, Clone, PartialEq, Default)] 380 | pub struct OrderOptionals<'a> { 381 | pub remark: Option<&'a str>, 382 | pub stop: Option<&'a str>, 383 | pub stop_price: Option<&'a str>, 384 | pub stp: Option<&'a str>, 385 | pub trade_type: Option<&'a str>, 386 | pub time_in_force: Option<&'a str>, 387 | pub cancel_after: Option, 388 | pub post_only: Option, 389 | pub hidden: Option, 390 | pub iceberg: Option, 391 | pub visible_size: Option<&'a str>, 392 | } 393 | 394 | #[allow(dead_code)] 395 | /// Generates new empty OrderOptionals struct ready for building. 396 | impl<'a> OrderOptionals<'a> { 397 | pub fn new() -> Self { 398 | OrderOptionals { 399 | remark: None, 400 | stop: None, 401 | stop_price: None, 402 | stp: None, 403 | time_in_force: None, 404 | trade_type: None, 405 | cancel_after: None, 406 | post_only: None, 407 | hidden: None, 408 | iceberg: None, 409 | visible_size: None, 410 | } 411 | } 412 | 413 | pub fn remark(&mut self, r: &'a str) -> &mut Self { 414 | self.remark = Some(r); 415 | self 416 | } 417 | 418 | pub fn stop(&mut self, s: &'a str) -> &mut Self { 419 | self.stop = Some(s); 420 | self 421 | } 422 | 423 | pub fn stop_price(&mut self, s: &'a str) -> &mut Self { 424 | self.stop_price = Some(s); 425 | self 426 | } 427 | 428 | pub fn stp(&mut self, s: &'a str) -> &mut Self { 429 | self.stp = Some(s); 430 | self 431 | } 432 | 433 | pub fn time_in_force(&mut self, t: &'a str) -> &mut Self { 434 | self.time_in_force = Some(t); 435 | self 436 | } 437 | 438 | pub fn trade_type(&mut self, t: &'a str) -> &mut Self { 439 | self.trade_type = Some(t); 440 | self 441 | } 442 | 443 | pub fn cancel_after(&mut self, c: i64) -> &mut Self { 444 | self.cancel_after = Some(c); 445 | self 446 | } 447 | 448 | pub fn post_only(&mut self, p: bool) -> &mut Self { 449 | self.post_only = Some(p); 450 | self 451 | } 452 | 453 | pub fn hidden(&mut self, h: bool) -> &mut Self { 454 | self.hidden = Some(h); 455 | self 456 | } 457 | 458 | pub fn iceberg(&mut self, i: bool) -> &mut Self { 459 | self.iceberg = Some(i); 460 | self 461 | } 462 | 463 | pub fn visible_size(&mut self, v: &'a str) -> &mut Self { 464 | self.visible_size = Some(v); 465 | self 466 | } 467 | 468 | /// Builds an OrderOptional Type from chained optional funtions 469 | /// to be used with posting orders. Only contains optional inputs 470 | /// the post order functions require specific required inputs. 471 | /// See those functions' documentation for details. 472 | pub fn build(&self) -> Self { 473 | Self { 474 | remark: self.remark, 475 | stop: self.stop, 476 | stop_price: self.stop_price, 477 | stp: self.stp, 478 | time_in_force: self.time_in_force, 479 | trade_type: self.trade_type, 480 | cancel_after: self.cancel_after, 481 | post_only: self.post_only, 482 | hidden: self.hidden, 483 | iceberg: self.iceberg, 484 | visible_size: self.visible_size, 485 | } 486 | } 487 | } 488 | 489 | /// OrderInfoOptionals contains a builder pattern that can be used to more easily take advantage of optional inputs. 490 | /// 491 | /// Example: 492 | /// ``` rust 493 | /// use kucoin_rs::kucoin::trade::OrderInfoOptionals; 494 | /// 495 | /// let options = OrderInfoOptionals::new() 496 | /// .symbol("BTC-USDT") 497 | /// .side("buy") 498 | /// .build(); 499 | /// ``` 500 | /// 501 | /// See the Kucoin documentation for full list of options relative to market and limit orders. 502 | #[derive(Debug, Clone, PartialEq, Default)] 503 | pub struct OrderInfoOptionals<'a> { 504 | pub status: Option<&'a str>, 505 | pub symbol: Option<&'a str>, 506 | pub side: Option<&'a str>, 507 | pub r#type: Option<&'a str>, 508 | pub trade_type: Option<&'a str>, 509 | pub start_at: Option, 510 | pub end_at: Option, 511 | pub current_page: Option, 512 | pub page_size: Option, 513 | } 514 | 515 | impl<'a> OrderInfoOptionals<'a> { 516 | pub fn new() -> Self { 517 | OrderInfoOptionals { 518 | status: None, 519 | symbol: None, 520 | side: None, 521 | r#type: None, 522 | trade_type: None, 523 | start_at: None, 524 | end_at: None, 525 | current_page: None, 526 | page_size: None, 527 | } 528 | } 529 | 530 | pub fn status(&mut self, s: &'a str) -> &mut Self { 531 | self.status = Some(s); 532 | self 533 | } 534 | 535 | pub fn symbol(&mut self, s: &'a str) -> &mut Self { 536 | self.symbol = Some(s); 537 | self 538 | } 539 | 540 | pub fn side(&mut self, s: &'a str) -> &mut Self { 541 | self.side = Some(s); 542 | self 543 | } 544 | 545 | pub fn order_type(&mut self, s: &'a str) -> &mut Self { 546 | self.r#type = Some(s); 547 | self 548 | } 549 | 550 | pub fn trade_type(&mut self, s: &'a str) -> &mut Self { 551 | self.trade_type = Some(s); 552 | self 553 | } 554 | 555 | pub fn start_at(&mut self, i: i64) -> &mut Self { 556 | self.start_at = Some(i); 557 | self 558 | } 559 | 560 | pub fn end_at(&mut self, i: i64) -> &mut Self { 561 | self.end_at = Some(i); 562 | self 563 | } 564 | 565 | pub fn current_page(&mut self, i: i32) -> &mut Self { 566 | self.current_page = Some(i); 567 | self 568 | } 569 | 570 | pub fn page_size(&mut self, i: i32) -> &mut Self { 571 | self.page_size = Some(i); 572 | self 573 | } 574 | 575 | /// Builds an OrderInfoOptional Type from chained optional funtions 576 | /// to be used with getting lists of orders. Only contains optional inputs 577 | /// the post order functions require specific required inputs. 578 | /// See the function's documentation for details. 579 | pub fn build(&self) -> Self { 580 | OrderInfoOptionals { 581 | status: self.status, 582 | symbol: self.symbol, 583 | side: self.side, 584 | r#type: self.r#type, 585 | trade_type: self.trade_type, 586 | start_at: self.start_at, 587 | end_at: self.end_at, 588 | current_page: self.current_page, 589 | page_size: self.page_size, 590 | } 591 | } 592 | } 593 | 594 | /// FillsOptionals contains a builder pattern that can be used to more easily take advantage of optional inputs. 595 | /// 596 | /// Example: 597 | /// ``` rust 598 | /// use kucoin_rs::kucoin::trade::FillsOptionals; 599 | /// let options = FillsOptionals::new() 600 | /// .symbol("BTC-USDT") 601 | /// .side("buy") 602 | /// .build(); 603 | /// ``` 604 | /// 605 | /// See the Kucoin documentation for full list of options relative to market and limit orders. 606 | #[derive(Debug, Clone, PartialEq, Default)] 607 | pub struct FillsOptionals<'a> { 608 | pub order_id: Option<&'a str>, 609 | pub symbol: Option<&'a str>, 610 | pub side: Option<&'a str>, 611 | pub r#type: Option<&'a str>, 612 | pub start_at: Option, 613 | pub end_at: Option, 614 | pub trade_type: Option<&'a str>, 615 | pub current_page: Option, 616 | pub page_size: Option, 617 | } 618 | 619 | impl<'a> FillsOptionals<'a> { 620 | pub fn new() -> Self { 621 | FillsOptionals { 622 | order_id: None, 623 | symbol: None, 624 | side: None, 625 | r#type: None, 626 | start_at: None, 627 | end_at: None, 628 | trade_type: None, 629 | current_page: None, 630 | page_size: None, 631 | } 632 | } 633 | 634 | pub fn order_id(&mut self, s: &'a str) -> &mut Self { 635 | self.order_id = Some(s); 636 | self 637 | } 638 | 639 | pub fn symbol(&mut self, s: &'a str) -> &mut Self { 640 | self.symbol = Some(s); 641 | self 642 | } 643 | 644 | pub fn side(&mut self, s: &'a str) -> &mut Self { 645 | self.side = Some(s); 646 | self 647 | } 648 | 649 | pub fn order_type(&mut self, s: &'a str) -> &mut Self { 650 | self.r#type = Some(s); 651 | self 652 | } 653 | 654 | pub fn trade_type(&mut self, s: &'a str) -> &mut Self { 655 | self.trade_type = Some(s); 656 | self 657 | } 658 | 659 | pub fn start_at(&mut self, i: i64) -> &mut Self { 660 | self.start_at = Some(i); 661 | self 662 | } 663 | 664 | pub fn end_at(&mut self, i: i64) -> &mut Self { 665 | self.end_at = Some(i); 666 | self 667 | } 668 | 669 | pub fn current_page(&mut self, i: i32) -> &mut Self { 670 | self.current_page = Some(i); 671 | self 672 | } 673 | 674 | pub fn page_size(&mut self, i: i32) -> &mut Self { 675 | self.page_size = Some(i); 676 | self 677 | } 678 | 679 | /// Builds an FillsOptional Type from chained optional funtions 680 | /// to be used with getting lists of fills. Only contains optional inputs 681 | /// the post order functions require specific required inputs. 682 | /// See the function's documentation for details. 683 | pub fn build(&self) -> Self { 684 | FillsOptionals { 685 | order_id: self.order_id, 686 | symbol: self.symbol, 687 | side: self.side, 688 | r#type: self.r#type, 689 | trade_type: self.trade_type, 690 | start_at: self.start_at, 691 | end_at: self.end_at, 692 | current_page: self.current_page, 693 | page_size: self.page_size, 694 | } 695 | } 696 | } 697 | 698 | #[cfg(test)] 699 | mod test { 700 | use crate::kucoin::trade::{FillsOptionals, OrderInfoOptionals, OrderOptionals}; 701 | #[test] 702 | fn use_build_pattern_all_order_optionals() { 703 | let options = OrderOptionals { 704 | remark: Some("Test build pattern"), 705 | stop: Some("loss"), 706 | stop_price: Some("12.321"), 707 | stp: Some("CO"), 708 | time_in_force: Some("GTT"), 709 | trade_type: Some("TRADE"), 710 | cancel_after: Some(1_231_231_321_321), 711 | post_only: Some(true), 712 | hidden: Some(true), 713 | iceberg: Some(false), 714 | visible_size: Some("1.23"), 715 | }; 716 | 717 | let builder_options = OrderOptionals::new() 718 | .remark("Test build pattern") 719 | .stop("loss") 720 | .stop_price("12.321") 721 | .stp("CO") 722 | .time_in_force("GTT") 723 | .trade_type("TRADE") 724 | .cancel_after(1_231_231_321_321) 725 | .post_only(true) 726 | .hidden(true) 727 | .iceberg(false) 728 | .visible_size("1.23") 729 | .build(); 730 | 731 | assert_eq!(builder_options, options) 732 | } 733 | 734 | #[test] 735 | fn use_build_pattern_some_order_optionals() { 736 | let options = OrderOptionals { 737 | remark: Some("Test build pattern"), 738 | stop: None, 739 | stop_price: None, 740 | stp: Some("CO"), 741 | time_in_force: Some("GTT"), 742 | trade_type: Some("TRADE"), 743 | cancel_after: Some(1_231_231_321_321), 744 | post_only: Some(true), 745 | hidden: None, 746 | iceberg: None, 747 | visible_size: None, 748 | }; 749 | 750 | let builder_options = OrderOptionals::new() 751 | .remark("Test build pattern") 752 | .stp("CO") 753 | .time_in_force("GTT") 754 | .trade_type("TRADE") 755 | .cancel_after(1_231_231_321_321) 756 | .post_only(true) 757 | .build(); 758 | 759 | assert_eq!(builder_options, options) 760 | } 761 | 762 | #[test] 763 | fn use_build_pattern_all_order_info_optionals() { 764 | let options = OrderInfoOptionals { 765 | status: Some("active"), 766 | symbol: Some("BTC-USDT"), 767 | side: Some("buy"), 768 | r#type: Some("limit"), 769 | trade_type: Some("TRADE"), 770 | start_at: Some(1_580_683_419_725), 771 | end_at: Some(1_580_683_800_000), 772 | current_page: Some(1), 773 | page_size: Some(50), 774 | }; 775 | 776 | let build_options = OrderInfoOptionals::new() 777 | .status("active") 778 | .symbol("BTC-USDT") 779 | .side("buy") 780 | .order_type("limit") 781 | .trade_type("TRADE") 782 | .start_at(1_580_683_419_725) 783 | .end_at(1_580_683_800_000) 784 | .current_page(1) 785 | .page_size(50) 786 | .build(); 787 | 788 | assert_eq!(options, build_options) 789 | } 790 | 791 | #[test] 792 | fn use_build_pattern_some_order_info_optionals() { 793 | let options = OrderInfoOptionals { 794 | status: None, 795 | symbol: Some("BTC-USDT"), 796 | side: None, 797 | r#type: None, 798 | trade_type: None, 799 | start_at: Some(1_580_683_419_725), 800 | end_at: Some(1_580_683_800_000), 801 | current_page: None, 802 | page_size: None, 803 | }; 804 | 805 | let build_options = OrderInfoOptionals::new() 806 | .symbol("BTC-USDT") 807 | .start_at(1_580_683_419_725) 808 | .end_at(1_580_683_800_000) 809 | .build(); 810 | 811 | assert_eq!(options, build_options) 812 | } 813 | 814 | #[test] 815 | fn use_build_pattern_all_fills_optionals() { 816 | let options = FillsOptionals { 817 | order_id: Some("asdasd-sadasda-asxsaxs"), 818 | symbol: Some("BTC-USDT"), 819 | side: Some("buy"), 820 | r#type: Some("limit"), 821 | trade_type: Some("TRADE"), 822 | start_at: Some(1_580_683_419_725), 823 | end_at: Some(1_580_683_800_000), 824 | current_page: Some(1), 825 | page_size: Some(50), 826 | }; 827 | 828 | let build_options = FillsOptionals::new() 829 | .order_id("asdasd-sadasda-asxsaxs") 830 | .symbol("BTC-USDT") 831 | .side("buy") 832 | .order_type("limit") 833 | .trade_type("TRADE") 834 | .start_at(1_580_683_419_725) 835 | .end_at(1_580_683_800_000) 836 | .current_page(1) 837 | .page_size(50) 838 | .build(); 839 | 840 | assert_eq!(options, build_options) 841 | } 842 | 843 | #[test] 844 | fn use_build_pattern_some_fills_optionals() { 845 | let options = FillsOptionals { 846 | order_id: None, 847 | symbol: Some("BTC-USDT"), 848 | side: None, 849 | r#type: None, 850 | trade_type: None, 851 | start_at: Some(1_580_683_419_725), 852 | end_at: Some(1_580_683_800_000), 853 | current_page: None, 854 | page_size: None, 855 | }; 856 | 857 | let build_options = FillsOptionals::new() 858 | .symbol("BTC-USDT") 859 | .start_at(1_580_683_419_725) 860 | .end_at(1_580_683_800_000) 861 | .build(); 862 | 863 | assert_eq!(options, build_options) 864 | } 865 | } 866 | -------------------------------------------------------------------------------- /src/kucoin/user.rs: -------------------------------------------------------------------------------- 1 | use reqwest::header; 2 | use std::collections::HashMap; 3 | 4 | use super::client::Kucoin; 5 | use super::error::APIError; 6 | use super::model::user::{ 7 | AccountHolds, AccountId, AccountInfo, AccountType, Accounts, DepositAddress, DepositList, 8 | DepositListV1, OrderId, SingleAccount, SubAccountBalances, TransferableBalance, UserInfo, 9 | WithdrawalId, WithdrawalList, WithdrawalListV1, WithdrawalQuotas, 10 | }; 11 | use super::model::{APIData, APIDatum, Method, Pagination}; 12 | use super::utils::format_query; 13 | 14 | impl Kucoin { 15 | pub async fn get_user_subaccount_info(&self) -> Result, APIError> { 16 | let endpoint = String::from("/api/v1/sub/user"); 17 | let url = format!("{}{}", &self.prefix, endpoint); 18 | let header = self 19 | .sign_headers(endpoint, None, None, Method::GET) 20 | .unwrap(); 21 | let resp = self.get(url, Some(header)).await?.json().await?; 22 | Ok(resp) 23 | } 24 | 25 | pub async fn create_account( 26 | &self, 27 | account_type: AccountType, 28 | currency: &str, 29 | ) -> Result, APIError> { 30 | let endpoint = String::from("/api/v1/accounts"); 31 | let url = format!("{}{}", &self.prefix, endpoint); 32 | let mut params: HashMap = HashMap::new(); 33 | match account_type { 34 | AccountType::Main => params.insert(String::from("type"), String::from("main")), 35 | AccountType::Margin => params.insert(String::from("type"), String::from("margin")), 36 | AccountType::Trade => params.insert(String::from("type"), String::from("trade")), 37 | }; 38 | params.insert(String::from("currency"), currency.to_string()); 39 | let header = self 40 | .sign_headers(endpoint, Some(¶ms), None, Method::POST) 41 | .unwrap(); 42 | let resp = self 43 | .post(url, Some(header), Some(params)) 44 | .await? 45 | .json() 46 | .await?; 47 | Ok(resp) 48 | } 49 | 50 | pub async fn get_accounts_list( 51 | &self, 52 | currency: Option<&str>, 53 | acct_type: Option<&str>, 54 | ) -> Result, APIError> { 55 | let mut params: HashMap = HashMap::new(); 56 | let headers: header::HeaderMap; 57 | let url: String; 58 | let endpoint = String::from("/api/v1/accounts"); 59 | if let Some(c) = currency { 60 | params.insert("currency".to_string(), c.to_owned()); 61 | } 62 | if let Some(a) = acct_type { 63 | params.insert("type".to_string(), a.to_owned()); 64 | } 65 | if !params.is_empty() { 66 | let query = format_query(¶ms); 67 | url = format!("{}{}{}", &self.prefix, endpoint, query); 68 | headers = self 69 | .sign_headers(endpoint, None, Some(query), Method::GET) 70 | .unwrap(); 71 | } else { 72 | url = format!("{}{}", &self.prefix, endpoint); 73 | headers = self 74 | .sign_headers(endpoint, None, None, Method::GET) 75 | .unwrap(); 76 | } 77 | let resp = self.get(url, Some(headers)).await?.json().await?; 78 | Ok(resp) 79 | } 80 | 81 | pub async fn get_account(&self, account_id: &str) -> Result, APIError> { 82 | let endpoint = format!("/api/v1/accounts/{}", account_id); 83 | let url = format!("{}{}", &self.prefix, endpoint); 84 | let headers = self 85 | .sign_headers(endpoint, None, None, Method::GET) 86 | .unwrap(); 87 | let resp = self.get(url, Some(headers)).await?.json().await?; 88 | Ok(resp) 89 | } 90 | 91 | pub async fn get_account_ledgers( 92 | &self, 93 | account_id: &str, 94 | start_at: Option, 95 | end_at: Option, 96 | current_page: Option, 97 | page_size: Option, 98 | ) -> Result>, APIError> { 99 | let endpoint = format!("/api/v1/accounts/{}/ledgers", account_id); 100 | let url: String; 101 | let headers: header::HeaderMap; 102 | let mut params: HashMap = HashMap::new(); 103 | if let Some(t) = start_at { 104 | params.insert(String::from("startAt"), t.to_string()); 105 | } 106 | if let Some(t) = end_at { 107 | params.insert(String::from("endAt"), t.to_string()); 108 | } 109 | if let Some(c) = current_page { 110 | params.insert(String::from("currentPage"), c.to_string()); 111 | } 112 | if let Some(p) = page_size { 113 | params.insert(String::from("pageSize"), p.to_string()); 114 | } 115 | if !params.is_empty() { 116 | let query = format_query(¶ms); 117 | url = format!("{}{}{}", &self.prefix, endpoint, query); 118 | headers = self 119 | .sign_headers(endpoint, None, Some(query), Method::GET) 120 | .unwrap(); 121 | } else { 122 | url = format!("{}{}", &self.prefix, endpoint); 123 | headers = self 124 | .sign_headers(endpoint, None, None, Method::GET) 125 | .unwrap(); 126 | } 127 | let resp = self.get(url, Some(headers)).await?.json().await?; 128 | Ok(resp) 129 | } 130 | 131 | pub async fn get_hold( 132 | &self, 133 | account_id: &str, 134 | current_page: Option, 135 | page_size: Option, 136 | ) -> Result>, APIError> { 137 | let endpoint = format!("/api/v1/accounts/{}/holds", account_id); 138 | let url: String; 139 | let headers: header::HeaderMap; 140 | let mut params: HashMap = HashMap::new(); 141 | if let Some(c) = current_page { 142 | params.insert(String::from("currentPage"), c.to_string()); 143 | } 144 | if let Some(p) = page_size { 145 | params.insert(String::from("pageSize"), p.to_string()); 146 | } 147 | if !params.is_empty() { 148 | let query = format_query(¶ms); 149 | url = format!("{}{}{}", &self.prefix, endpoint, query); 150 | headers = self 151 | .sign_headers(endpoint, None, Some(query), Method::GET) 152 | .unwrap(); 153 | } else { 154 | url = format!("{}{}", &self.prefix, endpoint); 155 | headers = self 156 | .sign_headers(endpoint, None, None, Method::GET) 157 | .unwrap(); 158 | } 159 | let resp = self.get(url, Some(headers)).await?.json().await?; 160 | Ok(resp) 161 | } 162 | 163 | pub async fn get_subaccount_balances( 164 | &self, 165 | account_id: &str, 166 | ) -> Result, APIError> { 167 | let endpoint = format!("/api/v1/sub-accounts/{}", account_id); 168 | let url = format!("{}{}", &self.prefix, endpoint); 169 | let headers = self 170 | .sign_headers(endpoint, None, None, Method::GET) 171 | .unwrap(); 172 | let resp = self.get(url, Some(headers)).await?.json().await?; 173 | Ok(resp) 174 | } 175 | 176 | pub async fn get_all_subaccount_balances( 177 | &self, 178 | ) -> Result, APIError> { 179 | let endpoint = String::from("/api/v1/sub-accounts"); 180 | let url = format!("{}{}", &self.prefix, endpoint); 181 | let headers = self 182 | .sign_headers(endpoint, None, None, Method::GET) 183 | .unwrap(); 184 | let resp = self.get(url, Some(headers)).await?.json().await?; 185 | Ok(resp) 186 | } 187 | 188 | pub async fn get_transferable_balance( 189 | &self, 190 | currency: &str, 191 | account_type: AccountType, 192 | ) -> Result, APIError> { 193 | let mut endpoint = format! {"/api/v1/accounts/transferable?currency={}", currency}; 194 | match account_type { 195 | AccountType::Main => endpoint.push_str("&type=MAIN"), 196 | AccountType::Margin => endpoint.push_str("&type=MARGIN"), 197 | AccountType::Trade => endpoint.push_str("&type=TRADE"), 198 | }; 199 | let url = format!("{}{}", &self.prefix, endpoint); 200 | let headers = self 201 | .sign_headers(endpoint, None, None, Method::GET) 202 | .unwrap(); 203 | let resp = self.get(url, Some(headers)).await?.json().await?; 204 | Ok(resp) 205 | } 206 | 207 | #[allow(clippy::too_many_arguments)] 208 | pub async fn transfer_to_subaccount( 209 | &self, 210 | client_oid: &str, 211 | currency: &str, 212 | amount: f32, 213 | direction: &str, 214 | sub_user_id: &str, 215 | account_type: Option<&str>, 216 | sub_account_type: Option<&str>, 217 | ) -> Result, APIError> { 218 | let endpoint = String::from("/api/v2/accounts/sub-transfer"); 219 | let url = format!("{}{}", &self.prefix, endpoint); 220 | let mut params: HashMap = HashMap::new(); 221 | params.insert(String::from("clientOid"), client_oid.to_string()); 222 | params.insert(String::from("currency"), currency.to_string()); 223 | params.insert(String::from("amount"), amount.to_string()); 224 | params.insert(String::from("direction"), direction.to_string()); 225 | params.insert(String::from("subUserId"), sub_user_id.to_string()); 226 | if let Some(a) = account_type { 227 | params.insert(String::from("accountType"), a.to_string()); 228 | } 229 | if let Some(s) = sub_account_type { 230 | params.insert(String::from("subAccountType"), s.to_string()); 231 | } 232 | let headers = self 233 | .sign_headers(endpoint, Some(¶ms), None, Method::POST) 234 | .unwrap(); 235 | let resp = self 236 | .post(url, Some(headers), Some(params)) 237 | .await? 238 | .json() 239 | .await?; 240 | Ok(resp) 241 | } 242 | 243 | pub async fn inner_transfer( 244 | &self, 245 | client_oid: &str, 246 | currency: &str, 247 | from: &str, 248 | to: &str, 249 | amount: &str, 250 | ) -> Result, APIError> { 251 | let endpoint = String::from("/api/v2/accounts/inner-transfer"); 252 | let url = format!("{}{}", &self.prefix, endpoint); 253 | let mut params: HashMap = HashMap::new(); 254 | params.insert(String::from("clientOid"), client_oid.to_string()); 255 | params.insert(String::from("currency"), currency.to_string()); 256 | params.insert(String::from("from"), from.to_string()); 257 | params.insert(String::from("to"), to.to_string()); 258 | params.insert(String::from("amount"), amount.to_string()); 259 | let headers = self 260 | .sign_headers(endpoint, Some(¶ms), None, Method::POST) 261 | .unwrap(); 262 | let resp = self 263 | .post(url, Some(headers), Some(params)) 264 | .await? 265 | .json() 266 | .await?; 267 | Ok(resp) 268 | } 269 | 270 | pub async fn create_deposit_address( 271 | &self, 272 | currency: &str, 273 | chain: Option<&str>, 274 | ) -> Result, APIError> { 275 | let endpoint = String::from("/api/v1/deposit-addresses"); 276 | let url = format!("{}{}", &self.prefix, endpoint); 277 | let mut params: HashMap = HashMap::new(); 278 | params.insert(String::from("currency"), currency.to_string()); 279 | if let Some(c) = chain { 280 | params.insert(String::from("chain"), c.to_string()); 281 | } 282 | let headers = self 283 | .sign_headers(endpoint, Some(¶ms), None, Method::POST) 284 | .unwrap(); 285 | let resp = self 286 | .post(url, Some(headers), Some(params)) 287 | .await? 288 | .json() 289 | .await?; 290 | Ok(resp) 291 | } 292 | 293 | pub async fn get_deposit_address( 294 | &self, 295 | currency: &str, 296 | chain: Option<&str>, 297 | ) -> Result, APIError> { 298 | let endpoint = String::from("/api/v2/deposit-addresses"); 299 | let mut params: HashMap = HashMap::new(); 300 | params.insert(String::from("currency"), currency.to_string()); 301 | if let Some(c) = chain { 302 | params.insert(String::from("chain"), c.to_string()); 303 | } 304 | let query = format_query(¶ms); 305 | let url = format!("{}{}{}", &self.prefix, endpoint, query); 306 | let headers = self 307 | .sign_headers(endpoint, None, Some(query), Method::GET) 308 | .unwrap(); 309 | let resp = self.get(url, Some(headers)).await?; 310 | let api_data = resp.json().await?; 311 | Ok(api_data) 312 | } 313 | 314 | pub async fn get_deposit_list( 315 | &self, 316 | currency: Option<&str>, 317 | start_at: Option, 318 | end_at: Option, 319 | status: Option<&str>, 320 | current_page: Option, 321 | page_size: Option, 322 | ) -> Result>, APIError> { 323 | let endpoint = String::from("/api/v1/deposits"); 324 | let mut params: HashMap = HashMap::new(); 325 | if let Some(c) = currency { 326 | params.insert(String::from("currency"), c.to_string()); 327 | } 328 | if let Some(t) = start_at { 329 | params.insert(String::from("startAt"), t.to_string()); 330 | } 331 | if let Some(t) = end_at { 332 | params.insert(String::from("endAt"), t.to_string()); 333 | } 334 | if let Some(s) = status { 335 | params.insert(String::from("status"), s.to_string()); 336 | } 337 | if let Some(c) = current_page { 338 | params.insert(String::from("currentPage"), c.to_string()); 339 | } 340 | if let Some(p) = page_size { 341 | params.insert(String::from("pageSize"), p.to_string()); 342 | } 343 | let query = format_query(¶ms); 344 | let url = format!("{}{}{}", &self.prefix, endpoint, query); 345 | let headers = self 346 | .sign_headers(endpoint, None, Some(query), Method::GET) 347 | .unwrap(); 348 | let resp = self.get(url, Some(headers)).await?; 349 | let api_data = resp.json().await?; 350 | Ok(api_data) 351 | } 352 | 353 | pub async fn get_v1_deposit_list( 354 | &self, 355 | currency: Option<&str>, 356 | start_at: Option, 357 | end_at: Option, 358 | status: Option<&str>, 359 | current_page: Option, 360 | page_size: Option, 361 | ) -> Result>, APIError> { 362 | let endpoint = String::from("/api/v1/deposits"); 363 | let mut params: HashMap = HashMap::new(); 364 | if let Some(c) = currency { 365 | params.insert(String::from("currency"), c.to_string()); 366 | } 367 | if let Some(t) = start_at { 368 | params.insert(String::from("startAt"), t.to_string()); 369 | } 370 | if let Some(t) = end_at { 371 | params.insert(String::from("endAt"), t.to_string()); 372 | } 373 | if let Some(s) = status { 374 | params.insert(String::from("status"), s.to_string()); 375 | } 376 | if let Some(c) = current_page { 377 | params.insert(String::from("currentPage"), c.to_string()); 378 | } 379 | if let Some(p) = page_size { 380 | params.insert(String::from("pageSize"), p.to_string()); 381 | } 382 | let query = format_query(¶ms); 383 | let url = format!("{}{}{}", &self.prefix, endpoint, query); 384 | let headers = self 385 | .sign_headers(endpoint, None, Some(query), Method::GET) 386 | .unwrap(); 387 | let resp = self.get(url, Some(headers)).await?; 388 | let api_data = resp.json().await?; 389 | Ok(api_data) 390 | } 391 | 392 | pub async fn get_withdrawals_list( 393 | &self, 394 | currency: Option<&str>, 395 | start_at: Option, 396 | end_at: Option, 397 | status: Option<&str>, 398 | current_page: Option, 399 | page_size: Option, 400 | ) -> Result>, APIError> { 401 | let endpoint = String::from("/api/v1/withdrawals"); 402 | let mut params: HashMap = HashMap::new(); 403 | if let Some(c) = currency { 404 | params.insert(String::from("currency"), c.to_string()); 405 | } 406 | if let Some(t) = start_at { 407 | params.insert(String::from("startAt"), t.to_string()); 408 | } 409 | if let Some(t) = end_at { 410 | params.insert(String::from("endAt"), t.to_string()); 411 | } 412 | if let Some(s) = status { 413 | params.insert(String::from("status"), s.to_string()); 414 | } 415 | if let Some(c) = current_page { 416 | params.insert(String::from("currentPage"), c.to_string()); 417 | } 418 | if let Some(p) = page_size { 419 | params.insert(String::from("pageSize"), p.to_string()); 420 | } 421 | let query = format_query(¶ms); 422 | let url = format!("{}{}{}", &self.prefix, endpoint, query); 423 | let headers = self 424 | .sign_headers(endpoint, None, Some(query), Method::GET) 425 | .unwrap(); 426 | let resp = self.get(url, Some(headers)).await?; 427 | let api_data = resp.json().await?; 428 | Ok(api_data) 429 | } 430 | 431 | pub async fn get_v1_withdrawals_list( 432 | &self, 433 | currency: Option<&str>, 434 | start_at: Option, 435 | end_at: Option, 436 | status: Option<&str>, 437 | current_page: Option, 438 | page_size: Option, 439 | ) -> Result>, APIError> { 440 | let endpoint = String::from("/api/v1/withdrawals"); 441 | let mut params: HashMap = HashMap::new(); 442 | if let Some(c) = currency { 443 | params.insert(String::from("currency"), c.to_string()); 444 | } 445 | if let Some(t) = start_at { 446 | params.insert(String::from("startAt"), t.to_string()); 447 | } 448 | if let Some(t) = end_at { 449 | params.insert(String::from("endAt"), t.to_string()); 450 | } 451 | if let Some(s) = status { 452 | params.insert(String::from("status"), s.to_string()); 453 | } 454 | if let Some(p) = current_page { 455 | params.insert(String::from("currentPage"), p.to_string()); 456 | } 457 | if let Some(p) = page_size { 458 | params.insert(String::from("pageSize"), p.to_string()); 459 | } 460 | let query = format_query(¶ms); 461 | let url = format!("{}{}{}", &self.prefix, endpoint, query); 462 | let headers = self 463 | .sign_headers(endpoint, None, Some(query), Method::GET) 464 | .unwrap(); 465 | let resp = self.get(url, Some(headers)).await?; 466 | let api_data = resp.json().await?; 467 | Ok(api_data) 468 | } 469 | 470 | pub async fn get_withdrawals_quotas( 471 | &self, 472 | currency: &str, 473 | chain: Option<&str>, 474 | ) -> Result, APIError> { 475 | let endpoint = String::from("/api/v1/withdrawals/quotas"); 476 | let mut params: HashMap = HashMap::new(); 477 | params.insert(String::from("currency"), currency.to_string()); 478 | if let Some(c) = chain { 479 | params.insert(String::from("chain"), c.to_string()); 480 | } 481 | let query = format_query(¶ms); 482 | let url = format!("{}{}{}", &self.prefix, endpoint, query); 483 | let headers = self 484 | .sign_headers(endpoint, None, Some(query), Method::GET) 485 | .unwrap(); 486 | let resp = self.get(url, Some(headers)).await?; 487 | let api_data = resp.json().await?; 488 | Ok(api_data) 489 | } 490 | 491 | #[allow(clippy::too_many_arguments)] 492 | pub async fn apply_withdrawal( 493 | &self, 494 | currency: &str, 495 | address: &str, 496 | amount: i32, 497 | memo: Option<&str>, 498 | is_inner: Option, 499 | remark: Option<&str>, 500 | chain: Option<&str>, 501 | ) -> Result, APIError> { 502 | let endpoint = String::from("/api/v1/withdrawals"); 503 | let url = format!("{}{}", &self.prefix, endpoint); 504 | let mut params: HashMap = HashMap::new(); 505 | params.insert(String::from("currency"), currency.to_string()); 506 | params.insert(String::from("address"), address.to_string()); 507 | params.insert(String::from("amount"), amount.to_string()); 508 | if let Some(m) = memo { 509 | params.insert(String::from("memo"), m.to_string()); 510 | } 511 | if let Some(i) = is_inner { 512 | params.insert(String::from("isInner"), i.to_string()); 513 | } 514 | if let Some(r) = remark { 515 | params.insert(String::from("remark"), r.to_string()); 516 | } 517 | if let Some(c) = chain { 518 | params.insert(String::from("chain"), c.to_string()); 519 | } 520 | let headers = self 521 | .sign_headers(endpoint, Some(¶ms), None, Method::POST) 522 | .unwrap(); 523 | let resp = self.post(url, Some(headers), Some(params)).await?; 524 | let api_data = resp.json().await?; 525 | Ok(api_data) 526 | } 527 | 528 | pub async fn cancel_withdrawal(&self, withdrawal_id: &str) -> Result { 529 | let endpoint = format!("/api/v1/withdrawals/{}", withdrawal_id); 530 | let url = format!("{}{}", &self.prefix, endpoint); 531 | let headers = self 532 | .sign_headers(endpoint, None, None, Method::DELETE) 533 | .unwrap(); 534 | let resp = self.delete(url, Some(headers)).await?; 535 | let api_data = resp.text().await?; 536 | Ok(api_data) 537 | } 538 | } 539 | -------------------------------------------------------------------------------- /src/kucoin/utils.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::time::{SystemTime, UNIX_EPOCH}; 3 | 4 | pub fn get_time() -> u128 { 5 | let start = SystemTime::now(); 6 | let since_the_epoch = start 7 | .duration_since(UNIX_EPOCH) 8 | .expect("Time went backwards"); 9 | 10 | since_the_epoch.as_millis() 11 | } 12 | 13 | /// Formats a query from a provided referenced hash map. Note, ordering is not assured. 14 | pub fn format_query(params: &HashMap) -> String { 15 | let mut query = String::new(); 16 | for (key, val) in params.iter() { 17 | let segment = format!("{}={}", key, val); 18 | if query.is_empty() { 19 | query = format!("?{}", segment); 20 | } else { 21 | query = format!("{}&{}", query, segment); 22 | } 23 | } 24 | query 25 | } 26 | 27 | #[cfg(test)] 28 | mod test { 29 | use crate::kucoin::utils::format_query; 30 | use std::collections::HashMap; 31 | #[test] 32 | fn format_query_test() { 33 | let mut params: HashMap = HashMap::new(); 34 | 35 | params.insert("quantity".to_string(), 0.51.to_string()); 36 | params.insert("symbol".to_string(), "BTC-USDT".to_string()); 37 | params.insert("price".to_string(), 124.12.to_string()); 38 | 39 | let query = format_query(¶ms); 40 | assert_eq!(query.contains("symbol=BTC-USDT"), true); 41 | assert_eq!(query.contains("price=124.12"), true); 42 | assert_eq!(query.contains("quantity=0.51"), true); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/kucoin/websocket.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use futures::{prelude::*, stream::SplitStream, StreamExt}; 4 | use pin_project::*; 5 | use reqwest::header; 6 | use std::time::Duration; 7 | use streamunordered::{StreamUnordered, StreamYield}; 8 | use tokio::net::TcpStream; 9 | use tokio::sync::Mutex; 10 | use tokio::time; 11 | use tokio_tungstenite::{connect_async, tungstenite::Message, WebSocketStream}; 12 | use url::Url; 13 | 14 | use failure; 15 | use serde_json; 16 | use std::{ 17 | pin::Pin, 18 | task::{Context, Poll}, 19 | }; 20 | 21 | use super::client::Kucoin; 22 | use super::error::APIError; 23 | use super::model::websocket::{ 24 | DefaultMsg, InstanceServers, KucoinWebsocketMsg, Subscribe, WSTopic, WSType, 25 | }; 26 | use super::model::{APIDatum, Method}; 27 | use super::utils::get_time; 28 | 29 | type WSStream = WebSocketStream< 30 | tokio_tungstenite::stream::Stream>, 31 | >; 32 | pub type StoredStream = SplitStream; 33 | 34 | #[pin_project] 35 | #[derive(Default)] 36 | pub struct KucoinWebsocket { 37 | subscriptions: HashMap, 38 | tokens: HashMap, 39 | #[pin] 40 | streams: StreamUnordered, 41 | } 42 | 43 | impl Stream for KucoinWebsocket { 44 | type Item = Result; 45 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 46 | match self.as_mut().project().streams.poll_next(cx) { 47 | Poll::Ready(Some((y, _))) => match y { 48 | StreamYield::Item(item) => { 49 | // let heartbeat = self.heartbeats.get_mut(&token).unwrap(); 50 | Poll::Ready({ 51 | Some( 52 | item.map_err(APIError::Websocket) 53 | .and_then(|m| parse_message(m)), 54 | ) 55 | }) 56 | } 57 | StreamYield::Finished(_) => Poll::Pending, 58 | }, 59 | Poll::Ready(None) => panic!("No Stream Subscribed"), 60 | Poll::Pending => Poll::Pending, 61 | } 62 | } 63 | } 64 | 65 | impl KucoinWebsocket { 66 | pub async fn subscribe(&mut self, url: String, ws_topic: Vec) -> Result<(), APIError> { 67 | let endpoint = Url::parse(&url).unwrap(); 68 | let (ws_stream, _) = connect_async(endpoint).await?; 69 | 70 | let (sink, read) = ws_stream.split(); 71 | let sink_mutex = Mutex::new(sink); 72 | 73 | for topic in ws_topic.iter() { 74 | let sub = Subscribe::new(topic); 75 | 76 | sink_mutex 77 | .lock() 78 | .await 79 | .send(Message::Text(serde_json::to_string(&sub).unwrap())) 80 | .await?; 81 | } 82 | 83 | // Ping heartbeat 84 | tokio::spawn(async move { 85 | loop { 86 | time::sleep(Duration::from_secs(30)).await; 87 | let ping = DefaultMsg { 88 | id: get_time().to_string(), 89 | r#type: "ping".to_string(), 90 | }; 91 | let resp = sink_mutex 92 | .lock() 93 | .await 94 | .send(Message::Text(serde_json::to_string(&ping).unwrap())) 95 | .map_err(APIError::Websocket) 96 | .await; 97 | 98 | if let Err(e) = resp { 99 | match e { 100 | APIError::Websocket(e) => { 101 | format_err!("Error sending Ping: {}", e); 102 | break; 103 | } 104 | _ => format_err!("None websocket error sending Ping: {}", e), 105 | }; 106 | }; 107 | } 108 | }); 109 | 110 | let token = self.streams.push(read); 111 | self.subscriptions.insert(ws_topic[0].clone(), token); 112 | self.tokens.insert(token, ws_topic[0].clone()); 113 | 114 | Ok(()) 115 | } 116 | 117 | pub fn unsubscribe(&mut self, ws_topic: WSTopic) -> Option { 118 | let streams = Pin::new(&mut self.streams); 119 | self.subscriptions 120 | .get(&ws_topic) 121 | .and_then(|token| StreamUnordered::take(streams, *token)) 122 | } 123 | } 124 | 125 | fn parse_message(msg: Message) -> Result { 126 | match msg { 127 | Message::Text(msg) => { 128 | if msg.contains("\"type\":\"welcome\"") || msg.contains("\"type\":\"ack\"") { 129 | Ok(KucoinWebsocketMsg::WelcomeMsg(serde_json::from_str(&msg)?)) 130 | } else if msg.contains("\"type\":\"ping\"") { 131 | Ok(KucoinWebsocketMsg::PingMsg(serde_json::from_str(&msg)?)) 132 | } else if msg.contains("\"type\":\"pong\"") { 133 | Ok(KucoinWebsocketMsg::PongMsg(serde_json::from_str(&msg)?)) 134 | } else if msg.contains("\"subject\":\"trade.ticker\"") { 135 | Ok(KucoinWebsocketMsg::TickerMsg(serde_json::from_str(&msg)?)) 136 | } else if msg.contains("\"topic\":\"/market/ticker:all\"") { 137 | Ok(KucoinWebsocketMsg::AllTickerMsg(serde_json::from_str( 138 | &msg, 139 | )?)) 140 | } else if msg.contains("\"subject\":\"trade.snapshot\"") { 141 | Ok(KucoinWebsocketMsg::SnapshotMsg(serde_json::from_str(&msg)?)) 142 | } else if msg.contains("\"subject\":\"trade.l2update\"") { 143 | Ok(KucoinWebsocketMsg::OrderBookMsg(serde_json::from_str( 144 | &msg, 145 | )?)) 146 | } else if msg.contains("/market/match:") { 147 | Ok(KucoinWebsocketMsg::MatchMsg(serde_json::from_str(&msg)?)) 148 | } else if msg.contains("\"subject\":\"trade.l3received\"") { 149 | Ok(KucoinWebsocketMsg::Level3ReceivedMsg(serde_json::from_str( 150 | &msg, 151 | )?)) 152 | } else if msg.contains("\"subject\":\"trade.l3open\"") { 153 | Ok(KucoinWebsocketMsg::Level3OpenMsg(serde_json::from_str( 154 | &msg, 155 | )?)) 156 | } else if msg.contains("\"subject\":\"trade.l3done\"") { 157 | Ok(KucoinWebsocketMsg::Level3DoneMsg(serde_json::from_str( 158 | &msg, 159 | )?)) 160 | } else if msg.contains("\"subject\":\"trade.l3match\"") { 161 | Ok(KucoinWebsocketMsg::Level3MatchMsg(serde_json::from_str( 162 | &msg, 163 | )?)) 164 | } else if msg.contains("\"subject\":\"trade.l3change\"") { 165 | Ok(KucoinWebsocketMsg::Level3ChangeMsg(serde_json::from_str( 166 | &msg, 167 | )?)) 168 | } else if msg.contains("\"subject\":\"level2\"") { 169 | Ok(KucoinWebsocketMsg::OrderBookDepthMsg(serde_json::from_str( 170 | &msg, 171 | )?)) 172 | } else if msg.contains("\"subject\":\"received\"") { 173 | Ok(KucoinWebsocketMsg::FullMatchReceivedMsg( 174 | serde_json::from_str(&msg)?, 175 | )) 176 | } else if msg.contains("\"subject\":\"open\"") { 177 | Ok(KucoinWebsocketMsg::FullMatchOpenMsg(serde_json::from_str( 178 | &msg, 179 | )?)) 180 | } else if msg.contains("\"subject\":\"done\"") { 181 | Ok(KucoinWebsocketMsg::FullMatchDoneMsg(serde_json::from_str( 182 | &msg, 183 | )?)) 184 | } else if msg.contains("\"subject\":\"match\"") { 185 | Ok(KucoinWebsocketMsg::FullMatchMatchMsg(serde_json::from_str( 186 | &msg, 187 | )?)) 188 | } else if msg.contains("\"subject\":\"update\"") { 189 | Ok(KucoinWebsocketMsg::FullMatchChangeMsg( 190 | serde_json::from_str(&msg)?, 191 | )) 192 | } else if msg.contains("/indicator/index:") { 193 | Ok(KucoinWebsocketMsg::IndexPriceMsg(serde_json::from_str( 194 | &msg, 195 | )?)) 196 | } else if msg.contains("/indicator/markPrice:") { 197 | Ok(KucoinWebsocketMsg::MarketPriceMsg(serde_json::from_str( 198 | &msg, 199 | )?)) 200 | } else if msg.contains("/margin/fundingBook:") { 201 | Ok(KucoinWebsocketMsg::OrderBookChangeMsg( 202 | serde_json::from_str(&msg)?, 203 | )) 204 | } else if msg.contains("\"type\":\"stop\"") || msg.contains("\"type\":\"activate\"") { 205 | Ok(KucoinWebsocketMsg::StopOrderMsg(serde_json::from_str( 206 | &msg, 207 | )?)) 208 | } else if msg.contains("/account/balance") { 209 | Ok(KucoinWebsocketMsg::BalancesMsg(serde_json::from_str(&msg)?)) 210 | } else if msg.contains("debt.ratio") { 211 | Ok(KucoinWebsocketMsg::DebtRatioMsg(serde_json::from_str( 212 | &msg, 213 | )?)) 214 | } else if msg.contains("position.status") { 215 | Ok(KucoinWebsocketMsg::PositionChangeMsg(serde_json::from_str( 216 | &msg, 217 | )?)) 218 | } else if msg.contains("order.open") { 219 | Ok(KucoinWebsocketMsg::MarginTradeOpenMsg( 220 | serde_json::from_str(&msg)?, 221 | )) 222 | } else if msg.contains("order.update") { 223 | Ok(KucoinWebsocketMsg::MarginTradeUpdateMsg( 224 | serde_json::from_str(&msg)?, 225 | )) 226 | } else if msg.contains("order.done") { 227 | Ok(KucoinWebsocketMsg::MarginTradeDoneMsg( 228 | serde_json::from_str(&msg)?, 229 | )) 230 | } else if msg.contains("error") { 231 | Ok(KucoinWebsocketMsg::Error(msg)) 232 | } else if msg.contains("\"topic\":\"/spotMarket/tradeOrders\"") { 233 | if msg.contains("\"type\":\"open\"") { 234 | Ok(KucoinWebsocketMsg::TradeOpenMsg(serde_json::from_str( 235 | &msg, 236 | )?)) 237 | } else if msg.contains("\"type\":\"match\"") { 238 | Ok(KucoinWebsocketMsg::TradeMatchMsg(serde_json::from_str( 239 | &msg, 240 | )?)) 241 | } else if msg.contains("\"type\":\"filled\"") { 242 | Ok(KucoinWebsocketMsg::TradeFilledMsg(serde_json::from_str( 243 | &msg, 244 | )?)) 245 | } else if msg.contains("\"type\":\"canceled\"") { 246 | Ok(KucoinWebsocketMsg::TradeCanceledMsg(serde_json::from_str( 247 | &msg, 248 | )?)) 249 | } else if msg.contains("\"type\":\"update\"") { 250 | Ok(KucoinWebsocketMsg::TradeUpdateMsg(serde_json::from_str( 251 | &msg, 252 | )?)) 253 | } else { 254 | Err(APIError::Other( 255 | "No KucoinWebSocketMsg type to parse".to_string(), 256 | )) 257 | } 258 | } else { 259 | Err(APIError::Other( 260 | "No KucoinWebSocketMsg type to parse".to_string(), 261 | )) 262 | } 263 | } 264 | Message::Binary(b) => Ok(KucoinWebsocketMsg::Binary(b)), 265 | Message::Pong(..) => Ok(KucoinWebsocketMsg::Pong), 266 | Message::Ping(..) => Ok(KucoinWebsocketMsg::Ping), 267 | Message::Close(..) => Err(APIError::Other("Socket closed error".to_string())), 268 | } 269 | } 270 | 271 | pub async fn close_socket( 272 | heartbeat: &mut tokio::task::JoinHandle<()>, 273 | ) -> Result<(), failure::Error> { 274 | heartbeat.await?; 275 | Ok(()) 276 | } 277 | 278 | impl Kucoin { 279 | pub fn websocket(&self) -> KucoinWebsocket { 280 | KucoinWebsocket::default() 281 | } 282 | 283 | pub async fn ws_bullet_private(&self) -> Result, APIError> { 284 | let endpoint = String::from("/api/v1/bullet-private"); 285 | let url: String = format!("{}{}", &self.prefix, endpoint); 286 | let header: header::HeaderMap = self 287 | .sign_headers(endpoint, None, None, Method::POST) 288 | .unwrap(); 289 | let resp = self.post(url, Some(header), None).await?; 290 | let api_data: APIDatum = resp.json().await?; 291 | Ok(api_data) 292 | } 293 | 294 | pub async fn ws_bullet_public(&self) -> Result, APIError> { 295 | let endpoint = String::from("/api/v1/bullet-public"); 296 | let url: String = format!("{}{}", &self.prefix, endpoint); 297 | let header: header::HeaderMap = self 298 | .sign_headers(endpoint, None, None, Method::POST) 299 | .unwrap(); 300 | let resp = self.post(url, Some(header), None).await?; 301 | let api_data: APIDatum = resp.json().await?; 302 | Ok(api_data) 303 | } 304 | 305 | pub async fn get_socket_endpoint(&self, ws_type: WSType) -> Result { 306 | let mut endpoint: String = String::new(); 307 | let mut token: String = String::new(); 308 | let timestamp = get_time(); 309 | match ws_type { 310 | WSType::Private => { 311 | let resp = &self.ws_bullet_private().await?; 312 | if let Some(r) = &resp.data { 313 | token = r.token.to_owned(); 314 | endpoint = r.instance_servers[0].endpoint.to_owned(); 315 | } 316 | } 317 | WSType::Public => { 318 | let resp = &self.ws_bullet_public().await?; 319 | if let Some(r) = &resp.data { 320 | token = r.token.to_owned(); 321 | endpoint = r.instance_servers[0].endpoint.to_owned(); 322 | } 323 | } 324 | } 325 | let url = format!( 326 | "{}?token={}&[connectId={}]?acceptUserMessage=\"true\"", 327 | endpoint, token, timestamp 328 | ); 329 | Ok(url) 330 | } 331 | } 332 | 333 | impl Subscribe { 334 | pub fn new(topic_type: &WSTopic) -> Self { 335 | let id = get_time().to_string(); 336 | let mut private_channel = false; 337 | let topic = match topic_type { 338 | WSTopic::Ticker(ref symbols) => format!("/market/ticker:{}", symbols.join(",")), 339 | WSTopic::AllTicker => String::from("/market/ticker:all"), 340 | WSTopic::Snapshot(ref symbol) => format!("/market/snapshot:{}", symbol), 341 | WSTopic::IndexPrice(ref symbols) => format!("/indicator/index:{}", symbols.join(",")), 342 | WSTopic::MarketPrice(ref symbols) => { 343 | format!("/indicator/markPrice:{}", symbols.join(",")) 344 | } 345 | WSTopic::OrderBook(ref symbols) => format!("/market/level2:{}", symbols.join(",")), 346 | WSTopic::OrderBookChange(ref symbols) => { 347 | format!("/margin/fundingBook:{}", symbols.join(",")) 348 | } 349 | WSTopic::OrderBookDepth5(ref symbols) => { 350 | format!("/spotMarket/level2Depth5:{}", symbols.join(",")) 351 | } 352 | WSTopic::OrderBookDepth50(ref symbols) => { 353 | format!("/spotMarket/level2Depth50:{}", symbols.join(",")) 354 | } 355 | WSTopic::Match(ref symbols) => format!("/market/match:{}", symbols.join(",")), 356 | WSTopic::Level3Public(ref symbols) => format!("/market/level3:{}", symbols.join(",")), 357 | WSTopic::FullMatch(ref symbols) => format!("/spotMarket/level3:{}", symbols.join(",")), 358 | WSTopic::Level3Private(ref symbols) => { 359 | private_channel = true; 360 | format!("/market/level3:{}", symbols.join(",")) 361 | } 362 | WSTopic::Balances => { 363 | private_channel = true; 364 | String::from("/account/balance") 365 | } 366 | WSTopic::StopOrder(ref symbols) => { 367 | private_channel = true; 368 | format!("/market/level3:{}", symbols.join(",")) 369 | } 370 | WSTopic::DebtRatio => { 371 | private_channel = true; 372 | String::from("/margin/position") 373 | } 374 | WSTopic::PositionChange => { 375 | private_channel = true; 376 | String::from("/margin/position") 377 | } 378 | WSTopic::MarginTradeOrder(ref symbol) => { 379 | private_channel = true; 380 | format!("/margin/loan:{}", symbol) 381 | } 382 | WSTopic::TradeOrders => { 383 | private_channel = true; 384 | String::from("/spotMarket/tradeOrders") 385 | } 386 | }; 387 | 388 | Subscribe { 389 | id, 390 | r#type: String::from("subscribe"), 391 | topic, 392 | private_channel, 393 | response: true, 394 | } 395 | } 396 | } 397 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # kucoin_rs 2 | //! kucoin_rs is an open source library SDK/API wrapper for the 3 | //! [Kucoin Cryptocurrency Exchange](https://www.kucoin.com/). 4 | //! 5 | //! Trading cryptocurrencies is high risk and there are no guarentees towards the stability or effectiveness 6 | //! of this project. Comments, contributions, stars and donations are, however, all welcome. 7 | //! 8 | //! ## Description 9 | //! 10 | //! kucoin_rs supports all currently available Kucoin REST and Websocket endpoints. It is designed to be 11 | //! async and relies primarily on the tokio async runtime, reqwest for the HTTP layer and tokio_tungstenite 12 | //! for the Websocket layer. 13 | //! 14 | //! For the official API documentation visit [Kucoin Docs](https://docs.kucoin.com/). 15 | //! 16 | //! Please be aware that due to the nature of a number of endpoints, the response structs and input parameters of 17 | //! several requests may contain Option\ and will require appropriate pattern matching. These generally coincide 18 | //! with optional input params which can be seen by visiting the official Kucoin API documentation noted above. 19 | //! 20 | //! See the Kucoin Client for all endpoint fn calls and required/optional input types, and endpoint models for specifics:
21 | //! * [`Kucoin Client`](./kucoin/client/struct.Kucoin.html) 22 | //! * [`API General Response Models`](./kucoin/model/index.html) 23 | //! * [`Market Response Models`](./kucoin/model/market/index.html) 24 | //! * [`Margin Response Models`](./kucoin/model/margin/index.html) 25 | //! * [`Trade Response Models`](./kucoin/model/trade/index.html) 26 | //! * [`User Response Models`](./kucoin/model/user/index.html) 27 | //! * [`Websocket Response Models`](./kucoin/model/websocket/index.html) 28 | //! 29 | //! These project docs also provide details regarding necessary input parameters and response structs, 30 | //! helping to identify cases where Option\ matching is and is not necessary. 31 | //! 32 | //! ## Getting Started 33 | //! 34 | //! The library can be used either directly through the git repository or by utilizing cargo and installing the desired version. Once 35 | //! the library is accessible, simply bring the extern crate into your project. 36 | //! 37 | //! If you want information on particular endpoints, please review the library documentation. 38 | //! 39 | //! ### Authorization 40 | //! 41 | //! Authorization is required for many of the endpoints. The [`Kucoin Client`](./kucoin/client/struct.Kucoin.html) handles all 42 | //! header construction but does require that the client is initialized with credentials to do so. To include credentials do the following: 43 | //! 44 | //! ``` 45 | //! use kucoin_rs::kucoin::client::{Kucoin, Credentials, KucoinEnv}; 46 | //! 47 | //! let credentials = Credentials::new( 48 | //! "xxxxxxxxxxxxxXXXXXXxxx", // API KEY 49 | //! "XXxxxxx-xxxxxx-xXxxxx-xxxx", // SECRET KEY 50 | //! "xxxxxx" // PASSPHRASE 51 | //! ); 52 | //! 53 | //! let api = Kucoin::new(KucoinEnv::Live, Some(credentials)); 54 | //! ``` 55 | //! A non-authorized client can be used for accessing Public Endpoints by inputting a None: `Kucoin::new(KucoinEnv::Live, None);` 56 | //! 57 | //! ## Examples 58 | //! 59 | //! Below are some basic examples. 60 | //! 61 | //! Private endpoints require an authorized client. Check above for further details on initializing kucoin_rs 62 | //! with appropriate credentials to access private endpoints 63 | //! 64 | //! ### REST Usage 65 | //! 66 | //! REST calls, like Websocket connections, require first setting up the client. Once the client is setup, calls can be made in whatever 67 | //! ways suit end-users' needs. 68 | //! 69 | //! Please note that endpoints have varying amounts of input parameter requirements and options. Required parameters are always direct inputs 70 | //! but types may vary. Optional requirements are wrapped in Option\, so be aware that a large number of calls require None or Some(T). 71 | //! inputs. The endpoints with signficant number of options take advantage of builder methods on optional structs. 72 | //! This documention provides details of where this is necessary. To check for specific endpoints, see: 73 | //! [`Kucoin Client`](./kucoin/client/struct.Kucoin.html). Optional structs with builders will be identified in the fn signatures. 74 | //! 75 | //! A simple example is: 76 | //! 77 | //! ```ignore 78 | //! extern crate kucoin_rs; 79 | //! 80 | //! use kucoin_rs::tokio; 81 | //! use kucoin_rs::failure; 82 | //! use kucoin_rs::kucoin::client::{Kucoin, Credentials, KucoinEnv}; 83 | //! 84 | //! #[tokio::main] 85 | //! async fn main() -> Result<(), failure::Error> { 86 | //! let api = Kucoin::new(KucoinEnv::Sandbox, None)?; 87 | //! let result = api.get_ticker("BTC-USDT").await?; 88 | //! match result.data { 89 | //! Some(d) => println!("{:#?}", d), 90 | //! None => println!("Code: {}, msg: {:#?}", result.code, result.msg), 91 | //! } 92 | //! Ok(()) 93 | //! } 94 | //! ``` 95 | //! 96 | //! An example with custom error handling is: 97 | //! 98 | //! ```ignore 99 | //! extern crate kucoin_rs; 100 | //! 101 | //! use kucoin_rs::tokio; 102 | //! use kucoin_rs::failure; 103 | //! use kucoin_rs::kucoin::client::{Kucoin, Credentials, KucoinEnv}; 104 | //! use kucoin_rs::kucoin::error::APIError; 105 | //! 106 | //! #[tokio::main] 107 | //! async fn main() -> Result<(), failure::Error> { 108 | //! let result = api.get_server_time().await; 109 | //! match result { 110 | //! Err(e) => { 111 | //! match e { 112 | //! APIError::HTTP(e) => eprintln!("Reqwest Error: {}", e), 113 | //! _ => eprintln!("Non HTTP Error: {}", e), 114 | //! } 115 | //! }, 116 | //! Ok(s) => { 117 | //! match s.data { 118 | //! Some(d) => println!("{:#?}", d), 119 | //! None => println!("Code: {}, msg: {:#?}", s.code, s.msg), 120 | //! } 121 | //! } 122 | //! } 123 | //! Ok(()) 124 | //! } 125 | //! ``` 126 | //! 127 | //! 128 | //! ### Websocket Usage 129 | //! 130 | //! Websockets require several steps to initalize. A single websocket can accept up to 10 subscriptions, 131 | //! as per Kucoin limitations. Due to this, the instantiation of the socket takes a Vec\<[WSTopic](./kucoin/model/websocket/enum.WSTopic.html)\>. 132 | //! The reason is because multiple subscriptions can be initialized from one call. Below is a simplified single subscription with a line-by-line 133 | //! short explanation including some basic options for specified error handling. 134 | //! 135 | //! ```ignore 136 | //! extern crate kucoin_rs; 137 | //! 138 | //! use kucoin_rs::tokio; 139 | //! use kucoin_rs::failure; 140 | //! use kucoin_rs::tokio::stream::StreamExt; 141 | //! 142 | //! use kucoin_rs::kucoin::client::{Kucoin, Credentials, KucoinEnv}; 143 | //! use kucoin_rs::kucoin::model::websocket::{Subscribe, KucoinWebsocketMsg, WSType, WSTopic, WSResp}; 144 | //! 145 | //! #[tokio::main] 146 | //! async fn main() -> Result<(), failure::Error> { 147 | //! // If credentials are needed, generate a new Credentials struct w/ the necessary keys 148 | //! let credentials = Credentials::new( 149 | //! "xxxxxxxxxxxxxXXXXXXxxx", 150 | //! "XXxxxxx-xxxxxx-xXxxxx-xxxx", 151 | //! "xxxxxx" 152 | //! ); 153 | //! 154 | //! // Initialize the Kucoin API struct 155 | //! let api = Kucoin::new(KucoinEnv::Live, Some(credentials))?; 156 | //! 157 | //! // Generate the dynamic Public or Private websocket url and endpoint from Kucoin 158 | //! // which includes a token required for connecting 159 | //! let url = api.get_socket_endpoint(WSType::Public).await?; 160 | //! 161 | //! // Initialize the websocket 162 | //! let mut ws = api.websocket(); 163 | //! 164 | //! // Generate a Vec of desired subs. Note they need to be public or private 165 | //! // depending on the url 166 | //! let subs = vec![WSTopic::Ticker(vec!["BTC-USDT".to_string()])]; 167 | //! 168 | //! // Initalize your subscription and use await to unwrap the future 169 | //! ws.subscribe(url, subs).await?; 170 | //! 171 | //! // Handle incoming responses matching messages. Note, the message matching is 172 | //! // not required for a single subscription but may be desired 173 | //! // for more complex event handling for multi-subscribed sockets add the additional 174 | //! // KucoinWebSocketMsg matches. 175 | //! while let Some(msg) = ws.try_next().await? { 176 | //! match msg { 177 | //! KucoinWebsocketMsg::TickerMsg(msg) => println!("{:#?}", msg), 178 | //! KucoinWebsocketMsg::PongMsg(msg) => println!("{:#?}", msg), // Optional 179 | //! KucoinWebsocketMsg::WelcomeMsg(msg) => println!("{:#?}", msg), // Optional 180 | //! _ => (), 181 | //! } 182 | //! } 183 | //! Ok(()) 184 | //! } 185 | //! ``` 186 | //! [`KucoinWebsocketMsg`](./kucoin/model/websocket/enum.KucoinWebsocketMsg.html) has all the message response types 187 | //! available and can be referenced to identify desired endpoints. 188 | //! 189 | //! [`WSTopic`](./kucoin/model/websocket/enum.WSTopic.html) has all the available websocket topics/endpoints that are 190 | //! available for subscription. 191 | //! 192 | //! Note that Level3 data has been separated by message type despite it requiring only a single subscription. 193 | //! All other subscriptions coincide 1:1 with their response type and KucoinWebsocketMsg, 194 | //! excluding their Ping, Pong and Welcome messages. Ping, Pong and Welcome can be tracked through their own match arm. 195 | //! The reasoning for this exception is that for the majority of use cases, each Level3 message has to be handled 196 | //! in its own way to properly construct an orderbook. By separating the messages by type from the incoming 197 | //! stream at the library level, it helps to reduce duplication for the end user. 198 | //! 199 | //! ## Error Handling 200 | //! 201 | //! kucoin_rs uses the [`failure crate`](https://crates.io/crates/failure) to propagate errors. Kucoin REST errors are 202 | //! passed as part of the response structs, however by default, reqwest errors panic. For websocket endpoints, similarly, 203 | //! by default most protocol and connection errors will panic. Use of `?` will result in panics as well. End users can however 204 | //! use the custom [`APIError`](./kucoin/error/enum.APIError.html) enum to match error responses which provide non panic 205 | //! alternatives allowing for specified error handling. Users can also implement their own more comprehensive solutions. 206 | //! 207 | //! ## Contribution 208 | //! 209 | //! Contributions are more than welcome for fixing bugs, writing further documentation, writing further tests, 210 | //! adding features or helping to improve performance. I'll do my best to review and implement pull requests. 211 | //! 212 | //! ## Donations 213 | //! 214 | //! Donations are always appreciated and help support development of more open source trading tools. 215 | //! 216 | //! BTC: 3KvTuAnv7o2VAf4LGgg1MiDURd2DgjFGaa 217 | //! 218 | //! ETH: 0x7713a223e0e86355ac02b1e0de77695e822071cf 219 | //! 220 | //! NEO: AWngpjmoXPHiJH6rtf81brPiyPomYAqe8j 221 | //! 222 | //! Contact me for any other specific cryptocurrencies you'd prefer to use. 223 | //! 224 | //! ## License 225 | //! 226 | //! This project is open source and uses the MIT license. Feel free to utilize it in whatever way you see fit. 227 | 228 | pub extern crate futures; 229 | pub extern crate pin_project; 230 | pub extern crate reqwest; 231 | pub extern crate tokio; 232 | pub extern crate tokio_native_tls; 233 | pub extern crate tokio_tungstenite; 234 | pub extern crate tungstenite; 235 | pub extern crate url; 236 | 237 | pub extern crate serde; 238 | pub extern crate serde_json; 239 | 240 | #[macro_use] 241 | pub extern crate serde_derive; 242 | #[macro_use] 243 | pub extern crate failure; 244 | 245 | /// Kucoin API Module 246 | pub mod kucoin; 247 | --------------------------------------------------------------------------------