From 6b7429115251a43a8438e5842fefb4d6a1ce04e1 Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Fri, 22 Aug 2025 15:52:40 +0800 Subject: [PATCH 01/12] depost + trade endpoint --- Cargo.lock | 648 ++++++++++++++++++++++-------------------- Cargo.toml | 7 +- src/swift_server.rs | 463 +++++++++++++++++++----------- src/types/messages.rs | 33 ++- src/types/types.rs | 29 ++ src/ws_server.rs | 19 +- 6 files changed, 711 insertions(+), 488 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f4bf1b7..2ade7f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -111,9 +111,9 @@ dependencies = [ [[package]] name = "agave-feature-set" -version = "2.3.6" +version = "2.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e4bb8842e634f00f7f56bed7fcf67464bf2689378b3977350a8d0e6918a1ea" +checksum = "e35cc5b8887b993ba4975a23b6e098ee10db50e8e23ee3a9523035b7ca35b53b" dependencies = [ "ahash 0.8.12", "solana-epoch-schedule", @@ -125,9 +125,9 @@ dependencies = [ [[package]] name = "agave-reserved-account-keys" -version = "2.3.6" +version = "2.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2343e5e83d2a33f965dd2fd18840351d821de9a5a427880a05069cc60ec18a81" +checksum = "685cb445fe51b7b8a914d1b7dd5a0ea0b106fb8ea9454e84c4cd726a5d87c571" dependencies = [ "agave-feature-set", "solana-pubkey", @@ -360,9 +360,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" dependencies = [ "anstyle", "anstyle-parse", @@ -390,29 +390,29 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" [[package]] name = "arc-swap" @@ -575,37 +575,15 @@ dependencies = [ "tokio", ] -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -642,9 +620,9 @@ dependencies = [ [[package]] name = "aws-config" -version = "1.8.3" +version = "1.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0baa720ebadea158c5bda642ac444a2af0cdf7bb66b46d1e4533de5d1f449d0" +checksum = "c478f5b10ce55c9a33f87ca3404ca92768b144fc1bfdede7c0121214a8283a25" dependencies = [ "aws-credential-types", "aws-runtime", @@ -672,9 +650,9 @@ dependencies = [ [[package]] name = "aws-credential-types" -version = "1.2.4" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b68c2194a190e1efc999612792e25b1ab3abfefe4306494efaaabc25933c0cbe" +checksum = "1541072f81945fa1251f8795ef6c92c4282d74d59f88498ae7d4bf00f0ebdad9" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -707,9 +685,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.5.9" +version = "1.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2090e664216c78e766b6bac10fe74d2f451c02441d43484cd76ac9a295075f7" +checksum = "c034a1bc1d70e16e7f4e4caf7e9f7693e4c9c24cd91cf17c2a0b21abaebc7c8b" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -731,9 +709,9 @@ dependencies = [ [[package]] name = "aws-sdk-kafka" -version = "1.82.0" +version = "1.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a74a920f7e857541a870b5a771912340fb12935c9dcabc0345e54a3db9584ab0" +checksum = "2d567f6971f58c360f901b4542da20a8c749b76f60b4b511453b6fb9d26180b4" dependencies = [ "aws-credential-types", "aws-runtime", @@ -753,9 +731,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.78.0" +version = "1.81.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbd7bc4bd34303733bded362c4c997a39130eac4310257c79aae8484b1c4b724" +checksum = "79ede098271e3471036c46957cba2ba30888f53bda2515bf04b560614a30a36e" dependencies = [ "aws-credential-types", "aws-runtime", @@ -775,9 +753,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.79.0" +version = "1.82.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77358d25f781bb106c1a69531231d4fd12c6be904edb0c47198c604df5a2dbca" +checksum = "43326f724ba2cc957e6f3deac0ca1621a3e5d4146f5970c24c8a108dac33070f" dependencies = [ "aws-credential-types", "aws-runtime", @@ -797,9 +775,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.80.0" +version = "1.83.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e3ed2a9b828ae7763ddaed41d51724d2661a50c45f845b08967e52f4939cfc" +checksum = "a5468593c47efc31fdbe6c902d1a5fde8d9c82f78a3f8ccfe907b1e9434748cb" dependencies = [ "aws-credential-types", "aws-runtime", @@ -820,9 +798,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.3.3" +version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfb9021f581b71870a17eac25b52335b82211cdc092e02b6876b2bcefa61666" +checksum = "084c34162187d39e3740cb635acd73c4e3a551a36146ad6fe8883c929c9f876c" dependencies = [ "aws-credential-types", "aws-smithy-http", @@ -853,9 +831,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.62.2" +version = "0.62.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c82ba4cab184ea61f6edaafc1072aad3c2a17dcf4c0fce19ac5694b90d8b5f" +checksum = "7c4dacf2d38996cf729f55e7a762b30918229917eca115de45dfa8dfb97796c9" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", @@ -873,20 +851,20 @@ dependencies = [ [[package]] name = "aws-smithy-http-client" -version = "1.0.6" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f108f1ca850f3feef3009bdcc977be201bca9a91058864d9de0684e64514bee0" +checksum = "4fdbad9bd9dbcc6c5e68c311a841b54b70def3ca3b674c42fbebb265980539f8" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", "aws-smithy-types", "h2 0.3.27", - "h2 0.4.11", + "h2 0.4.12", "http 0.2.12", "http 1.3.1", "http-body 0.4.6", "hyper 0.14.32", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-rustls 0.24.2", "hyper-rustls 0.27.7", "hyper-util", @@ -896,6 +874,7 @@ dependencies = [ "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", + "tokio-rustls 0.26.2", "tower 0.5.2", "tracing", ] @@ -930,9 +909,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.8.5" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "660f70d9d8af6876b4c9aa8dcb0dbaf0f89b04ee9a4455bea1b4ba03b15f26f6" +checksum = "a3d57c8b53a72d15c8e190475743acf34e4996685e346a3448dd54ef696fc6e0" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -954,9 +933,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.8.5" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "937a49ecf061895fca4a6dd8e864208ed9be7546c0527d04bc07d502ec5fba1c" +checksum = "07f5e0fc8a6b3f2303f331b94504bbf754d85488f402d6f1dd7a6080f99afe56" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -1031,7 +1010,7 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", "itoa", "matchit 0.7.3", @@ -1066,7 +1045,7 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", "itoa", "matchit 0.8.4", @@ -1161,7 +1140,7 @@ checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1256,15 +1235,15 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.104", + "syn 2.0.106", "which", ] [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" dependencies = [ "serde", ] @@ -1356,7 +1335,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1383,9 +1362,9 @@ dependencies = [ [[package]] name = "brotli" -version = "8.0.1" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -1451,22 +1430,22 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.23.1" +version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "441473f2b4b0459a68628c744bc61d23e730fb00128b841d30fa4bb3972257e4" +checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1493,9 +1472,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.30" +version = "1.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f" dependencies = [ "jobserver", "libc", @@ -1531,7 +1510,7 @@ checksum = "45565fc9416b9896014f5732ac776f810ee53a66730c17e4020c3ec064a8f88f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1566,9 +1545,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.42" +version = "4.5.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" +checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" dependencies = [ "clap_builder", "clap_derive", @@ -1576,9 +1555,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.42" +version = "4.5.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" +checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" dependencies = [ "anstream", "anstyle", @@ -1588,14 +1567,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.41" +version = "4.5.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1668,9 +1647,12 @@ dependencies = [ [[package]] name = "const_panic" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98d1483e98c9d67f341ab4b3915cfdc54740bd6f5cccc9226ee0535d86aa8fb" +checksum = "bb8a602185c3c95b52f86dc78e55a6df9a287a7a93ddbcf012509930880cf879" +dependencies = [ + "typewit", +] [[package]] name = "constant_time_eq" @@ -1706,18 +1688,18 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core_extensions" -version = "1.5.3" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92c71dc07c9721607e7a16108336048ee978c3a8b129294534272e8bac96c0ee" +checksum = "42bb5e5d0269fd4f739ea6cedaf29c16d81c27a7ce7582008e90eb50dcd57003" dependencies = [ "core_extensions_proc_macros", ] [[package]] name = "core_extensions_proc_macros" -version = "1.5.3" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f3b219d28b6e3b4ac87bc1fc522e0803ab22e055da177bff0068c4150c61a6" +checksum = "533d38ecd2709b7608fb8e18e4504deb99e9a72879e6aa66373a76d8dc4259ea" [[package]] name = "cpufeatures" @@ -1868,7 +1850,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1892,7 +1874,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1903,7 +1885,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1980,7 +1962,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1992,20 +1974,20 @@ checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" [[package]] name = "drift-idl-gen" version = "0.2.0" -source = "git+https://github.com/drift-labs/drift-rs.git?rev=8059150#8059150f34c28f3296f2a9f8bbfef7845409183d" +source = "git+https://github.com/drift-labs/drift-rs.git?rev=004cf7c#004cf7c9ffbfa07e4455bd96e41b23a70ec48a9c" dependencies = [ "proc-macro2", "quote", "serde", "serde_json", "sha2 0.10.9", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "drift-pubsub-client" version = "0.1.1" -source = "git+https://github.com/drift-labs/drift-rs.git?rev=8059150#8059150f34c28f3296f2a9f8bbfef7845409183d" +source = "git+https://github.com/drift-labs/drift-rs.git?rev=004cf7c#004cf7c9ffbfa07e4455bd96e41b23a70ec48a9c" dependencies = [ "futures-util", "gjson", @@ -2025,7 +2007,7 @@ dependencies = [ [[package]] name = "drift-rs" version = "1.0.0-alpha.16" -source = "git+https://github.com/drift-labs/drift-rs.git?rev=8059150#8059150f34c28f3296f2a9f8bbfef7845409183d" +source = "git+https://github.com/drift-labs/drift-rs.git?rev=004cf7c#004cf7c9ffbfa07e4455bd96e41b23a70ec48a9c" dependencies = [ "abi_stable", "ahash 0.8.12", @@ -2352,7 +2334,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -2458,9 +2440,9 @@ checksum = "43503cc176394dd30a6525f5f36e838339b8b5619be33ed9a7783841580a97b6" [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "h2" @@ -2474,7 +2456,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.10.0", + "indexmap", "slab", "tokio", "tokio-util", @@ -2483,9 +2465,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", @@ -2493,7 +2475,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.3.1", - "indexmap 2.10.0", + "indexmap", "slab", "tokio", "tokio-util", @@ -2538,9 +2520,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" [[package]] name = "headers" @@ -2751,20 +2733,22 @@ dependencies = [ [[package]] name = "hyper" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", - "h2 0.4.11", + "futures-core", + "h2 0.4.12", "http 1.3.1", "http-body 1.0.1", "httparse", "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -2793,7 +2777,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http 1.3.1", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", "rustls 0.23.31", "rustls-native-certs 0.8.1", @@ -2810,7 +2794,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", "pin-project-lite", "tokio", @@ -2825,7 +2809,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", "native-tls", "tokio", @@ -2846,7 +2830,7 @@ dependencies = [ "futures-util", "http 1.3.1", "http-body 1.0.1", - "hyper 1.6.0", + "hyper 1.7.0", "ipnet", "libc", "percent-encoding", @@ -2972,16 +2956,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "indexmap" version = "2.10.0" @@ -2989,7 +2963,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.15.5", ] [[package]] @@ -3101,7 +3075,7 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3153,7 +3127,7 @@ dependencies = [ "serde_qs", "solana-account-decoder", "solana-sdk", - "thiserror 2.0.12", + "thiserror 2.0.15", ] [[package]] @@ -3189,9 +3163,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.174" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libloading" @@ -3379,9 +3353,9 @@ checksum = "b4f0c8427b39666bf970460908b213ec09b3b350f20c0c2eabcbba51704a08e6" dependencies = [ "base64 0.22.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", - "indexmap 2.10.0", + "indexmap", "ipnet", "metrics", "metrics-util", @@ -3504,7 +3478,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3554,7 +3528,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3613,7 +3587,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3701,7 +3675,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", - "indexmap 2.10.0", + "indexmap", ] [[package]] @@ -3721,7 +3695,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3795,12 +3769,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.36" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3823,9 +3797,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -3847,9 +3821,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.13.5" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" dependencies = [ "bytes", "prost-derive", @@ -3857,9 +3831,9 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.13.5" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1" dependencies = [ "heck 0.5.0", "itertools 0.14.0", @@ -3870,29 +3844,31 @@ dependencies = [ "prettyplease", "prost", "prost-types", + "pulldown-cmark", + "pulldown-cmark-to-cmark", "regex", - "syn 2.0.104", + "syn 2.0.106", "tempfile", ] [[package]] name = "prost-derive" -version = "0.13.5" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" dependencies = [ "anyhow", "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "prost-types" -version = "0.13.5" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72" dependencies = [ "prost", ] @@ -3932,6 +3908,26 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "pulldown-cmark" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" +dependencies = [ + "bitflags", + "memchr", + "unicase", +] + +[[package]] +name = "pulldown-cmark-to-cmark" +version = "21.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5b6a0769a491a08b31ea5c62494a8f144ee0987d86d670a8af4df1e1b7cde75" +dependencies = [ + "pulldown-cmark", +] + [[package]] name = "qstring" version = "0.7.2" @@ -3970,7 +3966,7 @@ dependencies = [ "rustc-hash 2.1.1", "rustls 0.23.31", "socket2 0.5.10", - "thiserror 2.0.12", + "thiserror 2.0.15", "tokio", "tracing", "web-time", @@ -3991,7 +3987,7 @@ dependencies = [ "rustls 0.23.31", "rustls-pki-types", "slab", - "thiserror 2.0.12", + "thiserror 2.0.15", "tinyvec", "tracing", "web-time", @@ -4262,9 +4258,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.22" +version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "async-compression", "base64 0.22.1", @@ -4273,11 +4269,11 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.4.11", + "h2 0.4.12", "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-rustls 0.27.7", "hyper-tls", "hyper-util", @@ -4470,7 +4466,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls-pemfile 1.0.4", + "rustls-pemfile", "schannel", "security-framework 2.11.1", ] @@ -4484,7 +4480,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.2.0", + "security-framework 3.3.0", ] [[package]] @@ -4496,15 +4492,6 @@ dependencies = [ "base64 0.21.7", ] -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "rustls-pki-types" version = "1.12.0" @@ -4539,9 +4526,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -4607,9 +4594,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" dependencies = [ "bitflags", "core-foundation 0.10.1", @@ -4669,14 +4656,14 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "serde_json" -version = "1.0.141" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "itoa", "memchr", @@ -4737,7 +4724,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4831,9 +4818,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -4864,9 +4851,9 @@ checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" [[package]] name = "slab" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" @@ -4914,9 +4901,9 @@ dependencies = [ [[package]] name = "solana-account-decoder" -version = "2.3.6" +version = "2.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2368a6ff4b9077501a13dca5b409947757c7b3690213c5cd5bd513a89bf343f1" +checksum = "a5963fbe3e1099613c270fd5ebc0ff5c6e88a2bea2505b6e348daa0466282cd6" dependencies = [ "Inflector", "base64 0.22.1", @@ -4951,15 +4938,15 @@ dependencies = [ "spl-token-2022", "spl-token-group-interface", "spl-token-metadata-interface", - "thiserror 2.0.12", + "thiserror 2.0.15", "zstd", ] [[package]] name = "solana-account-decoder-client-types" -version = "2.3.6" +version = "2.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fe163318f8531029ec3e1796f55f16b61dcf194d9502076cbe3505a815d9d5" +checksum = "59f2101f4cc33e3fbfc8d1d23ea35d8532d6f1fa6a7c7081742e886f98f33126" dependencies = [ "base64 0.22.1", "bs58", @@ -5056,7 +5043,7 @@ dependencies = [ "ark-serialize", "bytemuck", "solana-define-syscall", - "thiserror 2.0.12", + "thiserror 2.0.15", ] [[package]] @@ -5166,16 +5153,16 @@ dependencies = [ [[package]] name = "solana-curve25519" -version = "2.3.6" +version = "2.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad6269c8dded5d571c75a4a32997514f57f23757f2e18549ca3040586465e336" +checksum = "b162f50499b391b785d57b2f2c73e3b9754d88fd4894bef444960b00bda8dcca" dependencies = [ "bytemuck", "bytemuck_derive", "curve25519-dalek 4.1.3", "solana-define-syscall", "subtle", - "thiserror 2.0.12", + "thiserror 2.0.15", ] [[package]] @@ -5285,7 +5272,7 @@ dependencies = [ "solana-pubkey", "solana-sdk-ids", "solana-system-interface", - "thiserror 2.0.12", + "thiserror 2.0.15", ] [[package]] @@ -5766,7 +5753,7 @@ dependencies = [ "solana-sysvar", "solana-sysvar-id", "solana-vote-interface", - "thiserror 2.0.12", + "thiserror 2.0.15", "wasm-bindgen", ] @@ -5873,9 +5860,9 @@ dependencies = [ [[package]] name = "solana-rent-collector" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c1e19f5d5108b0d824244425e43bc78bbb9476e2199e979b0230c9f632d3bf4" +checksum = "127e6dfa51e8c8ae3aa646d8b2672bc4ac901972a338a9e1cd249e030564fb9d" dependencies = [ "serde", "serde_derive", @@ -5922,9 +5909,9 @@ dependencies = [ [[package]] name = "solana-rpc-client" -version = "2.3.6" +version = "2.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "609c855934b9b5ccdb0b1ab89313b4302eac4cf2bdb7298716104185614cd1ba" +checksum = "40231712d6f1e5833ff1e101954786cbd0b5301098ea42384f7bb3e553085852" dependencies = [ "async-trait", "base64 0.22.1", @@ -5962,9 +5949,9 @@ dependencies = [ [[package]] name = "solana-rpc-client-api" -version = "2.3.6" +version = "2.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f932401ae69ff436e18dc27de5279ec1056aebd471a9e4e48d4d4d40431d784" +checksum = "5a1be31922f97505007ccf969828b34e8dc43ce434a17f970b0edea8f0e66777" dependencies = [ "anyhow", "jsonrpc-core", @@ -5979,14 +5966,14 @@ dependencies = [ "solana-signer", "solana-transaction-error", "solana-transaction-status-client-types", - "thiserror 2.0.12", + "thiserror 2.0.15", ] [[package]] name = "solana-rpc-client-types" -version = "2.3.6" +version = "2.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ecc13a2e2daa6823289e8f840098bc85388a114c9bf939b357443c3a7849612" +checksum = "6e82a9b71f023a4bd511088f22e3c1f0e226a6e2e94b0656776509f234dd223a" dependencies = [ "base64 0.22.1", "bs58", @@ -6005,7 +5992,7 @@ dependencies = [ "solana-transaction-status-client-types", "solana-version", "spl-generic-token", - "thiserror 2.0.12", + "thiserror 2.0.15", ] [[package]] @@ -6081,7 +6068,7 @@ dependencies = [ "solana-transaction-context", "solana-transaction-error", "solana-validator-exit", - "thiserror 2.0.12", + "thiserror 2.0.15", "wasm-bindgen", ] @@ -6103,7 +6090,7 @@ dependencies = [ "bs58", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -6134,7 +6121,7 @@ dependencies = [ "borsh 1.5.7", "libsecp256k1", "solana-define-syscall", - "thiserror 2.0.12", + "thiserror 2.0.15", ] [[package]] @@ -6322,9 +6309,9 @@ dependencies = [ [[package]] name = "solana-svm-feature-set" -version = "2.3.6" +version = "2.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d525b3bb05c5a56c17ec7f4d5b9f838f0bcf006cf423a7c0e1b05ef4e10a2a" +checksum = "e65361fa1fb2a123319df6d9694c1c5ca20e555cda18eb1f953babf32e4cddd4" [[package]] name = "solana-system-interface" @@ -6359,9 +6346,9 @@ dependencies = [ [[package]] name = "solana-sysvar" -version = "2.2.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50c92bc019c590f5e42c61939676e18d14809ed00b2a59695dd5c67ae72c097" +checksum = "b8c3595f95069f3d90f275bb9bd235a1973c4d059028b0a7f81baca2703815db" dependencies = [ "base64 0.22.1", "bincode", @@ -6439,9 +6426,9 @@ dependencies = [ [[package]] name = "solana-transaction-context" -version = "2.3.6" +version = "2.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62899fc8ec399458db3332ec51dfc8379ca8bb5615133510c8b4dca4c5d48111" +checksum = "aefd75e49dd990f7fdbe562a539a7b046a839aadf43843845d766a2a6a2adfef" dependencies = [ "bincode", "serde", @@ -6468,9 +6455,9 @@ dependencies = [ [[package]] name = "solana-transaction-status" -version = "2.3.6" +version = "2.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d824edb8ca9fd7b35499c88709ea4a4d5c74a765e5e1912ee6d8b9964265e82" +checksum = "287a86e28777cdc8c0745ff5700a2c3741a2a7a72a347a93815e832adfe39dc5" dependencies = [ "Inflector", "agave-reserved-account-keys", @@ -6507,14 +6494,14 @@ dependencies = [ "spl-token-2022", "spl-token-group-interface", "spl-token-metadata-interface", - "thiserror 2.0.12", + "thiserror 2.0.15", ] [[package]] name = "solana-transaction-status-client-types" -version = "2.3.6" +version = "2.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d10a5585f489ca00b0d9fdfd4ffb159450facca7ce52ca025268975f74013c8" +checksum = "9e91068d54435121280c4a2f1c280d8d18381e3ccf54057c4530f40f26c2be1c" dependencies = [ "base64 0.22.1", "bincode", @@ -6530,7 +6517,7 @@ dependencies = [ "solana-transaction", "solana-transaction-context", "solana-transaction-error", - "thiserror 2.0.12", + "thiserror 2.0.15", ] [[package]] @@ -6541,9 +6528,9 @@ checksum = "7bbf6d7a3c0b28dd5335c52c0e9eae49d0ae489a8f324917faf0ded65a812c1d" [[package]] name = "solana-version" -version = "2.3.6" +version = "2.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c01beecdd0f0e720f69dae5d99a8cf3cf9beafeba776d9e4febcab24653e078" +checksum = "b4607a9de98043bcf7db9e5d90b31fefb728c80eec901595b6931d7cdc1558b2" dependencies = [ "agave-feature-set", "rand 0.8.5", @@ -6580,9 +6567,9 @@ dependencies = [ [[package]] name = "solana-zk-sdk" -version = "2.3.6" +version = "2.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05857892ac50fe03c125d8445fd790c6768015b76f4ad1e4b4b1499938b357f0" +checksum = "3bb171c0f76c420a7cb6aabbe5fa85a1a009d5bb4009189c43e1a03aff9446d7" dependencies = [ "aes-gcm-siv", "base64 0.22.1", @@ -6609,7 +6596,7 @@ dependencies = [ "solana-signature", "solana-signer", "subtle", - "thiserror 2.0.12", + "thiserror 2.0.15", "wasm-bindgen", "zeroize", ] @@ -6627,7 +6614,7 @@ dependencies = [ "spl-associated-token-account-client", "spl-token", "spl-token-2022", - "thiserror 2.0.12", + "thiserror 2.0.15", ] [[package]] @@ -6660,19 +6647,19 @@ checksum = "d9e8418ea6269dcfb01c712f0444d2c75542c04448b480e87de59d2865edc750" dependencies = [ "quote", "spl-discriminator-syn", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "spl-discriminator-syn" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1f05593b7ca9eac7caca309720f2eafb96355e037e6d373b909a80fe7b69b9" +checksum = "5d1dbc82ab91422345b6df40a79e2b78c7bce1ebb366da323572dd60b7076b67" dependencies = [ "proc-macro2", "quote", "sha2 0.10.9", - "syn 2.0.104", + "syn 2.0.106", "thiserror 1.0.69", ] @@ -6740,7 +6727,7 @@ dependencies = [ "solana-program-option", "solana-pubkey", "solana-zk-sdk", - "thiserror 2.0.12", + "thiserror 2.0.15", ] [[package]] @@ -6755,7 +6742,7 @@ dependencies = [ "solana-msg", "solana-program-error", "spl-program-error-derive", - "thiserror 2.0.12", + "thiserror 2.0.15", ] [[package]] @@ -6767,7 +6754,7 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.9", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -6789,7 +6776,7 @@ dependencies = [ "spl-pod", "spl-program-error", "spl-type-length-value", - "thiserror 2.0.12", + "thiserror 2.0.15", ] [[package]] @@ -6817,7 +6804,7 @@ dependencies = [ "solana-rent", "solana-sdk-ids", "solana-sysvar", - "thiserror 2.0.12", + "thiserror 2.0.15", ] [[package]] @@ -6861,14 +6848,14 @@ dependencies = [ "spl-token-metadata-interface", "spl-transfer-hook-interface", "spl-type-length-value", - "thiserror 2.0.12", + "thiserror 2.0.15", ] [[package]] name = "spl-token-confidential-transfer-ciphertext-arithmetic" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94ab20faf7b5edaa79acd240e0f21d5a2ef936aa99ed98f698573a2825b299c4" +checksum = "cddd52bfc0f1c677b41493dafa3f2dbbb4b47cf0990f08905429e19dc8289b35" dependencies = [ "base64 0.22.1", "bytemuck", @@ -6893,18 +6880,18 @@ dependencies = [ "solana-sdk-ids", "solana-zk-sdk", "spl-pod", - "thiserror 2.0.12", + "thiserror 2.0.15", ] [[package]] name = "spl-token-confidential-transfer-proof-generation" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae5b124840d4aed474cef101d946a798b806b46a509ee4df91021e1ab1cef3ef" +checksum = "fa27b9174bea869a7ebf31e0be6890bce90b1a4288bc2bbf24bd413f80ae3fde" dependencies = [ "curve25519-dalek 4.1.3", "solana-zk-sdk", - "thiserror 2.0.12", + "thiserror 2.0.15", ] [[package]] @@ -6923,7 +6910,7 @@ dependencies = [ "solana-pubkey", "spl-discriminator", "spl-pod", - "thiserror 2.0.12", + "thiserror 2.0.15", ] [[package]] @@ -6944,7 +6931,7 @@ dependencies = [ "spl-discriminator", "spl-pod", "spl-type-length-value", - "thiserror 2.0.12", + "thiserror 2.0.15", ] [[package]] @@ -6969,7 +6956,7 @@ dependencies = [ "spl-program-error", "spl-tlv-account-resolution", "spl-type-length-value", - "thiserror 2.0.12", + "thiserror 2.0.15", ] [[package]] @@ -6987,7 +6974,7 @@ dependencies = [ "solana-program-error", "spl-discriminator", "spl-pod", - "thiserror 2.0.12", + "thiserror 2.0.15", ] [[package]] @@ -7011,7 +6998,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -7035,6 +7022,7 @@ dependencies = [ "axum-macros", "axum-prometheus", "base64 0.22.1", + "bincode", "clap", "dashmap", "dotenv", @@ -7075,9 +7063,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.104" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -7101,7 +7089,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -7164,11 +7152,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "80d76d3f064b981389ecb4b6b7f45a0bf9fdac1d5b9204c7bd6714fecc302850" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.15", ] [[package]] @@ -7179,18 +7167,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "44d29feb33e986b6ea906bd9c3559a856983f92371b3eaa5e83782a351623de0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -7235,9 +7223,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] @@ -7250,9 +7238,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.47.0" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", @@ -7276,7 +7264,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -7318,6 +7306,7 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", ] [[package]] @@ -7349,9 +7338,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -7381,40 +7370,38 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.10.0", + "indexmap", "toml_datetime", "winnow", ] [[package]] name = "tonic" -version = "0.12.3" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +checksum = "67ac5a8627ada0968acec063a4746bf79588aa03ccb66db2f75d7dce26722a40" dependencies = [ - "async-stream", "async-trait", - "axum 0.7.9", + "axum 0.8.4", "base64 0.22.1", "bytes", "flate2", - "h2 0.4.11", + "h2 0.4.12", "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-timeout", "hyper-util", "percent-encoding", "pin-project", - "prost", "rustls-native-certs 0.8.1", - "rustls-pemfile 2.2.0", - "socket2 0.5.10", + "socket2 0.6.0", + "sync_wrapper", "tokio", "tokio-rustls 0.26.2", "tokio-stream", - "tower 0.4.13", + "tower 0.5.2", "tower-layer", "tower-service", "tracing", @@ -7423,29 +7410,54 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.12.3" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" +checksum = "49e323d8bba3be30833707e36d046deabf10a35ae8ad3cae576943ea8933e25d" dependencies = [ "prettyplease", "proc-macro2", - "prost-build", - "prost-types", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "tonic-health" -version = "0.12.3" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1eaf34ddb812120f5c601162d5429933c9b527d901ab0e7f930d3147e33a09b2" +checksum = "d8aa31379f0851de4f223496a469ffcaae24ad4ce736493179c3e42135e2af5c" dependencies = [ - "async-stream", "prost", "tokio", "tokio-stream", "tonic", + "tonic-prost", +] + +[[package]] +name = "tonic-prost" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9c511b9a96d40cb12b7d5d00464446acf3b9105fd3ce25437cfe41c92b1c87d" +dependencies = [ + "bytes", + "prost", + "tonic", +] + +[[package]] +name = "tonic-prost-build" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ef298fcd01b15e135440c4b8c974460ceca4e6a5af7f1c933b08e4d2875efa1" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn 2.0.106", + "tempfile", + "tonic-build", ] [[package]] @@ -7454,15 +7466,6 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ - "futures-core", - "futures-util", - "indexmap 1.9.3", - "pin-project", - "pin-project-lite", - "rand 0.8.5", - "slab", - "tokio", - "tokio-util", "tower-layer", "tower-service", "tracing", @@ -7476,9 +7479,12 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", + "indexmap", "pin-project-lite", + "slab", "sync_wrapper", "tokio", + "tokio-util", "tower-layer", "tower-service", "tracing", @@ -7550,7 +7556,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -7617,7 +7623,7 @@ dependencies = [ "native-tls", "rand 0.9.2", "sha1", - "thiserror 2.0.12", + "thiserror 2.0.15", "utf-8", ] @@ -7633,6 +7639,18 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +[[package]] +name = "typewit" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97e72ba082eeb9da9dc68ff5a2bf727ef6ce362556e8d29ec1aed3bd05e7d86a" + +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -7714,9 +7732,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" dependencies = [ "getrandom 0.3.3", "js-sys", @@ -7728,13 +7746,13 @@ dependencies = [ [[package]] name = "uuid-macro-internal" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b682e8c381995ea03130e381928e0e005b7c9eb483c6c8682f50e07b33c2b7" +checksum = "22b7ad00068276db5fea436dba78daa7891b8d60db76e4f51cbdefbdecdab97e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -7807,7 +7825,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "wasm-bindgen-shared", ] @@ -7842,7 +7860,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -8160,8 +8178,8 @@ checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" [[package]] name = "yellowstone-grpc-client" -version = "8.0.0" -source = "git+https://github.com/rpcpool/yellowstone-grpc?branch=master#5dc0817ec0ddfc609ef5deabc74fae1e339417ad" +version = "9.0.0" +source = "git+https://github.com/rpcpool/yellowstone-grpc?branch=master#fd1e5dd37bb0346f353c6f7522b184aa103c1bd3" dependencies = [ "bytes", "futures", @@ -8173,8 +8191,8 @@ dependencies = [ [[package]] name = "yellowstone-grpc-proto" -version = "8.0.0" -source = "git+https://github.com/rpcpool/yellowstone-grpc?branch=master#5dc0817ec0ddfc609ef5deabc74fae1e339417ad" +version = "9.0.0" +source = "git+https://github.com/rpcpool/yellowstone-grpc?branch=master#fd1e5dd37bb0346f353c6f7522b184aa103c1bd3" dependencies = [ "anyhow", "bincode", @@ -8194,6 +8212,8 @@ dependencies = [ "solana-transaction-status", "tonic", "tonic-build", + "tonic-prost", + "tonic-prost-build", ] [[package]] @@ -8216,7 +8236,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "synstructure", ] @@ -8237,7 +8257,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -8257,7 +8277,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "synstructure", ] @@ -8278,7 +8298,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -8294,9 +8314,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", @@ -8311,7 +8331,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3feeb77..43e7e84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,10 +22,11 @@ axum = { version = "^0.8.3", features = ["ws"] } axum-extra = { version = "0.10.1", features = ["typed-header"] } axum-prometheus = "0.7.0" base64 = "0.22.1" +bincode = "1" clap = { version = "4.0", features = ["derive"] } dashmap = "6.1.0" dotenv = "0.15.0" -drift-rs = { git = "https://github.com/drift-labs/drift-rs.git" , rev = "8059150" } +drift-rs = { git = "https://github.com/drift-labs/drift-rs.git" , rev = "004cf7c" } ed25519-dalek = "1.0.1" env_logger = "0.11" faster-hex = "0.10.0" @@ -38,8 +39,8 @@ rdkafka = { version = "0.37.0", features = ["ssl", "sasl"] } redis = { version = "0.29.1", features = ["tokio-comp", "tokio-native-tls-comp"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.127" -solana-rpc-client-api = "2.1" -solana-sdk = "2.1" +solana-rpc-client-api = "2.3" +solana-sdk = "2.3" tokio = { version = "1.0", features = ["full"] } # fork with Ws compression enabled tokio-tungstenite = { git = "https://github.com/drift-labs/tokio-tungstenite", features = ["native-tls"] } diff --git a/src/swift_server.rs b/src/swift_server.rs index 07273f1..ec3a472 100644 --- a/src/swift_server.rs +++ b/src/swift_server.rs @@ -11,15 +11,15 @@ use crate::{ super_slot_subscriber::SuperSlotSubscriber, types::{ messages::{ - IncomingSignedMessage, OrderMetadataAndMessage, ProcessOrderResponse, - PROCESS_ORDER_RESPONSE_ERROR_MSG_DELIVERY_FAILED, + DepositAndPlaceRequest, IncomingSignedMessage, OrderMetadataAndMessage, + ProcessOrderResponse, PROCESS_ORDER_RESPONSE_ERROR_MSG_DELIVERY_FAILED, PROCESS_ORDER_RESPONSE_ERROR_MSG_INVALID_ORDER, PROCESS_ORDER_RESPONSE_ERROR_MSG_INVALID_ORDER_AMOUNT, PROCESS_ORDER_RESPONSE_ERROR_MSG_ORDER_SLOT_TOO_OLD, PROCESS_ORDER_RESPONSE_ERROR_MSG_VERIFY_SIGNATURE, PROCESS_ORDER_RESPONSE_IGNORE_PUBKEY, PROCESS_ORDER_RESPONSE_MESSAGE_SUCCESS, }, - types::unix_now_ms, + types::{unix_now_ms, RequestContext}, }, user_account_fetcher::UserAccountFetcher, util::{ @@ -27,9 +27,10 @@ use crate::{ metrics::{metrics_handler, MetricsServerParams, SwiftServerMetrics}, }, }; +use anchor_lang::{AnchorDeserialize, Discriminator}; use axum::{ extract::State, - http::{self, Method}, + http::{self, Method, StatusCode}, routing::{get, post}, Json, Router, }; @@ -37,8 +38,9 @@ use base64::Engine; use dotenv::dotenv; use drift_rs::{ constants::high_leverage_mode_account, + drift_idl, event_subscriber::PubsubClient, - math::account_list_builder::AccountsListBuilder, + math::{account_list_builder::AccountsListBuilder, constants::PRICE_PRECISION}, swift_order_subscriber::{SignedMessageInfo, SignedOrderType}, types::{ accounts::{HighLeverageModeConfig, User}, @@ -120,7 +122,34 @@ pub async fn process_order_wrapper( ) -> impl axum::response::IntoResponse { let uuid_raw = extract_uuid(&incoming_message.message); let uuid = core::str::from_utf8(&uuid_raw).unwrap_or("00000000"); - let (status, resp) = process_order(server_params, incoming_message).await; + let context = RequestContext::from_incoming_message(&incoming_message); + + let (status, resp) = match process_order(server_params, incoming_message, &context).await { + Ok(order_metadata) => { + let metrics_labels = &[ + context.market_type, + &context.market_index.to_string(), + match order_metadata.will_sanitize { + true => "true", + false => "false", + }, + ]; + let topic = format!("swift_orders_{}_{}", metrics_labels[0], metrics_labels[1]); + let payload = order_metadata.encode(); + + server_params + .publish_order( + &topic, + &payload, + order_metadata.uuid(), + metrics_labels, + &context, + ) + .await + } + Err(err) => err, + }; + log::info!( target: "server", "{status}|{uuid}|{:?}|ui={}", resp.error.as_deref().unwrap_or(""), @@ -132,8 +161,8 @@ pub async fn process_order_wrapper( pub async fn process_order( server_params: &'static ServerParams, incoming_message: IncomingSignedMessage, -) -> (http::StatusCode, ProcessOrderResponse) { - let process_order_time = unix_now_ms(); + context: &RequestContext, +) -> Result { let IncomingSignedMessage { taker_pubkey, signature: taker_signature, @@ -153,13 +182,13 @@ pub async fn process_order( target: "server", "Ignoring order from farmer pubkey: {taker_authority}" ); - return ( + return Err(( axum::http::StatusCode::BAD_REQUEST, ProcessOrderResponse { message: PROCESS_ORDER_RESPONSE_IGNORE_PUBKEY, error: None, }, - ); + )); } server_params.metrics.taker_orders_counter.inc(); @@ -170,26 +199,35 @@ pub async fn process_order( signing_authority }; - let log_prefix = format!("[process_order {taker_authority}: {process_order_time}]"); log::trace!( target: "server", - "{log_prefix}: Received order with signing pubkey: {signing_pubkey}" + "{}: Received order with signing pubkey: {signing_pubkey}", + context.log_prefix, ); let signed_msg = match incoming_message.verify_and_get_signed_message() { Ok(m) => m, Err(e) => { - log::warn!("{log_prefix}: Error verifying signed message: {e:?}, signer: {}, taker_authority: {}", incoming_message.signing_authority, incoming_message.taker_authority); - return ( + log::warn!( + "{}: Error verifying signed message: {e:?}, signer: {}, taker_authority: {}", + context.log_prefix, + incoming_message.signing_authority, + incoming_message.taker_authority + ); + return Err(( axum::http::StatusCode::BAD_REQUEST, ProcessOrderResponse { message: PROCESS_ORDER_RESPONSE_ERROR_MSG_VERIFY_SIGNATURE, error: Some(e.to_string()), }, - ); + )); } }; - let is_delegated = signed_msg.is_delegated(); + let delegate_signer = if signed_msg.is_delegated() { + Some(&signing_pubkey) + } else { + None + }; let current_slot = server_params.slot_subscriber.current_slot(); let SignedMessageInfo { @@ -197,10 +235,7 @@ pub async fn process_order( order_params, taker_pubkey, uuid, - } = match extract_signed_message_info(signed_msg, &taker_authority, current_slot) { - Ok(info) => info, - Err(err) => return err, - }; + } = extract_signed_message_info(signed_msg, &taker_authority, current_slot)?; // check the order is valid for execution by program let market = server_params @@ -212,23 +247,18 @@ pub async fn process_order( ) { log::warn!( target: "server", - "{log_prefix}: Order did not validate: {err:?}, {order_params:?}", + "{}: Order did not validate: {err:?}, {order_params:?}", + context.log_prefix ); - return ( + return Err(( axum::http::StatusCode::BAD_REQUEST, ProcessOrderResponse { message: PROCESS_ORDER_RESPONSE_ERROR_MSG_INVALID_ORDER, error: Some(err.to_string()), }, - ); + )); } - let delegate_signer = if is_delegated { - Some(&signing_pubkey) - } else { - None - }; - match server_params .simulate_taker_order_rpc(&taker_pubkey, &order_params, delegate_signer, current_slot) .await @@ -248,21 +278,23 @@ pub async fn process_order( .inc(); log::warn!( target: "server", - "{log_prefix}: Order sim failed (taker: {taker_pubkey:?}, delegate: {delegate_signer:?}, market: {:?}-{}): {sim_err_str}. Logs: {logs:?}", - order_params.market_type, + "{}: Order sim failed (taker: {taker_pubkey:?}, delegate: {delegate_signer:?}, market: {}-{}): {sim_err_str}. Logs: {logs:?}", + context.log_prefix, + order_params.market_type.as_str(), order_params.market_index, ); log::warn!( target: "server", - "{log_prefix}: failed order params: {order_params:?}" + "{}: failed order params: {order_params:?}", + context.log_prefix, ); - return ( + return Err(( status, ProcessOrderResponse { message: PROCESS_ORDER_RESPONSE_ERROR_MSG_INVALID_ORDER, error: Some(sim_err_str), }, - ); + )); } }; @@ -273,153 +305,27 @@ pub async fn process_order( taker_authority, order_message: *signed_msg, order_signature: taker_signature.into(), - ts: process_order_time, + ts: context.recv_ts, uuid, will_sanitize, }; - let encoded = order_metadata.encode(); - let market_index = order_params.market_index; - let market_type = order_params.market_type; - - let topic = format!("swift_orders_{}_{market_index}", market_type.as_str()); - - if let Some(kafka_producer) = &server_params.kafka_producer { - let enqueue_result = match kafka_producer - .send_result(FutureRecord::::to(&topic).payload(&encoded)) - { - Ok(fut) => fut.await, - Err((err, _)) => { - log::error!( - target: "kafka", - "{log_prefix}: Failed to queue order: {order_metadata:?}, error: {err:?}" - ); - server_params.metrics.kafka_forward_fail_counter.inc(); - return ( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - ProcessOrderResponse { - message: PROCESS_ORDER_RESPONSE_ERROR_MSG_DELIVERY_FAILED, - error: Some(format!("kafka publish error: {err:?}")), - }, - ); - } - }; - - match enqueue_result { - Ok(Ok(_delivery_result)) => { - log::trace!(target: "kafka", "{log_prefix}: Sent message for order: {order_metadata:?}"); - server_params - .metrics - .current_slot_gauge - .set(current_slot as f64); - - server_params - .metrics - .order_type_counter - .with_label_values(&[ - market_type.as_str(), - &market_index.to_string(), - match will_sanitize { - true => "true", - false => "false", - }, - ]) - .inc(); - server_params - .metrics - .kafka_inflight_count - .set(kafka_producer.in_flight_count() as i64); + server_params + .metrics + .current_slot_gauge + .set(current_slot as f64); - server_params - .metrics - .response_time_histogram - .observe((unix_now_ms() - process_order_time) as f64); - - log::info!(target: "kafka", "published to kafka: {}", order_metadata.uuid()); - ( - axum::http::StatusCode::OK, - ProcessOrderResponse { - message: PROCESS_ORDER_RESPONSE_MESSAGE_SUCCESS, - error: None, - }, - ) - } - Ok(Err((e, _))) => { - log::error!( - target: "kafka", - "{log_prefix}: Failed to deliver order: {order_metadata:?}, error: {e:?}" - ); - server_params.metrics.kafka_forward_fail_counter.inc(); - ( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - ProcessOrderResponse { - message: PROCESS_ORDER_RESPONSE_ERROR_MSG_DELIVERY_FAILED, - error: Some(format!("kafka publish error: {e:?}")), - }, - ) - } - Err(_) => { - log::error!( - target: "kafka", - "{log_prefix}: Failed to queue order: {order_metadata:?}" - ); - server_params.metrics.kafka_forward_fail_counter.inc(); - ( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - ProcessOrderResponse { - message: PROCESS_ORDER_RESPONSE_ERROR_MSG_DELIVERY_FAILED, - error: Some("kafka publish error".into()), - }, - ) - } - } - } else { - let mut conn = server_params.redis_pool.clone().unwrap(); - match conn.publish::(topic, encoded).await { - Ok(_) => { - log::trace!(target: "redis", "{log_prefix}: Sent redis message for order: {order_metadata:?}"); - server_params - .metrics - .current_slot_gauge - .set(current_slot as f64); - server_params - .metrics - .order_type_counter - .with_label_values(&[market_type.as_str(), &market_index.to_string(), "false"]) - .inc(); - - server_params - .metrics - .response_time_histogram - .observe((unix_now_ms() - process_order_time) as f64); - - ( - axum::http::StatusCode::OK, - ProcessOrderResponse { - message: PROCESS_ORDER_RESPONSE_MESSAGE_SUCCESS, - error: None, - }, - ) - } - Err(e) => ( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - ProcessOrderResponse { - message: PROCESS_ORDER_RESPONSE_ERROR_MSG_DELIVERY_FAILED, - error: Some(format!("redis publish error: {e:?}")), - }, - ), - } - } + Ok(order_metadata) } pub async fn send_heartbeat(server_params: &'static ServerParams) { - let hearbeat_time = unix_now_ms(); - let log_prefix = format!("[hearbeat: {hearbeat_time}]"); + let heartbeat_time = unix_now_ms(); + let log_prefix = format!("[heartbeat: {heartbeat_time}]"); if let Some(kafka_producer) = &server_params.kafka_producer { match kafka_producer .send( - FutureRecord::::to("hearbeat").payload(&"love you".to_string()), + FutureRecord::::to("heartbeat").payload(&"love you".to_string()), Timeout::After(Duration::ZERO), ) .await @@ -465,6 +371,102 @@ pub async fn send_heartbeat(server_params: &'static ServerParams) { } } +pub async fn deposit_trade( + State(server_params): State<&'static ServerParams>, + Json(req): Json, +) -> impl axum::response::IntoResponse { + let min_trade_value = 100 * PRICE_PRECISION as u64; + + if req.deposit_tx.signatures.is_empty() + || req.deposit_tx.message.instructions.len() != 1 + || req.deposit_tx.verify().is_err() + { + return ( + StatusCode::BAD_REQUEST, + Json(ProcessOrderResponse { + message: "", + error: Some("invalid deposit tx".into()), + }), + ); + } + + // verify deposit ix exists and amount + let ix = &req.deposit_tx.message.instructions[0]; + if &ix.data[..8] == drift_idl::instructions::Deposit::DISCRIMINATOR { + if let Ok(deposit_ix) = + drift_idl::instructions::Deposit::deserialize(&mut ix.data.as_slice()) + { + let spot_oracle = server_params + .drift + .try_get_oracle_price_data_and_slot(MarketId::spot(deposit_ix.market_index)) + .expect("got price"); + let deposit_value = spot_oracle.data.price as u64 * deposit_ix.amount; + if deposit_value < min_trade_value { + return ( + StatusCode::BAD_REQUEST, + Json(ProcessOrderResponse { + message: "", + error: Some(format!("deposit size must be > ${min_trade_value}")), + }), + ); + } + } else { + return ( + StatusCode::BAD_REQUEST, + Json(ProcessOrderResponse { + message: "", + error: Some("invalid deposit ix".into()), + }), + ); + } + } else { + return ( + StatusCode::BAD_REQUEST, + Json(ProcessOrderResponse { + message: "", + error: Some("invalid deposit ix".into()), + }), + ); + } + + let context = RequestContext::from_incoming_message(&req.swift_order); + let (status, resp) = match process_order(server_params, req.swift_order, &context).await { + Ok(order_metadata) => { + let metrics_labels = &[ + context.market_type, + &context.market_index.to_string(), + match order_metadata.will_sanitize { + true => "true", + false => "false", + }, + ]; + let topic = format!( + "swift_orders_deposit_{}_{}", + metrics_labels[0], metrics_labels[1] + ); + let payload = serde_json::json!({ + "depositTx": base64::prelude::BASE64_STANDARD + .encode(bincode::serialize(&req.deposit_tx).unwrap()), + "order": order_metadata.encode(), + }) + .to_string(); + + server_params + .publish_order( + &topic, + &payload, + order_metadata.uuid(), + metrics_labels, + &context, + ) + .await + } + Err(err) => err, + }; + + (status, Json(resp)) +} + pub async fn health_check( State(server_params): State<&'static ServerParams>, ) -> impl axum::response::IntoResponse { @@ -663,6 +665,7 @@ pub async fn start_server() { let app = Router::new() .fallback(fallback) .route("/orders", post(process_order_wrapper)) + .route("/depositTrade", post(deposit_trade)) .route("/health", get(health_check)) .layer(cors) .with_state(state); @@ -1076,6 +1079,128 @@ impl ServerParams { } } } + + async fn publish_order( + &self, + topic: &str, + payload: &String, + uuid: &str, + metrics_labels: &[&str; 3], + context: &RequestContext, + ) -> (axum::http::StatusCode, ProcessOrderResponse) { + if let Some(kafka_producer) = &self.kafka_producer { + let enqueue_result = match kafka_producer + .send_result(FutureRecord::::to(topic).payload(payload)) + { + Ok(fut) => fut.await, + Err((err, _)) => { + log::error!( + target: "kafka", + "{}: Failed to queue order: {uuid}, error: {err:?}", + context.log_prefix, + ); + self.metrics.kafka_forward_fail_counter.inc(); + return ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + ProcessOrderResponse { + message: PROCESS_ORDER_RESPONSE_ERROR_MSG_DELIVERY_FAILED, + error: Some(format!("kafka publish error: {err:?}")), + }, + ); + } + }; + + match enqueue_result { + Ok(Ok(_delivery_result)) => { + log::trace!(target: "kafka", "{}: Sent message for order: {uuid}", context.log_prefix); + self.metrics + .order_type_counter + .with_label_values(metrics_labels) + .inc(); + + self.metrics + .kafka_inflight_count + .set(kafka_producer.in_flight_count() as i64); + + self.metrics + .response_time_histogram + .observe((unix_now_ms() - context.recv_ts) as f64); + + log::info!(target: "kafka", "published to kafka: {uuid}"); + ( + axum::http::StatusCode::OK, + ProcessOrderResponse { + message: PROCESS_ORDER_RESPONSE_MESSAGE_SUCCESS, + error: None, + }, + ) + } + Ok(Err((e, _))) => { + log::error!( + target: "kafka", + "{}: Failed to deliver order: {uuid}, error: {e:?}", + context.log_prefix + ); + self.metrics.kafka_forward_fail_counter.inc(); + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + ProcessOrderResponse { + message: PROCESS_ORDER_RESPONSE_ERROR_MSG_DELIVERY_FAILED, + error: Some(format!("kafka publish error: {e:?}")), + }, + ) + } + Err(_) => { + log::error!( + target: "kafka", + "{}: Failed to queue order: {uuid}", + context.log_prefix, + ); + self.metrics.kafka_forward_fail_counter.inc(); + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + ProcessOrderResponse { + message: PROCESS_ORDER_RESPONSE_ERROR_MSG_DELIVERY_FAILED, + error: Some("kafka publish error".into()), + }, + ) + } + } + } else { + let mut conn = self.redis_pool.clone().unwrap(); + match conn + .publish::(topic.to_string(), payload.to_string()) + .await + { + Ok(_) => { + log::trace!(target: "redis", "{}: Sent redis message for order: {uuid}", context.log_prefix); + self.metrics + .order_type_counter + .with_label_values(metrics_labels) + .inc(); + + self.metrics + .response_time_histogram + .observe((unix_now_ms() - context.recv_ts) as f64); + + ( + axum::http::StatusCode::OK, + ProcessOrderResponse { + message: PROCESS_ORDER_RESPONSE_MESSAGE_SUCCESS, + error: None, + }, + ) + } + Err(e) => ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + ProcessOrderResponse { + message: PROCESS_ORDER_RESPONSE_ERROR_MSG_DELIVERY_FAILED, + error: Some(format!("redis publish error: {e:?}")), + }, + ), + } + } + } } /// extract collateral ratio from program sim logs diff --git a/src/types/messages.rs b/src/types/messages.rs index cc9ea9a..e17ce5c 100644 --- a/src/types/messages.rs +++ b/src/types/messages.rs @@ -13,7 +13,14 @@ use drift_rs::{ }; use ed25519_dalek::{PublicKey, Signature, Verifier}; use serde_json::json; -use solana_sdk::pubkey::Pubkey; +use solana_sdk::{pubkey::Pubkey, transaction::Transaction}; + +#[derive(serde::Deserialize, Clone, Debug, PartialEq)] +pub struct DepositAndPlaceRequest { + #[serde(deserialize_with = "deser_transaction")] + pub deposit_tx: Transaction, + pub swift_order: IncomingSignedMessage, +} #[derive(serde::Deserialize, Clone, Debug, PartialEq)] pub struct IncomingSignedMessage { @@ -166,6 +173,7 @@ pub enum WsClientMessage { #[derive(Clone, Debug)] pub struct WsMessage<'a> { channel: &'a str, + deposit: Option<&'a str>, order: Option<&'a OrderMetadataAndMessage>, nonce: Option<&'a str>, message: Option<&'a str>, @@ -185,9 +193,16 @@ impl<'a> WsMessage<'a> { WsMessage::new("subscribe") } + pub const fn deposit_trade(tx: &'a str) -> Self { + let mut this = WsMessage::new("deposit_trade"); + this.deposit = Some(tx); + this + } + pub const fn new(channel: &'a str) -> Self { Self { channel, + deposit: None, order: None, nonce: None, message: None, @@ -236,6 +251,10 @@ impl<'a> WsMessage<'a> { message["error"] = json!(error); } + if let Some(deposit) = self.deposit { + message["deposit"] = json!(deposit); + } + message.to_string() } } @@ -270,6 +289,18 @@ where MarketType::from_str(market_type).map_err(|_| serde::de::Error::custom("perp or spot")) } +/// Deserialize solana transaction +pub fn deser_transaction<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let payload: &str = serde::Deserialize::deserialize(deserializer)?; + let buf = base64::prelude::BASE64_STANDARD + .decode(payload) + .map_err(serde::de::Error::custom)?; + bincode::deserialize(&buf).map_err(serde::de::Error::custom) +} + #[cfg(test)] mod tests { use drift_rs::types::{ diff --git a/src/types/types.rs b/src/types/types.rs index 7714608..ad64722 100644 --- a/src/types/types.rs +++ b/src/types/types.rs @@ -1,5 +1,9 @@ use std::time::UNIX_EPOCH; +use drift_rs::types::MarketType; + +use crate::types::messages::IncomingSignedMessage; + #[derive(PartialEq, Debug, strum_macros::AsRefStr)] pub enum WsError { /// Client tried to do a protected action while unauthenticated @@ -32,3 +36,28 @@ pub fn unix_now_ms() -> u64 { .unwrap() .as_millis() as u64 } + +#[derive(Clone)] +pub struct RequestContext { + pub recv_ts: u64, + pub log_prefix: String, + pub market_index: u16, + pub market_type: &'static str, +} + +impl RequestContext { + pub fn from_incoming_message(msg: &IncomingSignedMessage) -> Self { + let recv_ts = unix_now_ms(); + let info = msg.message.info(&msg.taker_authority); + + Self { + market_index: info.order_params.market_index, + market_type: match info.order_params.market_type { + MarketType::Perp => "perp", + MarketType::Spot => "spot", + }, + recv_ts, + log_prefix: format!("[process_order {}: {recv_ts}]", msg.taker_authority), + } + } +} diff --git a/src/ws_server.rs b/src/ws_server.rs index c65ad0f..34d9880 100644 --- a/src/ws_server.rs +++ b/src/ws_server.rs @@ -297,6 +297,11 @@ impl WsConnection { market_type.as_str(), market_index.unwrap() ); + let deposit_topic = format!( + "swift_deposit_orders_{}_{}", + market_type.as_str(), + market_index.unwrap() + ); match action { SubscribeActions::Subscribe => { @@ -308,6 +313,11 @@ impl WsConnection { self.pubkey, ); self.subscribed_topics.insert(topic.clone(), tx.subscribe()); + + if let Some(tx) = shared_state.subscriptions.get(&deposit_topic) { + self.subscribed_topics + .insert(deposit_topic.clone(), tx.subscribe()); + } Ok(()) } else { log::info!( @@ -331,6 +341,7 @@ impl WsConnection { self.pubkey, ); self.subscribed_topics.remove(&topic); + self.subscribed_topics.remove(&deposit_topic); Ok(()) } } @@ -813,8 +824,14 @@ pub async fn start_server() { market.market_type(), market.market_index ); - topic_names.push(topic.clone()); + let deposit_topic = format!( + "swift_orders_deposit_{}_{}", + market.market_type(), + market.market_index + ); + topic_names.extend_from_slice(&[topic.clone(), deposit_topic.clone()]); subscriptions.insert(topic, broadcast::channel(10).0); + subscriptions.insert(deposit_topic, broadcast::channel(10).0); } // Registry for metrics From 222b67cb35873f484e998587c121475c7d82c122 Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Fri, 22 Aug 2025 16:02:50 +0800 Subject: [PATCH 02/12] fix ws server consumer --- src/swift_server.rs | 2 +- src/types/messages.rs | 11 +++++------ src/ws_server.rs | 34 +++++++++++++++++++++++++++++++--- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/swift_server.rs b/src/swift_server.rs index ec3a472..575d986 100644 --- a/src/swift_server.rs +++ b/src/swift_server.rs @@ -445,7 +445,7 @@ pub async fn deposit_trade( metrics_labels[0], metrics_labels[1] ); let payload = serde_json::json!({ - "depositTx": base64::prelude::BASE64_STANDARD + "deposit": base64::prelude::BASE64_STANDARD .encode(bincode::serialize(&req.deposit_tx).unwrap()), "order": order_metadata.encode(), }) diff --git a/src/types/messages.rs b/src/types/messages.rs index e17ce5c..78e3029 100644 --- a/src/types/messages.rs +++ b/src/types/messages.rs @@ -193,12 +193,6 @@ impl<'a> WsMessage<'a> { WsMessage::new("subscribe") } - pub const fn deposit_trade(tx: &'a str) -> Self { - let mut this = WsMessage::new("deposit_trade"); - this.deposit = Some(tx); - this - } - pub const fn new(channel: &'a str) -> Self { Self { channel, @@ -210,6 +204,11 @@ impl<'a> WsMessage<'a> { } } + pub fn set_deposit(mut self, tx: &'a str) -> Self { + self.deposit = Some(tx); + self + } + pub fn set_order(mut self, order: &'a OrderMetadataAndMessage) -> Self { self.order = Some(order); self diff --git a/src/ws_server.rs b/src/ws_server.rs index 34d9880..fbdb8b0 100644 --- a/src/ws_server.rs +++ b/src/ws_server.rs @@ -33,6 +33,7 @@ use rdkafka::{ consumer::{Consumer, StreamConsumer}, Message as KafkaMessage, }; +use serde::Deserialize; use tokio::{ io::AsyncWriteExt, sync::{ @@ -645,8 +646,29 @@ async fn subscribe_kafka_consumer( let payload_str = std::str::from_utf8(payload) .context("Failed to convert payload to string") .unwrap(); - let order_metadata = OrderMetadataAndMessage::decode(payload_str) - .context("Failed to decode order metadata"); + + let (order_metadata, deposit) = if !topic.contains("deposit") { + ( + OrderMetadataAndMessage::decode(payload_str) + .context("Failed to decode order metadata"), + None, + ) + } else { + #[derive(Deserialize)] + struct DepositOrder<'a> { + #[serde(borrow)] + deposit: &'a str, + #[serde(borrow)] + order: &'a str, + } + let value: DepositOrder = serde_json::from_str(payload_str).unwrap(); + ( + OrderMetadataAndMessage::decode(value.order) + .context("Failed to decode order metadata"), + Some(value.deposit), + ) + }; + if let Err(ref err) = order_metadata { log::error!(target: "kafka", "Failed to decode order metadata: {err:?}, {order_metadata:?}"); continue; @@ -664,7 +686,13 @@ async fn subscribe_kafka_consumer( "received message: {message_uuid} after: {:?}", unix_now_ms() - order_metadata.ts, ); - let message = WsMessage::new(topic).set_order(&order_metadata); + + let mut message = WsMessage::new(topic).set_order(&order_metadata); + // order has a deposit tx attached + if let Some(deposit) = deposit { + message = message.set_deposit(deposit); + } + log::debug!("message jsonify(): {:?}", message); match tx.send(OrderNotification { json: message.jsonify(), From 0133071eccbeeb2e0cae2cbbc5bd3df63a927ead Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Fri, 22 Aug 2025 18:04:57 +0800 Subject: [PATCH 03/12] e2e test --- src/swift_server.rs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/swift_server.rs b/src/swift_server.rs index 575d986..a7b4e35 100644 --- a/src/swift_server.rs +++ b/src/swift_server.rs @@ -49,7 +49,6 @@ use drift_rs::{ ProgramError, SdkError, SignedMsgTriggerOrderParams, VersionedMessage, VersionedTransaction, }, - utils::load_keypair_multi_format, Context, DriftClient, RpcClient, TransactionBuilder, Wallet, }; use log::warn; @@ -393,9 +392,7 @@ pub async fn deposit_trade( // verify deposit ix exists and amount let ix = &req.deposit_tx.message.instructions[0]; if &ix.data[..8] == drift_idl::instructions::Deposit::DISCRIMINATOR { - if let Ok(deposit_ix) = - drift_idl::instructions::Deposit::deserialize(&mut ix.data.as_slice()) - { + if let Ok(deposit_ix) = drift_idl::instructions::Deposit::deserialize(&mut &ix.data[8..]) { let spot_oracle = server_params .drift .try_get_oracle_price_data_and_slot(MarketId::spot(deposit_ix.market_index)) @@ -415,7 +412,7 @@ pub async fn deposit_trade( StatusCode::BAD_REQUEST, Json(ProcessOrderResponse { message: "", - error: Some("invalid deposit ix".into()), + error: Some("invalid deposit ix encoding".into()), }), ); } @@ -521,13 +518,6 @@ pub async fn start_server() { dotenv().ok(); - let keypair = - load_keypair_multi_format(env::var("PRIVATE_KEY").expect("PRIVATE_KEY set").as_str()); - if let Err(err) = keypair { - log::error!(target: "server", "Failed to load swift private key: {err:?}"); - return; - } - let use_kafka: bool = env::var("USE_KAFKA").unwrap_or_else(|_| "false".to_string()) == "true"; let running_local = env::var("RUNNING_LOCAL").unwrap_or("false".to_string()) == "true"; let drift_env = env::var("ENV").unwrap_or("devnet".to_string()); @@ -592,7 +582,7 @@ pub async fn start_server() { "mainnet-beta" => Context::MainNet, _ => panic!("Invalid drift environment: {drift_env}"), }; - let wallet = Wallet::new(keypair.unwrap()); + let wallet = Wallet::new(Keypair::new()); let client = DriftClient::new(context, RpcClient::new(rpc_endpoint), wallet) .await .expect("initialized client"); From a05a01211082b8a55d615e1c0c85192ab75bc247 Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Mon, 25 Aug 2025 11:10:34 +0800 Subject: [PATCH 04/12] update redis consumer --- src/ws_server.rs | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/ws_server.rs b/src/ws_server.rs index fbdb8b0..1f83e38 100644 --- a/src/ws_server.rs +++ b/src/ws_server.rs @@ -299,7 +299,7 @@ impl WsConnection { market_index.unwrap() ); let deposit_topic = format!( - "swift_deposit_orders_{}_{}", + "swift_orders_deposit_{}_{}", market_type.as_str(), market_index.unwrap() ); @@ -318,6 +318,8 @@ impl WsConnection { if let Some(tx) = shared_state.subscriptions.get(&deposit_topic) { self.subscribed_topics .insert(deposit_topic.clone(), tx.subscribe()); + } else { + log::info!(target: "ws", "deposit topic not subbed"); } Ok(()) } else { @@ -777,13 +779,35 @@ async fn subscribe_redis_pubsub( let payload_str = std::str::from_utf8(payload) .context("Failed to convert payload to string") .unwrap(); - let order_metadata = - OrderMetadataAndMessage::decode(payload_str).context("Failed to decode order metadata"); + + let (order_metadata, deposit) = if !topic.contains("deposit") { + ( + OrderMetadataAndMessage::decode(payload_str) + .context("Failed to decode order metadata"), + None, + ) + } else { + #[derive(Deserialize)] + struct DepositOrder<'a> { + #[serde(borrow)] + deposit: &'a str, + #[serde(borrow)] + order: &'a str, + } + let value: DepositOrder = serde_json::from_str(payload_str).unwrap(); + ( + OrderMetadataAndMessage::decode(value.order) + .context("Failed to decode order metadata"), + Some(value.deposit), + ) + }; + if let Err(ref err) = order_metadata { log::error!(target: "ws", "Failed to decode order metadata: {err:?}, {order_metadata:?}"); continue; } let order_metadata = order_metadata.unwrap(); + server_params .metrics .kafka_message_forward_latency @@ -796,7 +820,11 @@ async fn subscribe_redis_pubsub( "received message: {message_uuid} after: {:?}", unix_now_ms() - order_metadata.ts, ); - let message = WsMessage::new(topic).set_order(&order_metadata); + let mut message = WsMessage::new(topic).set_order(&order_metadata); + // order has a deposit tx attached + if let Some(deposit) = deposit { + message = message.set_deposit(deposit); + } log::trace!("message jsonify(): {:?}", message); match tx.send(OrderNotification { json: message.jsonify(), From a884f98863c70081019e016c83cf643b88b744db Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Thu, 28 Aug 2025 12:02:39 +0800 Subject: [PATCH 05/12] depositToTrade changes: - sim deposit tx - require VersionedTransaction --- src/swift_server.rs | 34 ++++++++++++++++++++++++++++------ src/types/messages.rs | 6 +++--- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/swift_server.rs b/src/swift_server.rs index a7b4e35..b4de34b 100644 --- a/src/swift_server.rs +++ b/src/swift_server.rs @@ -374,11 +374,11 @@ pub async fn deposit_trade( State(server_params): State<&'static ServerParams>, Json(req): Json, ) -> impl axum::response::IntoResponse { - let min_trade_value = 100 * PRICE_PRECISION as u64; + let min_deposit_value = 100 * PRICE_PRECISION as u64; if req.deposit_tx.signatures.is_empty() - || req.deposit_tx.message.instructions.len() != 1 - || req.deposit_tx.verify().is_err() + || req.deposit_tx.message.instructions().len() != 1 + || req.deposit_tx.verify_with_results().iter().all(|x| *x) { return ( StatusCode::BAD_REQUEST, @@ -390,7 +390,7 @@ pub async fn deposit_trade( } // verify deposit ix exists and amount - let ix = &req.deposit_tx.message.instructions[0]; + let ix = &req.deposit_tx.message.instructions()[0]; if &ix.data[..8] == drift_idl::instructions::Deposit::DISCRIMINATOR { if let Ok(deposit_ix) = drift_idl::instructions::Deposit::deserialize(&mut &ix.data[8..]) { let spot_oracle = server_params @@ -398,12 +398,12 @@ pub async fn deposit_trade( .try_get_oracle_price_data_and_slot(MarketId::spot(deposit_ix.market_index)) .expect("got price"); let deposit_value = spot_oracle.data.price as u64 * deposit_ix.amount; - if deposit_value < min_trade_value { + if deposit_value < min_deposit_value { return ( StatusCode::BAD_REQUEST, Json(ProcessOrderResponse { message: "", - error: Some(format!("deposit size must be > ${min_trade_value}")), + error: Some(format!("deposit size must be > ${min_deposit_value}")), }), ); } @@ -426,6 +426,28 @@ pub async fn deposit_trade( ); } + match server_params + .drift + .simulate_tx(req.deposit_tx.message.clone()) + .await + { + Ok(res) => { + if let Some(err) = res.err { + log::info!(target: "server", "deposit sim failed: {err:?}"); + return ( + StatusCode::BAD_REQUEST, + Json(ProcessOrderResponse { + message: "", + error: Some("invalid deposit tx".into()), + }), + ); + } + } + Err(err) => { + log::info!(target: "server", "deposit sim network err: {err:?}"); + } + } + let context = RequestContext::from_incoming_message(&req.swift_order); let (status, resp) = match process_order(server_params, req.swift_order, &context).await { Ok(order_metadata) => { diff --git a/src/types/messages.rs b/src/types/messages.rs index 78e3029..8dfbd7d 100644 --- a/src/types/messages.rs +++ b/src/types/messages.rs @@ -13,12 +13,12 @@ use drift_rs::{ }; use ed25519_dalek::{PublicKey, Signature, Verifier}; use serde_json::json; -use solana_sdk::{pubkey::Pubkey, transaction::Transaction}; +use solana_sdk::{pubkey::Pubkey, transaction::VersionedTransaction}; #[derive(serde::Deserialize, Clone, Debug, PartialEq)] pub struct DepositAndPlaceRequest { #[serde(deserialize_with = "deser_transaction")] - pub deposit_tx: Transaction, + pub deposit_tx: VersionedTransaction, pub swift_order: IncomingSignedMessage, } @@ -289,7 +289,7 @@ where } /// Deserialize solana transaction -pub fn deser_transaction<'de, D>(deserializer: D) -> Result +pub fn deser_transaction<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de>, { From 559b07571bdab909afc4062a7c9dbc20b734df9f Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Thu, 28 Aug 2025 13:17:51 +0800 Subject: [PATCH 06/12] depositTrade: whitelist account init ixs --- src/swift_server.rs | 74 ++++++++++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/src/swift_server.rs b/src/swift_server.rs index b4de34b..4f553c3 100644 --- a/src/swift_server.rs +++ b/src/swift_server.rs @@ -2,7 +2,7 @@ use std::{ collections::HashSet, env, net::SocketAddr, - sync::{atomic::AtomicBool, Arc}, + sync::{atomic::AtomicBool, Arc, LazyLock}, time::{Duration, SystemTime}, }; @@ -370,6 +370,20 @@ pub async fn send_heartbeat(server_params: &'static ServerParams) { } } +static DEPOSIT_IX_WHITELIST: LazyLock> = LazyLock::new(|| { + HashSet::<[u8; 8]>::from_iter([ + drift_idl::instructions::Deposit::DISCRIMINATOR + .try_into() + .unwrap(), + drift_idl::instructions::EnableUserHighLeverageMode::DISCRIMINATOR + .try_into() + .unwrap(), + drift_idl::instructions::InitializeSignedMsgUserOrders::DISCRIMINATOR + .try_into() + .unwrap(), + ]) +}); + pub async fn deposit_trade( State(server_params): State<&'static ServerParams>, Json(req): Json, @@ -390,38 +404,56 @@ pub async fn deposit_trade( } // verify deposit ix exists and amount - let ix = &req.deposit_tx.message.instructions()[0]; - if &ix.data[..8] == drift_idl::instructions::Deposit::DISCRIMINATOR { - if let Ok(deposit_ix) = drift_idl::instructions::Deposit::deserialize(&mut &ix.data[8..]) { - let spot_oracle = server_params - .drift - .try_get_oracle_price_data_and_slot(MarketId::spot(deposit_ix.market_index)) - .expect("got price"); - let deposit_value = spot_oracle.data.price as u64 * deposit_ix.amount; - if deposit_value < min_deposit_value { + let mut has_deposit = false; + for ix in req.deposit_tx.message.instructions() { + let ix_disc = &ix.data[..8]; + if !DEPOSIT_IX_WHITELIST.contains(ix_disc) { + return ( + StatusCode::BAD_REQUEST, + Json(ProcessOrderResponse { + message: "", + error: Some("unsupported deposit ix".into()), + }), + ); + } + + if ix_disc == drift_idl::instructions::Deposit::DISCRIMINATOR { + has_deposit = true; + if let Ok(deposit_ix) = + drift_idl::instructions::Deposit::deserialize(&mut &ix.data[8..]) + { + let spot_oracle = server_params + .drift + .try_get_oracle_price_data_and_slot(MarketId::spot(deposit_ix.market_index)) + .expect("got price"); + let deposit_value = spot_oracle.data.price as u64 * deposit_ix.amount; + if deposit_value < min_deposit_value { + return ( + StatusCode::BAD_REQUEST, + Json(ProcessOrderResponse { + message: "", + error: Some(format!("deposit value must be > ${min_deposit_value}")), + }), + ); + } + } else { return ( StatusCode::BAD_REQUEST, Json(ProcessOrderResponse { message: "", - error: Some(format!("deposit size must be > ${min_deposit_value}")), + error: Some("invalid deposit ix encoding".into()), }), ); } - } else { - return ( - StatusCode::BAD_REQUEST, - Json(ProcessOrderResponse { - message: "", - error: Some("invalid deposit ix encoding".into()), - }), - ); } - } else { + } + + if !has_deposit { return ( StatusCode::BAD_REQUEST, Json(ProcessOrderResponse { message: "", - error: Some("invalid deposit ix".into()), + error: Some("missing deposit ix".into()), }), ); } From 408c054912dab88d5307f47559a9e55a4c393666 Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Fri, 29 Aug 2025 14:04:39 +0800 Subject: [PATCH 07/12] depositToTrade: allow all deposit txs (#87) * depositToTrade: allow all deposit txs * improve logs, fix sim --- Cargo.lock | 1 + Cargo.toml | 1 + src/swift_server.rs | 248 ++++++++++++++++++++++---------------------- 3 files changed, 126 insertions(+), 124 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2ade7f6..172e96c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7039,6 +7039,7 @@ dependencies = [ "redis", "serde", "serde_json", + "solana-account-decoder-client-types", "solana-rpc-client-api", "solana-sdk", "strum_macros", diff --git a/Cargo.toml b/Cargo.toml index 43e7e84..3975e4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ uuid = { version = "1.10.0", features = ["v4", "fast-rng", "macro-diagnostics", tower-http = { version = "0.6.2", features= ["cors"] } strum_macros = "0.27.0" zstd = "0.13.3" +solana-account-decoder-client-types = "2.3.0" [features] default = ["rdkafka/cmake-build"] diff --git a/src/swift_server.rs b/src/swift_server.rs index 4f553c3..8246fa5 100644 --- a/src/swift_server.rs +++ b/src/swift_server.rs @@ -2,7 +2,7 @@ use std::{ collections::HashSet, env, net::SocketAddr, - sync::{atomic::AtomicBool, Arc, LazyLock}, + sync::{atomic::AtomicBool, Arc}, time::{Duration, SystemTime}, }; @@ -27,7 +27,7 @@ use crate::{ metrics::{metrics_handler, MetricsServerParams, SwiftServerMetrics}, }, }; -use anchor_lang::{AnchorDeserialize, Discriminator}; +use anchor_lang::AnchorDeserialize; use axum::{ extract::State, http::{self, Method, StatusCode}, @@ -38,15 +38,14 @@ use base64::Engine; use dotenv::dotenv; use drift_rs::{ constants::high_leverage_mode_account, - drift_idl, event_subscriber::PubsubClient, - math::{account_list_builder::AccountsListBuilder, constants::PRICE_PRECISION}, + math::account_list_builder::AccountsListBuilder, swift_order_subscriber::{SignedMessageInfo, SignedOrderType}, types::{ accounts::{HighLeverageModeConfig, User}, errors::ErrorCode, CommitmentConfig, MarketId, MarketType, OrderParams, OrderType, PositionDirection, - ProgramError, SdkError, SignedMsgTriggerOrderParams, VersionedMessage, + ProgramError, SdkError, SdkResult, SignedMsgTriggerOrderParams, VersionedMessage, VersionedTransaction, }, Context, DriftClient, RpcClient, TransactionBuilder, Wallet, @@ -58,7 +57,12 @@ use rdkafka::{ util::Timeout, }; use redis::{aio::MultiplexedConnection, AsyncCommands}; -use solana_rpc_client_api::{client_error, config::RpcSimulateTransactionConfig}; +use solana_account_decoder_client_types::UiAccountEncoding; +use solana_rpc_client_api::{ + client_error, + config::{RpcSimulateTransactionAccountsConfig, RpcSimulateTransactionConfig}, + response::RpcSimulateTransactionResult, +}; use solana_sdk::{ clock::Slot, hash::Hash, @@ -123,7 +127,8 @@ pub async fn process_order_wrapper( let uuid = core::str::from_utf8(&uuid_raw).unwrap_or("00000000"); let context = RequestContext::from_incoming_message(&incoming_message); - let (status, resp) = match process_order(server_params, incoming_message, &context).await { + let (status, resp) = match process_order(server_params, incoming_message, false, &context).await + { Ok(order_metadata) => { let metrics_labels = &[ context.market_type, @@ -160,6 +165,7 @@ pub async fn process_order_wrapper( pub async fn process_order( server_params: &'static ServerParams, incoming_message: IncomingSignedMessage, + skip_sim: bool, context: &RequestContext, ) -> Result { let IncomingSignedMessage { @@ -258,44 +264,46 @@ pub async fn process_order( )); } - match server_params - .simulate_taker_order_rpc(&taker_pubkey, &order_params, delegate_signer, current_slot) - .await - { - Ok(sim_res) => { - server_params - .metrics - .rpc_simulation_status - .with_label_values(&[sim_res.as_str()]) - .inc(); - } - Err((status, sim_err_str, logs)) => { - server_params - .metrics - .rpc_simulation_status - .with_label_values(&["invalid"]) - .inc(); - log::warn!( - target: "server", - "{}: Order sim failed (taker: {taker_pubkey:?}, delegate: {delegate_signer:?}, market: {}-{}): {sim_err_str}. Logs: {logs:?}", - context.log_prefix, - order_params.market_type.as_str(), - order_params.market_index, - ); - log::warn!( - target: "server", - "{}: failed order params: {order_params:?}", - context.log_prefix, - ); - return Err(( - status, - ProcessOrderResponse { - message: PROCESS_ORDER_RESPONSE_ERROR_MSG_INVALID_ORDER, - error: Some(sim_err_str), - }, - )); - } - }; + if !skip_sim { + match server_params + .simulate_taker_order_rpc(&taker_pubkey, &order_params, delegate_signer, current_slot) + .await + { + Ok(sim_res) => { + server_params + .metrics + .rpc_simulation_status + .with_label_values(&[sim_res.as_str()]) + .inc(); + } + Err((status, sim_err_str, logs)) => { + server_params + .metrics + .rpc_simulation_status + .with_label_values(&["invalid"]) + .inc(); + log::warn!( + target: "server", + "{}: Order sim failed (taker: {taker_pubkey:?}, delegate: {delegate_signer:?}, market: {}-{}): {sim_err_str}. Logs: {logs:?}", + context.log_prefix, + order_params.market_type.as_str(), + order_params.market_index, + ); + log::warn!( + target: "server", + "{}: failed order params: {order_params:?}", + context.log_prefix, + ); + return Err(( + status, + ProcessOrderResponse { + message: PROCESS_ORDER_RESPONSE_ERROR_MSG_INVALID_ORDER, + error: Some(sim_err_str), + }, + )); + } + }; + } // If fat fingered order that requires sanitization, then just send the order let will_sanitize = server_params.simulate_will_auction_params_sanitize(&order_params); @@ -370,30 +378,27 @@ pub async fn send_heartbeat(server_params: &'static ServerParams) { } } -static DEPOSIT_IX_WHITELIST: LazyLock> = LazyLock::new(|| { - HashSet::<[u8; 8]>::from_iter([ - drift_idl::instructions::Deposit::DISCRIMINATOR - .try_into() - .unwrap(), - drift_idl::instructions::EnableUserHighLeverageMode::DISCRIMINATOR - .try_into() - .unwrap(), - drift_idl::instructions::InitializeSignedMsgUserOrders::DISCRIMINATOR - .try_into() - .unwrap(), - ]) -}); - pub async fn deposit_trade( State(server_params): State<&'static ServerParams>, Json(req): Json, ) -> impl axum::response::IntoResponse { - let min_deposit_value = 100 * PRICE_PRECISION as u64; + let signed_order_info = req + .swift_order + .message + .info(&req.swift_order.taker_authority); + + let uuid = core::str::from_utf8(&signed_order_info.uuid).unwrap_or(""); + log::info!( + target: "server", + "[{uuid}] depositToTrade request | authority={:?},subaccount={:?}", + req.swift_order.taker_authority, + req.swift_order.taker_pubkey + ); if req.deposit_tx.signatures.is_empty() - || req.deposit_tx.message.instructions().len() != 1 - || req.deposit_tx.verify_with_results().iter().all(|x| *x) + || req.deposit_tx.verify_with_results().iter().any(|x| !*x) { + log::info!(target: "server", "[{uuid}] invalid deposit tx"); return ( StatusCode::BAD_REQUEST, Json(ProcessOrderResponse { @@ -403,69 +408,18 @@ pub async fn deposit_trade( ); } - // verify deposit ix exists and amount - let mut has_deposit = false; - for ix in req.deposit_tx.message.instructions() { - let ix_disc = &ix.data[..8]; - if !DEPOSIT_IX_WHITELIST.contains(ix_disc) { - return ( - StatusCode::BAD_REQUEST, - Json(ProcessOrderResponse { - message: "", - error: Some("unsupported deposit ix".into()), - }), - ); - } - - if ix_disc == drift_idl::instructions::Deposit::DISCRIMINATOR { - has_deposit = true; - if let Ok(deposit_ix) = - drift_idl::instructions::Deposit::deserialize(&mut &ix.data[8..]) - { - let spot_oracle = server_params - .drift - .try_get_oracle_price_data_and_slot(MarketId::spot(deposit_ix.market_index)) - .expect("got price"); - let deposit_value = spot_oracle.data.price as u64 * deposit_ix.amount; - if deposit_value < min_deposit_value { - return ( - StatusCode::BAD_REQUEST, - Json(ProcessOrderResponse { - message: "", - error: Some(format!("deposit value must be > ${min_deposit_value}")), - }), - ); - } - } else { - return ( - StatusCode::BAD_REQUEST, - Json(ProcessOrderResponse { - message: "", - error: Some("invalid deposit ix encoding".into()), - }), - ); - } - } - } - - if !has_deposit { - return ( - StatusCode::BAD_REQUEST, - Json(ProcessOrderResponse { - message: "", - error: Some("missing deposit ix".into()), - }), - ); - } - - match server_params - .drift - .simulate_tx(req.deposit_tx.message.clone()) - .await + // ensure deposit tx is valid + let mut user_after_deposit = None; + match simulate_tx( + &server_params.drift, + req.deposit_tx.message.clone(), + &[req.swift_order.taker_pubkey], + ) + .await { Ok(res) => { if let Some(err) = res.err { - log::info!(target: "server", "deposit sim failed: {err:?}"); + log::info!(target: "server", "[{uuid}] deposit sim failed: {err:?}, logs: {:?}", res.logs); return ( StatusCode::BAD_REQUEST, Json(ProcessOrderResponse { @@ -474,14 +428,32 @@ pub async fn deposit_trade( }), ); } + if let Some(acc) = res.accounts { + user_after_deposit = + User::try_from_slice(&acc[0].as_ref().unwrap().data.decode().unwrap()).ok(); + } } Err(err) => { - log::info!(target: "server", "deposit sim network err: {err:?}"); + log::info!(target: "server", "[{uuid}] deposit sim network err: {uuid}: {err:?}"); + } + } + + if let Some(user) = user_after_deposit { + if !server_params.simulate_taker_order_local(&signed_order_info.order_params, &user) { + log::info!(target: "server", "[{uuid}] local order sim failed"); + return ( + StatusCode::BAD_REQUEST, + Json(ProcessOrderResponse { + message: "", + error: Some("invalid order".into()), + }), + ); } } let context = RequestContext::from_incoming_message(&req.swift_order); - let (status, resp) = match process_order(server_params, req.swift_order, &context).await { + // TODO: deposit tx should enable sim to pass, if it didn't before otherwise order is invalid + let (status, resp) = match process_order(server_params, req.swift_order, true, &context).await { Ok(order_metadata) => { let metrics_labels = &[ context.market_type, @@ -1422,6 +1394,34 @@ fn dump_account_state( log::debug!(target: "accountState", "{}", base64::engine::general_purpose::STANDARD.encode(compressed)); } +/// Simulate the tx on remote RPC node +pub async fn simulate_tx( + drift: &DriftClient, + tx: VersionedMessage, + accounts: &[Pubkey], +) -> SdkResult { + let response = drift + .rpc() + .simulate_transaction_with_config( + &VersionedTransaction { + message: tx, + // must provide a signature for the RPC call to work + signatures: vec![Signature::new_unique()], + }, + RpcSimulateTransactionConfig { + sig_verify: false, + replace_recent_blockhash: true, + accounts: Some(RpcSimulateTransactionAccountsConfig { + encoding: Some(UiAccountEncoding::Base64Zstd), + addresses: accounts.iter().map(|x| x.to_string()).collect(), + }), + ..Default::default() + }, + ) + .await; + response.map(|r| r.value).map_err(Into::into) +} + #[cfg(test)] mod tests { use std::collections::HashMap; From f0bcbaa9e78ed8d101543cc13fa4d5b1a3331432 Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Tue, 9 Sep 2025 16:10:02 +0800 Subject: [PATCH 08/12] require taker includes placeSignedTakerOderMsg ix for depoit+trade tx --- src/swift_server.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/swift_server.rs b/src/swift_server.rs index 8246fa5..5dd41ba 100644 --- a/src/swift_server.rs +++ b/src/swift_server.rs @@ -27,7 +27,7 @@ use crate::{ metrics::{metrics_handler, MetricsServerParams, SwiftServerMetrics}, }, }; -use anchor_lang::AnchorDeserialize; +use anchor_lang::{AnchorDeserialize, Discriminator}; use axum::{ extract::State, http::{self, Method, StatusCode}, @@ -38,6 +38,7 @@ use base64::Engine; use dotenv::dotenv; use drift_rs::{ constants::high_leverage_mode_account, + drift_idl, event_subscriber::PubsubClient, math::account_list_builder::AccountsListBuilder, swift_order_subscriber::{SignedMessageInfo, SignedOrderType}, @@ -408,6 +409,25 @@ pub async fn deposit_trade( ); } + // verify deposit ix exists and amount + let mut has_place_ix = false; + for ix in req.deposit_tx.message.instructions() { + if &ix.data[..8] == drift_idl::instructions::PlaceSignedMsgTakerOrder::DISCRIMINATOR { + has_place_ix = true; + } + } + + if !has_place_ix { + log::info!(target: "server", "[{uuid}] missing place order ix"); + return ( + StatusCode::BAD_REQUEST, + Json(ProcessOrderResponse { + message: "", + error: Some("missing placeSignedMsgTakerOrder ix".into()), + }), + ); + } + // ensure deposit tx is valid let mut user_after_deposit = None; match simulate_tx( From 5930f15266af7a01d03df29014c60b928aa477ec Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Wed, 10 Sep 2025 11:51:17 +0800 Subject: [PATCH 09/12] swift max margin ratio (#88) --- Cargo.lock | 99 +++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 2 +- src/swift_server.rs | 9 +++- src/types/messages.rs | 76 +++++++++++++++++++++++++++++---- src/types/types.rs | 2 +- 5 files changed, 172 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 172e96c..33f8aec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1974,7 +1974,7 @@ checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" [[package]] name = "drift-idl-gen" version = "0.2.0" -source = "git+https://github.com/drift-labs/drift-rs.git?rev=004cf7c#004cf7c9ffbfa07e4455bd96e41b23a70ec48a9c" +source = "git+https://github.com/drift-labs/drift-rs.git?rev=cbc476e2#cbc476e2d2017b0b3fa6f06b6fc763433398bc99" dependencies = [ "proc-macro2", "quote", @@ -1987,7 +1987,7 @@ dependencies = [ [[package]] name = "drift-pubsub-client" version = "0.1.1" -source = "git+https://github.com/drift-labs/drift-rs.git?rev=004cf7c#004cf7c9ffbfa07e4455bd96e41b23a70ec48a9c" +source = "git+https://github.com/drift-labs/drift-rs.git?rev=cbc476e2#cbc476e2d2017b0b3fa6f06b6fc763433398bc99" dependencies = [ "futures-util", "gjson", @@ -2007,7 +2007,7 @@ dependencies = [ [[package]] name = "drift-rs" version = "1.0.0-alpha.16" -source = "git+https://github.com/drift-labs/drift-rs.git?rev=004cf7c#004cf7c9ffbfa07e4455bd96e41b23a70ec48a9c" +source = "git+https://github.com/drift-labs/drift-rs.git?rev=cbc476e2#cbc476e2d2017b0b3fa6f06b6fc763433398bc99" dependencies = [ "abi_stable", "ahash 0.8.12", @@ -2024,6 +2024,7 @@ dependencies = [ "hex", "jupiter-swap-api-client", "log", + "pythnet-sdk", "regex", "serde", "serde_json", @@ -2033,7 +2034,6 @@ dependencies = [ "solana-sdk", "solana-transaction-status", "spl-associated-token-account", - "spl-token-2022", "thiserror 1.0.69", "tokio", "tokio-stream", @@ -2168,6 +2168,15 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "fast-math" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2465292146cdfc2011350fe3b1c616ac83cf0faeedb33463ba1c332ed8948d66" +dependencies = [ + "ieee754", +] + [[package]] name = "faster-hex" version = "0.10.0" @@ -2593,6 +2602,9 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "hmac" @@ -2956,6 +2968,12 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "ieee754" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9007da9cacbd3e6343da136e98b0d2df013f553d35bdec8b518f07bea768e19c" + [[package]] name = "indexmap" version = "2.10.0" @@ -3454,6 +3472,20 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -3464,6 +3496,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -3490,6 +3531,28 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -3928,6 +3991,25 @@ dependencies = [ "pulldown-cmark", ] +[[package]] +name = "pythnet-sdk" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498d20fd330277697aaee92f341bdabdb4695b10e05f054157a18ad8b7746a17" +dependencies = [ + "bincode", + "borsh 0.10.4", + "bytemuck", + "byteorder", + "fast-math", + "hex", + "rustc_version", + "serde", + "sha3", + "slow_primes", + "thiserror 1.0.69", +] + [[package]] name = "qstring" version = "0.7.2" @@ -4855,6 +4937,15 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +[[package]] +name = "slow_primes" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58267dd2fbaa6dceecba9e3e106d2d90a2b02497c0e8b01b8759beccf5113938" +dependencies = [ + "num", +] + [[package]] name = "smallvec" version = "1.15.1" diff --git a/Cargo.toml b/Cargo.toml index 3975e4a..f21a859 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ bincode = "1" clap = { version = "4.0", features = ["derive"] } dashmap = "6.1.0" dotenv = "0.15.0" -drift-rs = { git = "https://github.com/drift-labs/drift-rs.git" , rev = "004cf7c" } +drift-rs = { git = "https://github.com/drift-labs/drift-rs.git" , rev = "cbc476e2" } ed25519-dalek = "1.0.1" env_logger = "0.11" faster-hex = "0.10.0" diff --git a/src/swift_server.rs b/src/swift_server.rs index 5dd41ba..5a283e7 100644 --- a/src/swift_server.rs +++ b/src/swift_server.rs @@ -124,7 +124,7 @@ pub async fn process_order_wrapper( State(server_params): State<&'static ServerParams>, Json(incoming_message): Json, ) -> impl axum::response::IntoResponse { - let uuid_raw = extract_uuid(&incoming_message.message); + let uuid_raw = extract_uuid(&incoming_message.order()); let uuid = core::str::from_utf8(&uuid_raw).unwrap_or("00000000"); let context = RequestContext::from_incoming_message(&incoming_message); @@ -385,7 +385,7 @@ pub async fn deposit_trade( ) -> impl axum::response::IntoResponse { let signed_order_info = req .swift_order - .message + .order() .info(&req.swift_order.taker_authority); let uuid = core::str::from_utf8(&signed_order_info.uuid).unwrap_or(""); @@ -1638,6 +1638,7 @@ mod tests { slot: current_slot, stop_loss_order_params: None, take_profit_order_params: None, + max_margin_ratio: None, }); let result = extract_signed_message_info(&delegated_msg, &taker_authority, current_slot); @@ -1666,6 +1667,7 @@ mod tests { ..Default::default() }), take_profit_order_params: None, + max_margin_ratio: None, }); let result = extract_signed_message_info(&delegated_msg, &taker_authority, current_slot); @@ -1698,6 +1700,7 @@ mod tests { slot: current_slot, stop_loss_order_params: None, take_profit_order_params: None, + max_margin_ratio: None, }); let result = extract_signed_message_info(&authority_msg, &taker_authority, current_slot); @@ -1728,6 +1731,7 @@ mod tests { base_asset_amount: 0, ..Default::default() }), + max_margin_ratio: None, }); let result = extract_signed_message_info(&authority_msg, &taker_authority, current_slot); @@ -1759,6 +1763,7 @@ mod tests { slot: current_slot - 501, // Slot too old stop_loss_order_params: None, take_profit_order_params: None, + max_margin_ratio: None, }); let result = extract_signed_message_info(&delegated_msg, &taker_authority, current_slot); diff --git a/src/types/messages.rs b/src/types/messages.rs index 8dfbd7d..84db133 100644 --- a/src/types/messages.rs +++ b/src/types/messages.rs @@ -8,13 +8,63 @@ use anyhow::{Context, Result}; use arrayvec::ArrayVec; use base64::Engine; use drift_rs::{ - swift_order_subscriber::{deser_signed_msg_type, SignedOrderType}, - types::MarketType, + swift_order_subscriber::{SignedOrderType, SWIFT_DELEGATE_MSG_PREFIX}, + types::{MarketType, SignedMsgOrderParamsDelegateMessage}, }; use ed25519_dalek::{PublicKey, Signature, Verifier}; use serde_json::json; use solana_sdk::{pubkey::Pubkey, transaction::VersionedTransaction}; +#[derive(Clone, Debug, PartialEq)] +pub struct SignedOrderTypeWithLen { + /// length of the signed order when borsh encoded + pub signed_len: usize, + /// signed order message + pub order: SignedOrderType, +} + +/// Deserialize hex-ified, borsh bytes as a `SignedOrderType` +pub fn deser_signed_msg_type_with_len<'de, D>( + deserializer: D, +) -> Result +where + D: serde::Deserializer<'de>, +{ + let payload: &str = serde::Deserialize::deserialize(deserializer)?; + if payload.len() % 2 != 0 { + return Err(serde::de::Error::custom("Hex string length must be even")); + } + + // decode expecting the largest possible variant + let borsh_len = payload.len() / 2; + if (borsh_len > SignedMsgOrderParamsDelegateMessage::INIT_SPACE + 8) || payload.is_empty() { + return Err(serde::de::Error::custom("invalid signed message hex")); + } + + // messages from older clients that are too short will naturally pad to the largest message size + let mut borsh_buf = [0u8; SignedMsgOrderParamsDelegateMessage::INIT_SPACE + 8]; + faster_hex::hex_decode(payload.as_bytes(), &mut borsh_buf[..borsh_len]) + .map_err(serde::de::Error::custom)?; + + // this is basically the same as if we derived AnchorDeserialize on `SignedOrderType` _expect_ it does not + // add a u8 to distinguish the enum + if borsh_buf[..8] == SWIFT_DELEGATE_MSG_PREFIX { + AnchorDeserialize::deserialize(&mut &borsh_buf[8..]) + .map(|o| SignedOrderTypeWithLen { + signed_len: borsh_len, + order: SignedOrderType::Delegated(o), + }) + .map_err(serde::de::Error::custom) + } else { + AnchorDeserialize::deserialize(&mut &borsh_buf[8..]) + .map(|o| SignedOrderTypeWithLen { + signed_len: borsh_len, + order: SignedOrderType::Authority(o), + }) + .map_err(serde::de::Error::custom) + } +} + #[derive(serde::Deserialize, Clone, Debug, PartialEq)] pub struct DepositAndPlaceRequest { #[serde(deserialize_with = "deser_transaction")] @@ -28,8 +78,8 @@ pub struct IncomingSignedMessage { pub taker_pubkey: Pubkey, #[serde(deserialize_with = "deser_signature")] pub signature: Signature, - #[serde(deserialize_with = "deser_signed_msg_type")] - pub message: SignedOrderType, + #[serde(deserialize_with = "deser_signed_msg_type_with_len")] + pub message: SignedOrderTypeWithLen, #[serde(deserialize_with = "deser_pubkey", default = "Default::default")] pub signing_authority: Pubkey, #[serde(deserialize_with = "deser_pubkey", default = "Default::default")] @@ -37,6 +87,10 @@ pub struct IncomingSignedMessage { } impl IncomingSignedMessage { + /// Return the swift signed order + pub fn order(&self) -> SignedOrderType { + self.message.order + } /// Verify taker signature against hex encoded `message` pub fn verify_signature(&self) -> Result<()> { let pubkey = if self.signing_authority != Pubkey::default() { @@ -48,7 +102,9 @@ impl IncomingSignedMessage { }?; // client hex encodes msg before signing so use that as comparison - let msg_data = &self.message.to_borsh(); + // since we allow older clients with potentially shorter messages than the current IDL + // need to truncate the message to the size the client signed (exclude default padded bytes) + let msg_data = &self.order().to_borsh()[..self.message.signed_len]; let mut hex_bytes = vec![0; msg_data.len() * 2]; // 2 hex bytes per msg byte let _ = faster_hex::hex_encode(msg_data, &mut hex_bytes).expect("hexified"); @@ -58,7 +114,7 @@ impl IncomingSignedMessage { } pub fn verify_and_get_signed_message(&self) -> Result<&SignedOrderType> { self.verify_signature()?; - Ok(&self.message) + Ok(&self.message.order) } } @@ -327,7 +383,7 @@ mod tests { actual.signing_authority == solana_sdk::pubkey!("GiMXQkJXLVjScmQDkoLJShBJpTh9SDPvT2AZQq8NyEBf") ); - if let SignedOrderType::Delegated(signed_msg) = actual.message { + if let SignedOrderType::Delegated(signed_msg) = actual.order() { let expected = SignedMsgOrderParamsDelegateMessage { signed_msg_order_params: OrderParams { order_type: OrderType::Market, @@ -353,6 +409,7 @@ mod tests { uuid: [115, 56, 108, 117, 74, 76, 90, 101], take_profit_order_params: None, stop_loss_order_params: None, + max_margin_ratio: None, }; assert_eq!(signed_msg, expected); } else { @@ -405,6 +462,8 @@ mod tests { #[test] fn deserialize_incoming_signed_message_with_signing_authority() { + // NB: this message verifies backwards compatibility with older clients + // it was not built/signed with the max_margin_ratio field or newer additions so verifies the padding mechanisms let message = r#"{ "market_index": 2, "market_type": "perp", @@ -422,7 +481,7 @@ mod tests { == solana_sdk::pubkey!("4rmhwytmKH1XsgGAUyUUH7U64HS5FtT6gM8HGKAfwcFE") ); - if let SignedOrderType::Authority(signed_msg) = actual.message { + if let SignedOrderType::Authority(signed_msg) = actual.order() { let expected = SignedMsgOrderParamsMessage { signed_msg_order_params: OrderParams { order_type: OrderType::Market, @@ -448,6 +507,7 @@ mod tests { uuid: [115, 56, 108, 117, 74, 76, 90, 101], take_profit_order_params: None, stop_loss_order_params: None, + max_margin_ratio: None, }; assert_eq!(signed_msg, expected); } else { diff --git a/src/types/types.rs b/src/types/types.rs index ad64722..dc856ed 100644 --- a/src/types/types.rs +++ b/src/types/types.rs @@ -48,7 +48,7 @@ pub struct RequestContext { impl RequestContext { pub fn from_incoming_message(msg: &IncomingSignedMessage) -> Self { let recv_ts = unix_now_ms(); - let info = msg.message.info(&msg.taker_authority); + let info = msg.order().info(&msg.taker_authority); Self { market_index: info.order_params.market_index, From 4d1068e66df6d23f39d0119dfa1c3f957cb52f6d Mon Sep 17 00:00:00 2001 From: wphan Date: Wed, 17 Sep 2025 15:56:55 -0700 Subject: [PATCH 10/12] add tests for deser_signed_msg_type_with_len on old messages --- README.md | 46 ++++++++++++++++-- src/types/messages.rs | 109 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index df397a6..83531ad 100644 --- a/README.md +++ b/README.md @@ -31,16 +31,56 @@ graph TD C -->|Place taker + fill txs| I ``` -## Build +## Dependency setup ensure an x86_64 toolchain is configured for building `swift-server` ```shell -rustup install 1.83.0-x86_64-apple-darwin +rustup install 1.87.0-x86_64-apple-darwin # run inside swift-server directory -rustup override set 1.83.0-x86_64-apple-darwin +rustup override set 1.87.0-x86_64-apple-darwin +``` + +if on a newer ARM mac, need to install x86 libraries to run tests and build locally +```shell +# install x86-64 brew +arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + +# install x86 deps +arch -x86_64 /usr/local/bin/brew install librdkafka cyrus-sasl pkg-config zstd lz4 openssl@3 +``` + +run tests: +```shell +# Ensure Intel Homebrew is used first +export PATH="/usr/local/bin:/usr/local/sbin:$PATH" + +# Make sure no ARM include/lib paths are leaking in +unset CPATH CFLAGS CXXFLAGS CPPFLAGS SDKROOT LIBRARY_PATH DYLD_LIBRARY_PATH + +# Run tests under Rosetta with dynamic linking to Intel librdkafka +arch -x86_64 env \ + RUSTUP_TOOLCHAIN=1.87.0-x86_64-apple-darwin \ + RDKAFKA_BUILD=0 \ + RDKAFKA_STATIC=0 \ + PKG_CONFIG_PATH="/usr/local/lib/pkgconfig:/usr/local/opt/openssl@3/lib/pkgconfig" \ + DYLD_FALLBACK_LIBRARY_PATH="/usr/local/lib:/usr/local/opt/librdkafka/lib:/usr/local/opt/cyrus-sasl/lib" \ + cargo test + +# Run a specific test +arch -x86_64 env \ + RUSTUP_TOOLCHAIN=1.87.0-x86_64-apple-darwin \ + RDKAFKA_BUILD=0 \ + RDKAFKA_STATIC=0 \ + PKG_CONFIG_PATH="/usr/local/lib/pkgconfig:/usr/local/opt/openssl@3/lib/pkgconfig" \ + DYLD_FALLBACK_LIBRARY_PATH="/usr/local/lib:/usr/local/opt/librdkafka/lib:/usr/local/opt/cyrus-sasl/lib" \ + cargo test \ + types::messages::tests -- --nocapture ``` + +## Build + ```shell cargo build --release ``` diff --git a/src/types/messages.rs b/src/types/messages.rs index 84db133..6ea655a 100644 --- a/src/types/messages.rs +++ b/src/types/messages.rs @@ -361,6 +361,7 @@ mod tests { use drift_rs::types::{ OrderParams, OrderTriggerCondition, OrderType, PositionDirection, PostOnlyParam, SignedMsgOrderParamsDelegateMessage, SignedMsgOrderParamsMessage, + SignedMsgTriggerOrderParams, }; use nanoid::nanoid; @@ -598,4 +599,112 @@ mod tests { assert_eq!(order_metadata_json["will_sanitize"], false,); } + + #[test] + fn deser_signed_msg_type_with_len_from_raw_bytes_v0() { + // v0 message, before we started adding fields to the message + let payload: Vec = vec![ + 200, 213, 166, 94, 34, 52, 245, 93, 0, 1, 0, 3, 0, 96, 254, 205, 0, 0, 0, 0, 64, 85, + 32, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 10, 1, 128, 133, 181, 13, 0, 0, 0, 0, + 1, 64, 85, 32, 14, 0, 0, 0, 0, 2, 0, 41, 9, 0, 0, 0, 0, 0, 0, 67, 82, 79, 51, 105, 114, + 71, 49, 1, 0, 28, 78, 14, 0, 0, 0, 0, 0, 96, 254, 205, 0, 0, 0, 0, 1, 64, 58, 105, 13, + 0, 0, 0, 0, 0, 96, 254, 205, 0, 0, 0, 0, + ]; + + let hex = faster_hex::hex_string(&payload); + + #[derive(serde::Deserialize)] + struct Wrapper { + #[serde(deserialize_with = "deser_signed_msg_type_with_len")] + message: SignedOrderTypeWithLen, + } + + let json = format!(r#"{{"message":"{}"}}"#, hex); + let wrapper: Wrapper = serde_json::from_str(&json).expect("deserializes"); + + assert_eq!(wrapper.message.signed_len, payload.len()); + + match wrapper.message.order { + SignedOrderType::Authority(m) => { + assert_eq!(m.signed_msg_order_params.auction_duration, Some(10)); + assert_eq!( + m.signed_msg_order_params.auction_start_price, + Some(230000000) + ); + assert_eq!(m.signed_msg_order_params.auction_end_price, Some(237000000)); + assert_eq!(m.sub_account_id, 2); + assert_eq!(m.slot, 2345); + assert_eq!( + m.take_profit_order_params, + Some(SignedMsgTriggerOrderParams { + trigger_price: 240000000, + base_asset_amount: 3456000000, + }) + ); + assert_eq!( + m.stop_loss_order_params, + Some(SignedMsgTriggerOrderParams { + trigger_price: 225000000, + base_asset_amount: 3456000000, + }) + ); + assert_eq!(m.max_margin_ratio, None); + } + SignedOrderType::Delegated(_) => panic!("expected Authority variant"), + } + } + + #[test] + fn deser_signed_msg_type_with_len_from_raw_bytes_v1() { + // v1 message, added max_margin_ratio field + let payload: Vec = vec![ + 200, 213, 166, 94, 34, 52, 245, 93, 0, 1, 0, 3, 0, 96, 254, 205, 0, 0, 0, 0, 64, 85, + 32, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 10, 1, 128, 133, 181, 13, 0, 0, 0, 0, + 1, 64, 85, 32, 14, 0, 0, 0, 0, 2, 0, 41, 9, 0, 0, 0, 0, 0, 0, 67, 82, 79, 51, 105, 114, + 71, 49, 1, 0, 28, 78, 14, 0, 0, 0, 0, 0, 96, 254, 205, 0, 0, 0, 0, 1, 64, 58, 105, 13, + 0, 0, 0, 0, 0, 96, 254, 205, 0, 0, 0, 0, 1, 255, 255, + ]; + + let hex = faster_hex::hex_string(&payload); + + #[derive(serde::Deserialize)] + struct Wrapper { + #[serde(deserialize_with = "deser_signed_msg_type_with_len")] + message: SignedOrderTypeWithLen, + } + + let json = format!(r#"{{"message":"{}"}}"#, hex); + let wrapper: Wrapper = serde_json::from_str(&json).expect("deserializes"); + + assert_eq!(wrapper.message.signed_len, payload.len()); + + match wrapper.message.order { + SignedOrderType::Authority(m) => { + assert_eq!(m.signed_msg_order_params.auction_duration, Some(10)); + assert_eq!( + m.signed_msg_order_params.auction_start_price, + Some(230000000) + ); + assert_eq!(m.signed_msg_order_params.auction_end_price, Some(237000000)); + assert_eq!(m.sub_account_id, 2); + assert_eq!(m.slot, 2345); + assert_eq!( + m.take_profit_order_params, + Some(SignedMsgTriggerOrderParams { + trigger_price: 240000000, + base_asset_amount: 3456000000, + }) + ); + assert_eq!( + m.stop_loss_order_params, + Some(SignedMsgTriggerOrderParams { + trigger_price: 225000000, + base_asset_amount: 3456000000, + }) + ); + assert_eq!(m.max_margin_ratio, Some(65535)); + } + SignedOrderType::Delegated(_) => panic!("expected Authority variant"), + } + } } From e834a8c4370a3b13e1eb00d3abdad219d8cd9932 Mon Sep 17 00:00:00 2001 From: moosecat Date: Thu, 18 Sep 2025 20:33:47 -0700 Subject: [PATCH 11/12] add max margin ratio when simming (#90) * add max margin ratio when simming * check max_margin for deposit+trade --------- Co-authored-by: jordy25519 --- Cargo.lock | 6 +-- Cargo.toml | 2 +- src/swift_server.rs | 119 +++++++++++++++++++++++++++++++------------- 3 files changed, 88 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 33f8aec..17a4882 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1974,7 +1974,7 @@ checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" [[package]] name = "drift-idl-gen" version = "0.2.0" -source = "git+https://github.com/drift-labs/drift-rs.git?rev=cbc476e2#cbc476e2d2017b0b3fa6f06b6fc763433398bc99" +source = "git+https://github.com/drift-labs/drift-rs.git?rev=cd60371#cd60371cbff430140c2577796f00bb6bc13be857" dependencies = [ "proc-macro2", "quote", @@ -1987,7 +1987,7 @@ dependencies = [ [[package]] name = "drift-pubsub-client" version = "0.1.1" -source = "git+https://github.com/drift-labs/drift-rs.git?rev=cbc476e2#cbc476e2d2017b0b3fa6f06b6fc763433398bc99" +source = "git+https://github.com/drift-labs/drift-rs.git?rev=cd60371#cd60371cbff430140c2577796f00bb6bc13be857" dependencies = [ "futures-util", "gjson", @@ -2007,7 +2007,7 @@ dependencies = [ [[package]] name = "drift-rs" version = "1.0.0-alpha.16" -source = "git+https://github.com/drift-labs/drift-rs.git?rev=cbc476e2#cbc476e2d2017b0b3fa6f06b6fc763433398bc99" +source = "git+https://github.com/drift-labs/drift-rs.git?rev=cd60371#cd60371cbff430140c2577796f00bb6bc13be857" dependencies = [ "abi_stable", "ahash 0.8.12", diff --git a/Cargo.toml b/Cargo.toml index f21a859..749c591 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ bincode = "1" clap = { version = "4.0", features = ["derive"] } dashmap = "6.1.0" dotenv = "0.15.0" -drift-rs = { git = "https://github.com/drift-labs/drift-rs.git" , rev = "cbc476e2" } +drift-rs = { git = "https://github.com/drift-labs/drift-rs.git" , rev = "cd60371" } ed25519-dalek = "1.0.1" env_logger = "0.11" faster-hex = "0.10.0" diff --git a/src/swift_server.rs b/src/swift_server.rs index 5a283e7..81206da 100644 --- a/src/swift_server.rs +++ b/src/swift_server.rs @@ -236,12 +236,15 @@ pub async fn process_order( }; let current_slot = server_params.slot_subscriber.current_slot(); - let SignedMessageInfo { - slot: _taker_slot, - order_params, - taker_pubkey, - uuid, - } = extract_signed_message_info(signed_msg, &taker_authority, current_slot)?; + let ( + SignedMessageInfo { + slot: _taker_slot, + order_params, + taker_pubkey, + uuid, + }, + max_margin_ratio, + ) = extract_signed_message_info(signed_msg, &taker_authority, current_slot)?; // check the order is valid for execution by program let market = server_params @@ -267,7 +270,13 @@ pub async fn process_order( if !skip_sim { match server_params - .simulate_taker_order_rpc(&taker_pubkey, &order_params, delegate_signer, current_slot) + .simulate_taker_order_rpc( + &taker_pubkey, + &order_params, + delegate_signer, + current_slot, + max_margin_ratio, + ) .await { Ok(sim_res) => { @@ -387,6 +396,16 @@ pub async fn deposit_trade( .swift_order .order() .info(&req.swift_order.taker_authority); + let current_slot = server_params.slot_subscriber.current_slot(); + + let max_margin_ratio = match extract_signed_message_info( + &req.swift_order.order(), + &req.swift_order.taker_authority, + current_slot, + ) { + Ok((_info, max_margin_ratio)) => max_margin_ratio, + Err((_status, err)) => return (StatusCode::BAD_REQUEST, Json(err)), + }; let uuid = core::str::from_utf8(&signed_order_info.uuid).unwrap_or(""); log::info!( @@ -458,8 +477,12 @@ pub async fn deposit_trade( } } - if let Some(user) = user_after_deposit { - if !server_params.simulate_taker_order_local(&signed_order_info.order_params, &user) { + if let Some(mut user) = user_after_deposit { + if !server_params.simulate_taker_order_local( + &signed_order_info.order_params, + &mut user, + max_margin_ratio, + ) { log::info!(target: "server", "[{uuid}] local order sim failed"); return ( StatusCode::BAD_REQUEST, @@ -879,7 +902,8 @@ impl ServerParams { fn simulate_taker_order_local( &self, order_params: &OrderParams, - user: &drift_rs::types::accounts::User, + user: &mut drift_rs::types::accounts::User, + max_margin_ratio: Option, ) -> bool { let state = match self.drift.state_account() { Ok(s) => s, @@ -917,6 +941,7 @@ impl ServerParams { &state, order_params, Some(&mut hlm), + max_margin_ratio, ) { Ok(_) => true, Err(err) => { @@ -932,6 +957,7 @@ impl ServerParams { taker_order_params: &OrderParams, delegate_signer: Option<&Pubkey>, slot: Slot, + max_margin_ratio: Option, ) -> Result>)> { let mut sim_result = SimulationStatus::Disabled; @@ -951,7 +977,7 @@ impl ServerParams { } let user_result = user_with_timeout.unwrap(); - let user = user_result.map_err(|err| { + let mut user = user_result.map_err(|err| { ( axum::http::StatusCode::NOT_FOUND, format!("unable to fetch user: {err:?}"), @@ -983,22 +1009,27 @@ impl ServerParams { let t1 = SystemTime::now(); log::info!(target: "sim", "fetch user: {:?}", SystemTime::now().duration_since(t0)); - if self.simulate_taker_order_local(taker_order_params, &user) { + if self.simulate_taker_order_local(taker_order_params, &mut user, max_margin_ratio) { sim_result = SimulationStatus::Success; log::info!(target: "sim", "simulate tx (local): {:?}", SystemTime::now().duration_since(t1)); return Ok(sim_result); } // fallback to network sim - let message = TransactionBuilder::new( + let mut tx = TransactionBuilder::new( self.drift.program_data(), *taker_subaccount_pubkey, std::borrow::Cow::Owned(user), false, ) - .with_priority_fee(5_000, Some(1_400_000)) - .place_orders(vec![*taker_order_params]) - .build(); + .with_priority_fee(5_000, Some(1_400_000)); + if let Some(margin_ratio) = max_margin_ratio { + tx = tx.update_user_perp_position_custom_margin_ratio( + taker_order_params.market_index, + margin_ratio, + ); + } + let message = tx.place_orders(vec![*taker_order_params]).build(); let simulate_result_with_timeout = tokio::time::timeout( self.config.simulation_timeout, @@ -1309,7 +1340,7 @@ fn extract_signed_message_info( signed_msg: &SignedOrderType, taker_authority: &Pubkey, current_slot: Slot, -) -> Result { +) -> Result<(SignedMessageInfo, Option), (axum::http::StatusCode, ProcessOrderResponse)> { match signed_msg { SignedOrderType::Delegated(x) => { validate_order( @@ -1318,12 +1349,15 @@ fn extract_signed_message_info( x.slot, current_slot, )?; - Ok(SignedMessageInfo { - taker_pubkey: x.taker_pubkey, - order_params: x.signed_msg_order_params, - uuid: x.uuid, - slot: x.slot, - }) + Ok(( + SignedMessageInfo { + taker_pubkey: x.taker_pubkey, + order_params: x.signed_msg_order_params, + uuid: x.uuid, + slot: x.slot, + }, + x.max_margin_ratio, + )) } SignedOrderType::Authority(x) => { validate_order( @@ -1332,12 +1366,15 @@ fn extract_signed_message_info( x.slot, current_slot, )?; - Ok(SignedMessageInfo { - taker_pubkey: Wallet::derive_user_account(taker_authority, x.sub_account_id), - order_params: x.signed_msg_order_params, - uuid: x.uuid, - slot: x.slot, - }) + Ok(( + SignedMessageInfo { + taker_pubkey: Wallet::derive_user_account(taker_authority, x.sub_account_id), + order_params: x.signed_msg_order_params, + uuid: x.uuid, + slot: x.slot, + }, + x.max_margin_ratio, + )) } } } @@ -1642,7 +1679,7 @@ mod tests { }); let result = extract_signed_message_info(&delegated_msg, &taker_authority, current_slot); - assert!(result.is_ok_and(|info| { + assert!(result.is_ok_and(|(info, _)| { info.slot == current_slot && info.order_params.base_asset_amount == LAMPORTS_PER_SOL && info.order_params.order_type == OrderType::Market @@ -1704,7 +1741,7 @@ mod tests { }); let result = extract_signed_message_info(&authority_msg, &taker_authority, current_slot); - assert!(result.is_ok_and(|info| { + assert!(result.is_ok_and(|(info, _margin_ratio)| { info.slot == current_slot && info.order_params.base_asset_amount == LAMPORTS_PER_SOL && info.order_params.order_type == OrderType::Market @@ -1789,8 +1826,8 @@ mod tests { .await .unwrap(); - let taker_pubkey = Keypair::new().pubkey(); - let taker_pubkey2 = Keypair::new().pubkey(); + let mut taker_pubkey = Keypair::new().pubkey(); + let mut taker_pubkey2 = Keypair::new().pubkey(); let delegate_pubkey = Keypair::new().pubkey(); let users: HashMap = [ ( @@ -1839,7 +1876,13 @@ mod tests { // Test let result = server_params - .simulate_taker_order_rpc(&taker_pubkey, &order_params, Some(&delegate_pubkey), 1_000) + .simulate_taker_order_rpc( + &mut taker_pubkey, + &order_params, + Some(&delegate_pubkey), + 1_000, + None, + ) .await; assert!(result.is_err_and(|(status, msg, _)| { dbg!(&msg); @@ -1848,7 +1891,13 @@ mod tests { })); let result = server_params - .simulate_taker_order_rpc(&taker_pubkey2, &order_params, Some(&delegate_pubkey), 1_000) + .simulate_taker_order_rpc( + &mut taker_pubkey2, + &order_params, + Some(&delegate_pubkey), + 1_000, + None, + ) .await; // it fails later at remote sim since the account is not a real drift account assert!(result.is_err_and(|(status, msg, _)| { From 17e93d730707e118c12f1a340c5871018aa736f5 Mon Sep 17 00:00:00 2001 From: wphan Date: Fri, 19 Sep 2025 10:04:29 -0700 Subject: [PATCH 12/12] allow 30 slot stale tx sim --- src/swift_server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/swift_server.rs b/src/swift_server.rs index 81206da..73533a7 100644 --- a/src/swift_server.rs +++ b/src/swift_server.rs @@ -1043,7 +1043,7 @@ impl ServerParams { sig_verify: false, replace_recent_blockhash: true, commitment: Some(CommitmentConfig::confirmed()), - min_context_slot: Some(slot), + min_context_slot: Some(slot - 30), // allow tx sim on up to 30 slots stale context ..Default::default() }, ),