From ecb548a3e6f99769544cde2320a32882b06d9d3d Mon Sep 17 00:00:00 2001 From: Beast Date: Mon, 27 Apr 2026 20:45:39 +0800 Subject: [PATCH 1/4] feat: add exchange rate api, update dependencies --- Cargo.lock | 1087 +++++++++++++++++++------ Cargo.toml | 10 +- config/default.toml | 4 + config/example.toml | 4 + config/test.toml | 3 + src/config.rs | 10 + src/errors.rs | 16 +- src/handlers/auth.rs | 7 +- src/handlers/exchange_rate.rs | 12 + src/handlers/mod.rs | 1 + src/http_server.rs | 3 + src/routes/exchange_rate.rs | 7 + src/routes/mod.rs | 6 +- src/services/exchange_rate_service.rs | 197 +++++ src/services/mod.rs | 1 + src/services/signature_service.rs | 7 +- src/utils/generate_referral_code.rs | 5 +- src/utils/test_app_state.rs | 7 +- 18 files changed, 1134 insertions(+), 253 deletions(-) create mode 100644 src/handlers/exchange_rate.rs create mode 100644 src/routes/exchange_rate.rs create mode 100644 src/services/exchange_rate_service.rs diff --git a/Cargo.lock b/Cargo.lock index 2bec34b..f749867 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,7 +45,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -69,6 +69,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", + "const-random", "getrandom 0.3.4", "once_cell", "version_check", @@ -122,7 +123,7 @@ checksum = "f4e9e31d834fe25fe991b8884e4b9f0e59db4a97d86e05d1464d6899c013cd62" dependencies = [ "alloy-primitives", "num_enum", - "strum", + "strum 0.27.2", ] [[package]] @@ -149,7 +150,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -185,7 +186,7 @@ dependencies = [ "futures", "futures-util", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] @@ -228,7 +229,7 @@ dependencies = [ "alloy-rlp", "crc", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -253,7 +254,7 @@ dependencies = [ "alloy-rlp", "borsh", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -302,7 +303,7 @@ dependencies = [ "alloy-provider", "alloy-sol-types", "async-trait", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -343,7 +344,7 @@ dependencies = [ "http 1.3.1", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] @@ -370,7 +371,7 @@ dependencies = [ "futures-utils-wasm", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -445,7 +446,7 @@ dependencies = [ "reqwest 0.13.2", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "url", @@ -538,7 +539,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -564,7 +565,7 @@ dependencies = [ "either", "elliptic-curve", "k256", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -580,7 +581,7 @@ dependencies = [ "async-trait", "k256", "rand 0.8.5", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -671,7 +672,7 @@ dependencies = [ "parking_lot", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tower 0.5.2", "tracing", @@ -707,7 +708,7 @@ dependencies = [ "nybbles", "serde", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] @@ -796,7 +797,7 @@ checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", - "cpufeatures", + "cpufeatures 0.2.17", "password-hash", ] @@ -1544,11 +1545,15 @@ dependencies = [ [[package]] name = "bip39" -version = "2.2.0" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d193de1f7487df1914d3a568b772458861d33f9c54249612cc2893d6915054" +checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" dependencies = [ - "bitcoin_hashes 0.13.0", + "bitcoin_hashes", + "rand 0.8.5", + "rand_core 0.6.4", + "serde", + "unicode-normalization", ] [[package]] @@ -1566,28 +1571,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" -[[package]] -name = "bitcoin-internals" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" - [[package]] name = "bitcoin-io" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" -[[package]] -name = "bitcoin_hashes" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" -dependencies = [ - "bitcoin-internals", - "hex-conservative 0.1.2", -] - [[package]] name = "bitcoin_hashes" version = "0.14.1" @@ -1595,7 +1584,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" dependencies = [ "bitcoin-io", - "hex-conservative 0.2.2", + "hex-conservative", ] [[package]] @@ -1655,6 +1644,20 @@ dependencies = [ "constant_time_eq 0.3.1", ] +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec 0.7.6", + "cc", + "cfg-if", + "constant_time_eq 0.4.2", + "cpufeatures 0.3.0", +] + [[package]] name = "block-buffer" version = "0.9.0" @@ -1724,10 +1727,11 @@ dependencies = [ [[package]] name = "bounded-collections" -version = "0.2.4" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ad8a0bed7827f0b07a5d23cec2e58cc02038a99e4ca81616cb2bb2025f804d" +checksum = "dee8eddd066a8825ec5570528e6880471210fd5d88cb6abbe1cfdd51ca249c33" dependencies = [ + "jam-codec", "log", "parity-scale-codec", "scale-info", @@ -1763,9 +1767,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ "serde", ] @@ -1823,7 +1827,7 @@ checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -1958,6 +1962,19 @@ dependencies = [ "yaml-rust2", ] +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.59.0", +] + [[package]] name = "const-hex" version = "1.18.1" @@ -1965,7 +1982,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "531185e432bb31db1ecda541e9e7ab21468d4d844ad7505e0546a49b4945d49b" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "proptest", "serde_core", ] @@ -1991,7 +2008,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "once_cell", "tiny-keccak", ] @@ -2028,6 +2045,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "convert_case" version = "0.6.0" @@ -2092,6 +2115,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc" version = "3.3.0" @@ -2107,6 +2139,31 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -2177,7 +2234,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", @@ -2611,6 +2668,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -2715,6 +2778,18 @@ dependencies = [ "syn 2.0.109", ] +[[package]] +name = "fastbloom" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" +dependencies = [ + "getrandom 0.3.4", + "libm", + "rand 0.9.2", + "siphasher", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -2784,6 +2859,15 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "static_assertions", +] + [[package]] name = "fixed-hash" version = "0.8.0" @@ -2869,6 +2953,22 @@ dependencies = [ "sp-crypto-hashing", ] +[[package]] +name = "frame-decode" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c470df86cf28818dd3cd2fc4667b80dbefe2236c722c3dc1d09e7c6c82d6dfcd" +dependencies = [ + "frame-metadata", + "parity-scale-codec", + "scale-decode", + "scale-encode", + "scale-info", + "scale-type-resolver", + "sp-crypto-hashing", + "thiserror 2.0.18", +] + [[package]] name = "frame-metadata" version = "23.0.0" @@ -3080,9 +3180,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -3270,6 +3370,8 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", + "rayon", + "serde", ] [[package]] @@ -3332,12 +3434,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hex-conservative" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" - [[package]] name = "hex-conservative" version = "0.2.2" @@ -3566,6 +3662,7 @@ dependencies = [ "tokio", "tokio-rustls 0.26.4", "tower-service", + "webpki-roots 1.0.7", ] [[package]] @@ -3827,6 +3924,19 @@ dependencies = [ "serde_core", ] +[[package]] +name = "indicatif" +version = "0.17.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width", + "web-time", +] + [[package]] name = "infer" version = "0.2.3" @@ -3944,6 +4054,34 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jam-codec" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb948eace373d99de60501a02fb17125d30ac632570de20dccc74370cdd611b9" +dependencies = [ + "arrayvec 0.7.6", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "jam-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "jam-codec-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "319af585c4c8a6b5552a52b7787a1ab3e4d59df7614190b1f85b9b842488789d" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.109", +] + [[package]] name = "jni" version = "0.21.1" @@ -4149,7 +4287,6 @@ dependencies = [ "once_cell", "serdect", "sha2 0.10.9", - "signature", ] [[package]] @@ -4158,7 +4295,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -4171,6 +4308,16 @@ dependencies = [ "sha3-asm", ] +[[package]] +name = "keccak-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2bd4c29270e724d3eaadf7bdc8700af4221fc0ed771b855eadcd1b98d52851" +dependencies = [ + "primitive-types 0.10.1", + "tiny-keccak", +] + [[package]] name = "keccak-hash" version = "0.11.0" @@ -4506,19 +4653,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "685a9ac4b61f4e728e1d2c6a7844609c16527aeb5e6c865915c08e619c16410f" -[[package]] -name = "nam-tiny-hderive" -version = "0.3.1-nam.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2cd44792ed5cd84dc9dedc3d572242ac00e76c244e85eb4bf34da2c6239ce30" -dependencies = [ - "base58", - "hmac 0.12.1", - "k256", - "sha2 0.10.9", - "zeroize", -] - [[package]] name = "native-tls" version = "0.2.14" @@ -4603,6 +4737,20 @@ dependencies = [ "windows-sys 0.61.2", ] +[[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" @@ -4611,6 +4759,7 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", + "rand 0.8.5", ] [[package]] @@ -4629,6 +4778,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + [[package]] name = "num-conv" version = "0.2.1" @@ -4717,6 +4876,12 @@ dependencies = [ "syn 2.0.109", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "nybbles" version = "0.4.8" @@ -4739,7 +4904,7 @@ checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" dependencies = [ "base64 0.13.1", "chrono", - "getrandom 0.2.16", + "getrandom 0.2.17", "http 0.2.12", "rand 0.8.5", "reqwest 0.11.27", @@ -4765,6 +4930,10 @@ name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "critical-section", + "portable-atomic", +] [[package]] name = "once_cell_polyfill" @@ -4954,19 +5123,6 @@ dependencies = [ "serde", ] -[[package]] -name = "parity-bip39" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e69bf016dc406eff7d53a7d3f7cf1c2e72c82b9088aac1118591e36dd2cd3e9" -dependencies = [ - "bitcoin_hashes 0.13.0", - "rand 0.8.5", - "rand_core 0.6.4", - "serde", - "unicode-normalization", -] - [[package]] name = "parity-scale-codec" version = "3.7.5" @@ -5197,26 +5353,41 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plonky2_maybe_rayon" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1e554181dc95243b8d9948ae7bae5759c7fb2502fed28f671f95ef38079406" +dependencies = [ + "rayon", +] + +[[package]] +name = "plonky2_util" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c32c137808ca984ab2458b612b7eb0462d853ee041a3136e83d54b96074c7610" + [[package]] name = "polkavm-common" -version = "0.24.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed9e5af472f729fcf3b3c1cf17508ddbb3505259dd6e2ee0fb5a29e105d22" +checksum = "49a5794b695626ba70d29e66e3f4f4835767452a6723f3a0bc20884b07088fe8" [[package]] name = "polkavm-derive" -version = "0.24.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176144f8661117ea95fa7cf868c9a62d6b143e8a2ebcb7582464c3faade8669a" +checksum = "95282a203ae1f6828a04ff334145c3f6dc718bba6d3959805d273358b45eab93" dependencies = [ "polkavm-derive-impl-macro", ] [[package]] name = "polkavm-derive-impl" -version = "0.24.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a21844afdfcc10c92b9ef288ccb926211af27478d1730fcd55e4aec710179d" +checksum = "6069dc7995cde6e612b868a02ce48b54397c6d2582bd1b97b63aabbe962cd779" dependencies = [ "polkavm-common", "proc-macro2", @@ -5226,9 +5397,9 @@ dependencies = [ [[package]] name = "polkavm-derive-impl-macro" -version = "0.24.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba0ef0f17ad81413ea1ca5b1b67553aedf5650c88269b673d3ba015c83bc2651" +checksum = "581d34cafec741dc5ffafbb341933c205b6457f3d76257a9d99fb56687219c91" dependencies = [ "polkavm-derive-impl", "syn 2.0.109", @@ -5254,7 +5425,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -5266,11 +5437,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + [[package]] name = "potential_utf" version = "0.1.4" @@ -5331,13 +5508,23 @@ dependencies = [ "syn 2.0.109", ] +[[package]] +name = "primitive-types" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e4722c697a58a99d5d06a08c30821d7c082a4632198de1eaa5a6c22ef42373" +dependencies = [ + "fixed-hash 0.7.0", + "uint 0.9.5", +] + [[package]] name = "primitive-types" version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ - "fixed-hash", + "fixed-hash 0.8.0", "impl-codec 0.6.0", "uint 0.9.5", ] @@ -5348,7 +5535,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" dependencies = [ - "fixed-hash", + "fixed-hash 0.8.0", "impl-codec 0.7.1", "impl-num-traits", "impl-serde", @@ -5446,7 +5633,7 @@ dependencies = [ "parking_lot", "procfs", "protobuf", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -5490,13 +5677,14 @@ dependencies = [ [[package]] name = "qp-dilithium-crypto" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb2f13d8793f7a79c42d33e4ebe9d470fe938dc55592ef97ef42d4298aa6a976" +checksum = "add1f1ae035a1c77d93ac6165caeaa07848a99760aa79ed7bde7720c1795e78e" dependencies = [ "log", "parity-scale-codec", - "qp-poseidon 1.0.1", + "qp-poseidon", + "qp-poseidon-core", "qp-rusty-crystals-dilithium", "qp-rusty-crystals-hdwallet", "scale-info", @@ -5508,9 +5696,9 @@ dependencies = [ [[package]] name = "qp-human-checkphrase" -version = "0.1.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f50cccc4088142493e9ea31749d8d5184b4077f753afbe54c4afcdb18e65824" +checksum = "bfbb513548cf4dc69a0e2249d3e18e2001dad4ebc39c008c4f1631504f0bbb2a" dependencies = [ "hex", "hmac 0.12.1", @@ -5520,75 +5708,148 @@ dependencies = [ ] [[package]] -name = "qp-poseidon" -version = "0.9.5" +name = "qp-plonky2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33181134496120c212a0a2098215cf45a68d50fe656de6fb30b31e73babe9383" +checksum = "a1d18516ef5ecd81ddcccb6beacdfe1578f44e4f05ccb0890998afcd3b87d01f" dependencies = [ + "ahash", + "anyhow", + "critical-section", + "getrandom 0.2.17", + "hashbrown 0.14.5", + "itertools 0.11.0", + "keccak-hash 0.8.0", "log", + "num", + "once_cell", "p3-field", "p3-goldilocks", - "parity-scale-codec", - "qp-poseidon-core 0.9.5", - "scale-info", + "p3-poseidon2", + "p3-symmetric", + "plonky2_maybe_rayon", + "plonky2_util", + "qp-plonky2-core", + "qp-plonky2-field", + "qp-plonky2-verifier", + "qp-poseidon-constants", + "rand 0.8.5", + "rand_chacha 0.3.1", "serde", - "sp-core", - "sp-runtime", - "sp-storage", - "sp-trie", + "static_assertions", + "unroll", + "web-time", ] [[package]] -name = "qp-poseidon" -version = "1.0.1" +name = "qp-plonky2-core" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0353086f7af1df7d45a1ecb995cf84b583c8211d7122f542044b37388b5effcd" +checksum = "0ad9961c2e2f6aca563eefd902697b0d21e9807c2c3b719ba1d4488bb03383c4" dependencies = [ + "ahash", + "anyhow", + "hashbrown 0.14.5", + "itertools 0.11.0", + "keccak-hash 0.8.0", "log", + "num", "p3-field", "p3-goldilocks", - "parity-scale-codec", - "qp-poseidon-core 1.0.1", - "scale-info", + "p3-poseidon2", + "p3-symmetric", + "plonky2_util", + "qp-plonky2-field", + "qp-poseidon-constants", + "rand 0.8.5", "serde", - "sp-core", - "sp-runtime", - "sp-storage", - "sp-trie", + "static_assertions", + "unroll", ] [[package]] -name = "qp-poseidon-constants" -version = "1.0.1" +name = "qp-plonky2-field" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d56b56652e9f44a43de9593e75d7c3e0c3a352e10675cf3024e5b3175711cd3" +checksum = "ecc7089b7ae09ef8fe4889353d2f2be7dffe6d87edfb5b03f9553ca0a1ca55da" dependencies = [ + "anyhow", + "itertools 0.11.0", + "num", + "plonky2_util", + "rand 0.8.5", + "rustc_version 0.4.1", + "serde", + "static_assertions", + "unroll", +] + +[[package]] +name = "qp-plonky2-verifier" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb155f950f3df3c0cf5fce846290f19c6b7a059fbefd9bdf9011fd6f01f84e79" +dependencies = [ + "ahash", + "anyhow", + "critical-section", + "hashbrown 0.14.5", + "itertools 0.11.0", + "keccak-hash 0.8.0", + "log", + "num", + "once_cell", "p3-field", "p3-goldilocks", "p3-poseidon2", - "rand 0.9.2", - "rand_chacha 0.9.0", + "p3-symmetric", + "plonky2_util", + "qp-plonky2-core", + "qp-plonky2-field", + "qp-poseidon-constants", + "serde", + "static_assertions", + "unroll", ] [[package]] -name = "qp-poseidon-core" -version = "0.9.5" +name = "qp-poseidon" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec326fc2631a929de09a38af2613a3db5230882c12a2f68205693ec632751e8b" +checksum = "e5b762feb45d8166b452610839f269ae35b76b2064c57b944ca1af19f665dc28" +dependencies = [ + "log", + "p3-field", + "p3-goldilocks", + "parity-scale-codec", + "qp-poseidon-constants", + "qp-poseidon-core", + "scale-info", + "serde", + "sp-core", + "sp-runtime", + "sp-storage", + "sp-trie", +] + +[[package]] +name = "qp-poseidon-constants" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300d8b01e4a492b202e739ab5f73215b261905911020cc0f74a22109365e3bd7" dependencies = [ "p3-field", "p3-goldilocks", "p3-poseidon2", - "p3-symmetric", "rand 0.9.2", "rand_chacha 0.9.0", ] [[package]] name = "qp-poseidon-core" -version = "1.0.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e658a373a7fb22babeda9ffcc8af0a894e6e3c008272ed735509eccb7769ead3" +checksum = "e3117d3df79617ee2eb4d4576d2f76f6dbc9bf3f466d4477d188ee89acbd7b07" dependencies = [ "p3-field", "p3-goldilocks", @@ -5600,64 +5861,173 @@ dependencies = [ [[package]] name = "qp-rusty-crystals-dilithium" -version = "2.0.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e77a42bfb3430fa3bf3f5a148f8132de301876ceb1bdf0891909c2728f044a58" +checksum = "e223f0218cd3d55a04d96cc9a82415d2edd5e37b5ebd0f466d44a22bf8b34532" dependencies = [ - "aes", - "cipher", - "sha2 0.10.9", - "subtle", + "zeroize", ] [[package]] name = "qp-rusty-crystals-hdwallet" -version = "1.0.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48fa242963fcd6bc970948b6904f18074673ff89cab8ed0133846a53c69deca7" +checksum = "ddbfc888856ab9d7b729d94ada8ddd4916cd3abbe67c1fc6af80c0ed32d17d3d" dependencies = [ "bip39", + "getrandom 0.2.17", "hex", "hex-literal", - "nam-tiny-hderive", - "qp-poseidon-core 1.0.1", + "hmac 0.12.1", + "qp-poseidon-core", "qp-rusty-crystals-dilithium", - "rand_chacha 0.9.0", - "rand_core 0.9.3", "serde", "serde_json", - "thiserror 2.0.17", + "sha2 0.10.9", + "thiserror 2.0.18", + "zeroize", +] + +[[package]] +name = "qp-wormhole-aggregator" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a23eea8e17c97632f5056d1fecdb83d2e997f79d823fb97c514823fce8580" +dependencies = [ + "anyhow", + "hex", + "qp-plonky2", + "qp-wormhole-circuit", + "qp-wormhole-inputs", + "qp-wormhole-prover", + "qp-zk-circuits-common", + "rand 0.8.5", + "rayon", + "serde", + "serde_json", +] + +[[package]] +name = "qp-wormhole-circuit" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72abd3357e1c486621431109d83a259076ed941fcc7f035353a629940c443d9" +dependencies = [ + "anyhow", + "hex", + "qp-plonky2", + "qp-wormhole-inputs", + "qp-zk-circuits-common", +] + +[[package]] +name = "qp-wormhole-circuit-builder" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d27c981d34a35cb10ee96e16c1fb44fbb4c52f3e58a787ce089c21bf5469514" +dependencies = [ + "anyhow", + "clap", + "qp-plonky2", + "qp-wormhole-aggregator", + "qp-wormhole-circuit", + "qp-zk-circuits-common", +] + +[[package]] +name = "qp-wormhole-inputs" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f69e54a0f450d349cb1169bbb743a7a846475a1210760308e38f55841e5aa5c0" +dependencies = [ + "anyhow", +] + +[[package]] +name = "qp-wormhole-prover" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68cee4d6a0317f89d1ec7a6c40a9592e5525c75b9cb21bd7d5d3b408301068bd" +dependencies = [ + "anyhow", + "qp-plonky2", + "qp-wormhole-circuit", + "qp-wormhole-inputs", + "qp-zk-circuits-common", +] + +[[package]] +name = "qp-wormhole-verifier" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83f7c0134a766c6624183d129abf12523247e38fc27860e3a9778f73321008ae" +dependencies = [ + "anyhow", + "qp-plonky2-verifier", + "qp-wormhole-inputs", +] + +[[package]] +name = "qp-zk-circuits-common" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5e1d764560fd797f71defbf75a1618596945c53e33033ac42cc7c2e4689f6" +dependencies = [ + "anyhow", + "hex", + "qp-plonky2", + "qp-poseidon-constants", + "qp-poseidon-core", + "qp-wormhole-inputs", + "rand 0.8.5", + "serde", ] [[package]] name = "quantus-cli" -version = "0.3.0" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3c419f08c81bfb02c799aa3918cd5d642759460572f5efc6dc0641b08544737" +checksum = "9110b3987d49c82f814feb05653fb631955d06d1f78932157d79060776adaf0f" dependencies = [ "aes-gcm", + "anyhow", "argon2", + "blake3", + "bytes", "chrono", "clap", "colored", "dirs", "hex", + "indicatif", "jsonrpsee", "parity-scale-codec", "qp-dilithium-crypto", - "qp-poseidon 0.9.5", + "qp-plonky2", + "qp-poseidon", + "qp-poseidon-core", "qp-rusty-crystals-dilithium", "qp-rusty-crystals-hdwallet", + "qp-wormhole-aggregator", + "qp-wormhole-circuit", + "qp-wormhole-circuit-builder", + "qp-wormhole-inputs", + "qp-wormhole-prover", + "qp-wormhole-verifier", + "qp-zk-circuits-common", + "quinn-proto", "rand 0.9.2", + "reqwest 0.12.24", "rpassword", + "rustls-webpki 0.103.13", "serde", "serde_json", "sha2 0.10.9", "sp-core", "sp-runtime", - "subxt", - "subxt-metadata", - "thiserror 2.0.17", + "subxt 0.44.3", + "subxt-metadata 0.44.3", + "thiserror 2.0.18", "tokio", "toml 0.9.8", ] @@ -5682,7 +6052,7 @@ dependencies = [ "rustc-hash", "rustls 0.23.35", "socket2 0.6.1", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -5696,6 +6066,7 @@ checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ "aws-lc-rs", "bytes", + "fastbloom", "getrandom 0.3.4", "lru-slab", "rand 0.9.2", @@ -5704,7 +6075,7 @@ dependencies = [ "rustls 0.23.35", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -5826,7 +6197,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] @@ -5866,6 +6237,26 @@ dependencies = [ "rustversion", ] +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -5881,9 +6272,9 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -5975,7 +6366,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 0.25.4", "winreg", ] @@ -6005,6 +6396,8 @@ dependencies = [ "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls 0.23.35", "rustls-pki-types", "serde", "serde_json", @@ -6012,6 +6405,7 @@ dependencies = [ "sync_wrapper 1.0.2", "tokio", "tokio-native-tls", + "tokio-rustls 0.26.4", "tower 0.5.2", "tower-http 0.6.8", "tower-service", @@ -6019,6 +6413,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots 1.0.7", ] [[package]] @@ -6082,7 +6477,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -6280,7 +6675,7 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.8", + "rustls-webpki 0.103.13", "subtle", "zeroize", ] @@ -6330,7 +6725,7 @@ dependencies = [ "rustls 0.23.35", "rustls-native-certs", "rustls-platform-verifier-android", - "rustls-webpki 0.103.8", + "rustls-webpki 0.103.13", "security-framework 3.5.1", "security-framework-sys", "webpki-root-certs 0.26.11", @@ -6351,7 +6746,7 @@ dependencies = [ "rustls 0.23.35", "rustls-native-certs", "rustls-platform-verifier-android", - "rustls-webpki 0.103.8", + "rustls-webpki 0.103.13", "security-framework 3.5.1", "security-framework-sys", "webpki-root-certs 1.0.4", @@ -6376,9 +6771,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", @@ -6465,7 +6860,7 @@ dependencies = [ "scale-decode-derive", "scale-type-resolver", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -6492,7 +6887,7 @@ dependencies = [ "scale-encode-derive", "scale-type-resolver", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -6554,7 +6949,7 @@ dependencies = [ "quote", "scale-info", "syn 2.0.109", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -6572,7 +6967,7 @@ dependencies = [ "scale-encode", "scale-type-resolver", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "yap", ] @@ -6685,7 +7080,7 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" dependencies = [ - "bitcoin_hashes 0.14.1", + "bitcoin_hashes", "rand 0.8.5", "secp256k1-sys 0.10.1", "serde", @@ -6826,15 +7221,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -6937,7 +7332,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.10.7", ] @@ -6949,7 +7344,7 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.9.0", "opaque-debug", ] @@ -6961,7 +7356,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.10.7", ] @@ -7033,7 +7428,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", ] @@ -7202,9 +7597,9 @@ dependencies = [ [[package]] name = "sp-application-crypto" -version = "41.0.0" +version = "44.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c668f1ce424bc131f40ade33fa4c0bd4dcd2428479e1e291aad66d4b00c74f" +checksum = "c33baebe847fc50edccd36d0e0e86df21d4db93876b5d74aadae9d8e96ca35e2" dependencies = [ "parity-scale-codec", "scale-info", @@ -7215,9 +7610,9 @@ dependencies = [ [[package]] name = "sp-arithmetic" -version = "27.0.0" +version = "28.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2929fd12ac6ca3cfac7f62885866810ba4e9464814dbaa87592b5b5681b29aee" +checksum = "4e06588d1c43f60b9bb7f989785689842cc4cc8ce0e19d1c47686b1ff5fe9548" dependencies = [ "docify", "integer-sqrt", @@ -7230,12 +7625,13 @@ dependencies = [ [[package]] name = "sp-core" -version = "37.0.0" +version = "39.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e1a46a6b2323401e4489184846a7fb7d89091b42602a2391cd3ef652ede2850" +checksum = "b0f32d2a9af72fe90bec51076d0e109ef3c25aa1d2a1eef15cf3588acd4a23da" dependencies = [ "ark-vrf", "array-bytes", + "bip39", "bitflags 1.3.2", "blake2", "bounded-collections", @@ -7251,7 +7647,6 @@ dependencies = [ "libsecp256k1", "log", "merlin", - "parity-bip39", "parity-scale-codec", "parking_lot", "paste", @@ -7266,7 +7661,6 @@ dependencies = [ "sp-crypto-hashing", "sp-debug-derive", "sp-externalities", - "sp-runtime-interface", "sp-std", "sp-storage", "ss58-registry", @@ -7304,9 +7698,9 @@ dependencies = [ [[package]] name = "sp-externalities" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cbf059dce180a8bf8b6c8b08b6290fa3d1c7f069a60f1df038ab5dd5fc0ba6" +checksum = "76b67582d8eb400e730d4abaa9f8841898fa36782a2c6b7f61676e5dd6f8166c" dependencies = [ "environmental", "parity-scale-codec", @@ -7315,9 +7709,9 @@ dependencies = [ [[package]] name = "sp-io" -version = "41.0.1" +version = "44.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f244e9a2818d21220ceb0915ac73a462814a92d0c354a124a818abdb7f4f66" +checksum = "84c3b7db2a4f180e3362e374754983e3ddc844b7a1cd2c2e5b71ab0bd3673dfe" dependencies = [ "bytes", "docify", @@ -7342,9 +7736,9 @@ dependencies = [ [[package]] name = "sp-keystore" -version = "0.43.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "269d0ee360f6d072f9203485afea35583ac151521a525cc48b2a107fc576c2d9" +checksum = "fc62157d26f8c6847e2827168f71edea83f9f2c3cc12b8fb694dbe58aefe5972" dependencies = [ "parity-scale-codec", "parking_lot", @@ -7364,11 +7758,12 @@ dependencies = [ [[package]] name = "sp-runtime" -version = "42.0.0" +version = "45.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b25d4d3811410317175ff121b3ff8c8b723504dadf37cd418b5192a5098d11bf" +checksum = "7f799c308ab442aa1c80b193db8c76f36dcc5a911408bf8861511987f4e4f2ee" dependencies = [ "binary-merkle-tree", + "bytes", "docify", "either", "hash256-std-hasher", @@ -7388,21 +7783,21 @@ dependencies = [ "sp-std", "sp-trie", "sp-weights", + "strum 0.26.3", "tracing", "tuplex", ] [[package]] name = "sp-runtime-interface" -version = "30.0.0" +version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fcd9c219da8c85d45d5ae1ce80e73863a872ac27424880322903c6ac893c06e" +checksum = "22644a2fabb5c246911ecde30fdb7f0801c90f5e611b1147140055ad7b6dabab" dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec", "polkavm-derive", - "primitive-types 0.13.1", "sp-externalities", "sp-runtime-interface-proc-macro", "sp-std", @@ -7414,9 +7809,9 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" -version = "19.0.0" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca35431af10a450787ebfdcb6d7a91c23fa91eafe73a3f9d37db05c9ab36154b" +checksum = "04178084ae654b3924934a56943ee73e3562db4d277e948393561b08c3b5b5fe" dependencies = [ "Inflector", "expander", @@ -7428,9 +7823,9 @@ dependencies = [ [[package]] name = "sp-state-machine" -version = "0.46.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "483422b016ee9ddba949db6d3092961ed58526520f0586df74dc07defd922a58" +checksum = "1b5bfda052a2fe9be497139e0c5d0a51946873f3cd7c2ff81bdbcb8b446caa37" dependencies = [ "hash-db", "log", @@ -7468,11 +7863,12 @@ dependencies = [ [[package]] name = "sp-tracing" -version = "17.1.0" +version = "19.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6147a5b8c98b9ed4bf99dc033fab97a468b4645515460974c8784daeb7c35433" +checksum = "f2c7372456c39cc81e15befe54d0caab8378f2b30fd34d1bcb5f0f56631c6b6e" dependencies = [ "parity-scale-codec", + "regex", "tracing", "tracing-core", "tracing-subscriber", @@ -7480,9 +7876,9 @@ dependencies = [ [[package]] name = "sp-trie" -version = "40.0.0" +version = "42.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b2e157c9cf44a1a9d20f3c69322e302db70399bf3f218211387fe009dd4041c" +checksum = "6beed4d77d66f085443eac37171d88b2dbf6f7358d9d3451c11479ddfce60d6e" dependencies = [ "ahash", "foldhash 0.1.5", @@ -7506,9 +7902,9 @@ dependencies = [ [[package]] name = "sp-wasm-interface" -version = "22.0.0" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdbc579c72fc03263894a0077383f543a093020d75741092511bb05a440ada6" +checksum = "dd177d0658f3df0492f28bd39d665133a7868db5aa66c8642c949b6265430719" dependencies = [ "anyhow", "impl-trait-for-tuples", @@ -7518,9 +7914,9 @@ dependencies = [ [[package]] name = "sp-weights" -version = "32.0.0" +version = "33.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8a1d448faceb064bb114df31fc45ff86ea2ee8fd17810c4357a578d081f7732" +checksum = "b4c34d353fdc6469da8fae9248ffc1f34faaf04bec8cabc43fd77681dcbc8517" dependencies = [ "bounded-collections", "parity-scale-codec", @@ -7614,7 +8010,7 @@ dependencies = [ "tracing", "url", "uuid", - "webpki-roots", + "webpki-roots 0.25.4", ] [[package]] @@ -7815,13 +8211,35 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros 0.26.4", +] + [[package]] name = "strum" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros", + "strum_macros 0.27.2", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.109", ] [[package]] @@ -7893,12 +8311,49 @@ dependencies = [ "serde", "serde_json", "sp-crypto-hashing", - "subxt-core", - "subxt-lightclient", - "subxt-macro", - "subxt-metadata", - "subxt-rpcs", - "thiserror 2.0.17", + "subxt-core 0.43.0", + "subxt-lightclient 0.43.0", + "subxt-macro 0.43.0", + "subxt-metadata 0.43.0", + "subxt-rpcs 0.43.0", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tracing", + "url", + "wasm-bindgen-futures", + "web-time", +] + +[[package]] +name = "subxt" +version = "0.44.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15d478a97cff6a704123c9a3871eff832f8ea4a477390a8ea5fd7cfedd41bf6f" +dependencies = [ + "async-trait", + "derive-where", + "either", + "frame-metadata", + "futures", + "hex", + "jsonrpsee", + "parity-scale-codec", + "primitive-types 0.13.1", + "scale-bits", + "scale-decode", + "scale-encode", + "scale-info", + "scale-value", + "serde", + "serde_json", + "sp-crypto-hashing", + "subxt-core 0.44.3", + "subxt-lightclient 0.44.3", + "subxt-macro 0.44.3", + "subxt-metadata 0.44.3", + "subxt-rpcs 0.44.3", + "thiserror 2.0.18", "tokio", "tokio-util", "tracing", @@ -7919,9 +8374,26 @@ dependencies = [ "quote", "scale-info", "scale-typegen", - "subxt-metadata", + "subxt-metadata 0.43.0", "syn 2.0.109", - "thiserror 2.0.17", + "thiserror 2.0.18", +] + +[[package]] +name = "subxt-codegen" +version = "0.44.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "461338acd557773106546b474fbb48d47617735fd50941ddc516818006daf8a0" +dependencies = [ + "heck 0.5.0", + "parity-scale-codec", + "proc-macro2", + "quote", + "scale-info", + "scale-typegen", + "subxt-metadata 0.44.3", + "syn 2.0.109", + "thiserror 2.0.18", ] [[package]] @@ -7933,12 +8405,12 @@ dependencies = [ "base58", "blake2", "derive-where", - "frame-decode", + "frame-decode 0.8.3", "frame-metadata", "hashbrown 0.14.5", "hex", "impl-serde", - "keccak-hash", + "keccak-hash 0.11.0", "parity-scale-codec", "primitive-types 0.13.1", "scale-bits", @@ -7949,8 +8421,38 @@ dependencies = [ "serde", "serde_json", "sp-crypto-hashing", - "subxt-metadata", - "thiserror 2.0.17", + "subxt-metadata 0.43.0", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "subxt-core" +version = "0.44.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "002d360ac0827c882d5a808261e06c11a5e7ad2d7c295176d5126a9af9aa5f23" +dependencies = [ + "base58", + "blake2", + "derive-where", + "frame-decode 0.9.0", + "frame-metadata", + "hashbrown 0.14.5", + "hex", + "impl-serde", + "keccak-hash 0.11.0", + "parity-scale-codec", + "primitive-types 0.13.1", + "scale-bits", + "scale-decode", + "scale-encode", + "scale-info", + "scale-value", + "serde", + "serde_json", + "sp-crypto-hashing", + "subxt-metadata 0.44.3", + "thiserror 2.0.18", "tracing", ] @@ -7965,7 +8467,24 @@ dependencies = [ "serde", "serde_json", "smoldot-light", - "thiserror 2.0.17", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "subxt-lightclient" +version = "0.44.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab0c7a6504798b1c4a7dbe4cac9559560826e5df3f021efa3e9dd6393050521" +dependencies = [ + "futures", + "futures-util", + "serde", + "serde_json", + "smoldot-light", + "thiserror 2.0.18", "tokio", "tokio-stream", "tracing", @@ -7982,9 +8501,26 @@ dependencies = [ "proc-macro-error2", "quote", "scale-typegen", - "subxt-codegen", - "subxt-metadata", - "subxt-utils-fetchmetadata", + "subxt-codegen 0.43.0", + "subxt-metadata 0.43.0", + "subxt-utils-fetchmetadata 0.43.0", + "syn 2.0.109", +] + +[[package]] +name = "subxt-macro" +version = "0.44.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc844e7877b6fe4a4013c5836a916dee4e58fc875b98ccc18b5996db34b575c3" +dependencies = [ + "darling 0.20.11", + "parity-scale-codec", + "proc-macro-error2", + "quote", + "scale-typegen", + "subxt-codegen 0.44.3", + "subxt-metadata 0.44.3", + "subxt-utils-fetchmetadata 0.44.3", "syn 2.0.109", ] @@ -7994,13 +8530,28 @@ version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c134068711c0c46906abc0e6e4911204420331530738e18ca903a5469364d9f" dependencies = [ - "frame-decode", + "frame-decode 0.8.3", "frame-metadata", "hashbrown 0.14.5", "parity-scale-codec", "scale-info", "sp-crypto-hashing", - "thiserror 2.0.17", + "thiserror 2.0.18", +] + +[[package]] +name = "subxt-metadata" +version = "0.44.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b2f2a52d97d7539febc0006d6988081150b1c1a3e4a357ca02ab5cdb34072bc" +dependencies = [ + "frame-decode 0.9.0", + "frame-metadata", + "hashbrown 0.14.5", + "parity-scale-codec", + "scale-info", + "sp-crypto-hashing", + "thiserror 2.0.18", ] [[package]] @@ -8019,9 +8570,33 @@ dependencies = [ "primitive-types 0.13.1", "serde", "serde_json", - "subxt-core", - "subxt-lightclient", - "thiserror 2.0.17", + "subxt-core 0.43.0", + "subxt-lightclient 0.43.0", + "thiserror 2.0.18", + "tokio-util", + "tracing", + "url", +] + +[[package]] +name = "subxt-rpcs" +version = "0.44.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec54130c797530e6aa6a52e8ba9f95fd296d19da2f9f3e23ed5353a83573f74" +dependencies = [ + "derive-where", + "frame-metadata", + "futures", + "hex", + "impl-serde", + "jsonrpsee", + "parity-scale-codec", + "primitive-types 0.13.1", + "serde", + "serde_json", + "subxt-core 0.44.3", + "subxt-lightclient 0.44.3", + "thiserror 2.0.18", "tokio-util", "tracing", "url", @@ -8035,7 +8610,18 @@ checksum = "8c4fb8fd6b16ecd3537a29d70699f329a68c1e47f70ed1a46d64f76719146563" dependencies = [ "hex", "parity-scale-codec", - "thiserror 2.0.17", + "thiserror 2.0.18", +] + +[[package]] +name = "subxt-utils-fetchmetadata" +version = "0.44.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4664a0b726f11b1d6da990872f9528be090d3570c2275c9b89ba5bbc8e764592" +dependencies = [ + "hex", + "parity-scale-codec", + "thiserror 2.0.18", ] [[package]] @@ -8175,8 +8761,8 @@ dependencies = [ "sp-core", "sp-runtime", "sqlx", - "subxt", - "thiserror 2.0.17", + "subxt 0.43.0", + "thiserror 2.0.18", "tiny-keccak", "tokio", "toml 0.9.8", @@ -8228,11 +8814,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -8248,9 +8834,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -8810,6 +9396,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -8832,6 +9424,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "unroll" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad948c1cb799b1a70f836077721a92a35ac177d4daddf4c20a633786d4cf618" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -9193,6 +9795,15 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "webpki-roots" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "whoami" version = "1.6.1" @@ -9832,3 +10443,9 @@ dependencies = [ "quote", "syn 2.0.109", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 54ed578..85366aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,9 +21,9 @@ path = "src/bin/create_raid.rs" [dependencies] # Quantus crates -qp-human-checkphrase = "0.1.2" -qp-rusty-crystals-dilithium = "2.0.0" -quantus-cli = "0.3.0" +qp-human-checkphrase = "2.0.0" +qp-rusty-crystals-dilithium = "2.4.0" +quantus-cli = "1.3.3" rusx = {git = "https://github.com/Quantus-Network/rusx", tag = "v0.6.1"} # Async runtime @@ -51,8 +51,8 @@ sqlx = {version = "0.7", features = [ # Signature verification # Using quantus-cli's dilithium crypto for signature verification hex = "0.4" -sp-core = "37.0.0" -sp-runtime = "42.0.0" +sp-core = "39.0.0" +sp-runtime = "45.0.0" # Configuration clap = {version = "4.5", features = ["derive"]} diff --git a/config/default.toml b/config/default.toml index 177037a..2021a34 100644 --- a/config/default.toml +++ b/config/default.toml @@ -90,3 +90,7 @@ infura_api_key = "change-me" infura_base_url = "https://mainnet.infura.io/v3" etherscan_calls_per_sec = 3 max_concurrent_requests = 1 + +[exchange_rate] +# https://www.exchangerate-api.com/ — v6 key for latest/{base} rates +api_key = "change-me" diff --git a/config/example.toml b/config/example.toml index d2cbac8..e9b9ed1 100644 --- a/config/example.toml +++ b/config/example.toml @@ -101,6 +101,10 @@ infura_base_url = "https://mainnet.infura.io/v3" etherscan_calls_per_sec = 3 max_concurrent_requests = 1 +[exchange_rate] +# https://www.exchangerate-api.com/ — v6 key for latest/{base} rates +api_key = "change-me" + # Example environment variable overrides: # TASKMASTER_BLOCKCHAIN__NODE_URL="ws://remote-node:9944" # TASKMASTER_BLOCKCHAIN__WALLET_PASSWORD="super_secure_password" diff --git a/config/test.toml b/config/test.toml index 278e16c..a201afa 100644 --- a/config/test.toml +++ b/config/test.toml @@ -90,3 +90,6 @@ infura_api_key = "change-me" infura_base_url = "https://mainnet.infura.io/v3" etherscan_calls_per_sec = 3 max_concurrent_requests = 1 + +[exchange_rate] +api_key = "test-key" diff --git a/src/config.rs b/src/config.rs index d284348..ef455ba 100644 --- a/src/config.rs +++ b/src/config.rs @@ -21,6 +21,7 @@ pub struct Config { pub x_association: XAssociationConfig, pub remote_configs: RemoteConfigsConfig, pub risk_checker: RiskCheckerConfig, + pub exchange_rate: ExchangeRateConfig, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -112,6 +113,12 @@ pub struct RiskCheckerConfig { pub max_concurrent_requests: usize, } +/// Exchange rate API (e.g. [ExchangeRate-API v6](https://www.exchangerate-api.com/)). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ExchangeRateConfig { + pub api_key: String, +} + impl Config { pub fn load(config_path: &str) -> Result { let settings = config::Config::builder() @@ -267,6 +274,9 @@ impl Default for Config { etherscan_calls_per_sec: 3, max_concurrent_requests: 1, }, + exchange_rate: ExchangeRateConfig { + api_key: "change-me".to_string(), + }, } } } diff --git a/src/errors.rs b/src/errors.rs index f3b1df9..a048c75 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -12,7 +12,8 @@ use crate::{ handlers::{address::AddressHandlerError, auth::AuthHandlerError, referral::ReferralHandlerError, HandlerError}, models::ModelError, services::{ - graphql_client::GraphqlError, risk_checker_service::RiskCheckerError, wallet_config_service::WalletConfigsError, + exchange_rate_service::ExchangeRateError, graphql_client::GraphqlError, risk_checker_service::RiskCheckerError, + wallet_config_service::WalletConfigsError, }, }; @@ -42,6 +43,8 @@ pub enum AppError { Telegram(u16, String), #[error("Risk checker error: {0}")] RiskChecker(#[from] RiskCheckerError), + #[error("Exchange rate error: {0}")] + ExchangeRate(#[from] ExchangeRateError), } pub type AppResult = Result; @@ -73,6 +76,9 @@ impl IntoResponse for AppError { // --- Risk Checker --- AppError::RiskChecker(err) => map_risk_checker_error(err), + // --- Exchange Rate --- + AppError::ExchangeRate(err) => map_exchange_rate_error(err), + // --- Everything else --- e @ (AppError::Join(_) | AppError::Graphql(_) @@ -202,3 +208,11 @@ fn map_risk_checker_error(err: RiskCheckerError) -> (StatusCode, String) { } } } + +fn map_exchange_rate_error(err: ExchangeRateError) -> (StatusCode, String) { + match err { + ExchangeRateError::Api(err) => (StatusCode::BAD_REQUEST, err), + ExchangeRateError::Http(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()), + ExchangeRateError::Json(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()), + } +} diff --git a/src/handlers/auth.rs b/src/handlers/auth.rs index effc21e..fdf818a 100644 --- a/src/handlers/auth.rs +++ b/src/handlers/auth.rs @@ -352,6 +352,7 @@ mod tests { }, }; use axum::{body::Body, http, routing::get}; + use qp_rusty_crystals_dilithium::SensitiveBytes32; use rusx::{ auth::TwitterToken, resources::{ @@ -560,15 +561,15 @@ mod tests { let v: serde_json::Value = serde_json::from_slice(&bytes).unwrap(); let temp_session_id = v["temp_session_id"].as_str().unwrap().to_string(); let challenge = v["challenge"].as_str().unwrap().to_string(); - let entropy = [3u8; 32]; - let kp = qp_rusty_crystals_dilithium::ml_dsa_87::Keypair::generate(&entropy); + let entropy = SensitiveBytes32::from(&mut [3u8; 32]); + let kp = qp_rusty_crystals_dilithium::ml_dsa_87::Keypair::generate(entropy); let pk_hex = hex::encode(kp.public.to_bytes()); let addr = quantus_cli::qp_dilithium_crypto::types::DilithiumPublic::try_from(kp.public.to_bytes().as_slice()) .unwrap() .into_account() .to_ss58check(); let msg = format!("taskmaster:login:1|challenge={}|address={}", challenge, addr); - let sig_hex = hex::encode(kp.sign(msg.as_bytes(), None, Some([7u8; 32]))); + let sig_hex = hex::encode(kp.sign(msg.as_bytes(), None, Some([7u8; 32])).unwrap()); let verify_payload = serde_json::json!({ "temp_session_id": temp_session_id, diff --git a/src/handlers/exchange_rate.rs b/src/handlers/exchange_rate.rs new file mode 100644 index 0000000..baab52c --- /dev/null +++ b/src/handlers/exchange_rate.rs @@ -0,0 +1,12 @@ +use axum::{extract::State, Json}; +use serde_json::json; + +use crate::{handlers::SuccessResponse, http_server::AppState, AppError}; + +pub async fn handle_get_exchange_rate( + State(state): State, +) -> Result>, AppError> { + let exchange_rate = state.exchange_rate_service.get_snapshot().await?; + + Ok(SuccessResponse::new(json!(exchange_rate.conversion_rates))) +} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 62ddd3a..59226da 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -10,6 +10,7 @@ use crate::{ pub mod address; pub mod auth; pub mod config; +pub mod exchange_rate; pub mod raid_quest; pub mod referral; pub mod relevant_tweet; diff --git a/src/http_server.rs b/src/http_server.rs index ff67a7e..f803b07 100644 --- a/src/http_server.rs +++ b/src/http_server.rs @@ -13,6 +13,7 @@ use tower_http::{ trace::TraceLayer, }; +use crate::services::exchange_rate_service::ExchangeRateService; use crate::{ db_persistence::DbPersistence, metrics::{metrics_handler, track_metrics, Metrics}, @@ -30,6 +31,7 @@ pub struct AppState { pub graphql_client: Arc, pub wallet_config_service: Arc, pub risk_checker_service: Arc, + pub exchange_rate_service: Arc, pub config: Arc, pub challenges: Arc>>, pub oauth_sessions: Arc>>, @@ -98,6 +100,7 @@ pub async fn start_server( config.remote_configs.wallet_configs_file.clone(), )?), risk_checker_service: Arc::new(RiskCheckerService::new(&config.risk_checker)), + exchange_rate_service: Arc::new(ExchangeRateService::new(&config.exchange_rate.api_key)), config, twitter_gateway, challenges: Arc::new(RwLock::new(HashMap::new())), diff --git a/src/routes/exchange_rate.rs b/src/routes/exchange_rate.rs new file mode 100644 index 0000000..0256b39 --- /dev/null +++ b/src/routes/exchange_rate.rs @@ -0,0 +1,7 @@ +use axum::{routing::get, Router}; + +use crate::{handlers::exchange_rate::handle_get_exchange_rate, http_server::AppState}; + +pub fn exchange_rate_routes() -> Router { + Router::new().route("/exchange-rate", get(handle_get_exchange_rate)) +} diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 44d52dd..d95fa34 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -7,14 +7,15 @@ use risk_checker::risk_checker_routes; use crate::{ http_server::AppState, routes::{ - address::address_routes, raid_quest::raid_quest_routes, relevant_tweet::relevant_tweet_routes, - tweet_author::tweet_author_routes, + address::address_routes, exchange_rate::exchange_rate_routes, raid_quest::raid_quest_routes, + relevant_tweet::relevant_tweet_routes, tweet_author::tweet_author_routes, }, }; pub mod address; pub mod auth; pub mod config; +pub mod exchange_rate; pub mod raid_quest; pub mod referral; pub mod relevant_tweet; @@ -31,4 +32,5 @@ pub fn api_routes(state: AppState) -> Router { .merge(config_routes()) .merge(raid_quest_routes(state)) .merge(risk_checker_routes()) + .merge(exchange_rate_routes()) } diff --git a/src/services/exchange_rate_service.rs b/src/services/exchange_rate_service.rs new file mode 100644 index 0000000..48c3a9e --- /dev/null +++ b/src/services/exchange_rate_service.rs @@ -0,0 +1,197 @@ +use std::{ + collections::HashMap, + sync::Arc, + time::{SystemTime, UNIX_EPOCH}, +}; + +use serde::Deserialize; +use thiserror::Error; +use tokio::sync::Mutex; + +#[derive(Debug, Clone)] +pub struct ExchangeRateSnapshot { + pub conversion_rates: HashMap, + pub time_next_update_unix: i64, +} + +#[derive(Debug, Error)] +pub enum ExchangeRateError { + #[error("HTTP request failed: {0}")] + Http(#[from] reqwest::Error), + + #[error("exchange rate API error: {0}")] + Api(String), + + #[error("JSON parse error: {0}")] + Json(#[from] serde_json::Error), +} + +#[derive(Debug, Clone)] +pub struct ExchangeRateService { + client: reqwest::Client, + /// Full prefix including key, e.g. `https://v6.exchangerate-api.com/v6/{key}`. + base_url: String, + cache: Arc>>, + base_currency: String, +} + +impl ExchangeRateService { + pub fn new(api_key: &str) -> Self { + let base_url = format!("https://v6.exchangerate-api.com/v6/{}", api_key); + let client = reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(30)) + .build() + .unwrap_or_else(|_| reqwest::Client::new()); + + Self { + client, + base_url, + cache: Arc::new(Mutex::new(HashMap::new())), + base_currency: "USD".to_string(), + } + } + + pub async fn get_snapshot(&self) -> Result { + let base = normalize_currency_code(&self.base_currency); + + let mut guard = self.cache.lock().await; + if let Some(s) = guard.get(&base) { + if cache_is_fresh(s) { + return Ok(s.clone()); + } + } + + let snapshot = self.fetch_latest(&base).await?; + guard.insert(base, snapshot.clone()); + Ok(snapshot) + } + + async fn fetch_latest(&self, base: &str) -> Result { + let url = format!("{}/latest/{}", self.base_url, base); + let response = self.client.get(&url).send().await?.error_for_status()?; + let text = response.text().await?; + let parsed: ExchangeRateApiV6Response = serde_json::from_str(&text)?; + + if parsed.result != "success" { + let detail = parsed.error_type.unwrap_or_else(|| "unknown error".to_string()); + return Err(ExchangeRateError::Api(format!("{}: {}", parsed.result, detail))); + } + + let conversion_rates = parsed + .conversion_rates + .ok_or_else(|| ExchangeRateError::Api("missing conversion_rates in success body".to_string()))?; + let time_next = parsed + .time_next_update_unix + .ok_or_else(|| ExchangeRateError::Api("missing time_next_update_unix".to_string()))?; + + Ok(ExchangeRateSnapshot { + conversion_rates, + time_next_update_unix: i64::try_from(time_next).unwrap_or(i64::MAX), + }) + } +} + +#[cfg(test)] +impl ExchangeRateService { + fn new_test(base_url: String) -> Self { + let client = reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(30)) + .build() + .unwrap_or_else(|_| reqwest::Client::new()); + Self { + client, + base_url, + cache: Arc::new(Mutex::new(HashMap::new())), + base_currency: "USD".to_string(), + } + } +} + +fn normalize_currency_code(s: &str) -> String { + s.trim().to_uppercase() +} + +fn now_unix_seconds() -> i64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|d| d.as_secs() as i64) + .unwrap_or(0) +} + +fn cache_is_fresh(snapshot: &ExchangeRateSnapshot) -> bool { + if snapshot.time_next_update_unix <= 0 { + return false; + } + now_unix_seconds() < snapshot.time_next_update_unix +} + +#[derive(Deserialize)] +struct ExchangeRateApiV6Response { + result: String, + conversion_rates: Option>, + time_next_update_unix: Option, + #[allow(dead_code)] + time_last_update_unix: Option, + #[serde(rename = "error-type", default)] + error_type: Option, +} + +#[cfg(test)] +mod tests { + use super::*; + use wiremock::matchers::{method, path}; + use wiremock::{Mock, MockServer, ResponseTemplate}; + + const MOCK_FUTURE_NEXT: i64 = 4_000_000_000; + + fn success_body() -> String { + serde_json::json!({ + "result": "success", + "time_last_update_unix": 0, + "time_next_update_unix": MOCK_FUTURE_NEXT, + "base_code": "USD", + "conversion_rates": { + "USD": 1.0, + "EUR": 0.9 + } + }) + .to_string() + } + + #[test] + fn normalize_uppercases_and_trims() { + assert_eq!(normalize_currency_code(" eur ").as_str(), "EUR"); + } + + #[test] + fn cache_fresh_honors_next_update() { + let fresh = ExchangeRateSnapshot { + conversion_rates: HashMap::new(), + time_next_update_unix: MOCK_FUTURE_NEXT, + }; + let stale = ExchangeRateSnapshot { + conversion_rates: HashMap::new(), + time_next_update_unix: 0, + }; + assert!(cache_is_fresh(&fresh)); + assert!(!cache_is_fresh(&stale)); + } + + /// Second `get_snapshot` must not call the network again (wiremock would panic on 2+ hits if we set expect(1)). + #[tokio::test] + async fn cache_reuses_one_http_call() { + let server = MockServer::start().await; + let v6 = format!("{}/v6/test-key", server.uri()); + + Mock::given(method("GET")) + .and(path("/v6/test-key/latest/USD")) + .respond_with(ResponseTemplate::new(200).set_body_string(success_body())) + .expect(1) + .mount(&server) + .await; + + let service = ExchangeRateService::new_test(v6); + let _a = service.get_snapshot().await.unwrap(); + let _b = service.get_snapshot().await.unwrap(); + } +} diff --git a/src/services/mod.rs b/src/services/mod.rs index 0186555..15d2080 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -1,4 +1,5 @@ pub mod alert_service; +pub mod exchange_rate_service; pub mod graphql_client; pub mod raid_leaderboard_service; pub mod risk_checker_service; diff --git a/src/services/signature_service.rs b/src/services/signature_service.rs index f3c0bf4..fb7141b 100644 --- a/src/services/signature_service.rs +++ b/src/services/signature_service.rs @@ -51,6 +51,7 @@ impl SignatureService { #[cfg(test)] mod tests { use super::*; + use qp_rusty_crystals_dilithium::SensitiveBytes32; use quantus_cli::qp_dilithium_crypto::types::DilithiumPublic; use sp_core::crypto::Ss58Codec; use std::convert::TryFrom; @@ -68,11 +69,11 @@ mod tests { #[test] fn sign_and_verify_roundtrip() { let msg = b"quantus-signature-test"; - let entropy = [7u8; 32]; + let entropy = SensitiveBytes32::from(&mut [7u8; 32]); let hedge = [2u8; 32]; - let kp = qp_rusty_crystals_dilithium::ml_dsa_87::Keypair::generate(&entropy); + let kp = qp_rusty_crystals_dilithium::ml_dsa_87::Keypair::generate(entropy); let pk = kp.public.to_bytes(); - let sig = kp.sign(msg, None, Some(hedge)); + let sig = kp.sign(msg, None, Some(hedge)).unwrap(); let pk_hex = hex::encode(pk); let sig_hex = hex::encode(sig); assert!(SignatureService::verify_message(msg, &sig_hex, &pk_hex).unwrap()); diff --git a/src/utils/generate_referral_code.rs b/src/utils/generate_referral_code.rs index d761413..b0849d3 100644 --- a/src/utils/generate_referral_code.rs +++ b/src/utils/generate_referral_code.rs @@ -1,4 +1,4 @@ -use qp_human_checkphrase::{address_to_checksum, load_bip39_list}; +use qp_human_checkphrase::{address_to_checksum, load_word_list}; use std::sync::OnceLock; use tokio::task; @@ -12,8 +12,7 @@ pub async fn generate_referral_code(address: String) -> Result AppState { let twitter_gateway = RusxGateway::new(config.x_oauth.clone(), None).unwrap(); let graphql_client = GraphqlClient::new(db.clone(), config.candidates.graphql_url.clone()); let risk_checker_service = RiskCheckerService::new(&config.risk_checker); + let exchange_rate_service = ExchangeRateService::new(&config.exchange_rate.api_key); let db = Arc::new(db); AppState { @@ -26,6 +30,7 @@ pub async fn create_test_app_state() -> AppState { WalletConfigService::new(config.remote_configs.wallet_configs_file.clone()).unwrap(), ), risk_checker_service: Arc::new(risk_checker_service), + exchange_rate_service: Arc::new(exchange_rate_service), config: Arc::new(config), twitter_gateway: Arc::new(twitter_gateway), oauth_sessions: Arc::new(Mutex::new(std::collections::HashMap::new())), From 4722826b9f32c11add69fa7e1023c8511cd6cde0 Mon Sep 17 00:00:00 2001 From: Beast Date: Tue, 28 Apr 2026 11:31:25 +0800 Subject: [PATCH 2/4] fix: test --- Cargo.lock | 5 +- Cargo.toml | 36 +- ...o_checksum_bip39.txt => final_wordlist.txt | 882 +++++++++--------- 3 files changed, 462 insertions(+), 461 deletions(-) rename crypto_checksum_bip39.txt => final_wordlist.txt (75%) diff --git a/Cargo.lock b/Cargo.lock index f749867..011628c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5696,9 +5696,8 @@ dependencies = [ [[package]] name = "qp-human-checkphrase" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfbb513548cf4dc69a0e2249d3e18e2001dad4ebc39c008c4f1631504f0bbb2a" +version = "2.0.1" +source = "git+https://github.com/Quantus-Network/qp-human-checkphrase?tag=v2.0.1#57ac13fd7f92afe8e89da30b94fc69fd68125f6f" dependencies = [ "hex", "hmac 0.12.1", diff --git a/Cargo.toml b/Cargo.toml index 85366aa..b07f7e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,32 +21,32 @@ path = "src/bin/create_raid.rs" [dependencies] # Quantus crates -qp-human-checkphrase = "2.0.0" +qp-human-checkphrase = { git = "https://github.com/Quantus-Network/qp-human-checkphrase", tag = "v2.0.1" } qp-rusty-crystals-dilithium = "2.4.0" quantus-cli = "1.3.3" -rusx = {git = "https://github.com/Quantus-Network/rusx", tag = "v0.6.1"} +rusx = { git = "https://github.com/Quantus-Network/rusx", tag = "v0.6.1" } # Async runtime -tokio = {version = "1.46", features = ["full", "test-util"]} +tokio = { version = "1.46", features = ["full", "test-util"] } # HTTP server -axum = {version = "0.7", features = ["tokio"]} +axum = { version = "0.7", features = ["tokio"] } tower = "0.4" tower-cookies = "0.10" -tower-http = {version = "0.5", features = ["cors", "trace"]} +tower-http = { version = "0.5", features = ["cors", "trace"] } # Serialization -serde = {version = "1.0.228", features = ["derive"]} +serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.145" # Database -sqlx = {version = "0.7", features = [ +sqlx = { version = "0.7", features = [ "runtime-tokio-rustls", "chrono", "postgres", "uuid", "migrate", -]} +] } # Signature verification # Using quantus-cli's dilithium crypto for signature verification @@ -55,26 +55,26 @@ sp-core = "39.0.0" sp-runtime = "45.0.0" # Configuration -clap = {version = "4.5", features = ["derive"]} +clap = { version = "4.5", features = ["derive"] } config = "0.14" toml = "0.9" # Time handling -chrono = {version = "0.4", features = ["serde"]} +chrono = { version = "0.4", features = ["serde"] } # Random generation rand = "0.9" -uuid = {version = "1.6", features = ["v4", "serde"]} +uuid = { version = "1.6", features = ["v4", "serde"] } # HTTP client for GraphQL -reqwest = {version = "0.11", features = ["json"]} +reqwest = { version = "0.11", features = ["json"] } # Ethereum / ENS -alloy = {version = "1.8", features = ["providers", "provider-http", "ens"]} +alloy = { version = "1.8", features = ["providers", "provider-http", "ens"] } # Logging tracing = "0.1" -tracing-subscriber = {version = "0.3", features = ["env-filter"]} +tracing-subscriber = { version = "0.3", features = ["env-filter"] } # Error handling anyhow = "1.0" @@ -85,13 +85,15 @@ argon2 = "0.5" dirs = "6.0" jsonwebtoken = "9.3.1" lazy_static = "1.5.0" -prometheus = {version = "0.14.0", features = ["process"]} +prometheus = { version = "0.14.0", features = ["process"] } subxt = "0.43.0" -tiny-keccak = {version = "2.0.2", features = ["keccak"]} +tiny-keccak = { version = "2.0.2", features = ["keccak"] } notify = "8.2.0" [dev-dependencies] mockall = "0.13" wiremock = "0.5" # Enable the testing feature ONLY for tests -rusx = {git = "https://github.com/Quantus-Network/rusx", tag = "v0.6.1", features = ["testing"]} +rusx = { git = "https://github.com/Quantus-Network/rusx", tag = "v0.6.1", features = [ + "testing", +] } diff --git a/crypto_checksum_bip39.txt b/final_wordlist.txt similarity index 75% rename from crypto_checksum_bip39.txt rename to final_wordlist.txt index 942040e..77530e1 100644 --- a/crypto_checksum_bip39.txt +++ b/final_wordlist.txt @@ -1,4 +1,3 @@ -abandon ability able about @@ -6,40 +5,47 @@ above absent absorb abstract -absurd -abuse +abundance access -accident +acclaim account -accuse +accurate +ace achieve -acid acoustic acquire across act action +active actor actress actual adapt add -addict address +adequate adjust +admire admit -adult +adopt +adorable advance +adventurous advice +advocate aerobic -affair +affection +affinity +affluence afford -afraid again age agent +agile agree ahead +aid aim air airport @@ -49,27 +55,37 @@ album alcohol alert alien +alight +alive all alley allow +allure +ally almost alone alpha already also alter +altruistic always amateur amazing +ambitious +ameliorate +amenity +amiable +amicable among amount amused analyst anchor ancient -anger +anew +angel angle -angry animal ankle announce @@ -78,23 +94,18 @@ another answer antenna antique -anxiety any -apart -apology appear apple approve april arch arctic +ardent +ardor area arena -argue -arm -armed armor -army around arrange arrest @@ -106,59 +117,66 @@ artist artwork ask aspect -assault +aspiration asset assist assume -asthma +astound +astute athlete atom -attack attend attitude attract +attune auction audit august aunt +aura +auspicious +authentic author auto autumn -average +available +avid avocado -avoid awake aware away +awe awesome -awful -awkward axis baby bachelor -bacon +backed badge bag balance balcony ball +balloon bamboo banana banner bar -barely bargain +barn barrel base basic basket -battle beach +beam bean +bear beauty because +beckon become -beef +beetle +befit before begin behave @@ -168,28 +186,27 @@ below belt bench benefit +benifits best -betray better between beyond bicycle bid +big bike -bind biology bird birth -bitter black blade -blame +blameless blanket -blast -bleak -bless -blind -blood +blessing +bliss +blithe +blockbuster +bloom blossom blouse blue @@ -199,10 +216,12 @@ board boat body boil -bomb +bold +bolster bone bonus book +boom boost border boring @@ -223,10 +242,10 @@ brick bridge brief bright +brilliance bring brisk broccoli -broken bronze broom brother @@ -235,15 +254,16 @@ brush bubble buddy budget -buffalo +buff build bulb bulk -bullet +bullish bundle -bunker +buoyant burden burger +burning burst bus business @@ -255,45 +275,37 @@ cabbage cabin cable cactus -cage cake -call calm +camel camera camp -can canal -cancel candy -cannon canoe canvas canyon capable capital captain -car carbon -card +care +carefree cargo carpet carry -cart -case cash -casino castle casual -cat catalog catch category -cattle caught cause caution cave ceiling +celebrate celery cement census @@ -309,24 +321,25 @@ chapter charge chase chat -cheap check cheese chef cherry chest -chicken +chic chief child +chill +chime chimney +chivalrous choice choose -chronic chuckle chunk churn -cigar cinnamon +cipher circle citizen city @@ -334,24 +347,20 @@ civil claim clap clarify -claw -clay +classy clean -clerk clever click client cliff climb clinic -clip clock clog close cloth cloud clown -club clump cluster clutch @@ -360,6 +369,7 @@ coast coconut code coffee +cohere coil coin collect @@ -381,6 +391,7 @@ control convince cook cool +cooperate copper copy coral @@ -396,39 +407,31 @@ course cousin cover coyote -crack +cozy cradle craft -cram crane -crash crater crawl -crazy cream credit creek -crew cricket -crime crisp -critic crop cross crouch crowd crucial -cruel cruise -crumble +crumb crunch crush -cry crystal cube culture -cup cupboard +cure curious current curtain @@ -438,36 +441,36 @@ custom cute cycle dad -damage -damp dance -danger +dank +dao daring +darling dash daughter +dauntless dawn day +dazzling deal +dear debate debris decade december +decent decide -decline decorate -decrease +decrypt +dedicated deer defense define -defy +deft degree -delay +delectable deliver -demand -demise -denial dentist -deny depart depend deposit @@ -478,40 +481,33 @@ describe desert design desk -despair -destroy detail detect develop device devote +dew +dextrous diagram dial -diamond diary dice diesel -diet differ digital dignity -dilemma +diligent dinner dinosaur +diplomat direct -dirt -disagree discover -disease dish -dismiss -disorder display distance divert divide -divorce -dizzy +divine doctor document dog @@ -525,9 +521,7 @@ door dose double dove -draft dragon -drama drastic draw dream @@ -535,14 +529,13 @@ dress drift drill drink -drip drive drop drum dry duck -dumb dune +durable during dust dutch @@ -554,19 +547,24 @@ eagle early earn earth -easily +ease east easy echo ecology economy +ecstatic edge edit educate +effective +efficient effort egg eight either +elan +elated elbow elder electric @@ -574,89 +572,98 @@ elegant element elephant elevator +elf elite +eloquence else embark +ember embody embrace emerge +eminence emotion +empathize +empire employ empower empty enable enact -end +enchant +encourage +encrypt +endear endless endorse -enemy energy -enforce engage engine +engrossing enhance enjoy +enlightened enlist enough enrich enroll ensure enter -entire +entice entry envelope episode +epoch equal equip -era erase -erode -erosion -error -erupt +ergonomic escape essay essence estate +esteem eternal ethics +euphoria +evenly +everlasting evidence -evil +evocative evoke evolve exact +exalt example excess exchange excite -exclude -excuse -execute +exellent +exemplar exercise -exhaust exhibit -exile exist exit +exonerate exotic expand expect expire explain -expose express +exquisite extend extra -eye +exuberance +exultant eyebrow fabric +fabulous face +facilitate faculty -fade -faint +fair faith -fall -false fame family famous @@ -664,30 +671,33 @@ fan fancy fantasy farm +fascinate fashion -fat -fatal +fast father -fatigue -fault +faucet +faultless favorite +fearless +feasible feature february -federal -fee -feed feel +feisty +felicity female fence +fertile +fervent festival fetch -fever few fiber fiction +fidelity field +fiery figure -file film filter final @@ -704,32 +714,38 @@ fit fitness fix flag +flair flame +flamingo flash flat flavor -flee +flawless +flex +flexible flight flip float flock floor +flourish flower +fluent fluid -flush -fly +flutter +flying foam focus fog -foil fold follow +fond food -foot -force +foolproof forest forget fork +formidable fortune forum forward @@ -737,41 +753,40 @@ fossil foster found fox -fragile +fragrant frame +free frequent fresh friend -fringe +frisky frog +frolic front -frost -frown frozen fruit fuel +fulfill fun funny furnace -fury future gadget gain galaxy gallery +galore game gap -garage -garbage garden garlic garment -gas -gasp gate gather -gauge gaze +gecko +geekier +gem general genius genre @@ -787,22 +802,27 @@ giraffe girl give glad -glance -glare +glamorous glass +glee glide glimpse +glisten +glitter globe -gloom glory glove glow glue goat +god goddess +godlike +godsend gold good goose +gorgeous gorilla gospel gossip @@ -811,45 +831,50 @@ gown grab grace grain -grant +grand grape grass +grateful gravity great green grid -grief +grin +gripping grit grocery group grow -grunt guard guess guide -guilt guitar -gun +gush +gusto +gutsy gym habit +hail hair +halcyon half -hammer +hallmark hamster hand happy harbor -hard -harsh +hardy +harmony harvest +hash hat +haunting have hawk -hazard head health heart -heavy +heaven hedgehog height hello @@ -859,6 +884,7 @@ hen hero hidden high +hilarious hill hint hip @@ -866,230 +892,235 @@ hire history hobby hockey -hold -hole +hodl holiday hollow +holy +homage home honey +honor hood +hooray hope +hoping horn -horror horse hospital host +hot +hotcake hotel -hour +hottest hover hub +hug huge +hugs human humble +humility humor hundred -hungry -hunt -hurdle -hurry -hurt husband hybrid +hydra ice icon idea identify -idle -ignore -ill -illegal -illness +idyllic +illuminate image imitate +immaculate immense +immortal immune impact -impose +impeccable +important +impressed improve impulse inch include income increase +incredible index indicate indoor industry +inestimable infant -inflict +influential inform +ingenious inhale inherit initial -inject -injury -inmate inner innocent input inquiry -insane -insect inside inspire install intact interest into +intricate +intuitive +invaluable invest invite involve +invulnerable iron +irreplaceable island isolate -issue item ivory jacket jaguar jar jazz -jealous jeans jelly +jellyfish jewel job join joke +jolly journey +jovial joy -judge +joyful +joyous +jubilant +judicious juice jump jungle junior -junk just kangaroo keen keep -ketchup key +keypair kick -kid kidney kind kingdom kiss -kit -kitchen -kite kitten kiwi knee knife knock know -lab +kudos label -labor ladder lady lake lamp +landmark language laptop large later latin +laud laugh laundry lava -law +lavender +lavish lawn lawsuit layer -lazy leader leaf +lean learn -leave -lecture -left -leg -legal +ledger legend leisure lemon +lemur lend length +lenient lens leopard lesson letter level -liar +levity liberty library -license -life +lifesaver lift light +likable like +liking +lily limb limit link lion liquid list -little +lit live lizard load -loan lobster local -lock logic -lonely long loop lottery -loud lounge +lovable love +loving loyal +lucid lucky +lucrative luggage lumber +luminous lunar lunch +lush +luster luxury lyrics machine -mad magic magnet -maid -mail main +majestic major make mammal -man -manage -mandate mango mansion -manual maple marble march -margin marine market marriage -mask +marvel mass master match @@ -1097,15 +1128,14 @@ material math matrix matter +mature maximum maze meadow -mean +meaningful measure -meat mechanic medal -media melody melt member @@ -1115,88 +1145,81 @@ menu mercy merge merit +merkle merry mesh +mesmerize message metal method -middle +meticulous midnight +mightily milk million mimic mind -minimum -minor +mint minute miracle mirror +mirth misery -miss -mistake -mix -mixed +mist mixture mobile model modify -mom moment monitor monkey -monster month +monumental moon moral more morning -mosquito mother motion motor mountain mouse move -movie much muffin -mule multiply muscle museum mushroom music -must mutual myself mystery myth -naive name -napkin narrow -nasty -nation nature +navigable near +neat neck need -negative -neglect -neither nephew nerve nest -net network neutral never news next +nft nice +nifty night +nimble noble -noise +node +noiseless nominee noodle normal @@ -1206,79 +1229,77 @@ notable note nothing notice +nourish novel now nuclear number nurse +nurturing nut oak -obey +oasis object oblige -obscure observe obtain obvious occur ocean october -odor -off +octopus offer office often -oil -okay -old olive olympic -omit once -one onion online only open opera -opinion -oppose option +opulent orange +orangutan orbit orchard order -ordinary organ orient original -orphan ostrich other outdoor outer output +outreach outside +outwit oval +ovation oven -over -own -owner +overjoyed +owl oxygen oyster ozone pact paddle page +painless pair palace palm panda panel panic +panoramic panther paper parade +pardon parent park parrot @@ -1287,25 +1308,23 @@ pass patch path patient -patrol +patriot pattern -pause pave payment peace peanut pear -peasant +peerless pelican -pen -penalty pencil people pepper perfect -permit +permissible person pet +phenomenal phone photo phrase @@ -1314,67 +1333,67 @@ piano picnic picture piece -pig +piety pigeon -pill pilot pink +pinnacle pioneer -pipe -pistol pitch pizza place planet -plastic plate play -please +pleasant pledge +plentiful pluck -plug -plunge +plus poem -poet +poetic +poignant point +poise polar -pole -police +polished pond pony pool +poppy popular portion +posh position possible post potato pottery -poverty -powder power practice praise +pray +precious predict +preeminent prefer +premier prepare present pretty -prevent -price -pride +priceless primary print priority -prison -private +privilege prize -problem +proactive process produce profit program project +prolific promote proof property @@ -1382,21 +1401,20 @@ prosper protect proud provide +prowess +prudence public pudding -pull -pulp pulse pumpkin -punch +punctual pupil puppy -purchase +pure purity purpose purse push -put puzzle pyramid quality @@ -1404,31 +1422,28 @@ quantum quarter question quick -quit -quiz +quiet quote rabbit raccoon -race -rack radar radio -rail rain raise rally ramp ranch -random -range rapid +rapport +rapture rare -rate -rather +ratified raven raw razor +reach ready +reaffirm real reason rebel @@ -1436,16 +1451,19 @@ rebuild recall receive recipe +reclaim record -recycle -reduce +rectified +redeemed +refine reflect reform -refuse +refresh +regal region -regret regular -reject +rejoice +rejuvenate relax release relief @@ -1453,21 +1471,22 @@ rely remain remember remind -remove +remunerate +renaissance render renew -rent +renown reopen repair repeat replace -report -require +reputable rescue resemble -resist +resilient resource response +restful result retire retreat @@ -1475,52 +1494,45 @@ return reunion reveal review +revolution reward rhythm -rib ribbon -rice rich ride -ridge -rifle right -rigid +rigorous ring -riot ripple -risk +rise ritual -rival river road -roast robot robust rocket +rollup romance roof -rookie room rose +rosy rotate rough round route royal rubber -rude -rug -rule -run runway rural -sad saddle -sadness safe +sage sail +saint salad +salamander +salient salmon salon salt @@ -1530,26 +1542,20 @@ sample sand satisfy satoshi -sauce -sausage save -say +savings +savvy scale scan -scare -scatter scene -scheme school science scissors -scorpion scout -scrap screen script -scrub sea +seamless search season seat @@ -1561,53 +1567,42 @@ seed seek segment select -sell seminar senior sense sentence +serene series service session -settle setup seven -shadow shaft -shallow share -shed shell -sheriff shield shift +shimmering shine ship shiver shock shoe -shoot shop -short shoulder -shove shrimp -shrug shuffle -shy sibling -sick side siege -sight -sign +significant silent silk silly silver similar simple -since +sincere sing siren sister @@ -1620,52 +1615,46 @@ ski skill skin skirt -skull slab -slam sleep slender -slice +slick slide -slight -slim -slogan slot -slow slush small smart smile -smoke +smitten smooth snack -snake -snap -sniff +snappy +snazzy snow soap +sober soccer social -sock -soda soft solar -soldier solid solution solve someone song soon -sorry +soothe +sophisticated sort soul sound soup source south +sovereign space spare +sparkle spatial spawn speak @@ -1675,10 +1664,9 @@ spell spend sphere spice -spider -spike spin spirit +splendid split spoil sponsor @@ -1688,7 +1676,6 @@ spot spray spread spring -spy square squeeze squirrel @@ -1697,41 +1684,47 @@ stadium staff stage stairs +stake stamp stand -start +star state +staunch stay -steak +steadfast steel +stellar stem step stereo -stick still -sting +stimulate +stirring stock stomach stone -stool story +stout stove strategy street -strike +striking +stroll strong -struggle student stuff -stumble +stunning +sturdier style +suave subject -submit +sublime +subsidize subway success such sudden -suffer +suffice sugar suggest suit @@ -1740,21 +1733,22 @@ sun sunny sunset super +supple supply supreme +supurb sure surface surge +surmount surprise surround survey -suspect sustain swallow -swamp +swank swap swarm -swear sweet swift swim @@ -1767,13 +1761,12 @@ syrup system table tackle +tact tag tail talent talk -tank -tape -target +tap task taste tattoo @@ -1783,10 +1776,10 @@ team tell ten tenant +tender tennis -tent term -test +terrific text thank that @@ -1799,19 +1792,20 @@ thing this thought three +thrill thrive throw thumb thunder ticket -tide +tidy tiger tilt timber time +tingle tiny tip -tired tissue title toast @@ -1820,8 +1814,8 @@ today toddler toe together -toilet token +tolerant tomato tomorrow tone @@ -1831,12 +1825,15 @@ tool tooth top topic +topnotch topple +tops torch tornado tortoise toss total +tough tourist toward tower @@ -1844,25 +1841,21 @@ town toy track trade -traffic -tragic train -transfer -trap -trash +tranquil travel tray treat tree +tremendous trend trial tribe -trick trigger trim -trip +triumph +trivial trophy -trouble truck true truly @@ -1871,8 +1864,7 @@ trust truth try tube -tuition -tumble +tulip tuna tunnel turkey @@ -1885,50 +1877,59 @@ twin twist two type -typical -ugly +ultimate +ultra umbrella -unable -unaware +unabashed +unaffected +unbeatable +unbiased +unbound uncle uncover +undamaged +undefeated under -undo -unfair +undisputable +unfazed unfold -unhappy -uniform +unified unique -unit +unity universe -unknown +unlimited unlock +unmatched +unparalleled +unquestionable +unreal +unrivaled until unusual unveil +unwavering +upbeat update upgrade +upheld uphold +uplift upon -upper -upset +upscale +upside urban urge +usable usage -use -used useful -useless usual utility -vacant vacuum -vague valid valley +valor +valuable valve -van -vanish vapor various vast @@ -1936,6 +1937,7 @@ vault vehicle velvet vendor +venerate venture venue verb @@ -1943,68 +1945,59 @@ verify version very vessel -veteran +vested viable vibrant -vicious victory video view +vigilant +vigor village vintage violin virtual -virus visa visit visual vital +vivacious vivid vocal voice -void volcano volume -vote +vouch voyage -wage -wagon -wait walk wall walnut want -warfare warm warrior wash -wasp -waste water wave way wealth -weapon wear -weasel weather web wedding weekend -weird welcome +well west wet whale what -wheat wheel when where -whip whisper +whole wide -width +wieldy wife wild will @@ -2014,35 +2007,42 @@ wine wing wink winner +wins winter wire wisdom wise wish witness +witty wolf woman +wombat +won wonder wood wool word work world -worry worth +wow wrap -wreck -wrestle wrist write -wrong +yak yard year yellow -you +yes +yield young youth +yummy +zeal zebra -zero +zen +zest +zippy zone -zoo +zoo \ No newline at end of file From e1d2b9ce280149708202eba3a71531f6edfdddcb Mon Sep 17 00:00:00 2001 From: Beast Date: Fri, 1 May 2026 18:11:00 +0800 Subject: [PATCH 3/4] fix: PR review issues - checkphrase wordlist - error leaking key - etc --- final_wordlist.txt | 4 +-- src/errors.rs | 14 ++++++-- src/handlers/exchange_rate.rs | 10 +++--- src/routes/exchange_rate.rs | 2 +- src/routes/mod.rs | 2 +- src/services/exchange_rate_service.rs | 50 +++++++++++++++++---------- 6 files changed, 52 insertions(+), 30 deletions(-) diff --git a/final_wordlist.txt b/final_wordlist.txt index 77530e1..042851e 100644 --- a/final_wordlist.txt +++ b/final_wordlist.txt @@ -892,7 +892,7 @@ hire history hobby hockey -hodl +hold holiday hollow holy @@ -1199,6 +1199,7 @@ myth name narrow nature +nautilus navigable near neat @@ -1212,7 +1213,6 @@ neutral never news next -nft nice nifty night diff --git a/src/errors.rs b/src/errors.rs index a048c75..a2f2943 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -211,8 +211,16 @@ fn map_risk_checker_error(err: RiskCheckerError) -> (StatusCode, String) { fn map_exchange_rate_error(err: ExchangeRateError) -> (StatusCode, String) { match err { - ExchangeRateError::Api(err) => (StatusCode::BAD_REQUEST, err), - ExchangeRateError::Http(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()), - ExchangeRateError::Json(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()), + ExchangeRateError::Api(_) | ExchangeRateError::Http(_) => { + (StatusCode::BAD_GATEWAY, "Failed to fetch exchange rates".to_string()) + } + ExchangeRateError::Json(_) => ( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to parse exchange rate response".to_string(), + ), + ExchangeRateError::Cache(_) => ( + StatusCode::INTERNAL_SERVER_ERROR, + "An internal server error occurred".to_string(), + ), } } diff --git a/src/handlers/exchange_rate.rs b/src/handlers/exchange_rate.rs index baab52c..b4a75c0 100644 --- a/src/handlers/exchange_rate.rs +++ b/src/handlers/exchange_rate.rs @@ -1,12 +1,12 @@ +use crate::{ + handlers::SuccessResponse, http_server::AppState, services::exchange_rate_service::ExchangeRateSnapshot, AppError, +}; use axum::{extract::State, Json}; -use serde_json::json; - -use crate::{handlers::SuccessResponse, http_server::AppState, AppError}; pub async fn handle_get_exchange_rate( State(state): State, -) -> Result>, AppError> { +) -> Result>, AppError> { let exchange_rate = state.exchange_rate_service.get_snapshot().await?; - Ok(SuccessResponse::new(json!(exchange_rate.conversion_rates))) + Ok(SuccessResponse::new(exchange_rate)) } diff --git a/src/routes/exchange_rate.rs b/src/routes/exchange_rate.rs index 0256b39..7041ed6 100644 --- a/src/routes/exchange_rate.rs +++ b/src/routes/exchange_rate.rs @@ -3,5 +3,5 @@ use axum::{routing::get, Router}; use crate::{handlers::exchange_rate::handle_get_exchange_rate, http_server::AppState}; pub fn exchange_rate_routes() -> Router { - Router::new().route("/exchange-rate", get(handle_get_exchange_rate)) + Router::new().route("/exchange-rates", get(handle_get_exchange_rate)) } diff --git a/src/routes/mod.rs b/src/routes/mod.rs index d95fa34..fec470e 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -29,8 +29,8 @@ pub fn api_routes(state: AppState) -> Router { .merge(auth_routes(state.clone())) .merge(relevant_tweet_routes(state.clone())) .merge(tweet_author_routes(state.clone())) - .merge(config_routes()) .merge(raid_quest_routes(state)) + .merge(config_routes()) .merge(risk_checker_routes()) .merge(exchange_rate_routes()) } diff --git a/src/services/exchange_rate_service.rs b/src/services/exchange_rate_service.rs index 48c3a9e..ef54c8e 100644 --- a/src/services/exchange_rate_service.rs +++ b/src/services/exchange_rate_service.rs @@ -1,14 +1,13 @@ use std::{ collections::HashMap, - sync::Arc, + sync::{Arc, RwLock}, time::{SystemTime, UNIX_EPOCH}, }; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use thiserror::Error; -use tokio::sync::Mutex; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ExchangeRateSnapshot { pub conversion_rates: HashMap, pub time_next_update_unix: i64, @@ -24,6 +23,9 @@ pub enum ExchangeRateError { #[error("JSON parse error: {0}")] Json(#[from] serde_json::Error), + + #[error("Cache error: {0}")] + Cache(String), } #[derive(Debug, Clone)] @@ -31,7 +33,7 @@ pub struct ExchangeRateService { client: reqwest::Client, /// Full prefix including key, e.g. `https://v6.exchangerate-api.com/v6/{key}`. base_url: String, - cache: Arc>>, + cache: Arc>>, base_currency: String, } @@ -41,12 +43,12 @@ impl ExchangeRateService { let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(30)) .build() - .unwrap_or_else(|_| reqwest::Client::new()); + .expect("TLS backend should be initialized, or the resolver should load the system configuration."); Self { client, base_url, - cache: Arc::new(Mutex::new(HashMap::new())), + cache: Arc::new(RwLock::new(None)), base_currency: "USD".to_string(), } } @@ -54,15 +56,24 @@ impl ExchangeRateService { pub async fn get_snapshot(&self) -> Result { let base = normalize_currency_code(&self.base_currency); - let mut guard = self.cache.lock().await; - if let Some(s) = guard.get(&base) { - if cache_is_fresh(s) { - return Ok(s.clone()); + { + let guard = self + .cache + .read() + .map_err(|_| ExchangeRateError::Cache("Failed to read cache".to_string()))?; + if let Some(s) = &*guard { + if cache_is_fresh(s) { + return Ok(s.clone()); + } } - } + } // guard dropped here, before any await point let snapshot = self.fetch_latest(&base).await?; - guard.insert(base, snapshot.clone()); + let mut write_guard = self + .cache + .write() + .map_err(|_| ExchangeRateError::Cache("Failed to write cache".to_string()))?; + *write_guard = Some(snapshot.clone()); Ok(snapshot) } @@ -80,13 +91,15 @@ impl ExchangeRateService { let conversion_rates = parsed .conversion_rates .ok_or_else(|| ExchangeRateError::Api("missing conversion_rates in success body".to_string()))?; - let time_next = parsed + let time_next_u64 = parsed .time_next_update_unix .ok_or_else(|| ExchangeRateError::Api("missing time_next_update_unix".to_string()))?; + let time_next_i64 = i64::try_from(time_next_u64) + .map_err(|_| ExchangeRateError::Api("time_next_update_unix is too large".to_string()))?; Ok(ExchangeRateSnapshot { conversion_rates, - time_next_update_unix: i64::try_from(time_next).unwrap_or(i64::MAX), + time_next_update_unix: time_next_i64, }) } } @@ -97,11 +110,12 @@ impl ExchangeRateService { let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(30)) .build() - .unwrap_or_else(|_| reqwest::Client::new()); + .expect("TLS backend should be initialized, or the resolver should load the system configuration."); + Self { client, base_url, - cache: Arc::new(Mutex::new(HashMap::new())), + cache: Arc::new(RwLock::new(None)), base_currency: "USD".to_string(), } } @@ -115,7 +129,7 @@ fn now_unix_seconds() -> i64 { SystemTime::now() .duration_since(UNIX_EPOCH) .map(|d| d.as_secs() as i64) - .unwrap_or(0) + .expect("Failed to get the duration since the UNIX_EPOCH") } fn cache_is_fresh(snapshot: &ExchangeRateSnapshot) -> bool { From de9397a26d4c41f90dd0c38047081c71c4ceed5c Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Sat, 2 May 2026 14:19:46 +0800 Subject: [PATCH 4/4] fix: log exchange rate errors and DRY new_test - map_exchange_rate_error now logs each variant via tracing::error! before returning a generic client message, so failures are no longer silent. Http variant is sanitised with reqwest::Error::without_url to avoid leaking the api_key in the URL. - new_test now reuses new() to avoid duplicating the client/cache construction. --- src/errors.rs | 29 ++++++++++++++++++--------- src/services/exchange_rate_service.rs | 14 +++---------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index a2f2943..e69321b 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -211,16 +211,27 @@ fn map_risk_checker_error(err: RiskCheckerError) -> (StatusCode, String) { fn map_exchange_rate_error(err: ExchangeRateError) -> (StatusCode, String) { match err { - ExchangeRateError::Api(_) | ExchangeRateError::Http(_) => { + ExchangeRateError::Api(detail) => { + tracing::error!("Exchange rate API error: {}", detail); (StatusCode::BAD_GATEWAY, "Failed to fetch exchange rates".to_string()) } - ExchangeRateError::Json(_) => ( - StatusCode::INTERNAL_SERVER_ERROR, - "Failed to parse exchange rate response".to_string(), - ), - ExchangeRateError::Cache(_) => ( - StatusCode::INTERNAL_SERVER_ERROR, - "An internal server error occurred".to_string(), - ), + ExchangeRateError::Http(e) => { + tracing::error!("Exchange rate HTTP error: {}", e.without_url()); + (StatusCode::BAD_GATEWAY, "Failed to fetch exchange rates".to_string()) + } + ExchangeRateError::Json(e) => { + tracing::error!("Exchange rate JSON parse error: {}", e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to parse exchange rate response".to_string(), + ) + } + ExchangeRateError::Cache(detail) => { + tracing::error!("Exchange rate cache error: {}", detail); + ( + StatusCode::INTERNAL_SERVER_ERROR, + "An internal server error occurred".to_string(), + ) + } } } diff --git a/src/services/exchange_rate_service.rs b/src/services/exchange_rate_service.rs index ef54c8e..0e454c2 100644 --- a/src/services/exchange_rate_service.rs +++ b/src/services/exchange_rate_service.rs @@ -107,17 +107,9 @@ impl ExchangeRateService { #[cfg(test)] impl ExchangeRateService { fn new_test(base_url: String) -> Self { - let client = reqwest::Client::builder() - .timeout(std::time::Duration::from_secs(30)) - .build() - .expect("TLS backend should be initialized, or the resolver should load the system configuration."); - - Self { - client, - base_url, - cache: Arc::new(RwLock::new(None)), - base_currency: "USD".to_string(), - } + let mut service = Self::new("test-key"); + service.base_url = base_url; + service } }