diff --git a/Cargo.lock b/Cargo.lock index 2871825..d501c78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,7 +40,7 @@ dependencies = [ "actix-service", "actix-utils", "ahash 0.8.7", - "base64 0.21.5", + "base64 0.21.6", "bitflags 2.4.1", "brotli", "bytes", @@ -74,8 +74,8 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ - "quote 1.0.34", - "syn 2.0.45", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -186,9 +186,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb1f50ebbb30eca122b188319a4398b3f7bb4a8cdf50ecfb73bfc6a3c3ce54f5" dependencies = [ "actix-router", - "proc-macro2 1.0.73", - "quote 1.0.34", - "syn 2.0.45", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -304,8 +304,8 @@ checksum = "2d5e1a413b311b039d29b61d0dbb401c9dbf04f792497ceca87593454bf6d7dd" dependencies = [ "anchor-syn", "anyhow", - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "regex", "syn 1.0.109", ] @@ -319,8 +319,8 @@ dependencies = [ "anchor-syn", "anyhow", "bs58 0.4.0", - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "rustversion", "syn 1.0.109", ] @@ -332,7 +332,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "788e44f9e8501dabeb6f9229da0f3268fb2ae3208912608ffaa056a72031296f" dependencies = [ "anchor-syn", - "proc-macro2 1.0.73", + "proc-macro2 1.0.76", "syn 1.0.109", ] @@ -343,8 +343,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea0c4d8c7e4a2605ede6fcdced9690288b2f74e24768619a85229d57e597bc97" dependencies = [ "anchor-syn", - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -356,8 +356,8 @@ checksum = "7a3b07d5c5d87b5edc72428b447b8e9ee1143b83dd1afc6a6b1d352c6a6164d8" dependencies = [ "anchor-syn", "anyhow", - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -369,8 +369,8 @@ checksum = "b22ad0445115dbea5869b1d062da49ae125abed9132fc20c33227f25e42dfa6b" dependencies = [ "anchor-syn", "anyhow", - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -382,8 +382,8 @@ checksum = "48daeff6781ba2f02961b0ad211feb9a2de75af345d42c62b1a252fd4dfb0724" dependencies = [ "anchor-syn", "anyhow", - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -393,8 +393,8 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4fe2886f92c4f33ec1b2b8b2b43ca1b9070cf4929e63c7eaaa09a9f2c0d5123" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -443,8 +443,8 @@ dependencies = [ "anyhow", "bs58 0.3.1", "heck", - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "serde", "serde_json", "sha2 0.9.9", @@ -478,9 +478,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca87830a3e3fb156dc96cfbd31cb620265dd053be734723f22b760d6cc3c3051" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "argh" @@ -499,9 +499,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56df0aeedf6b7a2fc67d06db35b09684c3e8da0c95f8f27685cb17e08413d87a" dependencies = [ "argh_shared", - "proc-macro2 1.0.73", - "quote 1.0.34", - "syn 2.0.45", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -547,8 +547,8 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", "synstructure", ] @@ -559,8 +559,8 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -595,13 +595,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.76" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531b97fb4cd3dfdce92c35dedbfdc1f0b9d8091c8ca943d6dae340ef5012d514" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", - "syn 2.0.45", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -650,9 +650,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "c79fed4cdb43e993fcdadc7e58a09fd0e3e649c4436fa11da71c9f1f3ee7feb9" [[package]] name = "base64ct" @@ -770,7 +770,7 @@ dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", "proc-macro-crate 0.1.5", - "proc-macro2 1.0.73", + "proc-macro2 1.0.76", "syn 1.0.109", ] @@ -782,9 +782,9 @@ checksum = "bf4918709cc4dd777ad2b6303ed03cb37f3ca0ccede8c1b0d28ac6db8f4710e0" dependencies = [ "once_cell", "proc-macro-crate 2.0.1", - "proc-macro2 1.0.73", - "quote 1.0.34", - "syn 2.0.45", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", "syn_derive", ] @@ -794,8 +794,8 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -805,8 +805,8 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -876,8 +876,8 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -896,9 +896,9 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", - "syn 2.0.45", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -1108,9 +1108,9 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -1126,44 +1126,37 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a9b73a36529d9c47029b9fb3a6f0ea3cc916a261195352ba19e770fc1748b2" +checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.17" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e3681d554572a651dda4186cd47240627c3d0114d45a95f6ad27f2f22e7548d" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.18" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" -dependencies = [ - "cfg-if", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crunchy" @@ -1265,8 +1258,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "convert_case", - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "rustc_version", "syn 1.0.109", ] @@ -1330,9 +1323,9 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", - "syn 2.0.45", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -1361,7 +1354,7 @@ dependencies = [ [[package]] name = "drift" version = "2.48.0" -source = "git+https://github.com/circuit-research/protocol-v2?branch=cargo-add-sdk#463a0b0cc0c367ed9e6a54acdeceeeb030953a19" +source = "git+https://github.com/circuit-research/protocol-v2?branch=cargo-add-sdk#65a863819f487761db771ee32cf782e4abf5000b" dependencies = [ "anchor-lang", "anchor-spl", @@ -1375,6 +1368,7 @@ dependencies = [ "num-traits", "phoenix-v1", "pyth-client", + "serde", "serum_dex", "solana-program", "solana-security-txt", @@ -1396,17 +1390,21 @@ dependencies = [ "serde", "serde_json", "thiserror", + "tokio", + "tokio-tungstenite 0.21.0", ] [[package]] name = "drift-sdk" version = "0.1.0" -source = "git+https://github.com/circuit-research/protocol-v2?branch=cargo-add-sdk#463a0b0cc0c367ed9e6a54acdeceeeb030953a19" +source = "git+https://github.com/circuit-research/protocol-v2?branch=cargo-add-sdk#65a863819f487761db771ee32cf782e4abf5000b" dependencies = [ "anchor-lang", "base64 0.13.1", + "bytemuck", "drift", "env_logger 0.10.1", + "events_emitter", "fnv", "futures-util", "log", @@ -1416,6 +1414,7 @@ dependencies = [ "solana-account-decoder", "solana-client", "solana-sdk", + "solana-transaction-status", "substreams-solana-macro", "thiserror", "tokio", @@ -1476,8 +1475,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "598d7f4a52f256bc6d065f0ee1c8dd07275952f9425bdfc1639595c924e1498e" dependencies = [ "bs58 0.4.0", - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "rustversion", "solana-program", "syn 1.0.109", @@ -1513,8 +1512,8 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8958699f9359f0b04e691a13850d48b7de329138023876d07cbd024c2c820598" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -1525,9 +1524,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f33313078bb8d4d05a2733a94ac4c2d8a0df9a2b84424ebf4f33bfc224a890e" dependencies = [ "once_cell", - "proc-macro2 1.0.73", - "quote 1.0.34", - "syn 2.0.45", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -1545,8 +1544,8 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "946ee94e3dbf58fdd324f9ce245c7b238d46a66f00e86a020b71996349e46cce" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -1598,6 +1597,12 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "events_emitter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ea822752927b9dcf033c360608f2ff455aae4eac992f501353bb518b1c486a" + [[package]] name = "fastrand" version = "2.0.1" @@ -1720,9 +1725,9 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", - "syn 2.0.45", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -2227,9 +2232,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.151" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libloading" @@ -2515,8 +2520,8 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -2588,8 +2593,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" dependencies = [ "proc-macro-crate 1.3.1", - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -2650,9 +2655,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", - "syn 2.0.45", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -2859,8 +2864,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "version_check", ] @@ -2870,8 +2875,8 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "version_check", ] @@ -2886,9 +2891,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.73" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dd5e8a1f1029c43224ad5898e50140c2aebb1705f19e67c918ebf5b9e797fe1" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] @@ -2908,8 +2913,8 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -2992,11 +2997,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a37c9326af5ed140c86a46655b5278de879853be5573c01df185b6f49a580a" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ - "proc-macro2 1.0.73", + "proc-macro2 1.0.76", ] [[package]] @@ -3182,7 +3187,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" dependencies = [ "async-compression", - "base64 0.21.5", + "base64 0.21.6", "bytes", "encoding_rs", "futures-core", @@ -3273,8 +3278,8 @@ version = "0.7.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5c462a1328c8e67e4d6dbad1eb0355dd43e8ab432c6e227a43657f16ade5033" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -3400,7 +3405,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.5", + "base64 0.21.6", ] [[package]] @@ -3487,44 +3492,44 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb1879ea93538b78549031e2d54da3e901fd7e75f2e4dc758d760937b123d10" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", - "syn 2.0.45", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] name = "serde_json" -version = "1.0.109" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0652c533506ad7a2e353cce269330d6afd8bdfb6d75e0ace5b35aacbd7b9e9" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" dependencies = [ "itoa", "ryu", @@ -3661,8 +3666,8 @@ version = "0.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3498d6ea2ba012f26ad3d79a19773ba8e1c7a69f14dec67e3ed51c723cc9f30a" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "shank_macro_impl", "shank_render", "syn 1.0.109", @@ -3675,8 +3680,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271c0b0b47ef930d7455d11a02164e3f0e71704d639bcaa6581f23e4b2073227" dependencies = [ "anyhow", - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "serde", "syn 1.0.109", ] @@ -3687,8 +3692,8 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "142e11124c70d1702424011209621551adf775988033dedea428ce4a21d3acdf" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "shank_macro_impl", ] @@ -3976,8 +3981,8 @@ version = "1.14.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57892538250428ad3dc3cbe05f6cd75ad14f4f16734fcb91bc7cd5fbb63d6315" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "rustc_version", "syn 1.0.109", ] @@ -4229,8 +4234,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d41a09b9cecd0a4df63c78a192adee99ebf2d3757c19713a68246e1d9789c7c" dependencies = [ "bs58 0.4.0", - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "rustversion", "syn 1.0.109", ] @@ -4497,19 +4502,19 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.45" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eae3c679c56dc214320b67a1bc04ef3dfbd6411f6443974b5e4893231298e66" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "unicode-ident", ] @@ -4520,9 +4525,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" dependencies = [ "proc-macro-error", - "proc-macro2 1.0.73", - "quote 1.0.34", - "syn 2.0.45", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -4531,8 +4536,8 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", "unicode-xid 0.2.4", ] @@ -4616,8 +4621,8 @@ version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", + "proc-macro2 1.0.76", + "quote 1.0.35", "syn 1.0.109", ] @@ -4709,9 +4714,9 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", - "syn 2.0.45", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -4861,9 +4866,9 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", - "syn 2.0.45", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -5108,9 +5113,9 @@ dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.73", - "quote 1.0.34", - "syn 2.0.45", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", "wasm-bindgen-shared", ] @@ -5132,7 +5137,7 @@ version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ - "quote 1.0.34", + "quote 1.0.35", "wasm-bindgen-macro-support", ] @@ -5142,9 +5147,9 @@ version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", - "syn 2.0.45", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5430,9 +5435,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.31" +version = "0.5.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a4882e6b134d6c28953a387571f1acdd3496830d5e36c5e3a1075580ea641c" +checksum = "b7520bbdec7211caa7c4e682eb1fbe07abe20cee6756b6e00f537c82c11816aa" dependencies = [ "memchr", ] @@ -5517,9 +5522,9 @@ version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", - "syn 2.0.45", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -5537,9 +5542,9 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ - "proc-macro2 1.0.73", - "quote 1.0.34", - "syn 2.0.45", + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c227c51..6d9700d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,6 @@ log = "0.4.20" rust_decimal = "1.33.1" serde = { version = "1.0.193", features = ["derive"] } serde_json = "*" -thiserror = "1.0.38" +tokio = "*" +tokio-tungstenite = "*" +thiserror = "1.0.38" \ No newline at end of file diff --git a/README.md b/README.md index 400d36b..0ecc73d 100644 --- a/README.md +++ b/README.md @@ -314,6 +314,112 @@ $ curl localhost:8080/v2/orders/cancelAndPlace -X POST -H 'content-type: applica }' ``` +## WebSockets +Websocket API is provided for live event streams by default at port `127.0.0.1:1337` + +## Subscribing +Subscribe to order and fills updates by a `subAccountId` (`0` is the drift default) +```json +{"method":"subscribe", "subAccountId":0} + +# unsubscribe +{"method":"unsubscribe", "subAccountId":0} +``` + +## Event Payloads + +event payloads can be distinguished by "channel" field and the "data" payload is keyed by the event type + +**order cancelled** +```json +{ + "data": { + "orderCancel": { + "orderId": 156, + "ts": 1704777451 + } + }, + "channel": "orders", + "subAccountId": 0 +} +``` + +**order expired** +if an order's `maxTs` is reached then it can be cancelled by protocol keeper bots, producing the following expired event. +```json +{ + "data": { + "orderExpire": { + "orderId": 156, + "fee": "-0.0012", + "ts": 1704777451 + } + }, + "channel": "orders", + "subAccountId": 0 +} +``` + +**order created** +- auction and trigger fields are only relevant for auction type or trigger type orders respectively. +- `price` is shown as `0` for market and oracle orders. +```json +{ + "data": { + "orderCreate": { + "order": { + "slot": 271243169, + "price": "0", + "amount": "0.1", + "filled": "0", + "triggerPrice": "0", + "auctionStartPrice": "0", + "auctionEndPrice": "0", + "maxTs": 0, + "oraclePriceOffset": "2", + "orderId": 157, + "marketIndex": 0, + "orderType": "limit", + "marketType": "perp", + "userOrderId": 102, + "direction": "buy", + "reduceOnly": false, + "postOnly": false, + "immediateOrCancel": false, + "auctionDuration": 0 + }, + "ts": 1704777347 + } + }, + "channel": "orders", + "subAccountId": 0 +} +``` + +**order fill** + +- `fee``: positive amounts are (maker) rebates + +```json +{ + "data": { + "fill": { + "side": "buy", + "fee": "0.002581", + "amount": "0.1", + "price": "103.22087", + "orderId": 157, + "ts": 1704777355 + } + }, + "channel": "fills", + "subAccountId": 0 +} +``` + +**order modify** +Modifying an order produces a cancel event followed by a create event with the same orderId + ## Delegated Signing Mode Passing the `--delegate ` flag will instruct the gateway to run in delegated signing mode. In this mode, the gateway will act for `DELEGATOR_PUBKEY` and sub-accounts while signing with the key provided via `DRIFT_GATEWAY_KEY`. diff --git a/src/controller.rs b/src/controller.rs index 8e4e5f2..aadb80f 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -12,7 +12,7 @@ use thiserror::Error; use crate::types::{ get_market_decimals, AllMarketsResponse, CancelAndPlaceRequest, CancelOrdersRequest, GetOrderbookRequest, GetOrdersRequest, GetOrdersResponse, GetPositionsRequest, - GetPositionsResponse, ModifyOrdersRequest, Order, OrderbookL2, PlaceOrdersRequest, + GetPositionsResponse, Market, ModifyOrdersRequest, Order, OrderbookL2, PlaceOrdersRequest, SpotPosition, TxResponse, }; @@ -30,9 +30,8 @@ pub enum ControllerError { #[derive(Clone)] pub struct AppState { - wallet: Wallet, - context: Context, - client: Arc>, + pub wallet: Wallet, + pub client: Arc>, dlob_client: DLOBClient, } @@ -76,9 +75,8 @@ impl AppState { Self { wallet, - context, client: Arc::new(client), - dlob_client: DLOBClient::new(dlob_endpoint, context), + dlob_client: DLOBClient::new(dlob_endpoint), } } @@ -96,9 +94,12 @@ impl AppState { ) -> GatewayResult { let sub_account = self.wallet.sub_account(sub_account_id); let account_data = self.client.get_user_account(&sub_account).await?; - let builder = - TransactionBuilder::new(self.context, sub_account, Cow::Borrowed(&account_data)) - .payer(self.wallet.signer()); + let builder = TransactionBuilder::new( + self.client.program_data(), + sub_account, + Cow::Borrowed(&account_data), + ) + .payer(self.wallet.signer()); let tx = if let Some(market) = req.market { builder.cancel_orders((market.market_index, market.market_type), None) @@ -191,8 +192,10 @@ impl AppState { } }) .map(|o| { - let base_decimals = - get_market_decimals(self.context, o.market_index, o.market_type); + let base_decimals = get_market_decimals( + self.client.program_data(), + Market::new(o.market_index, o.market_type), + ); Order::from_sdk_order(o, base_decimals) }) .collect(), @@ -200,8 +203,8 @@ impl AppState { } pub fn get_markets(&self) -> AllMarketsResponse { - let spot = drift_sdk::constants::spot_market_configs(self.context); - let perp = drift_sdk::constants::perp_market_configs(self.context); + let spot = self.client.program_data().spot_market_configs(); + let perp = self.client.program_data().perp_market_configs(); AllMarketsResponse { spot: spot.iter().map(|x| (*x).into()).collect(), @@ -219,8 +222,7 @@ impl AppState { .orders .into_iter() .map(|o| { - let base_decimals = - get_market_decimals(self.context, o.market.market_index, o.market.market_type); + let base_decimals = get_market_decimals(self.client.program_data(), o.market); o.to_order_params(base_decimals) }) .collect(); @@ -228,7 +230,7 @@ impl AppState { let sub_account = self.wallet.sub_account(sub_account_id); let account_data = self.client.get_user_account(&sub_account).await?; let builder = TransactionBuilder::new( - self.context, + self.client.program_data(), self.wallet.sub_account(0), Cow::Borrowed(&account_data), ) @@ -271,15 +273,18 @@ impl AppState { .orders .into_iter() .map(|o| { - let base_decimals = - get_market_decimals(self.context, o.market.market_index, o.market.market_type); + let base_decimals = get_market_decimals(self.client.program_data(), o.market); o.to_order_params(base_decimals) }) .collect(); - let tx = TransactionBuilder::new(self.context, sub_account, Cow::Borrowed(&account_data)) - .payer(self.wallet.signer()) - .place_orders(orders) - .build(); + let tx = TransactionBuilder::new( + self.client.program_data(), + sub_account, + Cow::Borrowed(&account_data), + ) + .payer(self.wallet.signer()) + .place_orders(orders) + .build(); self.client .sign_and_send(&self.wallet, tx) @@ -304,9 +309,8 @@ impl AppState { account_data.orders.iter().find(|x| x.order_id == order_id) { let base_decimals = get_market_decimals( - self.context, - onchain_order.market_index, - onchain_order.market_type, + self.client.program_data(), + Market::new(onchain_order.market_index, onchain_order.market_type), ); params.push((order_id, order.to_order_params(base_decimals))); continue; @@ -318,9 +322,8 @@ impl AppState { .find(|x| x.user_order_id == user_order_id) { let base_decimals = get_market_decimals( - self.context, - onchain_order.market_index, - onchain_order.market_type, + self.client.program_data(), + Market::new(onchain_order.market_index, onchain_order.market_type), ); params.push((onchain_order.order_id, order.to_order_params(base_decimals))); continue; @@ -334,10 +337,14 @@ impl AppState { )); } - let tx = TransactionBuilder::new(self.context, sub_account, Cow::Borrowed(account_data)) - .payer(self.wallet.signer()) - .modify_orders(params) - .build(); + let tx = TransactionBuilder::new( + self.client.program_data(), + sub_account, + Cow::Borrowed(account_data), + ) + .payer(self.wallet.signer()) + .modify_orders(params) + .build(); self.client .sign_and_send(&self.wallet, tx) @@ -348,7 +355,8 @@ impl AppState { pub async fn get_orderbook(&self, req: GetOrderbookRequest) -> GatewayResult { let book = self.dlob_client.get_l2(req.market.as_market_id()).await?; - Ok(OrderbookL2::new(book, req.market, self.context)) + let decimals = get_market_decimals(self.client.program_data(), req.market); + Ok(OrderbookL2::new(book, decimals)) } } diff --git a/src/main.rs b/src/main.rs index a22bfc7..33c5955 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use log::{error, info}; use controller::{AppState, ControllerError}; use drift_sdk::Pubkey; use serde_json::json; -use std::str::FromStr; +use std::{borrow::Borrow, str::FromStr, sync::Arc}; use types::{ CancelAndPlaceRequest, CancelOrdersRequest, GetOrderbookRequest, ModifyOrdersRequest, PlaceOrdersRequest, @@ -17,6 +17,7 @@ use types::{ mod controller; mod types; +mod websocket; #[derive(serde::Deserialize)] struct Args { @@ -162,6 +163,15 @@ async fn main() -> std::io::Result<()> { ); } + let client = Box::leak(Box::new(Arc::clone(state.client.borrow()))); + websocket::start_ws_server( + format!("{}:1337", &config.host).as_str(), + drift_sdk::utils::http_to_ws(config.rpc_host.as_str()).expect("ws endpoint"), + state.wallet.clone(), + client.program_data(), + ) + .await; + HttpServer::new(move || { App::new().app_data(web::Data::new(state.clone())).service( web::scope("/v2") diff --git a/src/types.rs b/src/types.rs index bdf2302..0c84904 100644 --- a/src/types.rs +++ b/src/types.rs @@ -3,16 +3,19 @@ //! - wrappers for presenting drift program types with less implementation detail //! use drift_sdk::{ - constants::{spot_market_config_by_index, BASE_PRECISION, PRICE_PRECISION}, + constants::{ProgramData, BASE_PRECISION, PRICE_PRECISION}, dlob::{self, L2Level, L2Orderbook}, types::{ - self as sdk_types, Context, MarketPrecision, MarketType, ModifyOrderParams, OrderParams, - PerpMarket, PositionDirection, PostOnlyParam, SpotMarket, + self as sdk_types, MarketPrecision, MarketType, ModifyOrderParams, OrderParams, PerpMarket, + PositionDirection, PostOnlyParam, SpotMarket, }, }; use rust_decimal::Decimal; use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; +/// decimal places in price values +pub const PRICE_DECIMALS: u32 = PRICE_PRECISION.ilog10(); + #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Order { @@ -45,7 +48,7 @@ impl Order { Order { market_index: value.market_index, market_type: value.market_type, - price: Decimal::new(value.price as i64, PRICE_PRECISION.ilog10()), + price: Decimal::new(value.price as i64, PRICE_DECIMALS), amount: Decimal::new(value.base_asset_amount as i64 * to_sign, base_decimals), filled: Decimal::new(value.base_asset_amount_filled as i64, base_decimals), immediate_or_cancel: value.immediate_or_cancel, @@ -59,7 +62,7 @@ impl Order { } else { Some(Decimal::new( value.oracle_price_offset as i64, - PRICE_PRECISION.ilog10(), + PRICE_DECIMALS, )) }, } @@ -134,15 +137,12 @@ pub struct PerpPosition { impl From for PerpPosition { fn from(value: sdk_types::PerpPosition) -> Self { - let amount = Decimal::new(value.base_asset_amount, BASE_PRECISION.ilog10()).normalize(); + let amount = Decimal::new(value.base_asset_amount, BASE_PRECISION.ilog10()); + let average_entry = Decimal::new(value.quote_entry_amount.abs(), PRICE_DECIMALS) / amount; Self { - amount, + amount: amount.normalize(), market_index: value.market_index, - average_entry: Decimal::new( - value.quote_entry_amount.abs() / value.base_asset_amount.abs().max(1), - PRICE_PRECISION.ilog10(), - ) - .normalize(), + average_entry: average_entry.normalize().round_dp(4), } } } @@ -321,6 +321,12 @@ pub struct Market { } impl Market { + pub fn new(market_index: u16, market_type: MarketType) -> Self { + Self { + market_index, + market_type, + } + } pub fn spot(index: u16) -> Self { Self { market_index: index, @@ -453,17 +459,12 @@ pub struct CancelAndPlaceRequest { /// Serialize DLOB with human readable numeric values pub struct OrderbookL2 { inner: L2Orderbook, - context: Context, - market: Market, + decimals: u32, } impl OrderbookL2 { - pub fn new(inner: L2Orderbook, market: Market, context: Context) -> Self { - Self { - inner, - market, - context, - } + pub fn new(inner: L2Orderbook, decimals: u32) -> Self { + Self { inner, decimals } } } @@ -478,16 +479,14 @@ impl Serialize for OrderbookL2 { "bids", &PriceLevelSerializer { inner: self.inner.bids.as_slice(), - market: self.market, - context: self.context, + decimals: self.decimals, }, )?; map.serialize_entry( "asks", &PriceLevelSerializer { inner: self.inner.asks.as_slice(), - market: self.market, - context: self.context, + decimals: self.decimals, }, )?; map.end() @@ -496,8 +495,8 @@ impl Serialize for OrderbookL2 { struct PriceLevelSerializer<'a> { inner: &'a [L2Level], - market: Market, - context: Context, + /// decimal precision of the asset + decimals: u32, } impl<'a> Serialize for PriceLevelSerializer<'a> { @@ -505,11 +504,7 @@ impl<'a> Serialize for PriceLevelSerializer<'a> { where S: Serializer, { - serializer.collect_seq( - self.inner - .iter() - .map(|l| PriceLevel::new(l, self.market, self.context)), - ) + serializer.collect_seq(self.inner.iter().map(|l| PriceLevel::new(l, self.decimals))) } } @@ -520,8 +515,7 @@ pub struct PriceLevel { } impl PriceLevel { - pub fn new(level: &dlob::L2Level, market: Market, context: Context) -> Self { - let decimals = get_market_decimals(context, market.market_index, market.market_type); + pub fn new(level: &dlob::L2Level, decimals: u32) -> Self { Self { price: Decimal::new(level.price as i64, PRICE_PRECISION.ilog10()), amount: Decimal::new(level.size as i64, decimals), @@ -531,16 +525,14 @@ impl PriceLevel { /// Return the number of decimal places for the market #[inline] -pub(crate) fn get_market_decimals( - context: Context, - market_index: u16, - market_type: MarketType, -) -> u32 { - if let MarketType::Perp = market_type { +pub(crate) fn get_market_decimals(program_data: &ProgramData, market: Market) -> u32 { + if let MarketType::Perp = market.market_type { BASE_PRECISION.ilog10() } else { - let market = spot_market_config_by_index(context, market_index).expect("market exists"); - market.decimals + let spot_market = program_data + .spot_market_config_by_index(market.market_index) + .expect("market exists"); + spot_market.decimals } } diff --git a/src/websocket.rs b/src/websocket.rs new file mode 100644 index 0000000..0bf3ac3 --- /dev/null +++ b/src/websocket.rs @@ -0,0 +1,442 @@ +//! Websocket server + +use std::ops::Neg; + +use drift_sdk::{ + constants::ProgramData, + event_subscriber::{DriftEvent, EventSubscriber, PubsubClient}, + types::{MarketType, Order, OrderType, PositionDirection}, + Pubkey, Wallet, +}; +use futures_util::{SinkExt, StreamExt}; +use log::{debug, info}; +use rust_decimal::Decimal; +use serde::{Deserialize, Serialize, Serializer}; +use serde_json::json; +use tokio::{ + net::{TcpListener, TcpStream}, + task::JoinHandle, +}; +use tokio_tungstenite::{accept_async, tungstenite::Message}; + +use crate::types::{get_market_decimals, Market, PRICE_DECIMALS}; + +/// Start the websocket server +pub async fn start_ws_server( + listen_address: &str, + ws_endpoint: String, + wallet: Wallet, + program_data: &'static ProgramData, +) { + // Create the event loop and TCP listener we'll accept connections on. + let listener = TcpListener::bind(&listen_address) + .await + .expect("failed to bind"); + info!("Ws server listening at: ws://{}", listen_address); + tokio::spawn(async move { + while let Ok((stream, _)) = listener.accept().await { + tokio::spawn(accept_connection( + stream, + ws_endpoint.clone(), + wallet.clone(), + program_data, + )); + } + }); +} + +async fn accept_connection( + stream: TcpStream, + ws_endpoint: String, + wallet: Wallet, + program_data: &'static ProgramData, +) { + let addr = stream.peer_addr().expect("peer address"); + let ws_stream = accept_async(stream).await.expect("Ws handshake"); + info!("accepted Ws connection: {}", addr); + + let (mut ws_out, mut ws_in) = ws_stream.split(); + let (message_tx, mut message_rx) = tokio::sync::mpsc::channel::(32); + let mut stream_handle: Option> = None; + + // writes messages to the connection + tokio::spawn(async move { + while let Some(msg) = message_rx.recv().await { + if msg.is_close() { + let _ = ws_out.close().await; + break; + } else { + ws_out.send(msg).await.expect("sent"); + } + } + }); + + // watches incoming messages from the connection + while let Some(Ok(msg)) = ws_in.next().await { + match msg { + Message::Text(ref request) => match serde_json::from_str::<'_, WsRequest>(request) { + Ok(request) => { + match request.method { + Method::Subscribe => { + // TODO: support subscriptions for individual channels and/or markets + if stream_handle.is_some() { + // no double subs + return; + } + debug!("subscribing to events for: {}", request.sub_account_id); + + let sub_account_address = + wallet.sub_account(request.sub_account_id as u16); + let mut event_stream = EventSubscriber::subscribe( + PubsubClient::new(ws_endpoint.as_str()) + .await + .expect("ws connect"), + sub_account_address, + ); + + let join_handle = tokio::spawn({ + let sub_account_id = request.sub_account_id; + let message_tx = message_tx.clone(); + async move { + while let Some(ref update) = event_stream.next().await { + let (channel, data) = map_drift_event( + program_data, + update, + sub_account_address, + ); + message_tx + .send(Message::text( + serde_json::to_string(&WsEvent { + data, + channel, + sub_account_id, + }) + .expect("serializes"), + )) + .await + .expect("capacity"); + } + } + }); + + stream_handle = Some(join_handle); + } + Method::Unsubscribe => { + debug!("unsubscribing: {}", request.sub_account_id); + // TODO: support ending by channel, this ends all channels + if let Some(task) = stream_handle.take() { + task.abort(); + } + } + } + } + Err(err) => { + message_tx + .try_send(Message::text( + json!({ + "error": "bad request", + "reason": err.to_string(), + }) + .to_string(), + )) + .expect("capacity"); + } + }, + Message::Close(frame) => { + let _ = message_tx.send(Message::Close(frame)).await; + break; + } + // tokio-tungstenite handles ping/pong transparently + _ => (), + } + } + info!("closing Ws connection: {}", addr); +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "lowercase")] +enum Method { + Subscribe, + Unsubscribe, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "lowercase")] +enum Channel { + Fills, + Orders, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct WsRequest { + method: Method, + sub_account_id: u8, +} + +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +struct WsEvent { + data: T, + channel: Channel, + sub_account_id: u8, +} + +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +enum AccountEvent { + #[serde(rename_all = "camelCase")] + Fill { + side: Side, + fee: Decimal, + amount: Decimal, + price: Decimal, + order_id: u32, + ts: u64, + }, + #[serde(rename_all = "camelCase")] + OrderCreate { order: OrderWithDecimals, ts: u64 }, + #[serde(rename_all = "camelCase")] + OrderCancel { order_id: u32, ts: u64 }, + #[serde(rename_all = "camelCase")] + OrderExpire { + order_id: u32, + fee: Decimal, + ts: u64, + }, +} + +impl AccountEvent { + fn fill( + side: PositionDirection, + fee: i64, + base_amount: u64, + quote_amount: u64, + order_id: u32, + ts: u64, + decimals: u32, + ) -> Self { + let base_amount = Decimal::new(base_amount as i64, decimals); + let price = Decimal::new(quote_amount as i64, PRICE_DECIMALS) / base_amount; + AccountEvent::Fill { + side: if let PositionDirection::Long = side { + Side::Buy + } else { + Side::Sell + }, + price: price.normalize(), + fee: Decimal::new(fee, PRICE_DECIMALS).normalize(), + order_id, + amount: base_amount.normalize(), + ts, + } + } +} + +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +enum Side { + Buy, + Sell, +} + +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +struct OrderWithDecimals { + /// The slot the order was placed + pub slot: u64, + /// The limit price for the order (can be 0 for market orders) + /// For orders with an auction, this price isn't used until the auction is complete + pub price: Decimal, + /// The size of the order + pub amount: Decimal, + /// The amount of the order filled + pub filled: Decimal, + /// At what price the order will be triggered. Only relevant for trigger orders + pub trigger_price: Decimal, + /// The start price for the auction. Only relevant for market/oracle orders + pub auction_start_price: Decimal, + /// The end price for the auction. Only relevant for market/oracle orders + pub auction_end_price: Decimal, + /// The time when the order will expire + pub max_ts: i64, + /// If set, the order limit price is the oracle price + this offset + pub oracle_price_offset: Decimal, + /// The id for the order. Each users has their own order id space + pub order_id: u32, + /// The perp/spot market index + pub market_index: u16, + /// The type of order + #[serde(serialize_with = "ser_order_type")] + pub order_type: OrderType, + /// Whether market is spot or perp + #[serde(serialize_with = "ser_market_type")] + pub market_type: MarketType, + /// User generated order id. Can make it easier to place/cancel orders + pub user_order_id: u8, + #[serde(serialize_with = "ser_position_direction")] + pub direction: PositionDirection, + /// Whether the order is allowed to only reduce position size + pub reduce_only: bool, + /// Whether the order must be a maker + pub post_only: bool, + /// Whether the order must be canceled the same slot it is placed + pub immediate_or_cancel: bool, + /// How many slots the auction lasts + pub auction_duration: u8, +} + +impl OrderWithDecimals { + fn from_order(value: Order, decimals: u32) -> Self { + Self { + slot: value.slot, + price: Decimal::new(value.price as i64, PRICE_DECIMALS).normalize(), + amount: Decimal::new(value.base_asset_amount as i64, decimals).normalize(), + filled: Decimal::new(value.base_asset_amount_filled as i64, decimals).normalize(), + trigger_price: Decimal::new(value.trigger_price as i64, PRICE_DECIMALS).normalize(), + auction_start_price: Decimal::new(value.auction_start_price, PRICE_DECIMALS) + .normalize(), + auction_end_price: Decimal::new(value.auction_end_price, PRICE_DECIMALS).normalize(), + oracle_price_offset: Decimal::new(value.oracle_price_offset as i64, PRICE_DECIMALS) + .normalize(), + max_ts: value.max_ts, + order_id: value.order_id, + market_index: value.market_index, + order_type: value.order_type, + market_type: value.market_type, + user_order_id: value.user_order_id, + direction: value.direction, + reduce_only: value.reduce_only, + post_only: value.post_only, + immediate_or_cancel: value.immediate_or_cancel, + auction_duration: value.auction_duration, + } + } +} + +fn ser_order_type(x: &OrderType, s: S) -> Result +where + S: Serializer, +{ + s.serialize_str(match x { + OrderType::Limit => "limit", + OrderType::Market => "market", + OrderType::Oracle => "oracle", + OrderType::TriggerLimit => "triggerLimit", + OrderType::TriggerMarket => "triggerMarket", + }) +} + +fn ser_position_direction(x: &PositionDirection, s: S) -> Result +where + S: Serializer, +{ + s.serialize_str(match x { + PositionDirection::Long => "buy", + PositionDirection::Short => "sell", + }) +} + +fn ser_market_type(x: &MarketType, s: S) -> Result +where + S: Serializer, +{ + s.serialize_str(match x { + MarketType::Perp => "perp", + MarketType::Spot => "spot", + }) +} + +// TODO: lookup market decimals... +/// Map drift-program events into gateway friendly types +fn map_drift_event( + program_data: &ProgramData, + event: &DriftEvent, + sub_account_address: Pubkey, +) -> (Channel, AccountEvent) { + match event { + DriftEvent::OrderFill { + maker, + maker_fee, + maker_order_id, + maker_side, + taker: _, + taker_fee, + taker_order_id, + taker_side, + base_asset_amount_filled, + quote_asset_amount_filled, + oracle_price: _, + market_index, + market_type, + ts, + } => { + let decimals = + get_market_decimals(program_data, Market::new(*market_index, *market_type)); + let fill = if *maker == Some(sub_account_address) { + AccountEvent::fill( + maker_side.unwrap(), + *maker_fee, + *base_asset_amount_filled, + *quote_asset_amount_filled, + *maker_order_id, + *ts, + decimals, + ) + } else { + AccountEvent::fill( + taker_side.unwrap(), + (*taker_fee) as i64, + *base_asset_amount_filled, + *quote_asset_amount_filled, + *taker_order_id, + *ts, + decimals, + ) + }; + + (Channel::Fills, fill) + } + DriftEvent::OrderCancel { + taker: _, + maker, + taker_order_id, + maker_order_id, + ts, + } => { + let order_id = if *maker == Some(sub_account_address) { + maker_order_id + } else { + taker_order_id + }; + ( + Channel::Orders, + AccountEvent::OrderCancel { + order_id: *order_id, + ts: *ts, + }, + ) + } + DriftEvent::OrderExpire { order_id, fee, ts } => ( + Channel::Orders, + AccountEvent::OrderExpire { + order_id: *order_id, + fee: Decimal::new((*fee as i64).neg(), PRICE_DECIMALS), + ts: *ts, + }, + ), + DriftEvent::OrderCreate { order, ts } => { + let decimals = get_market_decimals( + program_data, + Market::new(order.market_index, order.market_type), + ); + ( + Channel::Orders, + AccountEvent::OrderCreate { + order: OrderWithDecimals::from_order(*order, decimals), + ts: *ts, + }, + ) + } + } +}