diff --git a/Cargo.lock b/Cargo.lock index 9df51f7e71..01f00f70b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -455,6 +455,9 @@ name = "arrayvec" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +dependencies = [ + "serde 1.0.195", +] [[package]] name = "ascii-canvas" @@ -608,10 +611,12 @@ checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", "axum-core", + "base64 0.21.7", "bitflags 1.3.2", "bytes", "futures-util", - "http", + "headers", + "http 0.2.9", "http-body", "hyper", "itoa", @@ -622,7 +627,13 @@ dependencies = [ "pin-project-lite", "rustversion", "serde 1.0.195", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", "sync_wrapper", + "tokio", + "tokio-tungstenite 0.20.0", "tower", "tower-layer", "tower-service", @@ -637,7 +648,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http", + "http 0.2.9", "http-body", "mime", "rustversion", @@ -910,7 +921,7 @@ dependencies = [ "fastcrypto", "framework-builder", "hex", - "http", + "http 0.2.9", "linked-hash-map", "move-binary-format", "move-bytecode-utils", @@ -935,7 +946,7 @@ dependencies = [ "serde_bytes", "serde_json", "sha3 0.9.1", - "smallvec 1.11.0", + "smallvec 1.13.2", "tracing", ] @@ -1416,7 +1427,7 @@ source = "git+https://github.com/eigerco/celestia-node-rs.git?rev=129272e8d926b4 dependencies = [ "async-trait", "celestia-types", - "http", + "http 0.2.9", "jsonrpsee 0.20.3", "serde 1.0.195", "thiserror", @@ -2008,7 +2019,7 @@ dependencies = [ "gimli 0.26.2", "log", "regalloc2", - "smallvec 1.11.0", + "smallvec 1.13.2", "target-lexicon", ] @@ -2038,7 +2049,7 @@ dependencies = [ "hashbrown 0.12.3", "indexmap 1.9.3", "log", - "smallvec 1.11.0", + "smallvec 1.13.2", ] [[package]] @@ -2055,7 +2066,7 @@ checksum = "0d70abacb8cfef3dc8ff7e8836e9c1d70f7967dfdac824a4cd5e30223415aca6" dependencies = [ "cranelift-codegen", "log", - "smallvec 1.11.0", + "smallvec 1.13.2", "target-lexicon", ] @@ -3371,7 +3382,7 @@ dependencies = [ "futures-timer", "futures-util", "hashers", - "http", + "http 0.2.9", "instant", "jsonwebtoken 8.3.0", "once_cell", @@ -3381,7 +3392,7 @@ dependencies = [ "serde_json", "thiserror", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.20.0", "tracing", "tracing-futures", "url", @@ -3737,7 +3748,7 @@ dependencies = [ "serde 1.0.195", "serde_bytes", "sha3 0.9.1", - "smallvec 1.11.0", + "smallvec 1.13.2", "tracing", ] @@ -4105,7 +4116,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.9", "indexmap 1.9.3", "slab", "tokio", @@ -4170,6 +4181,30 @@ dependencies = [ "num-traits 0.2.16", ] +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http 0.2.9", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http 0.2.9", +] + [[package]] name = "heck" version = "0.3.3" @@ -4268,6 +4303,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.5" @@ -4275,7 +4321,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", - "http", + "http 0.2.9", "pin-project-lite", ] @@ -4323,7 +4369,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.9", "http-body", "httparse", "httpdate", @@ -4343,13 +4389,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" dependencies = [ "futures-util", - "http", + "http 0.2.9", "hyper", "log", - "rustls", + "rustls 0.21.6", "rustls-native-certs", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", "webpki-roots 0.23.1", ] @@ -4766,7 +4812,7 @@ dependencies = [ "futures-timer", "futures-util", "gloo-net", - "http", + "http 0.2.9", "jsonrpsee-core 0.16.3", "jsonrpsee-types 0.16.3", "pin-project", @@ -4774,7 +4820,7 @@ dependencies = [ "soketto", "thiserror", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", "tokio-util", "tracing", "webpki-roots 0.25.2", @@ -4787,14 +4833,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b005c793122d03217da09af68ba9383363caa950b90d3436106df8cabce935" dependencies = [ "futures-util", - "http", + "http 0.2.9", "jsonrpsee-core 0.20.3", "pin-project", "rustls-native-certs", "soketto", "thiserror", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", "tokio-util", "tracing", "url", @@ -4924,7 +4970,7 @@ checksum = "cf4d945a6008c9b03db3354fb3c83ee02d2faa9f2e755ec1dfb69c3551b8f4ba" dependencies = [ "futures-channel", "futures-util", - "http", + "http 0.2.9", "hyper", "jsonrpsee-core 0.16.3", "jsonrpsee-types 0.16.3", @@ -4983,7 +5029,7 @@ version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e1b3975ed5d73f456478681a417128597acd6a2487855fdb7b4a3d4d195bf5e" dependencies = [ - "http", + "http 0.2.9", "jsonrpsee-client-transport 0.16.3", "jsonrpsee-core 0.16.3", "jsonrpsee-types 0.16.3", @@ -4995,7 +5041,7 @@ version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bca9cb3933ccae417eb6b08c3448eb1cb46e39834e5b503e395e5e5bd08546c0" dependencies = [ - "http", + "http 0.2.9", "jsonrpsee-client-transport 0.20.3", "jsonrpsee-core 0.20.3", "jsonrpsee-types 0.20.3", @@ -6014,7 +6060,7 @@ dependencies = [ "move-vm-types", "sha2 0.9.9", "sha3 0.9.1", - "smallvec 1.11.0", + "smallvec 1.13.2", "walkdir", ] @@ -6041,7 +6087,7 @@ dependencies = [ "move-vm-types", "once_cell", "sha3 0.9.1", - "smallvec 1.11.0", + "smallvec 1.13.2", ] [[package]] @@ -6146,7 +6192,7 @@ dependencies = [ "move-core-types", "once_cell", "serde 1.0.195", - "smallvec 1.11.0", + "smallvec 1.13.2", ] [[package]] @@ -6277,7 +6323,7 @@ dependencies = [ "serde_bytes", "serde_json", "sha3 0.9.1", - "smallvec 1.11.0", + "smallvec 1.13.2", "smt", ] @@ -6321,7 +6367,7 @@ dependencies = [ "serde_bytes", "serde_json", "sha3 0.9.1", - "smallvec 1.11.0", + "smallvec 1.13.2", "smt", "wasmer", ] @@ -6649,7 +6695,7 @@ dependencies = [ "num-iter", "num-traits 0.2.16", "rand 0.8.5", - "smallvec 1.11.0", + "smallvec 1.13.2", "zeroize", ] @@ -6861,7 +6907,7 @@ dependencies = [ "flagset", "futures", "getrandom 0.2.10", - "http", + "http 0.2.9", "log", "md-5", "once_cell", @@ -7077,7 +7123,7 @@ dependencies = [ "instant", "libc", "redox_syscall 0.2.16", - "smallvec 1.11.0", + "smallvec 1.13.2", "winapi", ] @@ -7090,7 +7136,7 @@ dependencies = [ "cfg-if", "libc", "redox_syscall 0.3.5", - "smallvec 1.11.0", + "smallvec 1.13.2", "windows-targets 0.48.5", ] @@ -7562,7 +7608,7 @@ dependencies = [ "parking_lot 0.12.1", "protobuf", "protobuf-codegen-pure", - "smallvec 1.11.0", + "smallvec 1.13.2", "symbolic-demangle", "tempfile", "thiserror", @@ -8227,7 +8273,7 @@ dependencies = [ "fxhash", "log", "slice-group-by", - "smallvec 1.11.0", + "smallvec 1.13.2", ] [[package]] @@ -8307,7 +8353,7 @@ dependencies = [ "hex", "hmac", "home", - "http", + "http 0.2.9", "jsonwebtoken 9.2.0", "log", "once_cell", @@ -8336,7 +8382,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.9", "http-body", "hyper", "hyper-rustls", @@ -8345,11 +8391,12 @@ dependencies = [ "js-sys", "log", "mime", + "mime_guess", "native-tls", "once_cell", "percent-encoding", "pin-project-lite", - "rustls", + "rustls 0.21.6", "rustls-native-certs", "rustls-pemfile", "serde 1.0.195", @@ -8359,7 +8406,7 @@ dependencies = [ "system-configuration", "tokio", "tokio-native-tls", - "tokio-rustls", + "tokio-rustls 0.24.1", "tokio-socks", "tokio-util", "tower-service", @@ -8738,6 +8785,40 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "rooch-faucet" +version = "0.4.1" +dependencies = [ + "anyhow", + "async-trait", + "axum", + "clap 4.5.4", + "eyre", + "futures", + "http 0.2.9", + "move-core-types", + "moveos-types", + "prometheus", + "rocksdb", + "rooch-key", + "rooch-rpc-api", + "rooch-rpc-client", + "rooch-types", + "serde 1.0.195", + "serde_json", + "serde_with 2.3.3", + "serde_yaml 0.9.25", + "serenity", + "tap", + "tempfile", + "thiserror", + "tokio", + "tower", + "tower-http", + "tracing", + "tracing-subscriber", +] + [[package]] name = "rooch-framework" version = "0.4.1" @@ -8749,7 +8830,7 @@ dependencies = [ "brotli", "fastcrypto", "hex", - "http", + "http 0.2.9", "linked-hash-map", "move-binary-format", "move-bytecode-utils", @@ -8772,7 +8853,7 @@ dependencies = [ "serde 1.0.195", "serde_bytes", "sha3 0.9.1", - "smallvec 1.11.0", + "smallvec 1.13.2", "tracing", ] @@ -8821,7 +8902,7 @@ dependencies = [ "serde_bytes", "serde_json", "sha3 0.9.1", - "smallvec 1.11.0", + "smallvec 1.13.2", "tempfile", "tracing", "tracing-subscriber", @@ -8863,7 +8944,7 @@ dependencies = [ "serde 1.0.195", "serde_bytes", "sha3 0.9.1", - "smallvec 1.11.0", + "smallvec 1.13.2", "tracing", "tracing-subscriber", ] @@ -9617,6 +9698,20 @@ dependencies = [ "sct", ] +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring 0.17.7", + "rustls-pki-types", + "rustls-webpki 0.102.4", + "subtle", + "zeroize", +] + [[package]] name = "rustls-native-certs" version = "0.6.3" @@ -9638,6 +9733,12 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + [[package]] name = "rustls-webpki" version = "0.100.2" @@ -9658,6 +9759,17 @@ dependencies = [ "untrusted 0.7.1", ] +[[package]] +name = "rustls-webpki" +version = "0.102.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +dependencies = [ + "ring 0.17.7", + "rustls-pki-types", + "untrusted 0.9.0", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -9882,6 +9994,16 @@ dependencies = [ "cc", ] +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "serde 1.0.195", + "zeroize", +] + [[package]] name = "security-framework" version = "2.9.2" @@ -10077,6 +10199,16 @@ dependencies = [ "serde 1.0.195", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde 1.0.195", +] + [[package]] name = "serde_repr" version = "0.1.17" @@ -10184,6 +10316,33 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "serenity" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64da29158bb55d70677cacd4f4f8eab1acef005fb830d9c3bea411b090e96a9" +dependencies = [ + "arrayvec 0.7.4", + "async-trait", + "base64 0.21.7", + "bitflags 2.4.2", + "bytes", + "flate2", + "futures", + "mime_guess", + "percent-encoding", + "reqwest", + "secrecy", + "serde 1.0.195", + "serde_json", + "time", + "tokio", + "tokio-tungstenite 0.21.0", + "tracing", + "typemap_rev", + "url", +] + [[package]] name = "sha-1" version = "0.9.8" @@ -10418,9 +10577,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smart-default" @@ -10509,7 +10668,7 @@ dependencies = [ "base64 0.13.1", "bytes", "futures", - "http", + "http 0.2.9", "httparse", "log", "rand 0.8.5", @@ -11196,7 +11355,18 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.6", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", "tokio", ] @@ -11231,13 +11401,29 @@ checksum = "2b2dbec703c26b00d74844519606ef15d09a7d6857860f84ad223dec002ddea2" dependencies = [ "futures-util", "log", - "rustls", + "rustls 0.21.6", "tokio", - "tokio-rustls", - "tungstenite", + "tokio-rustls 0.24.1", + "tungstenite 0.20.0", "webpki-roots 0.23.1", ] +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "rustls 0.22.4", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.25.0", + "tungstenite 0.21.0", + "webpki-roots 0.26.1", +] + [[package]] name = "tokio-util" version = "0.7.8" @@ -11351,7 +11537,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.9", "http-body", "hyper", "hyper-timeout", @@ -11402,7 +11588,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http", + "http 0.2.9", "http-body", "http-range-header", "httpdate", @@ -11505,7 +11691,7 @@ checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ "nu-ansi-term", "sharded-slab", - "smallvec 1.11.0", + "smallvec 1.13.2", "thread_local", "tracing-core", "tracing-log", @@ -11539,11 +11725,32 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 0.2.9", "httparse", "log", "rand 0.8.5", - "rustls", + "rustls 0.21.6", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.1.0", + "httparse", + "log", + "rand 0.8.5", + "rustls 0.22.4", + "rustls-pki-types", "sha1", "thiserror", "url", @@ -11576,6 +11783,12 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "typemap_rev" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74b08b0c1257381af16a5c3605254d529d3e7e109f3c62befc5d168968192998" + [[package]] name = "typenum" version = "1.16.0" @@ -12057,7 +12270,7 @@ dependencies = [ "rkyv", "self_cell", "shared-buffer", - "smallvec 1.11.0", + "smallvec 1.13.2", "thiserror", "wasmer-types", "wasmer-vm", @@ -12077,7 +12290,7 @@ dependencies = [ "gimli 0.26.2", "more-asserts 0.2.2", "rayon", - "smallvec 1.11.0", + "smallvec 1.13.2", "target-lexicon", "tracing", "wasmer-compiler", @@ -12197,6 +12410,15 @@ version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" +[[package]] +name = "webpki-roots" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "which" version = "4.4.0" diff --git a/Cargo.toml b/Cargo.toml index 6f1bec222e..b5bebc93fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ members = [ "crates/rooch-benchmarks", "crates/rooch-test-transaction-builder", "crates/data_verify", + "crates/rooch-faucet", "frameworks/framework-builder", "frameworks/framework-release", "frameworks/moveos-stdlib", @@ -53,6 +54,7 @@ default-members = [ "moveos/moveos", "frameworks/framework-release", "crates/rooch", + "crates/rooch-faucet" ] # All workspace members should inherit these keys @@ -110,6 +112,7 @@ rooch-store = { path = "crates/rooch-store" } rooch-indexer = { path = "crates/rooch-indexer" } rooch-da = { path = "crates/rooch-da" } rooch-benchmarks = { path = "crates/rooch-benchmarks" } +rooch-faucet = { path = "crates/rooch-faucet" } rooch-test-transaction-builder = { path = "crates/rooch-test-transaction-builder" } data-verify = { path = "crates/data_verify" } @@ -245,6 +248,28 @@ diesel = { version = "2.1.0", features = [ "serde_json", "64-column-tables", ] } +axum = { version = "0.6.6", default-features = false, features = [ + "headers", + "tokio", + "http1", + "http2", + "json", + "matched-path", + "original-uri", + "form", + "query", + "ws", +] } +axum-extra = "0.4.2" +axum-server = { version = "0.5.1", default-features = false, features = [ + "tls-rustls", +] } +serenity = { version = "0.12.1", default-features = false, features = [ + "client", + "gateway", + "rustls_backend", + "model", +] } diesel-derive-enum = { version = "2.0.1", features = ["sqlite"] } diesel_migrations = { version = "2.0.0" } tap = "1.0.1" diff --git a/crates/rooch-faucet/src/discord.rs b/crates/rooch-faucet/src/discord.rs index bffe15c84c..1d65b6106b 100644 --- a/crates/rooch-faucet/src/discord.rs +++ b/crates/rooch-faucet/src/discord.rs @@ -1,9 +1,9 @@ // Copyright (c) RoochNetwork // SPDX-License-Identifier: Apache-2.0 -use crate::App; +use crate::{App, FaucetRequest, FixedBTCAddressRequest, FixedRoochAddressRequest}; use clap::Parser; -use rooch_types::address::BitcoinAddress; +use rooch_types::address::{BitcoinAddress, RoochAddress}; use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandOptionType}; use serenity::async_trait; use serenity::builder::{CreateCommand, CreateCommandOption}; @@ -23,17 +23,27 @@ pub struct DiscordConfig { impl App { async fn handle_faucet_request(&self, options: &[CommandDataOption]) -> String { let value = options - .get(0) + .first() .expect("Expected address option") .value .clone(); match value { CommandDataOptionValue::String(address) => { - let btc_address = - BitcoinAddress::from_str(address.as_str()).expect("Invalid address"); + let request = match address.starts_with("0x") { + true => FaucetRequest::FixedRoochAddressRequest(FixedRoochAddressRequest { + recipient: RoochAddress::from_str(address.as_str()) + .expect("Invalid address"), + }), + false => FaucetRequest::FixedBTCAddressRequest(FixedBTCAddressRequest { + recipient: BitcoinAddress::from_str(address.as_str()) + .expect("Invalid address"), + }), + }; - if let Err(err) = self.request(btc_address).await { + let address = request.recipient().to_string(); + + if let Err(err) = self.request(request).await { tracing::error!("Failed make faucet request for {address:?}: {}", err); format!("Internal Error: Failed to send funds to {address:?}") } else { @@ -70,8 +80,12 @@ impl EventHandler for App { let command = CreateCommand::new("faucet") .description("Request funds from the faucet") .add_option( - CreateCommandOption::new(CommandOptionType::String, "address", "Your BTC address") - .required(true), + CreateCommandOption::new( + CommandOptionType::String, + "address", + "Your BTC/Rooch address", + ) + .required(true), ); let guild_command = Command::create_global_command(&ctx.http, command).await; diff --git a/crates/rooch-faucet/src/faucet.rs b/crates/rooch-faucet/src/faucet.rs index 3fcf2c5485..b525199b63 100644 --- a/crates/rooch-faucet/src/faucet.rs +++ b/crates/rooch-faucet/src/faucet.rs @@ -1,27 +1,27 @@ // Copyright (c) RoochNetwork // SPDX-License-Identifier: Apache-2.0 -use crate::metrics::FaucetMetrics; -use crate::FaucetError; +use crate::{metrics::FaucetMetrics, FaucetError, FaucetRequest}; use anyhow::Result; use clap::Parser; use move_core_types::language_storage::StructTag; +use moveos_types::transaction::MoveAction; use prometheus::Registry; use rooch_key::keystore::account_keystore::AccountKeystore; use rooch_rpc_client::wallet_context::WalletContext; -use rooch_types::address::{BitcoinAddress, MultiChainAddress, RoochAddress}; +use rooch_types::address::{MultiChainAddress, RoochAddress}; use rooch_types::authentication_key::AuthenticationKey; use rooch_types::framework::transfer::TransferModule; use std::env; use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; -use tokio::sync::{mpsc::Receiver, RwLock}; +use tokio::sync::{mpsc::Receiver, RwLock, RwLockWriteGuard}; use rooch_rpc_api::jsonrpc_types::KeptVMStatusView; -use rooch_types::error::{RoochError, RoochResult}; +use rooch_types::error::RoochError; -pub const DEFAULT_AMOUNT: u64 = 1_000_000_000; +pub const DEFAULT_AMOUNT: u64 = 1_000 * 8; #[derive(Parser, Debug, Clone)] pub struct FaucetConfig { @@ -59,14 +59,17 @@ struct State { pub struct Faucet { state: Arc>, - faucet_receiver: Arc>>, + faucet_receiver: Arc>>, } +// TODO: add queue bitch run ? +// TODO: retry ? +// TODO: record faucet address impl Faucet { pub async fn new( prometheus_registry: &Registry, config: FaucetConfig, - faucet_receiver: Receiver, + faucet_receiver: Receiver, ) -> Result { let wallet = WalletContext::new(config.wallet_config_dir.clone()).unwrap(); let _metrics = FaucetMetrics::new(prometheus_registry); @@ -91,9 +94,20 @@ impl Faucet { } async fn monitor_faucet_requests(&self) -> Result<(), FaucetError> { - while let Some(address) = self.faucet_receiver.write().await.recv().await { - if let Err(e) = self.transfer_gases(address).await { - tracing::error!("Transfer gases failed {}", e) + while let Some(request) = self.faucet_receiver.write().await.recv().await { + match request { + FaucetRequest::FixedBTCAddressRequest(req) => { + let mul_addr = MultiChainAddress::from(req.recipient); + if let Err(e) = self.transfer_gases_with_multi_addr(mul_addr).await { + tracing::error!("Transfer gases failed {}", e) + } + } + FaucetRequest::FixedRoochAddressRequest(req) => { + if let Err(e) = self.transfer_gases(req.recipient).await { + tracing::error!("Transfer gases failed {}", e) + } + } + _ => {} } } @@ -106,27 +120,17 @@ impl Faucet { // } // } - // TODO: add queue bitch run ? - // TODO: retry ? - // TODO: record faucet address - async fn transfer_gases(&self, recipient: BitcoinAddress) -> Result<(), FaucetError> { - tracing::info!("transfer gases recipient: {}", recipient); - - let state = self.state.write().await; - + async fn execute_transaction<'a>( + &self, + action: MoveAction, + state: RwLockWriteGuard<'a, State>, + ) -> Result<(), FaucetError> { let sender: RoochAddress = state.context.client_config.active_address.unwrap(); - - let move_action = TransferModule::create_transfer_coin_to_multichain_address_action( - StructTag::from_str("0x3::gas_coin::GasCoin").unwrap(), - MultiChainAddress::from(recipient), - state.config.faucet_grant_amount.into(), - ); - let pwd = state.wallet_pwd.clone(); let result = if let Some(session_key) = state.config.session_key.clone() { let tx_data = state .context - .build_tx_data(sender, move_action, None) + .build_tx_data(sender, action, None) .await .map_err(FaucetError::internal)?; let tx = state @@ -139,7 +143,7 @@ impl Faucet { } else { state .context - .sign_and_execute(sender, move_action, pwd, None) + .sign_and_execute(sender, action, pwd, None) .await }; @@ -154,4 +158,35 @@ impl Faucet { Err(e) => Err(FaucetError::transfer(e)), } } + + async fn transfer_gases_with_multi_addr( + &self, + recipient: MultiChainAddress, + ) -> Result<(), FaucetError> { + tracing::info!("transfer gases recipient: {}", recipient); + + let state = self.state.write().await; + + let move_action = TransferModule::create_transfer_coin_to_multichain_address_action( + StructTag::from_str("0x3::gas_coin::GasCoin").unwrap(), + recipient, + state.config.faucet_grant_amount.into(), + ); + + self.execute_transaction(move_action, state).await + } + + async fn transfer_gases(&self, recipient: RoochAddress) -> Result<(), FaucetError> { + tracing::info!("transfer gases recipient: {}", recipient); + + let state = self.state.write().await; + + let move_action = TransferModule::create_transfer_coin_action( + StructTag::from_str("0x3::gas_coin::GasCoin").unwrap(), + recipient.into(), + state.config.faucet_grant_amount.into(), + ); + + self.execute_transaction(move_action, state).await + } } diff --git a/crates/rooch-faucet/src/metrics.rs b/crates/rooch-faucet/src/metrics.rs index 9f1a6217ab..94ae1752f7 100644 --- a/crates/rooch-faucet/src/metrics.rs +++ b/crates/rooch-faucet/src/metrics.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use prometheus::{ - register_histogram_with_registry, register_int_counter_with_registry, - register_int_gauge_with_registry, Histogram, IntCounter, IntGauge, Registry, + register_histogram_with_registry, register_int_counter_with_registry, Histogram, IntCounter, + Registry, }; /// Prometheus metrics which can be displayed in Grafana, queried and alerted on @@ -15,16 +15,15 @@ pub struct RequestMetrics { pub(crate) total_requests_succeeded: IntCounter, pub(crate) total_requests_shed: IntCounter, pub(crate) total_requests_failed: IntCounter, - pub(crate) total_requests_disconnected: IntCounter, pub(crate) process_latency: Histogram, } /// Metrics relevant to the running of the service #[derive(Clone, Debug)] pub struct FaucetMetrics { - pub(crate) current_executions_in_flight: IntGauge, - pub(crate) total_available_coins: IntGauge, - pub(crate) total_coin_requests_succeeded: IntGauge, + // pub(crate) current_executions_in_flight: IntGauge, + // pub(crate) total_available_coins: IntGauge, + // pub(crate) total_coin_requests_succeeded: IntGauge, } const LATENCY_SEC_BUCKETS: &[f64] = &[ @@ -58,13 +57,6 @@ impl RequestMetrics { registry, ) .unwrap(), - total_requests_disconnected: register_int_counter_with_registry!( - "total_requests_disconnected", - "Total number of requests where the client disconnected before the service \ - returned a response", - registry, - ) - .unwrap(), process_latency: register_histogram_with_registry!( "process_latency", "Latency of processing a Faucet request", @@ -77,26 +69,26 @@ impl RequestMetrics { } impl FaucetMetrics { - pub fn new(registry: &Registry) -> Self { + pub fn new(_: &Registry) -> Self { Self { - current_executions_in_flight: register_int_gauge_with_registry!( - "current_executions_in_flight", - "Current number of transactions being executed in Faucet", - registry, - ) - .unwrap(), - total_available_coins: register_int_gauge_with_registry!( - "total_available_coins", - "Total number of available coins in queue", - registry, - ) - .unwrap(), - total_coin_requests_succeeded: register_int_gauge_with_registry!( - "total_coin_requests_succeeded", - "Total number of requests processed successfully in Faucet (both batch and non_batched)", - registry, - ) - .unwrap(), + // current_executions_in_flight: register_int_gauge_with_registry!( + // "current_executions_in_flight", + // "Current number of transactions being executed in Faucet", + // registry, + // ) + // .unwrap(), + // total_available_coins: register_int_gauge_with_registry!( + // "total_available_coins", + // "Total number of available coins in queue", + // registry, + // ) + // .unwrap(), + // total_coin_requests_succeeded: register_int_gauge_with_registry!( + // "total_coin_requests_succeeded", + // "Total number of requests processed successfully in Faucet (both batch and non_batched)", + // registry, + // ) + // .unwrap(), } } } diff --git a/crates/rooch-faucet/src/requests.rs b/crates/rooch-faucet/src/requests.rs index b6f1dfe46b..3b18b0cfd9 100644 --- a/crates/rooch-faucet/src/requests.rs +++ b/crates/rooch-faucet/src/requests.rs @@ -1,8 +1,9 @@ // Copyright (c) RoochNetwork // SPDX-License-Identifier: Apache-2.0 -use rooch_types::address::RoochAddress; -use serde::{Deserialize, Serialize}; +use rooch_types::address::{BitcoinAddress, EthereumAddress, RoochAddress}; +use serde::{Deserialize, Deserializer, Serialize}; +use std::str::FromStr; #[derive(Serialize, Deserialize, Debug, Clone)] pub enum FaucetRequest { @@ -11,6 +12,16 @@ pub enum FaucetRequest { FixedBTCAddressRequest(FixedBTCAddressRequest), } +impl FaucetRequest { + pub fn recipient(&self) -> &dyn std::fmt::Display { + match self { + FaucetRequest::FixedRoochAddressRequest(req) => &req.recipient, + FaucetRequest::FixedBTCAddressRequest(req) => &req.recipient, + FaucetRequest::FixedETHAddressRequest(req) => &req.recipient, + } + } +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct FixedRoochAddressRequest { pub recipient: RoochAddress, @@ -18,10 +29,28 @@ pub struct FixedRoochAddressRequest { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct FixedETHAddressRequest { - pub recipient: String, + pub recipient: EthereumAddress, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Debug, Clone)] pub struct FixedBTCAddressRequest { - pub recipient: String, + pub recipient: BitcoinAddress, +} + +impl<'de> Deserialize<'de> for FixedBTCAddressRequest { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + struct TempFixedBTCAddressRequest { + recipient: String, + } + + let temp = TempFixedBTCAddressRequest::deserialize(deserializer)?; + let recipient = + BitcoinAddress::from_str(&temp.recipient).map_err(serde::de::Error::custom)?; + + Ok(FixedBTCAddressRequest { recipient }) + } } diff --git a/crates/rooch-faucet/src/web.rs b/crates/rooch-faucet/src/web.rs index eb332a2bc5..509d59084b 100644 --- a/crates/rooch-faucet/src/web.rs +++ b/crates/rooch-faucet/src/web.rs @@ -5,7 +5,6 @@ use std::{ borrow::Cow, env, net::{IpAddr, Ipv4Addr, SocketAddr}, - str::FromStr, time::Duration, }; use tokio::sync::mpsc::Sender; @@ -14,7 +13,6 @@ use tower::ServiceBuilder; use tower_http::cors::{Any, CorsLayer}; use crate::{FaucetError, FaucetRequest, FaucetResponse}; -use rooch_types::address::BitcoinAddress; use axum::{ error_handling::HandleErrorLayer, @@ -58,15 +56,15 @@ impl Default for AppConfig { #[derive(Clone, Debug)] pub struct App { - faucet_queue: Sender, + faucet_queue: Sender, } impl App { - pub fn new(faucet_queue: Sender) -> Self { + pub fn new(faucet_queue: Sender) -> Self { Self { faucet_queue } } - pub async fn request(&self, address: BitcoinAddress) -> Result<(), FaucetError> { + pub async fn request(&self, address: FaucetRequest) -> Result<(), FaucetError> { self.faucet_queue .send(address) .await @@ -138,40 +136,37 @@ async fn request_gas( Extension(app): Extension, Json(payload): Json, ) -> impl IntoResponse { - tracing::info!("request gas payload: {:?}", payload); + let recipient = payload.recipient().to_string(); - let result = match payload { - FaucetRequest::FixedBTCAddressRequest(requests) => { - let recipient = BitcoinAddress::from_str(requests.recipient.as_str()).unwrap(); - app.request(recipient).await - } - FaucetRequest::FixedRoochAddressRequest(_) => { - return ( - StatusCode::BAD_REQUEST, - Json(FaucetResponse::from(FaucetError::NotSupport( - "Rooch".to_string(), - ))), + tracing::info!("request gas payload: {:?}", recipient); + + if let FaucetRequest::FixedETHAddressRequest(_) = payload { + tracing::warn!("request gas with ETH: {:?}", recipient); + return ( + StatusCode::BAD_REQUEST, + Json(FaucetResponse::from(FaucetError::NotSupport( + "ETH".to_string(), + ))), + ); + } + + let result = app.request(payload).await; + + match result { + Ok(()) => { + tracing::info!("request gas success: {}", recipient); + ( + StatusCode::CREATED, + Json(FaucetResponse::from("Success".to_string())), ) } - FaucetRequest::FixedETHAddressRequest(_) => { - return ( - StatusCode::BAD_REQUEST, - Json(FaucetResponse::from(FaucetError::NotSupport( - "ETH".to_string(), - ))), + Err(e) => { + tracing::info!("request gas error: {}, {:?}", recipient, e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(FaucetResponse::from(e)), ) } - }; - - match result { - Ok(()) => ( - StatusCode::CREATED, - Json(FaucetResponse::from("Success".to_string())), - ), - Err(e) => ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(FaucetResponse::from(e)), - ), } } diff --git a/docker/Dockerfile b/docker/Dockerfile index 971dd200e3..5afeb313df 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -69,6 +69,8 @@ RUN set -eux; \ ENV RELEASE_PATH="/rooch/target/release" COPY --from=builder $RELEASE_PATH/rooch \ /rooch/ +COPY --from=builder $RELEASE_PATH/rooch-faucet \ + /rooch-faucet/ ENTRYPOINT [ "/rooch/rooch" ] CMD [ "server", "start" ] diff --git a/frameworks/rooch-framework/doc/bitcoin_address.md b/frameworks/rooch-framework/doc/bitcoin_address.md index 47eb204c4f..12eed261b5 100644 --- a/frameworks/rooch-framework/doc/bitcoin_address.md +++ b/frameworks/rooch-framework/doc/bitcoin_address.md @@ -18,6 +18,7 @@ - [Function `as_bytes`](#0x3_bitcoin_address_as_bytes) - [Function `into_bytes`](#0x3_bitcoin_address_into_bytes) - [Function `to_bech32`](#0x3_bitcoin_address_to_bech32) +- [Function `new`](#0x3_bitcoin_address_new) - [Function `verify_with_pk`](#0x3_bitcoin_address_verify_with_pk) @@ -249,6 +250,17 @@ Empty address is a special address that is used to if we parse address failed fr + + +## Function `new` + + + +
public fun new(raw_addr: &vector<u8>): bitcoin_address::BitcoinAddress
+
+ + + ## Function `verify_with_pk` diff --git a/frameworks/rooch-framework/sources/address_type/bitcoin_address.move b/frameworks/rooch-framework/sources/address_type/bitcoin_address.move index d50210a8d6..f96ed85279 100644 --- a/frameworks/rooch-framework/sources/address_type/bitcoin_address.move +++ b/frameworks/rooch-framework/sources/address_type/bitcoin_address.move @@ -95,6 +95,7 @@ module rooch_framework::bitcoin_address { abort 0 } + native public fun new(raw_addr: &vector): BitcoinAddress; native public fun verify_with_pk (addr: &vector, pk: &vector): bool; #[test] diff --git a/frameworks/rooch-framework/sources/auth_validator/bitcoin_validator.move b/frameworks/rooch-framework/sources/auth_validator/bitcoin_validator.move index 1f7804ab80..af60cb7230 100644 --- a/frameworks/rooch-framework/sources/auth_validator/bitcoin_validator.move +++ b/frameworks/rooch-framework/sources/auth_validator/bitcoin_validator.move @@ -90,7 +90,7 @@ module rooch_framework::bitcoin_validator { validate_signature(payload, tx_hash); - let bitcoin_addr = bitcoin_address::from_bytes(auth_payload::from_address(payload)); + let bitcoin_addr = bitcoin_address::new(&auth_payload::from_address(payload)); let multi_chain_addr = multichain_address::from_bitcoin(bitcoin_addr); // Check if the address and public key are related diff --git a/frameworks/rooch-framework/src/natives/gas_parameter/bitcoin_address.rs b/frameworks/rooch-framework/src/natives/gas_parameter/bitcoin_address.rs index 13a53789fa..09e5190bbc 100644 --- a/frameworks/rooch-framework/src/natives/gas_parameter/bitcoin_address.rs +++ b/frameworks/rooch-framework/src/natives/gas_parameter/bitcoin_address.rs @@ -7,4 +7,6 @@ use crate::natives::rooch_framework::bitcoin_address::GasParameters; crate::natives::gas_parameter::native::define_gas_parameters_for_natives!(GasParameters, "bitcoin_address", [ [.verify_with_pk.base, "verify_with_pk.base", 1000 * MUL], [.verify_with_pk.per_byte, "verify_with_pk.per_byte", 30 * MUL], + [.new.base, "new.base", 1000 * MUL], + [.new.per_byte, "new.per_byte", 30 * MUL], ]); diff --git a/frameworks/rooch-framework/src/natives/rooch_framework/bitcoin_address.rs b/frameworks/rooch-framework/src/natives/rooch_framework/bitcoin_address.rs index 18f497228f..ecfd99e5e2 100644 --- a/frameworks/rooch-framework/src/natives/rooch_framework/bitcoin_address.rs +++ b/frameworks/rooch-framework/src/natives/rooch_framework/bitcoin_address.rs @@ -19,8 +19,40 @@ use move_vm_types::{ values::{Value, VectorRef}, }; use moveos_stdlib::natives::helpers::{make_module_natives, make_native}; +use moveos_types::state::MoveStructState; +use rooch_types::address::BitcoinAddress; use smallvec::smallvec; +pub const E_INVALID_ADDRESS: u64 = 1; + +pub fn new( + gas_params: &FromBytesGasParameters, + _context: &mut NativeContext, + _ty_args: Vec, + mut args: VecDeque, +) -> PartialVMResult { + let addr_bytes = pop_arg!(args, VectorRef); + let addr_ref = addr_bytes.as_bytes_ref(); + + let cost = gas_params.base + gas_params.per_byte * NumBytes::new(addr_ref.len() as u64); + + let Ok(addr_str) = std::str::from_utf8(&addr_ref) else { + return Ok(NativeResult::err(cost, E_INVALID_ADDRESS)); + }; + + let addr = match BitcoinAddress::from_str(addr_str) { + Ok(addr) => addr, + Err(_) => { + return Ok(NativeResult::err(cost, E_INVALID_ADDRESS)); + } + }; + + Ok(NativeResult::ok( + cost, + smallvec![Value::struct_(addr.to_runtime_value_struct())], + )) +} + /// Returns true if the given pubkey is directly related to the address payload. pub fn verify_with_pk( gas_params: &FromBytesGasParameters, @@ -86,22 +118,27 @@ impl FromBytesGasParameters { #[derive(Debug, Clone)] pub struct GasParameters { + pub new: FromBytesGasParameters, pub verify_with_pk: FromBytesGasParameters, } impl GasParameters { pub fn zeros() -> Self { Self { + new: FromBytesGasParameters::zeros(), verify_with_pk: FromBytesGasParameters::zeros(), } } } pub fn make_all(gas_params: GasParameters) -> impl Iterator { - let natives = [( - "verify_with_pk", - make_native(gas_params.verify_with_pk, verify_with_pk), - )]; + let natives = [ + ("new", make_native(gas_params.new, new)), + ( + "verify_with_pk", + make_native(gas_params.verify_with_pk, verify_with_pk), + ), + ]; make_module_natives(natives) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a848909b73..8800888d88 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -658,6 +658,12 @@ importers: '@types/ws': specifier: ^8.5.5 version: 8.5.5 + base58-js: + specifier: ^1.0.0 + version: 1.0.5 + bech32: + specifier: ^2.0.0 + version: 2.0.0 events: specifier: ^3.3.0 version: 3.3.0 @@ -1843,16 +1849,6 @@ packages: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 - /@babel/plugin-syntax-flow@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-9RdCl0i+q0QExayk2nOS7853w08yLucnnPML6EN9S8fgMPVtdLDCdx/cOQ/i44Lb9UeQX9A35yaqBBOMMZxPxQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - dev: true - /@babel/plugin-syntax-flow@7.22.5(@babel/core@7.23.0): resolution: {integrity: sha512-9RdCl0i+q0QExayk2nOS7853w08yLucnnPML6EN9S8fgMPVtdLDCdx/cOQ/i44Lb9UeQX9A35yaqBBOMMZxPxQ==} engines: {node: '>=6.9.0'} @@ -1944,16 +1940,6 @@ packages: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 - /@babel/plugin-syntax-jsx@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-plugin-utils': 7.22.5 - dev: true - /@babel/plugin-syntax-jsx@7.22.5(@babel/core@7.23.0): resolution: {integrity: sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==} engines: {node: '>=6.9.0'} @@ -3018,20 +3004,6 @@ packages: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 - /@babel/plugin-transform-react-jsx@7.22.5(@babel/core@7.22.11): - resolution: {integrity: sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.11 - '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-module-imports': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.22.11) - '@babel/types': 7.23.0 - dev: true - /@babel/plugin-transform-react-jsx@7.22.5(@babel/core@7.23.0): resolution: {integrity: sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA==} engines: {node: '>=6.9.0'} @@ -13889,8 +13861,8 @@ packages: '@babel/plugin-transform-react-jsx': ^7.14.9 eslint: ^8.1.0 dependencies: - '@babel/plugin-syntax-flow': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-react-jsx': 7.22.5(@babel/core@7.22.11) + '@babel/plugin-syntax-flow': 7.22.5(@babel/core@7.23.5) + '@babel/plugin-transform-react-jsx': 7.22.5(@babel/core@7.23.5) eslint: 8.48.0 lodash: 4.17.21 string-natural-compare: 3.0.1 diff --git a/rooch-portal-v1/src/App.tsx b/rooch-portal-v1/src/App.tsx index a034cb62ba..3d1b4d0147 100644 --- a/rooch-portal-v1/src/App.tsx +++ b/rooch-portal-v1/src/App.tsx @@ -8,7 +8,7 @@ import { createEmotionCache } from '@/utils/create-emotion-cache' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { TestNetwork } from '@roochnetwork/rooch-sdk' +import { LocalNetwork } from '@roochnetwork/rooch-sdk' import { WalletProvider, RoochClientProvider, SupportChain } from '@roochnetwork/rooch-sdk-kit' import { DashboardLayout } from '@/pages/dashboard-layout' @@ -24,7 +24,7 @@ function App() { <> - + diff --git a/sdk/typescript/rooch-sdk-kit/src/types/address/multiChainAddress.ts b/sdk/typescript/rooch-sdk-kit/src/types/address/multiChainAddress.ts index c199f06658..9aa8d3299b 100644 --- a/sdk/typescript/rooch-sdk-kit/src/types/address/multiChainAddress.ts +++ b/sdk/typescript/rooch-sdk-kit/src/types/address/multiChainAddress.ts @@ -17,6 +17,10 @@ export class MultiChainAddress implements bcs.Serializable { this.rawAddress = getAddressInfo(address).bytes } + getRawAddress(): Uint8Array { + return this.rawAddress + } + toBytes(): Uint8Array { let bs = new bcs.BcsSerializer() this.serialize(bs) diff --git a/sdk/typescript/rooch-sdk/package.json b/sdk/typescript/rooch-sdk/package.json index 776bc20a1c..dd5cf2fe3e 100644 --- a/sdk/typescript/rooch-sdk/package.json +++ b/sdk/typescript/rooch-sdk/package.json @@ -78,7 +78,9 @@ "isomorphic-fetch": "^3.0.0", "superstruct": "^1.0.3", "tweetnacl": "^1.0.3", - "@ethersproject/bytes": "^5.7.0" + "@ethersproject/bytes": "^5.7.0", + "base58-js": "^1.0.0", + "bech32": "^2.0.0" }, "publishConfig": { "access": "public", diff --git a/sdk/typescript/rooch-sdk/src/address/base58-js.d.ts b/sdk/typescript/rooch-sdk/src/address/base58-js.d.ts new file mode 100644 index 0000000000..ee52016f5a --- /dev/null +++ b/sdk/typescript/rooch-sdk/src/address/base58-js.d.ts @@ -0,0 +1,15 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +declare module 'base58-js' +declare module 'sha256-uint8array' + +declare function createHash(algorithm?: string): Hash + +declare class Hash { + update(data: string, encoding?: string): this + update(data: Uint8Array): this + update(data: ArrayBufferView): this + + digest(): Uint8Array +} diff --git a/sdk/typescript/rooch-sdk/src/address/bitcoinAddress.ts b/sdk/typescript/rooch-sdk/src/address/bitcoinAddress.ts new file mode 100644 index 0000000000..179ac3ca98 --- /dev/null +++ b/sdk/typescript/rooch-sdk/src/address/bitcoinAddress.ts @@ -0,0 +1,183 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +import { base58_to_binary } from 'base58-js' +import { bech32, bech32m } from 'bech32' +import { createHash } from 'sha256-uint8array' + +const sha256 = (payload: Uint8Array) => createHash().update(payload).digest() + +enum Network { + mainnet = 'mainnet', + testnet = 'testnet', + regtest = 'regtest', +} + +// The method used to distinguish bitcoin address payload type. +// Ref: BitcoinAddressPayloadType https://github.com/rooch-network/rooch/blob/main/crates/rooch-types/src/address.rs +enum AddressType { + p2pkh = 0, + p2sh = 1, + p2wpkh = 2, + p2wsh = 2, + p2tr = 2, +} + +type AddressInfo = { + bytes: Uint8Array + bech32: boolean + network: Network + address: string + type: AddressType +} + +const addressTypes: { [key: number]: { type: AddressType; network: Network } } = { + 0x00: { + type: AddressType.p2pkh, + network: Network.mainnet, + }, + + 0x6f: { + type: AddressType.p2pkh, + network: Network.testnet, + }, + + 0x05: { + type: AddressType.p2sh, + network: Network.mainnet, + }, + + 0xc4: { + type: AddressType.p2sh, + network: Network.testnet, + }, +} + +const parseBech32 = (address: string): AddressInfo => { + let decoded + + try { + if (address.startsWith('bc1p') || address.startsWith('tb1p') || address.startsWith('bcrt1p')) { + decoded = bech32m.decode(address) + } else { + decoded = bech32.decode(address) + } + } catch (error) { + throw new Error('Invalid address') + } + + const mapPrefixToNetwork: { [key: string]: Network } = { + bc: Network.mainnet, + tb: Network.testnet, + bcrt: Network.regtest, + } + + const network: Network = mapPrefixToNetwork[decoded.prefix] + + if (network === undefined) { + throw new Error('Invalid address') + } + + const witnessVersion = decoded.words[0] + + if (witnessVersion < 0 || witnessVersion > 16) { + throw new Error('Invalid address') + } + const data = bech32.fromWords(decoded.words.slice(1)) + + let type + + if (data.length === 20) { + type = AddressType.p2wpkh + } else if (witnessVersion === 1) { + type = AddressType.p2tr + } else { + type = AddressType.p2wsh + } + + // replace version & add witness version + let bytes = new Uint8Array(data.length + 2) + bytes.set([type]) + bytes.set([witnessVersion], 1) + bytes.set(data, 2) + + return { + bytes: bytes, + bech32: true, + network, + address, + type, + } +} + +const getAddressInfo = (address: string): AddressInfo => { + let decoded: Uint8Array + const prefix = address.substr(0, 2).toLowerCase() + + if (prefix === 'bc' || prefix === 'tb') { + return parseBech32(address) + } + + try { + decoded = base58_to_binary(address) + } catch (error) { + throw new Error('Invalid address') + } + + const { length } = decoded + + if (length !== 25) { + throw new Error('Invalid address') + } + + const version = decoded[0] + + const checksum = decoded.slice(length - 4, length) + const body = decoded.slice(0, length - 4) + + const expectedChecksum = sha256(sha256(body)).slice(0, 4) + + if (checksum.some((value: number, index: number) => value !== expectedChecksum[index])) { + throw new Error('Invalid address') + } + + const validVersions = Object.keys(addressTypes).map(Number) + + if (!validVersions.includes(version)) { + throw new Error('Invalid address') + } + + const addressType = addressTypes[version] + + // replace version + let bytes = new Uint8Array(body.length) + bytes.set([addressType.type]) + bytes.set(body.slice(1), 1) + + return { + bytes: bytes, + ...addressType, + address, + bech32: false, + } +} + +const validate = (address: string, network?: Network) => { + try { + const addressInfo = getAddressInfo(address) + + if (network) { + return network === addressInfo.network + } + + return true + } catch (error) { + return false + } +} + +export { getAddressInfo, Network, AddressType, validate } + +export type { AddressInfo } + +export default validate diff --git a/sdk/typescript/rooch-sdk/src/address/index.ts b/sdk/typescript/rooch-sdk/src/address/index.ts new file mode 100644 index 0000000000..db32268aa0 --- /dev/null +++ b/sdk/typescript/rooch-sdk/src/address/index.ts @@ -0,0 +1,5 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +export * from './bitcoinAddress' +export * from './multiChainAddress' diff --git a/sdk/typescript/rooch-sdk/src/address/multiChainAddress.test.ts b/sdk/typescript/rooch-sdk/src/address/multiChainAddress.test.ts new file mode 100644 index 0000000000..ec0c56af2d --- /dev/null +++ b/sdk/typescript/rooch-sdk/src/address/multiChainAddress.test.ts @@ -0,0 +1,47 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +import { describe, it, expect } from 'vitest' + +import { RoochMultiChainID } from '../constants' +import { MultiChainAddress } from './multiChainAddress' + +describe('multiChainAddress', () => { + it('should parse multi chain address is ok', () => { + // native swgwit p2wpkh + let p2wpkhAddress = 'bc1pq5ttgyqu5pmfn9aqt09d978mky2fndxr3ed3ntszta75g9q6xrlqlwyl0r' + let expectMultiChainAddressBytesWithP2wpkh = new Uint8Array([ + 0, 0, 0, 0, 0, 0, 0, 0, 34, 2, 1, 5, 22, 180, 16, 28, 160, 118, 153, 151, 160, 91, 202, 210, + 248, 251, 177, 20, 153, 180, 195, 142, 91, 25, 174, 2, 95, 125, 68, 20, 26, 48, 254, + ]) + let p2wpkhMulticChainAddress = new MultiChainAddress(RoochMultiChainID.Bitcoin, p2wpkhAddress) + expect(p2wpkhMulticChainAddress.toBytes()).toEqual(expectMultiChainAddressBytesWithP2wpkh) + + // nestd segwit p2sh-p2wpkh + let p2shAddress = '39fVbRM2TNBdNZPYeAJM7sHCPKczaZL7LV' + let expectMultiChainAddressBytesWithP2SH = new Uint8Array([ + 0, 0, 0, 0, 0, 0, 0, 0, 21, 1, 87, 119, 69, 184, 41, 233, 101, 189, 166, 156, 217, 192, 62, 9, + 151, 234, 162, 97, 49, 192, + ]) + let p2shMulticChainAddress = new MultiChainAddress(RoochMultiChainID.Bitcoin, p2shAddress) + expect(p2shMulticChainAddress.toBytes()).toEqual(expectMultiChainAddressBytesWithP2SH) + + // taproot p2tr + let p2trAddress = 'bc1pq5ttgyqu5pmfn9aqt09d978mky2fndxr3ed3ntszta75g9q6xrlqlwyl0r' + let expectMultiChainAddressBytesWithP2TR = new Uint8Array([ + 0, 0, 0, 0, 0, 0, 0, 0, 34, 2, 1, 5, 22, 180, 16, 28, 160, 118, 153, 151, 160, 91, 202, 210, + 248, 251, 177, 20, 153, 180, 195, 142, 91, 25, 174, 2, 95, 125, 68, 20, 26, 48, 254, + ]) + let p2TRMulticChainAddress = new MultiChainAddress(RoochMultiChainID.Bitcoin, p2trAddress) + expect(p2TRMulticChainAddress.toBytes()).toEqual(expectMultiChainAddressBytesWithP2TR) + + // legacy p2pkh + let p2pkhAddress = '15MJa2Jx2yA5iERwTKENY2WdWF3vnN6KVe' + let expectMultiChainAddressBytesWithP2PKH = new Uint8Array([ + 0, 0, 0, 0, 0, 0, 0, 0, 21, 0, 47, 183, 125, 16, 71, 244, 105, 179, 253, 132, 178, 184, 60, 5, + 68, 57, 97, 253, 162, 187, + ]) + let p2pkhMulticChainAddress = new MultiChainAddress(RoochMultiChainID.Bitcoin, p2pkhAddress) + expect(p2pkhMulticChainAddress.toBytes()).toEqual(expectMultiChainAddressBytesWithP2PKH) + }) +}) diff --git a/sdk/typescript/rooch-sdk/src/address/multiChainAddress.ts b/sdk/typescript/rooch-sdk/src/address/multiChainAddress.ts new file mode 100644 index 0000000000..960ab1dbda --- /dev/null +++ b/sdk/typescript/rooch-sdk/src/address/multiChainAddress.ts @@ -0,0 +1,41 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +import { getAddressInfo } from './bitcoinAddress' +import { bech32 } from 'bech32' +import { RoochMultiChainID, RoochMultiChainIDToString } from '../constants' +import { bcs } from '../types' + +export class MultiChainAddress implements bcs.Serializable { + private readonly multiChainId: RoochMultiChainID + private readonly rawAddress: Uint8Array + + // TODO: support all Chain + constructor(multiChainId: RoochMultiChainID, address: string) { + this.multiChainId = multiChainId + + this.rawAddress = getAddressInfo(address).bytes + } + + getRawAddress(): Uint8Array { + return this.rawAddress + } + + toBytes(): Uint8Array { + let bs = new bcs.BcsSerializer() + this.serialize(bs) + return bs.getBytes() + } + + // TODO: remove this, add toString + toBech32(): string { + const data = [1].concat(bech32.toWords(this.rawAddress)) + const address = bech32.encode(RoochMultiChainIDToString(this.multiChainId), data) + return address + } + + serialize(se: bcs.BcsSerializer) { + se.serializeU64(this.multiChainId) + bcs.Helpers.serializeVectorU8(Array.from(this.rawAddress), se) + } +} diff --git a/sdk/typescript/rooch-sdk/src/client/roochClient.ts b/sdk/typescript/rooch-sdk/src/client/roochClient.ts index 99a0cf5943..595dc4d917 100644 --- a/sdk/typescript/rooch-sdk/src/client/roochClient.ts +++ b/sdk/typescript/rooch-sdk/src/client/roochClient.ts @@ -72,6 +72,7 @@ import { import { BcsSerializer } from '../generated/runtime/bcs/bcsSerializer' import { Buffer } from 'buffer' +import { MultiChainAddress } from '../address' export const ROOCH_CLIENT_BRAND = Symbol.for('@roochnetwork/rooch-sdk') @@ -441,7 +442,9 @@ export class RoochClient { const handleAddress = () => { switch (params.multiChainID) { case RoochMultiChainID.Bitcoin: - return Array.from(Buffer.from(params.address)) + return Array.from( + new MultiChainAddress(params.multiChainID, params.address).getRawAddress(), + ) case RoochMultiChainID.Ether: return Array.from(Buffer.from(params.address.substring(2), 'hex')) default: