Quellcode durchsuchen

Merge branch 'master'

Cesar Rodas vor 1 Jahr
Ursprung
Commit
911525001a
41 geänderte Dateien mit 5437 neuen und 0 gelöschten Zeilen
  1. 1786 0
      Cargo.lock
  2. 25 0
      Cargo.toml
  3. 16 0
      crates/client/Cargo.toml
  4. 24 0
      crates/client/src/error.rs
  5. 15 0
      crates/client/src/lib.rs
  6. 189 0
      crates/client/src/relayer.rs
  7. 173 0
      crates/client/src/relayers.rs
  8. 10 0
      crates/storage/Cargo.toml
  9. 26 0
      crates/storage/src/error.rs
  10. 13 0
      crates/storage/src/lib.rs
  11. 538 0
      crates/storage/src/rocksdb.rs
  12. 138 0
      crates/storage/tests/events.json
  13. 21 0
      crates/types/Cargo.toml
  14. 71 0
      crates/types/src/client/close.rs
  15. 39 0
      crates/types/src/client/event.rs
  16. 10 0
      crates/types/src/client/mod.rs
  17. 112 0
      crates/types/src/client/subscribe.rs
  18. 21 0
      crates/types/src/common.rs
  19. 18 0
      crates/types/src/lib.rs
  20. 73 0
      crates/types/src/relayer/eose.rs
  21. 74 0
      crates/types/src/relayer/event.rs
  22. 10 0
      crates/types/src/relayer/mod.rs
  23. 67 0
      crates/types/src/relayer/notice.rs
  24. 86 0
      crates/types/src/relayer/ok.rs
  25. 129 0
      crates/types/src/request.rs
  26. 200 0
      crates/types/src/response.rs
  27. 231 0
      crates/types/src/types/addr.rs
  28. 137 0
      crates/types/src/types/content/mod.rs
  29. 33 0
      crates/types/src/types/content/profile.rs
  30. 231 0
      crates/types/src/types/event.rs
  31. 80 0
      crates/types/src/types/filter.rs
  32. 63 0
      crates/types/src/types/id.rs
  33. 234 0
      crates/types/src/types/kind.rs
  34. 86 0
      crates/types/src/types/mod.rs
  35. 89 0
      crates/types/src/types/signature.rs
  36. 79 0
      crates/types/src/types/subscription_id.rs
  37. 50 0
      crates/types/src/types/tag/event.rs
  38. 162 0
      crates/types/src/types/tag/mod.rs
  39. 14 0
      crates/types/src/types/tag/public_key.rs
  40. 57 0
      src/bin/dump.rs
  41. 7 0
      src/main.rs

+ 1786 - 0
Cargo.lock

@@ -0,0 +1,1786 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
+[[package]]
+name = "base64"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
+
+[[package]]
+name = "bech32"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445"
+
+[[package]]
+name = "bindgen"
+version = "0.64.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4"
+dependencies = [
+ "bitflags",
+ "cexpr",
+ "clang-sys",
+ "lazy_static",
+ "lazycell",
+ "peeking_take_while",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "bytes"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
+
+[[package]]
+name = "bzip2-sys"
+version = "0.1.11+1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+dependencies = [
+ "jobserver",
+]
+
+[[package]]
+name = "cexpr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-integer",
+ "num-traits",
+ "time",
+ "wasm-bindgen",
+ "winapi",
+]
+
+[[package]]
+name = "clang-sys"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
+[[package]]
+name = "codespan-reporting"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+dependencies = [
+ "termcolor",
+ "unicode-width",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "custom_derive"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9"
+
+[[package]]
+name = "cxx"
+version = "1.0.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a140f260e6f3f79013b8bfc65e7ce630c9ab4388c6a89c71e07226f49487b72"
+dependencies = [
+ "cc",
+ "cxxbridge-flags",
+ "cxxbridge-macro",
+ "link-cplusplus",
+]
+
+[[package]]
+name = "cxx-build"
+version = "1.0.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da6383f459341ea689374bf0a42979739dc421874f112ff26f829b8040b8e613"
+dependencies = [
+ "cc",
+ "codespan-reporting",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "scratch",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "cxxbridge-flags"
+version = "1.0.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90201c1a650e95ccff1c8c0bb5a343213bdd317c6e600a93075bca2eff54ec97"
+
+[[package]]
+name = "cxxbridge-macro"
+version = "1.0.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b75aed41bb2e6367cae39e6326ef817a851db13c13e4f3263714ca3cfb8de56"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "enum_derive"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "406ac2a8c9eedf8af9ee1489bee9e50029278a6456c740f7454cf8a158abc816"
+
+[[package]]
+name = "env_logger"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
+dependencies = [
+ "humantime",
+ "is-terminal",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "errno"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.12",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
+
+[[package]]
+name = "futures-task"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
+
+[[package]]
+name = "futures-util"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "h2"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hermit-abi"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "http"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
+dependencies = [
+ "bytes",
+ "http",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+
+[[package]]
+name = "httpdate"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "hyper"
+version = "0.14.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.23.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c"
+dependencies = [
+ "http",
+ "hyper",
+ "rustls",
+ "rustls-native-certs",
+ "tokio",
+ "tokio-rustls",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "winapi",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
+dependencies = [
+ "cxx",
+ "cxx-build",
+]
+
+[[package]]
+name = "idna"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "instant-acme"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbbf90cf8ba6f21d654be86375ee5e516ea38540a842b7259d3ddb848181691c"
+dependencies = [
+ "base64 0.21.0",
+ "hyper",
+ "hyper-rustls",
+ "ring",
+ "serde",
+ "serde_json",
+ "thiserror",
+]
+
+[[package]]
+name = "io-lifetimes"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3"
+dependencies = [
+ "libc",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "is-terminal"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857"
+dependencies = [
+ "hermit-abi 0.3.1",
+ "io-lifetimes",
+ "rustix",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
+
+[[package]]
+name = "jobserver"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[package]]
+name = "libc"
+version = "0.2.139"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
+
+[[package]]
+name = "libloading"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
+dependencies = [
+ "cfg-if",
+ "winapi",
+]
+
+[[package]]
+name = "librocksdb-sys"
+version = "0.10.0+7.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fe4d5874f5ff2bc616e55e8c6086d478fcda13faf9495768a4aa1c22042d30b"
+dependencies = [
+ "bindgen",
+ "bzip2-sys",
+ "cc",
+ "glob",
+ "libc",
+ "libz-sys",
+ "lz4-sys",
+ "zstd-sys",
+]
+
+[[package]]
+name = "libz-sys"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf"
+dependencies = [
+ "cc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "link-cplusplus"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
+
+[[package]]
+name = "lock_api"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "lz4-sys"
+version = "1.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "mio"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
+dependencies = [
+ "libc",
+ "log",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "nostr"
+version = "0.1.0"
+dependencies = [
+ "env_logger",
+ "futures",
+ "futures-util",
+ "instant-acme",
+ "log",
+ "nostr-rs-client",
+ "nostr-rs-storage",
+ "nostr-rs-types",
+ "serde_json",
+ "thiserror",
+ "tokio",
+]
+
+[[package]]
+name = "nostr-rs-client"
+version = "0.1.0"
+dependencies = [
+ "futures",
+ "futures-util",
+ "log",
+ "nostr-rs-types",
+ "parking_lot",
+ "serde_json",
+ "thiserror",
+ "tokio",
+ "tokio-tungstenite",
+ "url",
+]
+
+[[package]]
+name = "nostr-rs-relayer"
+version = "0.1.0"
+dependencies = [
+ "nostr-rs-storage",
+ "nostr-rs-types",
+ "thiserror",
+]
+
+[[package]]
+name = "nostr-rs-storage"
+version = "0.1.0"
+dependencies = [
+ "nostr-rs-types",
+ "rocksdb",
+ "serde_json",
+ "thiserror",
+]
+
+[[package]]
+name = "nostr-rs-types"
+version = "0.1.0"
+dependencies = [
+ "base64 0.21.0",
+ "bech32",
+ "chrono",
+ "custom_derive",
+ "enum_derive",
+ "hex",
+ "rand",
+ "secp256k1",
+ "serde",
+ "serde_json",
+ "sha2",
+ "thiserror",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
+dependencies = [
+ "hermit-abi 0.2.6",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "peeking_take_while"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
+
+[[package]]
+name = "percent-encoding"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.52"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "regex"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
+
+[[package]]
+name = "ring"
+version = "0.16.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+dependencies = [
+ "cc",
+ "libc",
+ "once_cell",
+ "spin",
+ "untrusted",
+ "web-sys",
+ "winapi",
+]
+
+[[package]]
+name = "rocksdb"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "015439787fce1e75d55f279078d33ff14b4af5d93d995e8838ee4631301c8a99"
+dependencies = [
+ "libc",
+ "librocksdb-sys",
+ "serde",
+]
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustix"
+version = "0.36.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc"
+dependencies = [
+ "bitflags",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "rustls"
+version = "0.20.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f"
+dependencies = [
+ "log",
+ "ring",
+ "sct",
+ "webpki",
+]
+
+[[package]]
+name = "rustls-native-certs"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50"
+dependencies = [
+ "openssl-probe",
+ "rustls-pemfile",
+ "schannel",
+ "security-framework",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
+dependencies = [
+ "base64 0.21.0",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
+
+[[package]]
+name = "schannel"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3"
+dependencies = [
+ "windows-sys 0.42.0",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "scratch"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
+
+[[package]]
+name = "sct"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "secp256k1"
+version = "0.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4124a35fe33ae14259c490fd70fa199a32b9ce9502f2ee6bc4f81ec06fa65894"
+dependencies = [
+ "secp256k1-sys",
+]
+
+[[package]]
+name = "secp256k1-sys"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "642a62736682fdd8c71da0eb273e453c8ac74e33b9fb310e22ba5b03ec7651ff"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "security-framework"
+version = "2.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.153"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a382c72b4ba118526e187430bb4963cd6d55051ebf13d9b25574d379cc98d20"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.153"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ef476a5790f0f6decbc66726b6e5d63680ed518283e64c7df415989d880954f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha1"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "shlex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+
+[[package]]
+name = "socket2"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.12",
+]
+
+[[package]]
+name = "time"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
+dependencies = [
+ "libc",
+ "wasi 0.10.0+wasi-snapshot-preview1",
+ "winapi",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64"
+dependencies = [
+ "autocfg",
+ "bytes",
+ "libc",
+ "memchr",
+ "mio",
+ "num_cpus",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.23.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
+dependencies = [
+ "rustls",
+ "tokio",
+ "webpki",
+]
+
+[[package]]
+name = "tokio-tungstenite"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd"
+dependencies = [
+ "futures-util",
+ "log",
+ "rustls",
+ "rustls-native-certs",
+ "tokio",
+ "tokio-rustls",
+ "tungstenite",
+ "webpki",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "tower-service"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
+
+[[package]]
+name = "tracing"
+version = "0.1.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
+dependencies = [
+ "cfg-if",
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
+
+[[package]]
+name = "tungstenite"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788"
+dependencies = [
+ "base64 0.13.1",
+ "byteorder",
+ "bytes",
+ "http",
+ "httparse",
+ "log",
+ "rand",
+ "rustls",
+ "sha1",
+ "thiserror",
+ "url",
+ "utf-8",
+ "webpki",
+]
+
+[[package]]
+name = "typenum"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "524b68aca1d05e03fdf03fcdce2c6c94b6daf6d16861ddaa7e4f2b6638a9052c"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
+
+[[package]]
+name = "untrusted"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+
+[[package]]
+name = "url"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "want"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+dependencies = [
+ "log",
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
+
+[[package]]
+name = "web-sys"
+version = "0.3.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
+
+[[package]]
+name = "zstd-sys"
+version = "2.0.7+zstd.1.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94509c3ba2fe55294d752b79842c530ccfab760192521df74a081a78d2b3c7f5"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]

+ 25 - 0
Cargo.toml

@@ -0,0 +1,25 @@
+[package]
+name = "nostr"
+version = "0.1.0"
+edition = "2021"
+
+[workspace]
+members = [
+    "crates/types",
+    "crates/client",
+    "crates/relayer",
+    "crates/storage"
+]
+
+[dependencies]
+nostr-rs-types = { path = "crates/types" }
+nostr-rs-client = { path = "crates/client" }
+nostr-rs-storage = { path = "crates/storage" }
+tokio = { version = "1.26.0", features = ["full"] }
+env_logger = "0.10.0"
+serde_json = "1.0.94"
+futures-util = "0.3.27"
+log = "0.4.17"
+thiserror = "1.0.40"
+futures = "0.3.28"
+instant-acme = "0.2.0"

+ 16 - 0
crates/client/Cargo.toml

@@ -0,0 +1,16 @@
+[package]
+name = "nostr-rs-client"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+thiserror = "1.0.40"
+nostr-rs-types = { path = "../types" }
+tokio = { version = "1.26.0", features = ["sync", "macros", "rt", "time"] }
+tokio-tungstenite = { version = "0.18.0", features = ["rustls", "rustls-native-certs", "rustls-tls-native-roots"] }
+url = "2.3.1"
+serde_json = "1.0.94"
+futures-util = "0.3.27"
+parking_lot = "0.12.1"
+log = "0.4.17"
+futures = "0.3.28"

+ 24 - 0
crates/client/src/error.rs

@@ -0,0 +1,24 @@
+//! Errors for this crate
+use nostr_rs_types::Request;
+use tokio::sync::mpsc::error::SendError;
+use tokio_tungstenite::tungstenite::error::Error as TungsteniteError;
+
+/// Crate errors
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+    /// Error parsing the Url
+    #[error("Url: {0}")]
+    Url(#[from] url::ParseError),
+
+    /// WebSocket client error
+    #[error("Tungstenite: {0}")]
+    Tungstenite(#[from] TungsteniteError),
+
+    /// Internal error using the Sync channels
+    #[error("Sync: {0}")]
+    Sync(#[from] SendError<Request>),
+
+    /// The client has no connection to any relayer
+    #[error("There is no connection")]
+    Disconnected,
+}

+ 15 - 0
crates/client/src/lib.rs

@@ -0,0 +1,15 @@
+//! # Nostr Client
+//!
+//! This crate will connect to other relayer or relayers and will subscribe to
+//! events or perform any operation that can be done in the protocol.
+//!
+//! The client will let you connect to a pool of relayers oferring a simple
+//! protocol to talk to all of them at the same time.
+//!
+//! It will also have reconnection logic built-in internally.
+#![deny(missing_docs, warnings)]
+mod error;
+mod relayer;
+mod relayers;
+
+pub use self::{error::Error, relayer::Relayer, relayers::Relayers};

+ 189 - 0
crates/client/src/relayer.rs

@@ -0,0 +1,189 @@
+use crate::Error;
+use futures::Future;
+use futures_util::{SinkExt, StreamExt};
+use nostr_rs_types::{Request, Response};
+use parking_lot::RwLock;
+use std::{pin::Pin, sync::Arc};
+use tokio::{
+    sync::{broadcast, mpsc, oneshot},
+    time::{sleep, timeout, Duration},
+};
+use tokio_tungstenite::{connect_async, tungstenite::Message};
+use url::Url;
+
+/// Relayer object
+#[derive(Debug)]
+pub struct Relayer {
+    /// URL of the relayer
+    pub url: String,
+    /// Sender to the relayer. This can be used to send a Requests to this relayer
+    pub send_to_socket: mpsc::Sender<Request>,
+    /// Internal receiver. This is used to receive messages from the relayer
+    recv_from_socket: broadcast::Receiver<(Response, String)>,
+    /// This sender signals to background connection to stop
+    stop_service: oneshot::Sender<()>,
+}
+
+const NO_ACTIVITY_TIMEOUT_SECS: u64 = 30;
+
+impl Relayer {
+    /// Creates a new relayer
+    pub fn new<F>(
+        sent_messages: Arc<RwLock<Vec<Request>>>,
+        connection_retries: u16,
+        url: &str,
+        on_connection: Option<F>,
+    ) -> Result<Self, Error>
+    where
+        F: (Fn(Vec<Request>, mpsc::Sender<Request>) -> Pin<Box<dyn Future<Output = ()> + Send>>)
+            + Send
+            + Sync
+            + 'static,
+    {
+        let (send_to_socket, receiver) = mpsc::channel(100_000);
+        let (recv_from_socket, stop_service) = Self::spawn_background_client(
+            sent_messages,
+            send_to_socket.clone(),
+            receiver,
+            url,
+            connection_retries,
+            on_connection,
+        )?;
+
+        Ok(Self {
+            url: url.to_owned(),
+            send_to_socket,
+            stop_service,
+            recv_from_socket,
+        })
+    }
+
+    fn spawn_background_client<F>(
+        sent_messages: Arc<RwLock<Vec<Request>>>,
+        send_to_socket: mpsc::Sender<Request>,
+        mut receiver: mpsc::Receiver<Request>,
+        url_str: &str,
+        connection_retries: u16,
+        on_connection: Option<F>,
+    ) -> Result<(broadcast::Receiver<(Response, String)>, oneshot::Sender<()>), Error>
+    where
+        F: (Fn(Vec<Request>, mpsc::Sender<Request>) -> Pin<Box<dyn Future<Output = ()> + Send>>)
+            + Send
+            + Sync
+            + 'static,
+    {
+        let (publish_to_listener, recv_from_socket) = broadcast::channel(10_000);
+        let (stop_service, mut stopper_recv) = oneshot::channel();
+
+        let url = url_str.to_owned();
+        let url_parsed = Url::parse(&url)?;
+
+        tokio::spawn(async move {
+            let mut reconnect = true;
+            let mut retries = 0;
+
+            while reconnect && retries <= connection_retries {
+                log::warn!("{}: Connect attempt {}", url, retries);
+                retries += 1;
+                let mut socket = if let Ok(x) = connect_async(url_parsed.clone()).await {
+                    x.0
+                } else {
+                    log::warn!("{}: Failed to connect", url);
+                    sleep(Duration::from_secs(5)).await;
+                    continue;
+                };
+
+                log::info!("Connected to {}", url);
+
+                if let Some(on_connection) = &on_connection {
+                    let sent_messages = sent_messages.read().to_owned();
+                    on_connection(sent_messages, send_to_socket.clone()).await;
+                }
+
+                loop {
+                    tokio::select! {
+                        Ok(()) = &mut stopper_recv => {
+                            log::warn!("{}: Breaking client due external signal", url);
+                            reconnect = false;
+                            break;
+                        },
+
+                        Some(msg) = receiver.recv() => {
+                            if let Ok(json) = serde_json::to_string(&msg) {
+                                log::info!("{}: Sending {}", url, json);
+                                if let Err(x) = socket.send(Message::Text(json)).await {
+                                    log::error!("{} : Reconnecting due {}", url, x);
+                                    break;
+                                }
+                            }
+                        }
+                        msg = timeout(Duration::from_secs(NO_ACTIVITY_TIMEOUT_SECS), socket.next()) => {
+                            let msg = if let Ok(Some(Ok(msg))) = msg {
+                                    if let Ok(msg) = msg.into_text() {
+                                        msg
+                                    } else {
+                                        continue;
+                                    }
+                                } else {
+                                    log::error!("{} Reconnecting client due of empty recv: {:?}", url, msg);
+                                    break;
+                                };
+
+                            if msg.is_empty() {
+                                continue;
+                            }
+
+                            log::info!("New message: {}", msg);
+                            retries = 0;
+
+
+                            let msg: Result<Response, _> = serde_json::from_str(&msg);
+
+                            if let Ok(msg) = msg {
+                                if let Err(error) = publish_to_listener.send((msg, url.to_owned())) {
+                                    log::error!("{}: Reconnecting client because of {}", url, error);
+                                    break;
+                                }
+                            }
+                        }
+                        else => {
+                            log::warn!("{}: else", url);
+                            break;
+                        }
+                    }
+                    // Throttle down to not spam the server with reconnections
+                    sleep(Duration::from_millis(500)).await;
+                }
+            }
+
+            log::warn!("{}: Disconnected", url);
+        });
+
+        Ok((recv_from_socket, stop_service))
+    }
+
+    /// Checks if the relayer background connection is running
+    pub fn is_running(&self) -> bool {
+        !self.stop_service.is_closed()
+    }
+
+    /// Subscribe to responses sent through this relayer. If the relayer is not
+    /// running, this will return None
+    pub fn subscribe(&self) -> Option<broadcast::Receiver<(Response, String)>> {
+        if self.stop_service.is_closed() {
+            None
+        } else {
+            Some(self.recv_from_socket.resubscribe())
+        }
+    }
+
+    /// Sends a requests to this relayer
+    pub async fn send(&self, request: Request) -> Result<(), Error> {
+        Ok(self.send_to_socket.send(request).await?)
+    }
+
+    /// Stops the background thread that has the connection to this relayer
+    pub async fn stop(self) {
+        let _ = self.stop_service.send(());
+    }
+}

+ 173 - 0
crates/client/src/relayers.rs

@@ -0,0 +1,173 @@
+//! Relayers
+//!
+//! This is the main entry point to the client library.
+use crate::{Error, Relayer};
+use futures::Future;
+use futures_util::{stream::FuturesUnordered, StreamExt};
+use nostr_rs_types::{Request, Response};
+use parking_lot::RwLock;
+use std::{collections::HashMap, pin::Pin, sync::Arc};
+use tokio::sync::mpsc;
+
+/// Clients
+///
+/// This is a set of outgoing connections to relayers. This struct can connect
+/// async to N relayers offering a simple API to talk to all of them at the same
+/// time, and to receive messages
+#[derive(Debug, Clone)]
+pub struct Relayers {
+    clients: Arc<RwLock<HashMap<String, Relayer>>>,
+    messages: Arc<RwLock<Vec<Request>>>,
+}
+
+impl Default for Relayers {
+    fn default() -> Self {
+        Self {
+            clients: Arc::new(RwLock::new(HashMap::new())),
+            messages: Arc::new(RwLock::new(vec![])),
+        }
+    }
+}
+
+impl Relayers {
+    /// Receives a Response from any of the connected relayers
+    pub async fn recv(&self) -> Result<Option<(Response, String)>, Error> {
+        let mut subscriptions = self
+            .clients
+            .read()
+            .iter()
+            .map(|(_, c)| c.subscribe())
+            .collect::<Vec<Option<_>>>()
+            .into_iter()
+            .filter_map(|x| x)
+            .collect::<Vec<_>>();
+
+        if subscriptions.is_empty() {
+            return Err(Error::Disconnected);
+        }
+
+        let mut futures = FuturesUnordered::new();
+
+        for sub in subscriptions.iter_mut() {
+            futures.push(sub.recv());
+        }
+
+        if let Some(Ok(response)) = futures.next().await {
+            Ok(Some(response))
+        } else {
+            Ok(None)
+        }
+    }
+
+    /// Attempts to receive a Response from any of the relayers but won't wait if none
+    /// is available. It will return None if None is available
+    pub fn try_recv(&self) -> Result<Option<(Response, String)>, Error> {
+        let mut subscriptions = self
+            .clients
+            .read()
+            .iter()
+            .map(|(_, c)| c.subscribe())
+            .collect::<Vec<Option<_>>>()
+            .into_iter()
+            .filter_map(|x| x)
+            .collect::<Vec<_>>();
+
+        if subscriptions.is_empty() {
+            return Err(Error::Disconnected);
+        }
+
+        for sub in subscriptions.iter_mut() {
+            if let Ok(msg) = sub.try_recv() {
+                return Ok(Some(msg));
+            }
+        }
+        Ok(None)
+    }
+
+    /// Returns the number of active connections. If a connection to a relayer
+    /// is not active it will be removed from the list
+    pub fn check_active_connections(&self) -> usize {
+        let mut clients = self.clients.write();
+        let mut to_remove = vec![];
+        for (url, client) in clients.iter() {
+            if !client.is_running() {
+                to_remove.push(url.to_owned());
+            }
+        }
+
+        for url in to_remove.iter() {
+            clients.remove(url);
+        }
+
+        clients.len()
+    }
+
+    /// Sends a request to all the connected relayers
+    pub async fn send(&self, request: Request) {
+        let senders = self
+            .clients
+            .read()
+            .iter()
+            .map(|(_, c)| c.send_to_socket.clone())
+            .collect::<Vec<mpsc::Sender<_>>>();
+
+        // Add a copy of the request for incoming connections.
+        self.messages.write().push(request.clone());
+
+        for sender in senders.iter() {
+            let _ = sender.send(request.clone()).await;
+        }
+    }
+
+    /// Creates a connection to a new relayer. If the connection is successful a
+    /// Callback will be called, with a list of previously sent requests, and a
+    /// Sender to send new requests to this relayer alone.
+    ///
+    /// The same callback will be called for every reconnection to the same
+    /// relayer, also the callback will be called, giving the chance to re-send
+    /// sent requests to the new connections
+    ///
+    /// This function will open a connection at most once, if a connection
+    /// already exists false will be returned
+    pub async fn connect_to<F>(
+        &self,
+        url: &str,
+        connection_retries: u16,
+        on_connection: Option<F>,
+    ) -> Result<bool, Error>
+    where
+        F: (Fn(Vec<Request>, mpsc::Sender<Request>) -> Pin<Box<dyn Future<Output = ()> + Send>>)
+            + Send
+            + Sync
+            + 'static,
+    {
+        let mut clients = self.clients.write();
+        Ok(if clients.get(url).is_some() {
+            false
+        } else {
+            log::warn!("Connecting to {}", url);
+            clients.insert(
+                url.to_owned(),
+                Relayer::new(
+                    self.messages.clone(),
+                    connection_retries,
+                    url,
+                    on_connection,
+                )?,
+            );
+            true
+        })
+    }
+
+    /// Disconnects from all relayers
+    pub async fn stop(&self) {
+        let mut clients = self.clients.write();
+        let keys = clients.keys().cloned().collect::<Vec<_>>();
+
+        for key in keys.iter() {
+            if let Some(client) = clients.remove(key) {
+                client.stop().await;
+            }
+        }
+    }
+}

+ 10 - 0
crates/storage/Cargo.toml

@@ -0,0 +1,10 @@
+[package]
+name = "nostr-rs-storage"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+serde_json = "1.0"
+nostr-rs-types = { path = "../types" }
+thiserror = "1.0.40"
+rocksdb = { version = "0.20.1", features = ["multi-threaded-cf", "serde", "snappy"] }

+ 26 - 0
crates/storage/src/error.rs

@@ -0,0 +1,26 @@
+//! Errors for this crate
+use std::num::TryFromIntError;
+
+/// Error
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    /// Internal database error
+    #[error("RocksDB: {0}")]
+    RocksDb(#[from] rocksdb::Error),
+
+    /// Serialization error
+    #[error("Serde: {0}")]
+    Serde(#[from] serde_json::Error),
+
+    /// Internal error while converting types to integer
+    #[error("Internal error: {0}")]
+    Internal(#[from] TryFromIntError),
+
+    /// Transaction error
+    #[error("Tx: {0}")]
+    Tx(String),
+
+    /// Internal error
+    #[error("Unknown family column")]
+    InvalidColumnFamily,
+}

+ 13 - 0
crates/storage/src/lib.rs

@@ -0,0 +1,13 @@
+//! # Nostr Storage
+//!
+//! This crate will storage events into a database. It will also build index to
+//! find events by their tags, kind and references.
+//!
+//! Although the trait is generic this implementation uses rocksDB internal,
+//! which is a C++ database open source by Facebook. It is high level enough to
+//! let this crate build the needed indexes to find events quickly.
+#![deny(missing_docs, warnings)]
+mod error;
+mod rocksdb;
+
+pub use crate::{error::Error, rocksdb::RocksDb};

+ 538 - 0
crates/storage/src/rocksdb.rs

@@ -0,0 +1,538 @@
+//! Rocks DB implementation of the storage layer
+use crate::Error;
+use nostr_rs_types::types::{Event, Filter, Tag};
+use rocksdb::{BoundColumnFamily, ColumnFamilyDescriptor, Options, SliceTransform, WriteBatch, DB};
+use std::{collections::HashSet, path::Path, sync::Arc};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+enum ReferenceType {
+    Events,
+    Author,
+    RefPublicKey,
+    RefEvent,
+    Kind,
+}
+
+impl ReferenceType {
+    #[inline]
+    pub fn as_str(&self) -> &'static str {
+        match self {
+            Self::Events => "events",
+            Self::Author => "authors",
+            Self::RefPublicKey => "refs_by_public_key",
+            Self::RefEvent => "refs_by_ids",
+            Self::Kind => "kinds",
+        }
+    }
+}
+
+/// Storage implementation using RocksDB
+pub struct RocksDb {
+    db: DB,
+}
+
+fn nanoseconds() -> u128 {
+    std::time::SystemTime::now()
+        .duration_since(std::time::UNIX_EPOCH)
+        .unwrap()
+        .as_nanos()
+}
+
+impl RocksDb {
+    /// Creates a new instance passing rocksDb options and a path
+    pub fn with_custom_db<T: AsRef<Path>>(options: Options, path: T) -> Result<Self, Error> {
+        let db = DB::open_cf_descriptors(
+            &options,
+            path,
+            vec![
+                ColumnFamilyDescriptor::new(ReferenceType::Events.as_str(), options.clone()),
+                ColumnFamilyDescriptor::new(ReferenceType::Author.as_str(), options.clone()),
+                ColumnFamilyDescriptor::new(ReferenceType::RefEvent.as_str(), options.clone()),
+                ColumnFamilyDescriptor::new(ReferenceType::RefPublicKey.as_str(), options.clone()),
+                ColumnFamilyDescriptor::new(ReferenceType::Kind.as_str(), options.clone()),
+            ],
+        )?;
+        Ok(Self { db })
+    }
+
+    /// Creates a new instance, passing just the path, the default options are passed
+    pub fn new<T: AsRef<Path>>(path: T) -> Result<Self, Error> {
+        let mut options = Options::default();
+        options.create_if_missing(true);
+        options.create_missing_column_families(true);
+        options.set_compression_type(rocksdb::DBCompressionType::Snappy);
+        options.optimize_for_point_lookup(256 * 1024 * 1024);
+
+        let prefix_extractor = SliceTransform::create_fixed_prefix(32);
+        options.set_prefix_extractor(prefix_extractor);
+
+        Self::with_custom_db(options, path)
+    }
+
+    /// Takes a vector of bytes and appends the current time in nanoseconds to
+    /// it, to make the ID unique enough and to make it sortable by time.
+    #[inline]
+    fn generate_unique_id<T: AsRef<[u8]>>(id: T, order_by: u64) -> Vec<u8> {
+        let mut new_id = id.as_ref().to_vec();
+
+        // We need to reverse the number, to sort it. Otherwise, the first is
+        // going to be the oldest entry, instead of the newest.
+        let reversed_order_by = u64::MAX
+            .checked_sub(order_by)
+            .unwrap_or_default()
+            .to_be_bytes();
+        new_id.extend_from_slice(&reversed_order_by);
+
+        let now = u128::MAX
+            .checked_sub(nanoseconds())
+            .unwrap_or_default()
+            .to_be_bytes();
+
+        new_id.extend_from_slice(&now);
+        new_id
+    }
+
+    #[inline]
+    fn reference_to_cf_handle(
+        &self,
+        namespace: ReferenceType,
+    ) -> Result<Arc<BoundColumnFamily>, Error> {
+        self.db
+            .cf_handle(namespace.as_str())
+            .ok_or(Error::InvalidColumnFamily)
+    }
+
+    /// Stores a new event into the database. This action will also creates all
+    /// the needed indexes to find this event later by reference, author, kind or tag.
+    pub fn store(&self, event: &Event) -> Result<bool, Error> {
+        let event_id = event.id.clone();
+
+        if let Ok(Some(_)) = self.db.get_cf(
+            &self.reference_to_cf_handle(ReferenceType::Events)?,
+            *event_id,
+        ) {
+            return Ok(false);
+        }
+        let created_at = event.created_at().timestamp().try_into()?;
+        let author_id = Self::generate_unique_id(event.author(), created_at);
+        let json = serde_json::to_vec(event)?;
+        let kind: u32 = event.kind().into();
+        let kind_id = Self::generate_unique_id(kind.to_be_bytes(), created_at);
+
+        let mut buffer = WriteBatch::default();
+
+        buffer.put_cf(
+            &self.reference_to_cf_handle(ReferenceType::Events)?,
+            *event_id,
+            json,
+        );
+        buffer.put_cf(
+            &self.reference_to_cf_handle(ReferenceType::Author)?,
+            author_id,
+            *event_id,
+        );
+        buffer.put_cf(
+            &self.reference_to_cf_handle(ReferenceType::Kind)?,
+            kind_id,
+            *event_id,
+        );
+
+        for tag in event.tags().iter() {
+            match tag {
+                Tag::PubKey(p) => {
+                    let foreign_id = Self::generate_unique_id(&p.id, created_at);
+                    let local_id = Self::generate_unique_id(&event_id, created_at);
+
+                    buffer.put_cf(
+                        &self.reference_to_cf_handle(ReferenceType::RefPublicKey)?,
+                        foreign_id,
+                        *event_id,
+                    );
+                    buffer.put_cf(
+                        &self.reference_to_cf_handle(ReferenceType::RefEvent)?,
+                        local_id,
+                        &*p.id,
+                    );
+                }
+                Tag::Event(e) => {
+                    let foreign_id = Self::generate_unique_id(&e.id, created_at);
+                    let local_id = Self::generate_unique_id(&event_id, created_at);
+
+                    buffer.put_cf(
+                        &self.reference_to_cf_handle(ReferenceType::RefEvent)?,
+                        foreign_id,
+                        *event_id,
+                    );
+                    buffer.put_cf(
+                        &self.reference_to_cf_handle(ReferenceType::RefEvent)?,
+                        local_id,
+                        &*e.id,
+                    );
+                }
+                _ => {}
+            }
+        }
+
+        self.db.write(buffer).map_err(Error::RocksDb)?;
+
+        Ok(true)
+    }
+
+    /// Receives a list of event-IDs, and returns a list of events that match
+    /// the given filter.
+    ///
+    /// The list of event-IDs should come from an index from any namespace, and
+    /// the filter will load the events from the `Events` namespace and will
+    /// filter them by the given parameters.
+    fn load_ids_and_filter(
+        &self,
+        mut query: Filter,
+        mut event_ids: HashSet<Vec<u8>>,
+    ) -> Result<Vec<Event>, Error> {
+        let mut events = vec![];
+
+        if !query.ids.is_empty() {
+            let mut ids = query.ids.iter().map(|id| id.as_ref()).collect::<Vec<_>>();
+            ids.sort();
+            event_ids.retain(|id| ids.binary_search(&id.as_slice()).is_ok());
+        }
+
+        let mut authors = query
+            .authors
+            .iter()
+            .map(|id| id.as_ref())
+            .collect::<Vec<_>>();
+
+        authors.sort();
+        query.kinds.sort();
+
+        for id in event_ids.iter() {
+            if let Some(event) = self.get_event(id)? {
+                if !authors.is_empty() && authors.binary_search(&event.author().as_ref()).is_err() {
+                    continue;
+                }
+                if !query.kinds.is_empty() && query.kinds.binary_search(&event.kind()).is_err() {
+                    continue;
+                }
+                if let Some(since) = query.since {
+                    if event.created_at() < since {
+                        continue;
+                    }
+                }
+                if let Some(until) = query.until {
+                    if event.created_at() > until {
+                        continue;
+                    }
+                }
+                events.push(event);
+                if events.len() as u64 == query.limit {
+                    break;
+                }
+            }
+        }
+
+        Ok(events)
+    }
+
+    /// Get events from the database with a given filter
+    pub fn get_events_by_filter(&self, mut query: Filter) -> Result<Vec<Event>, Error> {
+        let mut event_ids = HashSet::new();
+        let limit = if query.limit == 0 {
+            None
+        } else {
+            Some(query.limit.try_into()?)
+        };
+
+        if !query.ids.is_empty() {
+            query
+                .ids
+                .iter()
+                .map(|id| event_ids.insert(id.as_ref().to_vec()))
+                .for_each(drop);
+            query.ids.clear();
+        } else if !query.authors.is_empty() {
+            let ns = self.reference_to_cf_handle(ReferenceType::Author)?;
+            for public_key in query.authors.iter() {
+                event_ids.extend(self.get_event_ids_by_ns_and_prefix(
+                    &ns,
+                    public_key.as_ref(),
+                    limit,
+                )?);
+            }
+            query.authors.clear();
+        } else if !query.kinds.is_empty() {
+            let ns = self.reference_to_cf_handle(ReferenceType::Kind)?;
+            for kind in query.kinds.iter() {
+                let kind: u32 = (*kind).into();
+                event_ids.extend(self.get_event_ids_by_ns_and_prefix(
+                    &ns,
+                    &kind.to_be_bytes(),
+                    limit,
+                )?);
+            }
+            println!("{:?} {}", event_ids, event_ids.len());
+            query.kinds.clear();
+        }
+
+        self.load_ids_and_filter(query, event_ids)
+    }
+
+    /// Similar function to get_events_by_author but instead of loading the
+    /// whole event, only the event id is loaded. This is done for performance,
+    /// to avoid loading the same event multiple times, also to only load from
+    /// the database the events that are needed (according to limits and other
+    /// constraints)
+    fn get_event_ids_by_ns_and_prefix(
+        &self,
+        cf_handle: &Arc<BoundColumnFamily>,
+        prefix: &[u8],
+        limit: Option<usize>,
+    ) -> Result<HashSet<Vec<u8>>, Error> {
+        let mut items = HashSet::new();
+        let limit = limit.unwrap_or(usize::MAX);
+
+        println!("F: {:?}", prefix);
+        for item in self.db.prefix_iterator_cf(cf_handle, prefix) {
+            println!("fix");
+            let (key, value) = match item {
+                Ok((k, v)) => (k, v),
+                Err(e) => return Err(Error::RocksDb(e)),
+            };
+
+            if !key.starts_with(prefix) {
+                break;
+            }
+
+            items.insert(value.to_vec());
+            if items.len() >= limit {
+                break;
+            }
+        }
+
+        Ok(items)
+    }
+
+    /// Get events by author
+    pub fn get_events_by_author<T: AsRef<[u8]>>(
+        &self,
+        public_key: T,
+        limit: Option<usize>,
+    ) -> Result<Vec<Event>, Error> {
+        let mut events: Vec<Event> = self
+            .get_event_ids_by_ns_and_prefix(
+                &self.reference_to_cf_handle(ReferenceType::Author)?,
+                public_key.as_ref(),
+                limit,
+            )?
+            .iter()
+            .map(|id| self.get_event(id))
+            .collect::<Result<Vec<Option<_>>, _>>()?
+            .into_iter()
+            .flatten()
+            .collect();
+
+        events.sort_by_key(|b| std::cmp::Reverse(b.created_at()));
+        Ok(events)
+    }
+
+    /// Loads all events that references a given event. The reference-type is provided and it can be any value of `ReferenceType`.
+    fn get_event_referenced_as<T: AsRef<[u8]>>(
+        &self,
+        ref_type: ReferenceType,
+        id: T,
+        limit: Option<usize>,
+    ) -> Result<Vec<Event>, Error> {
+        let mut items = vec![];
+        let limit = limit.unwrap_or(usize::MAX);
+        let prefix = id.as_ref();
+
+        for item in self
+            .db
+            .prefix_iterator_cf(&self.reference_to_cf_handle(ref_type)?, prefix)
+        {
+            let (key, value) = match item {
+                Ok((k, v)) => (k, v),
+                Err(e) => return Err(Error::RocksDb(e)),
+            };
+
+            if !key.starts_with(prefix) {
+                break;
+            }
+
+            if let Some(value) = self.get_event(value)? {
+                items.push(value);
+                if limit == items.len() {
+                    break;
+                }
+            }
+        }
+
+        Ok(items)
+    }
+
+    /// Loads a given event and all their related events (likes, reposts, etc)
+    pub fn get_event_and_related_events<T: AsRef<[u8]>>(
+        &self,
+        id: T,
+        limit: Option<usize>,
+    ) -> Result<Option<(Event, Vec<Event>)>, Error> {
+        if let Some(r) = self.get_event(&id)? {
+            Ok(Some((
+                r,
+                self.get_event_referenced_as(ReferenceType::RefEvent, &id, limit)?,
+            )))
+        } else {
+            Ok(None)
+        }
+    }
+
+    /// Returns an event by its ID
+    pub fn get_event<T: AsRef<[u8]>>(&self, id: T) -> Result<Option<Event>, Error> {
+        Ok(self
+            .db
+            .get_cf(&self.reference_to_cf_handle(ReferenceType::Events)?, id)?
+            .map(|event| serde_json::from_slice(&event))
+            .transpose()?)
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use nostr_rs_types::types::{Addr, Kind};
+    use std::{
+        fs::File,
+        io::{BufRead, BufReader},
+    };
+
+    fn get_db() -> RocksDb {
+        let db = RocksDb::new(format!("tests/db/{}", nanoseconds())).expect("db");
+
+        let file = File::open("./tests/events.json").expect("file");
+        let events = BufReader::new(file)
+            .lines()
+            .map(|line| serde_json::from_str(&line.expect("line")).expect("valid"))
+            .collect::<Vec<Event>>();
+
+        for event in events {
+            assert!(db.store(&event).expect("valid"));
+        }
+        db
+    }
+
+    #[test]
+    fn store_and_get() {
+        let db = RocksDb::new(format!("tests/db/{}", nanoseconds())).expect("db");
+        let json = "{\"content\":\"{\\\"lud06\\\":\\\"lnbc1p3a4wxvpp5x0pa6gr55fq5s9d3dxs0vz77mqxgdw63hhtgtlfz5zvm65847vnqdqqcqpjsp5402c8rtqxd4j97rnvuejuwl4sg473g6wg08d67fvn7qc4gtpkfks9q7sqqqqqqqqqqqqqqqqqqqsqqqqqysgqmqz9gxqyjw5qrzjqwryaup9lh50kkranzgcdnn2fgvx390wgj5jd07rwr3vxeje0glclleasn65surjcsqqqqlgqqqqqeqqjqyxj968tem9ps6ttm9ukv6ag4yc6qmgj2svrccfgp4n83fpktr3dsx6fq7grfzlqt982aaemahg9q29vzl9f627kh4j8h8xc2z2mtpdqqjlekah\\\",\\\"website\\\":\\\"\\\",\\\"nip05\\\":\\\"cesar@cesar.com.py\\\",\\\"picture\\\":\\\"https://pbs.twimg.com/profile_images/1175432935337537536/_Peu9vuJ_400x400.jpg\\\",\\\"display_name\\\":\\\"C\\\",\\\"about\\\":\\\"Rust and PHP\\\",\\\"name\\\":\\\"c\\\"}\",\"created_at\":1678476588,\"id\":\"3800c787a23288641c0b96cbcc87c26cbd3ea7bee53b7748422fdb100fb7b9f0\",\"kind\":0,\"pubkey\":\"b2815682cfc83fcd2c3add05785cf4573dd388457069974cc6d8cca06b3c3b78\",\"sig\":\"c8a12ce96833e4cd67bce0e9e50f831262ef0f0c0cff5e56c38a0c90867ed1a6621e9692948ef5e85a7ca3726c3f0f43fa7e1992536bc457317123bca8784f5f\",\"tags\":[]}";
+
+        let event: Event = serde_json::from_str(json).expect("valid");
+        assert_eq!(true, db.store(&event).expect("valid"));
+        assert_eq!(false, db.store(&event).expect("valid"));
+
+        let event1 = db.get_event(&event.id).expect("something");
+        assert_eq!(event1, Some(event));
+    }
+
+    #[test]
+    fn records_are_sorted_by_date_desc() {
+        let db = get_db();
+
+        let pk: Addr = "7bdef7be22dd8e59f4600e044aa53a1cf975a9dc7d27df5833bc77db784a5805"
+            .try_into()
+            .expect("pk");
+
+        let vec = db.get_events_by_author(pk, Some(10)).expect("records");
+        let dates = vec.iter().map(|e| e.created_at()).collect::<Vec<_>>();
+        let mut sorted_dates = dates.clone();
+        sorted_dates.sort_by(|a, b| b.cmp(a));
+
+        assert_eq!(vec.len(), 10);
+        assert_eq!(dates, sorted_dates);
+    }
+
+    #[test]
+    fn get_event_and_related_events() {
+        let db = get_db();
+
+        let id: Addr = "42224859763652914db53052103f0b744df79dfc4efef7e950fc0802fc3df3c5"
+            .try_into()
+            .expect("pk");
+
+        let events = db.get_event_and_related_events(id, None).expect("events");
+        assert!(events.is_some());
+        let events = events.expect("events");
+        assert_eq!(
+            events.0.id.to_string(),
+            "42224859763652914db53052103f0b744df79dfc4efef7e950fc0802fc3df3c5"
+        );
+        assert_eq!(events.1.len(), 2_538);
+
+        let mut kinds = events.1.iter().map(|x| x.kind()).collect::<Vec<_>>();
+        kinds.sort();
+        kinds.dedup();
+
+        assert_eq!(Kind::Reaction, kinds[0]);
+        assert_eq!(Kind::Unknown(42), kinds[1]);
+    }
+
+    #[test]
+    fn filter_by_authors() {
+        let db = get_db();
+        let query = Filter {
+            authors: vec![
+                "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c"
+                    .try_into()
+                    .expect("pk"),
+                "38fb689f2fb92d932d457b1ea56715292bdf2140b2c7d282e8b8e8d644483ad6"
+                    .try_into()
+                    .expect("pk"),
+            ],
+            ..Default::default()
+        };
+        let records = db.get_events_by_filter(query).expect("records");
+        assert_eq!(records.len(), 27);
+    }
+
+    #[test]
+    fn filter_by_author() {
+        let db = get_db();
+        let query = Filter {
+            authors: vec![
+                "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c"
+                    .try_into()
+                    .expect("pk"),
+            ],
+            ..Default::default()
+        };
+        let records = db.get_events_by_filter(query).expect("records");
+        assert_eq!(records.len(), 3);
+    }
+
+    #[test]
+    fn filter_by_author_and_kinds() {
+        let db = get_db();
+        let query = Filter {
+            authors: vec![
+                "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c"
+                    .try_into()
+                    .expect("pk"),
+            ],
+            kinds: vec![Kind::ShortTextNote, Kind::Reaction],
+            ..Default::default()
+        };
+        let records = db.get_events_by_filter(query).expect("records");
+        assert_eq!(records.len(), 2);
+    }
+
+    #[test]
+    fn filter_kind() {
+        let db = get_db();
+        let query = Filter {
+            kinds: vec![Kind::ShortTextNote],
+            ..Default::default()
+        };
+        let records = db.get_events_by_filter(query).expect("records");
+        assert_eq!(records.len(), 2_538);
+        println!("{:?}", records);
+    }
+}

Datei-Diff unterdrückt, da er zu groß ist
+ 138 - 0
crates/storage/tests/events.json


+ 21 - 0
crates/types/Cargo.toml

@@ -0,0 +1,21 @@
+[package]
+name = "nostr-rs-types"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+base64 = "0.21.0"
+bech32 = "0.9.1"
+chrono = "0.4.23"
+custom_derive = "0.1.7"
+enum_derive = "0.1.7"
+hex = "0.4"
+rand = "0.8.5"
+secp256k1 = { version = "0.26.0", features = ["global-context"] }
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+sha2 = "0.10.6"
+thiserror = "1.0.39"
+

+ 71 - 0
crates/types/src/client/close.rs

@@ -0,0 +1,71 @@
+//! Closes a subscription
+//!
+//! The client signals to the relayer to stop listening new events for the given
+//! subscription-ID
+use std::{collections::VecDeque, ops::Deref};
+
+use crate::{
+    common::SerializeDeserialize,
+    types::{subscription_id::Error, SubscriptionId},
+};
+use serde_json::Value;
+
+/// SubscriptionID to stop listening new events to
+#[derive(Clone, Debug)]
+pub struct Close(pub SubscriptionId);
+
+impl Deref for Close {
+    type Target = SubscriptionId;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl SerializeDeserialize for Close {
+    fn get_tag() -> &'static str {
+        "CLOSE"
+    }
+
+    fn serialize(&self) -> Result<Vec<Value>, String> {
+        Ok(vec![
+            Value::String(Self::get_tag().to_owned()),
+            Value::String((*self.0).to_owned()),
+        ])
+    }
+
+    fn deserialize(args: VecDeque<Value>) -> Result<Self, String> {
+        if args.is_empty() {
+            return Err("Invalid length".to_owned());
+        }
+        let subscription_id: SubscriptionId = args[0]
+            .as_str()
+            .ok_or_else(|| "Invalid message, expected string".to_owned())?
+            .try_into()
+            .map_err(|e: Error| e.to_string())?;
+        Ok(Self(subscription_id))
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::Request;
+
+    #[test]
+    fn parse() {
+        let json = r#"["CLOSE", "test"]"#;
+        let message: Request = serde_json::from_str(json).expect("message");
+        assert!(message.as_close().is_some());
+    }
+
+    #[test]
+    fn serialize() {
+        let notice = Close("test".try_into().expect("v"));
+        let m: Request = notice.into();
+        assert_eq!(
+            r#"["CLOSE","test"]"#,
+            serde_json::to_string(&m).expect("valid json")
+        );
+    }
+}

+ 39 - 0
crates/types/src/client/event.rs

@@ -0,0 +1,39 @@
+//! An event
+//!
+//! The client publishes an events to the relayer
+use crate::{common::SerializeDeserialize, types};
+use std::{collections::VecDeque, ops::Deref};
+
+/// Publishes an event. It must be signed by a client's public key
+#[derive(Debug, Clone)]
+pub struct Event(pub types::Event);
+
+impl Deref for Event {
+    type Target = types::Event;
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl SerializeDeserialize for Event {
+    fn get_tag() -> &'static str {
+        "EVENT"
+    }
+
+    fn serialize(&self) -> Result<Vec<serde_json::Value>, String> {
+        Ok(vec![
+            serde_json::Value::String(Self::get_tag().to_owned()),
+            serde_json::to_value(&self.0).map_err(|e| e.to_string())?,
+        ])
+    }
+
+    fn deserialize(mut args: VecDeque<serde_json::Value>) -> Result<Self, String>
+    where
+        Self: Sized,
+    {
+        Ok(Self(
+            serde_json::from_value(args.pop_front().ok_or("Invalid argument length")?)
+                .map_err(|e| e.to_string())?,
+        ))
+    }
+}

+ 10 - 0
crates/types/src/client/mod.rs

@@ -0,0 +1,10 @@
+//! # Nostr Protocol Types. Client side
+//!
+//! Messages that are going to be sent by a client to a relayers are defined in
+//! this mod. All inner types are defined here and they can be converted to the
+//! top-level Message to be send to a relayer
+pub mod close;
+pub mod event;
+pub mod subscribe;
+
+pub use self::{close::Close, event::Event, subscribe::Subscribe};

+ 112 - 0
crates/types/src/client/subscribe.rs

@@ -0,0 +1,112 @@
+//! # Nostr Protocol Types. Request
+//!
+//! Used to request events and subscribe to new updates.
+use crate::{common::SerializeDeserialize, types};
+use serde_json::Value;
+use std::collections::VecDeque;
+
+/// Request: used to request events and subscribe to new updates.
+///
+/// More details at https://github.com/nostr-protocol/nips/blob/master/01.md#communication-between-clients-and-relays
+#[derive(Debug, Clone)]
+pub struct Subscribe {
+    /// The new subscription ID. If not provided, a default one will be created
+    /// automatically
+    pub subscription_id: types::SubscriptionId,
+    /// List of filters to requests events and subscribe to new updates
+    /// Events that match any of the filters are to be returned, i.e., multiple filters are to be interpreted as || conditions.
+    pub filters: Vec<types::Filter>,
+}
+
+impl Default for Subscribe {
+    fn default() -> Self {
+        Self {
+            subscription_id: types::SubscriptionId::default(),
+            filters: vec![types::Filter::default()],
+        }
+    }
+}
+
+impl SerializeDeserialize for Subscribe {
+    fn get_tag() -> &'static str {
+        "REQ"
+    }
+
+    fn serialize(&self) -> Result<Vec<Value>, String> {
+        let mut r = vec![
+            Value::String(Self::get_tag().to_owned()),
+            Value::String((*self.subscription_id).to_owned()),
+        ];
+
+        for element in self.filters.iter() {
+            r.push(serde_json::to_value(element).map_err(|e| e.to_string())?);
+        }
+
+        Ok(r)
+    }
+
+    fn deserialize(mut array: VecDeque<Value>) -> Result<Self, String>
+    where
+        Self: Sized,
+    {
+        let subscription_id = array
+            .pop_front()
+            .ok_or("Missing element 1 in the array")?
+            .as_str()
+            .ok_or("Invalid type for element 1, expecting a string")?
+            .try_into()
+            .map_err(|e: types::subscription_id::Error| {
+                format!("Invalid subscription id: {}", e)
+            })?;
+
+        Ok(Subscribe {
+            subscription_id,
+            filters: if !array.is_empty() {
+                serde_json::from_value::<Vec<types::Filter>>(serde_json::Value::Array(
+                    array.into_iter().collect::<Vec<serde_json::Value>>(),
+                ))
+                .map_err(|e: serde_json::Error| e.to_string())?
+            } else {
+                vec![]
+            },
+        })
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::Request as ClientRequest;
+    use chrono::Utc;
+
+    #[test]
+    fn encoding_decoding() {
+        let r: ClientRequest = Subscribe {
+            filters: vec![
+                types::Filter {
+                    authors: vec![
+                        "npub1h0h2zfcp3z7acvc9l0qvpgfqznkalj77whsdkmmlwanfssys3ngsx336a8"
+                            .try_into()
+                            .expect("some addr object"),
+                    ],
+                    until: Some(Utc::now()),
+                    limit: 30,
+                    ..Default::default()
+                },
+                types::Filter {
+                    limit: 20,
+                    ..Default::default()
+                },
+            ],
+            ..Default::default()
+        }
+        .into();
+
+        let serialized = serde_json::to_string(&r).expect("valid json string");
+        let obj: ClientRequest = serde_json::from_str(&serialized).expect("valid request");
+        let obj = obj.as_request().expect("request");
+        let r = r.as_request().expect("request");
+        assert_eq!(r.subscription_id, obj.subscription_id);
+        assert_eq!(r.filters.len(), obj.filters.len());
+    }
+}

+ 21 - 0
crates/types/src/common.rs

@@ -0,0 +1,21 @@
+//! Common features shared between requests and responses
+//!
+use serde_json::Value;
+use std::collections::VecDeque;
+
+/// Serialize and Deserialize a message type
+///
+/// All messages inner types must implement this trait
+pub trait SerializeDeserialize {
+    /// The tag to identity the message by
+    fn get_tag() -> &'static str;
+
+    /// How to serialize into JSON. It must return a vector of serde_json::Value
+    fn serialize(&self) -> Result<Vec<Value>, String>;
+
+    /// How to deserialize, the first element (the tag) is already removed
+    /// before passing to this method
+    fn deserialize(array: VecDeque<Value>) -> Result<Self, String>
+    where
+        Self: Sized;
+}

+ 18 - 0
crates/types/src/lib.rs

@@ -0,0 +1,18 @@
+//! # Nostr Protocol Types
+//!
+//! The types needed to interact with a Nostr relayer, or to be become one.
+#![deny(missing_docs, warnings)]
+
+pub mod client;
+pub mod common;
+pub mod relayer;
+pub mod request;
+pub mod response;
+pub mod types;
+
+pub use self::{request::Request, response::Response};
+
+#[macro_use]
+extern crate custom_derive;
+#[macro_use]
+extern crate enum_derive;

+ 73 - 0
crates/types/src/relayer/eose.rs

@@ -0,0 +1,73 @@
+//! End of Storage Events
+//!
+//! This is how to the relayer signals the client they gave all the stored
+//! messages with the requested filter. Future events will be sent, as they
+//! appear to this relayer
+use crate::{
+    common::SerializeDeserialize,
+    types::{subscription_id::Error, SubscriptionId},
+};
+use serde_json::Value;
+use std::{collections::VecDeque, ops::Deref};
+
+/// This is how to the relayer signals the client they gave all the stored
+/// messages with the requested filter. Future events will be sent, as they
+/// appear to this relayer
+#[derive(Clone, Debug)]
+pub struct EndOfStoredEvents(pub SubscriptionId);
+
+impl Deref for EndOfStoredEvents {
+    type Target = SubscriptionId;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl SerializeDeserialize for EndOfStoredEvents {
+    fn get_tag() -> &'static str {
+        "EOSE"
+    }
+
+    fn serialize(&self) -> Result<Vec<Value>, String> {
+        Ok(vec![
+            Value::String(Self::get_tag().to_owned()),
+            Value::String((*self.0).to_owned()),
+        ])
+    }
+
+    fn deserialize(args: VecDeque<Value>) -> Result<Self, String> {
+        if args.is_empty() {
+            return Err("Invalid length".to_owned());
+        }
+        let subscription_id: SubscriptionId = args[0]
+            .as_str()
+            .ok_or_else(|| "Invalid message, expected string".to_owned())?
+            .try_into()
+            .map_err(|e: Error| e.to_string())?;
+        Ok(EndOfStoredEvents(subscription_id))
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::Response;
+
+    #[test]
+    fn parse() {
+        let json = r#"["EOSE", "test"]"#;
+        let message: Response = serde_json::from_str(json).expect("message");
+        assert!(message.as_end_of_stored_events().is_some());
+    }
+
+    #[test]
+    fn serialize() {
+        let notice = EndOfStoredEvents("test".try_into().expect("v"));
+        let m: Response = notice.into();
+        assert_eq!(
+            r#"["EOSE","test"]"#,
+            serde_json::to_string(&m).expect("valid json")
+        );
+    }
+}

+ 74 - 0
crates/types/src/relayer/event.rs

@@ -0,0 +1,74 @@
+//! Events sent by the relayer to client
+use crate::{
+    common::SerializeDeserialize,
+    types::{self, subscription_id::Error, SubscriptionId},
+};
+use serde_json::Value;
+use std::collections::VecDeque;
+
+/// An event sent to clients
+#[derive(Clone, Debug)]
+pub struct Event {
+    /// The subscription ID that matched the inner event
+    pub subscription_id: SubscriptionId,
+    /// The inner event
+    pub event: types::Event,
+}
+
+impl SerializeDeserialize for Event {
+    fn get_tag() -> &'static str {
+        "EVENT"
+    }
+
+    fn serialize(&self) -> Result<Vec<Value>, String> {
+        Ok(vec![
+            Value::String(Self::get_tag().to_owned()),
+            Value::String((*(self.subscription_id)).to_owned()),
+            serde_json::to_value(&self.event).map_err(|e| e.to_string())?,
+        ])
+    }
+
+    fn deserialize(mut args: VecDeque<Value>) -> Result<Self, String> {
+        if args.len() != 2 {
+            return Err("Invalid length".to_owned());
+        }
+        let subscription_id: SubscriptionId = args
+            .pop_front()
+            .ok_or_else(|| "Invalid array length".to_owned())?
+            .as_str()
+            .ok_or_else(|| "Invalid message, expected string".to_owned())?
+            .try_into()
+            .map_err(|e: Error| e.to_string())?;
+
+        let event: types::Event =
+            serde_json::from_value(args.pop_front().ok_or("Invalid array length")?)
+                .map_err(|e| e.to_string())?;
+
+        Ok(Self {
+            subscription_id,
+            event,
+        })
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use crate::Response;
+
+    #[test]
+    fn parse() {
+        let json = r#"["EVENT","640e914a22321",{"content":"{\"wss:\\/\\/nos.lol\":{\"write\":true,\"read\":true},\"wss:\\/\\/relay.damus.io\":{\"write\":true,\"read\":true},\"wss:\\/\\/brb.io\":{\"write\":true,\"read\":true},\"wss:\\/\\/nostr.orangepill.dev\":{\"write\":true,\"read\":true},\"wss:\\/\\/relay.current.fyi\":{\"write\":true,\"read\":true},\"wss:\\/\\/eden.nostr.land\":{\"write\":true,\"read\":true},\"wss:\\/\\/relay.snort.social\":{\"write\":true,\"read\":true}}","created_at":1678476548,"id":"b8d7f6a19c3d9625b9aade947166708fbc6ab2dd7e3f3af84f1de08ea10d6f38","kind":3,"pubkey":"b2815682cfc83fcd2c3add05785cf4573dd388457069974cc6d8cca06b3c3b78","sig":"352485a162805e72a1e278a4a7bc33facd54a71e0f4c23934f35eee57eaa38c62e6260bf7624e2bd96bc6bcf93373b06c5245e28a266bfe2fbf064224e713fd6","tags":[["p","3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"],["p","b2815682cfc83fcd2c3add05785cf4573dd388457069974cc6d8cca06b3c3b78"],["p","387519cafd325668ecffe59577f37238638da4cf2d985b82f932fc81d33da1e8"],["p","81d0ccce4591fc4e19e3ef752a2b003ef23a986cb31e7835ea7d8d7cd96d47ea"],["p","0861144c765ea10e39a48473a51bee604886e18abd0f831cc5ed7651e68a1caf"],["p","1779284c21126b5e1af6dcb84949ceacad781ce4ce0d1691292a41229465a54a"],["p","d7df5567015930b17c125b3a7cf29bef23aa5a68d09cd6518d291359606aab7b"],["p","82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2"],["p","32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"],["p","a47457722e10ba3a271fbe7040259a3c4da2cf53bfd1e198138214d235064fc2"],["p","e1055729d51e037b3c14e8c56e2c79c22183385d94aadb32e5dc88092cd0fef4"],["p","2bda4f03446bc1c6ff00594e350a1e7ec57fb9cdc4a7b52694223d56ce0599e9"],["p","c57717ec7a6af20b836a9468282948dc0adba64d30abfb40aa8c6664dde3cfc7"],["p","6094dd769f94fab1a2915c1a3d8360e49cec84977f433a872bea1763466db784"]]}]"#;
+        let message: Response = serde_json::from_str(json).expect("message");
+        assert!(message.as_event().is_some());
+    }
+
+    #[test]
+    fn serialize() {
+        let json = r#"["EVENT","640e914a22321",{"content":"{\"wss:\\/\\/nos.lol\":{\"write\":true,\"read\":true},\"wss:\\/\\/relay.damus.io\":{\"write\":true,\"read\":true},\"wss:\\/\\/brb.io\":{\"write\":true,\"read\":true},\"wss:\\/\\/nostr.orangepill.dev\":{\"write\":true,\"read\":true},\"wss:\\/\\/relay.current.fyi\":{\"write\":true,\"read\":true},\"wss:\\/\\/eden.nostr.land\":{\"write\":true,\"read\":true},\"wss:\\/\\/relay.snort.social\":{\"write\":true,\"read\":true}}","created_at":1678476548,"id":"b8d7f6a19c3d9625b9aade947166708fbc6ab2dd7e3f3af84f1de08ea10d6f38","kind":3,"pubkey":"b2815682cfc83fcd2c3add05785cf4573dd388457069974cc6d8cca06b3c3b78","sig":"352485a162805e72a1e278a4a7bc33facd54a71e0f4c23934f35eee57eaa38c62e6260bf7624e2bd96bc6bcf93373b06c5245e28a266bfe2fbf064224e713fd6","tags":[["p","3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"],["p","b2815682cfc83fcd2c3add05785cf4573dd388457069974cc6d8cca06b3c3b78"],["p","387519cafd325668ecffe59577f37238638da4cf2d985b82f932fc81d33da1e8"],["p","81d0ccce4591fc4e19e3ef752a2b003ef23a986cb31e7835ea7d8d7cd96d47ea"],["p","0861144c765ea10e39a48473a51bee604886e18abd0f831cc5ed7651e68a1caf"],["p","1779284c21126b5e1af6dcb84949ceacad781ce4ce0d1691292a41229465a54a"],["p","d7df5567015930b17c125b3a7cf29bef23aa5a68d09cd6518d291359606aab7b"],["p","82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2"],["p","32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"],["p","a47457722e10ba3a271fbe7040259a3c4da2cf53bfd1e198138214d235064fc2"],["p","e1055729d51e037b3c14e8c56e2c79c22183385d94aadb32e5dc88092cd0fef4"],["p","2bda4f03446bc1c6ff00594e350a1e7ec57fb9cdc4a7b52694223d56ce0599e9"],["p","c57717ec7a6af20b836a9468282948dc0adba64d30abfb40aa8c6664dde3cfc7"],["p","6094dd769f94fab1a2915c1a3d8360e49cec84977f433a872bea1763466db784"]]}]"#;
+        let message: Response = serde_json::from_str(json).expect("message");
+        let relayer = message.as_event().expect("relayer").to_owned();
+        let m: Response = relayer.into();
+
+        assert_eq!(json, serde_json::to_string(&m).expect("valid json"));
+    }
+}

+ 10 - 0
crates/types/src/relayer/mod.rs

@@ -0,0 +1,10 @@
+//! Relayer inner message
+//!
+//! This mod has all the messages that relayers may send to clients
+
+pub mod eose;
+pub mod event;
+pub mod notice;
+pub mod ok;
+
+pub use self::{eose::EndOfStoredEvents, event::Event, notice::Notice, ok::ROk};

+ 67 - 0
crates/types/src/relayer/notice.rs

@@ -0,0 +1,67 @@
+//! Notices
+//!
+//! Notices are the way relayers send errors with their description to clients
+//!
+//! This may happen when client makes mistakes at the protocol level
+use crate::common::SerializeDeserialize;
+use serde_json::Value;
+use std::{collections::VecDeque, ops::Deref};
+
+/// Notices are errors
+#[derive(Clone, Debug)]
+pub struct Notice(pub String);
+
+impl Deref for Notice {
+    type Target = str;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl SerializeDeserialize for Notice {
+    fn get_tag() -> &'static str {
+        "NOTICE"
+    }
+
+    fn serialize(&self) -> Result<Vec<Value>, String> {
+        Ok(vec![
+            Value::String(Self::get_tag().to_owned()),
+            Value::String(self.0.to_owned()),
+        ])
+    }
+
+    fn deserialize(args: VecDeque<Value>) -> Result<Self, String> {
+        if args.is_empty() {
+            return Err("Invalid length".to_owned());
+        }
+        let message = args[0]
+            .as_str()
+            .ok_or_else(|| "Invalid message, expected string".to_owned())?
+            .to_owned();
+        Ok(Notice(message))
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::Response;
+
+    #[test]
+    fn parse() {
+        let json = r#"["NOTICE", "test"]"#;
+        let message: Response = serde_json::from_str(json).expect("message");
+        assert!(message.as_notice().is_some());
+    }
+
+    #[test]
+    fn serialize() {
+        let notice = Notice("test".to_owned());
+        let m: Response = notice.into();
+        assert_eq!(
+            r#"["NOTICE","test"]"#,
+            serde_json::to_string(&m).expect("valid json")
+        );
+    }
+}

+ 86 - 0
crates/types/src/relayer/ok.rs

@@ -0,0 +1,86 @@
+//! OK
+//!
+//! When submitting events to relays, clients currently have no way to know if
+//! an event was successfully committed to the database. This NIP introduces the
+//! concept of command results which are like NOTICE's except provide more
+//! information about if an event was accepted or rejected.
+use crate::{
+    common::SerializeDeserialize,
+    types::{self, Addr},
+};
+use serde_json::Value;
+use std::collections::VecDeque;
+
+/// OK messages
+#[derive(Clone, Debug)]
+pub struct ROk {
+    /// Event Id
+    pub id: Addr,
+    /// Whether the event has been successful or not
+    pub status: bool,
+    /// Some message
+    pub message: String,
+}
+
+impl SerializeDeserialize for ROk {
+    fn get_tag() -> &'static str {
+        "OK"
+    }
+
+    fn serialize(&self) -> Result<Vec<Value>, String> {
+        Ok(vec![
+            Value::String(Self::get_tag().to_owned()),
+            Value::String(self.id.to_hex()),
+            Value::Bool(self.status),
+            Value::String(self.message.clone()),
+        ])
+    }
+
+    fn deserialize(args: VecDeque<Value>) -> Result<Self, String> {
+        if args.len() < 3 {
+            return Err("Invalid length".to_owned());
+        }
+        let id = args[0]
+            .as_str()
+            .ok_or_else(|| "Invalid type for element 1, expecting a string".to_owned())?
+            .try_into()
+            .map_err(|e: types::addr::Error| format!("Invalid id: {}", e))?;
+        let status = args[1].as_bool().ok_or("Invalid type, expected bool")?;
+        let message = args[2]
+            .as_str()
+            .ok_or_else(|| "Invalid message, expected string".to_owned())?
+            .to_owned();
+        Ok(Self {
+            id,
+            status,
+            message,
+        })
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::Response;
+
+    #[test]
+    fn parse() {
+        let json = r#"["OK", "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30", true, "pow: difficulty 25>=24"]"#;
+        let message: Response = serde_json::from_str(json).expect("message");
+        assert!(message.as_ok().is_some());
+    }
+
+    #[test]
+    fn serialize() {
+        let ok_ = ROk {
+            id: "a0b0".try_into().expect("valid_id"),
+            status: true,
+            message: "Some test".into(),
+        };
+        let m: Response = ok_.into();
+        assert_eq!(
+            r#"["OK","a0b0",true,"Some test"]"#,
+            serde_json::to_string(&m).expect("valid json")
+        );
+    }
+}

+ 129 - 0
crates/types/src/request.rs

@@ -0,0 +1,129 @@
+//! Request Message
+//!
+//! All messages that clients can send to relayers are abstracted in this mod
+use crate::{client, common::SerializeDeserialize, types};
+use serde::{
+    de::{self, Deserializer},
+    ser::{self, SerializeSeq, Serializer},
+    Deserialize,
+};
+use std::collections::VecDeque;
+
+custom_derive! {
+#[derive(Debug, Clone, EnumFromInner)]
+    /// Request Messages
+    ///
+    /// All requests from clients to relayer are abstracted in this struct
+    pub enum Request {
+        /// Close a subscription
+        Close(client::Close),
+        /// An Event from a client.  This is when a client wants to publish an
+        /// event. The event must be signed.
+        Event(client::Event),
+        /// Creates a subscription. Client tells the relayer which kind of events
+        /// they'd like to hear about
+        Request(client::Subscribe),
+    }
+}
+
+impl Request {
+    /// Returns the current message as a Request, if possible
+    pub fn as_request(&self) -> Option<&client::Subscribe> {
+        match self {
+            Self::Request(x) => Some(x),
+            _ => None,
+        }
+    }
+
+    /// Returns the current message as a close if possible
+    pub fn as_close(&self) -> Option<&client::Close> {
+        match self {
+            Self::Close(t) => Some(t),
+            _ => None,
+        }
+    }
+
+    /// Returns the current message as an event from the client, if possible
+    pub fn as_event(&self) -> Option<&types::Event> {
+        match self {
+            Self::Event(event) => Some(&**event),
+            _ => None,
+        }
+    }
+}
+
+impl ser::Serialize for Request {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let values = match self {
+            Self::Close(t) => t.serialize(),
+            Self::Event(t) => t.serialize(),
+            Self::Request(t) => t.serialize(),
+        }
+        .map_err(ser::Error::custom)?;
+
+        let mut seq = serializer.serialize_seq(Some(values.len()))?;
+        for value in values.iter() {
+            seq.serialize_element(value)?;
+        }
+        seq.end()
+    }
+}
+
+impl<'de> de::Deserialize<'de> for Request {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let mut array: VecDeque<serde_json::Value> = Deserialize::deserialize(deserializer)?;
+        if array.is_empty() {
+            return Err(de::Error::custom(
+                "Invalid array length, expecting at least one",
+            ));
+        }
+
+        let tag = array
+            .remove(0)
+            .ok_or_else(|| de::Error::custom("Invalid array length, expecting at least one"))?;
+
+        let tag = tag
+            .as_str()
+            .ok_or_else(|| de::Error::custom("Invalid type for element 0 of the array"))?;
+
+        match tag {
+            "EVENT" => Ok(client::Event::deserialize(array)
+                .map_err(de::Error::custom)?
+                .into()),
+            "CLOSE" => Ok(client::Close::deserialize(array)
+                .map_err(de::Error::custom)?
+                .into()),
+            "REQ" => Ok(client::Subscribe::deserialize(array)
+                .map_err(de::Error::custom)?
+                .into()),
+            tag => Err(de::Error::custom(format!("{} is not a support tag", tag))),
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn unsupported() {
+        let json = "[\"CLOSEX\", \"foo\"]";
+        let message: Result<Request, _> = serde_json::from_str(json);
+        assert!(message.is_err());
+    }
+
+    #[test]
+    fn close() {
+        let json = "[\"CLOSE\", \"foo\"]";
+        let message: Request = serde_json::from_str(json).expect("valid message");
+        let subscription_id = message.as_close().expect("valid subscription_id");
+
+        assert_eq!("foo".to_owned(), subscription_id.to_string());
+    }
+}

Datei-Diff unterdrückt, da er zu groß ist
+ 200 - 0
crates/types/src/response.rs


+ 231 - 0
crates/types/src/types/addr.rs

@@ -0,0 +1,231 @@
+//! Addresses
+//!
+//! Abstract public-key addresses, and their prefixes.
+//!
+//! It can also parse the bech32 version of the public key if provided (used by
+//! clients mostly)
+use bech32::{self, FromBase32, ToBase32, Variant};
+use serde::{
+    de::{self, Deserialize, Deserializer},
+    ser::{self, Serializer},
+};
+use std::{hash::Hash, ops::Deref};
+use thiserror::Error;
+
+/// Errors
+#[derive(Error, Debug)]
+pub enum Error {
+    /// Invalid Bech32, invalid human readable part.
+    #[error("Unexpected HRP (Human readable part)")]
+    UnexpectedHrp,
+
+    /// Error decoding, provided addresses is not a hex-encoded string
+    #[error("Error decoding: {0}")]
+    DecodingError(#[from] hex::FromHexError),
+
+    /// Data is longer than 32-bytes (when converted to binary/Vec<u32>)
+    #[error("Data is too long ({0} bytes). Addresses are up to 32 bytes")]
+    TooLong(usize),
+
+    /// Error decoding/encoding to Bech32
+    #[error("Bech32 error: {0}")]
+    Bech32(#[from] bech32::Error),
+}
+
+/// Human Readable Part
+///
+/// Which HDR has been used to encode this address with Bech32
+#[derive(Debug, Clone, PartialEq, Eq, Copy)]
+pub enum HumanReadablePart {
+    /// Public Key / Account Address
+    NPub,
+    /// Private key
+    NSec,
+    /// A user note / posts
+    Note,
+}
+
+impl ToString for HumanReadablePart {
+    fn to_string(&self) -> String {
+        match *self {
+            Self::NPub => "npub".to_owned(),
+            Self::NSec => "nsec".to_owned(),
+            Self::Note => "note".to_owned(),
+        }
+    }
+}
+
+/// Public-key based Address
+///
+/// Clients may want to use the Bech32 encoded address *but* the protocol only
+/// cares about hex-encoded binary data.
+#[derive(Debug, Clone, Eq)]
+pub struct Addr {
+    /// Bytes (up to 32 bytes)
+    pub bytes: Vec<u8>,
+    /// HDR if provided
+    pub hrp: Option<HumanReadablePart>,
+}
+
+impl AsRef<[u8]> for Addr {
+    fn as_ref(&self) -> &[u8] {
+        &self.bytes
+    }
+}
+
+impl Deref for Addr {
+    type Target = [u8];
+    fn deref(&self) -> &[u8] {
+        &self.bytes
+    }
+}
+
+impl PartialEq for Addr {
+    fn eq(&self, other: &Self) -> bool {
+        self.bytes.eq(&other.bytes)
+    }
+}
+
+impl Hash for Addr {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        self.bytes.hash(state)
+    }
+}
+
+impl Addr {
+    /// Instantiates a new Addr from a hex string with an optional human
+    /// readable part
+    pub fn try_from_public_key_str(
+        pk: &str,
+        hrp: Option<HumanReadablePart>,
+    ) -> Result<Self, Error> {
+        Addr::try_from_bytes(hex::decode(pk)?, hrp)
+    }
+
+    /// Instantiates a new Addr from a vector of bytes with an optional human
+    /// readable part
+    pub fn try_from_bytes(bytes: Vec<u8>, hrp: Option<HumanReadablePart>) -> Result<Self, Error> {
+        if bytes.len() > 32 {
+            return Err(Error::TooLong(bytes.len()));
+        }
+
+        Ok(Addr { bytes, hrp })
+    }
+
+    /// Creates a new Addr with another human readable part
+    pub fn change_type(self, new_hrp: Option<HumanReadablePart>) -> Self {
+        Self {
+            bytes: self.bytes,
+            hrp: new_hrp,
+        }
+    }
+
+    /// Returns the Addr to a string of hex numbers
+    pub fn to_hex(&self) -> String {
+        hex::encode(&self.bytes)
+    }
+
+    /// Attempts to create a human address, which is bench32 encoded
+    pub fn try_to_human(&self) -> Result<String, Error> {
+        Ok(bech32::encode(
+            self.hrp.map(|t| t.to_string()).unwrap_or_default().as_str(),
+            self.bytes.to_base32(),
+            Variant::Bech32,
+        )?)
+    }
+}
+
+/// Converts an Addr from an string
+///
+/// The first approach is try decoding the the Addr as a hex-encoded string.
+///
+/// The second approach is decoding with bech32
+impl TryFrom<String> for Addr {
+    type Error = Error;
+
+    fn try_from(value: String) -> Result<Self, Self::Error> {
+        if let Ok(bytes) = hex::decode(&value) {
+            return Ok(Self { bytes, hrp: None });
+        }
+        let (hrp, bytes, _) = bech32::decode(&value)?;
+        let hrp = match hrp.to_lowercase().as_str() {
+            "npub" => Ok(HumanReadablePart::NPub),
+            "nsec" => Ok(HumanReadablePart::NSec),
+            "note" => Ok(HumanReadablePart::Note),
+            _ => Err(Error::UnexpectedHrp),
+        }?;
+
+        Ok(Self {
+            bytes: Vec::<u8>::from_base32(&bytes)?,
+            hrp: Some(hrp),
+        })
+    }
+}
+
+impl TryFrom<&str> for Addr {
+    type Error = Error;
+
+    fn try_from(value: &str) -> Result<Self, Self::Error> {
+        value.to_owned().try_into()
+    }
+}
+
+impl<'de> Deserialize<'de> for Addr {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let s = <String>::deserialize(deserializer)?;
+        let t = s
+            .try_into()
+            .map_err(|e: Error| de::Error::custom(e.to_string()))?;
+        Ok(t)
+    }
+}
+
+impl ser::Serialize for Addr {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let str = self.to_hex();
+        serializer.serialize_str(&str)
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn deserializes_json() {
+        let json_str = "\"b2815682cfc83fcd2c3add05785cf4573dd388457069974cc6d8cca06b3c3b78\"";
+        let x: Addr = serde_json::from_str(json_str).expect("valid pk");
+        assert_eq!(None, x.hrp);
+        assert_eq!(
+            "npub1k2q4dqk0eqlu6tp6m5zhsh852u7a8zz9wp5ewnxxmrx2q6eu8duq3ydzzr",
+            x.change_type(Some(HumanReadablePart::NPub))
+                .try_to_human()
+                .expect("valid addr"),
+        );
+    }
+
+    #[test]
+    fn deserializes_from_public_key_and_json() {
+        let addr1 = Addr::try_from_public_key_str(
+            "b2815682cfc83fcd2c3add05785cf4573dd388457069974cc6d8cca06b3c3b78",
+            Some(HumanReadablePart::NPub),
+        )
+        .expect("valid addr");
+        assert_eq!(
+            "npub1k2q4dqk0eqlu6tp6m5zhsh852u7a8zz9wp5ewnxxmrx2q6eu8duq3ydzzr".to_owned(),
+            addr1.try_to_human().expect("valid address")
+        );
+
+        let addr2: Addr = serde_json::from_str(
+            "\"npub1k2q4dqk0eqlu6tp6m5zhsh852u7a8zz9wp5ewnxxmrx2q6eu8duq3ydzzr\"",
+        )
+        .expect("valid addr");
+        assert_eq!(addr1, addr2);
+    }
+}

+ 137 - 0
crates/types/src/types/content/mod.rs

@@ -0,0 +1,137 @@
+//! Content
+//!
+//! The content encoding logic is defined in this mod. The encoding is
+//! determined by the event kind.
+use super::Kind;
+use base64::{engine::general_purpose, Engine as _};
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+use thiserror::Error;
+
+pub mod profile;
+
+/// Common errors
+#[derive(Error, Debug)]
+pub enum Error {
+    /// A JSON encoding/decoding error
+    #[error("JSON: {0}")]
+    Json(#[from] serde_json::Error),
+
+    /// A Base64 encoding/decoding error
+    #[error("Base64: {0}")]
+    Base64(#[from] base64::DecodeError),
+}
+
+/// Relay settings
+#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
+pub struct RelaySettings {
+    /// Can this relay be used to read/subscribe to updates?
+    pub read: bool,
+    /// Can this relay be used to publish new events
+    pub write: bool,
+}
+
+/// Encrypted message
+#[derive(Eq, PartialEq, Clone, Debug)]
+pub struct EncryptedData {
+    /// Encrypted data, in raw bytes
+    pub encrypted_message: Vec<u8>,
+    /// The Initialization vector for the encrypted message
+    pub iv: Vec<u8>,
+}
+
+/// Event content
+#[derive(Eq, PartialEq, Clone, Debug)]
+pub enum Content {
+    /// Metadata
+    ///
+    /// The metadata is the profile information
+    Metadata(profile::Profile),
+    /// Short text notes
+    ShortTextNote(String),
+    /// Recommend relayers where a given public key or event publishes their content
+    RecommendRelayer(String),
+    /// List of contacts a public key has
+    ///
+    /// This can be interpreted as the list of accounts / public key a given
+    /// account follows
+    Contacts(HashMap<String, RelaySettings>),
+    /// Encrypted messages
+    ///
+    /// The encrypted messages are encrypted with the user's public key. They
+    /// can only be decrypted with the counterpart private key
+    EncryptedDirectMessage(EncryptedData),
+    //// An event that should be removed.
+    ///
+    /// A client can request relayers to remove their own contents. A relayer
+    /// may comply or not
+    EventDeletion,
+    /// Reposting content from another public key
+    Repost,
+    /// React to another event
+    Reaction(String),
+    /// Unknown event
+    Unknown(Kind, String),
+}
+
+impl Content {
+    /// Returns the Kind for self
+    pub fn kind(&self) -> Kind {
+        self.into()
+    }
+
+    /// Deserializes the content with a give kind
+    pub fn deserialize(kind: Kind, content: &str) -> Result<Self, Error> {
+        match kind {
+            Kind::Metadata => Ok(Self::Metadata(
+                serde_json::from_str(content).unwrap_or_default(),
+            )),
+            Kind::ShortTextNote => Ok(Self::ShortTextNote(content.to_owned())),
+            Kind::RecommendRelayer => Ok(Self::RecommendRelayer(content.to_owned())),
+            Kind::Contacts => Ok(Self::Contacts(
+                serde_json::from_str(content).unwrap_or_default(),
+            )),
+            Kind::EncryptedDirectMessages => {
+                let parts = content.split("?iv=").collect::<Vec<&str>>();
+                if parts.len() == 2 {
+                    let encrypted_message = general_purpose::STANDARD.decode(parts[0])?;
+                    let iv = general_purpose::STANDARD.decode(parts[1])?;
+
+                    Ok(Self::EncryptedDirectMessage(EncryptedData {
+                        encrypted_message,
+                        iv,
+                    }))
+                } else {
+                    Ok(Self::Unknown(Kind::Unknown(4), content.to_owned()))
+                }
+            }
+            Kind::EventDeletion => Ok(Self::EventDeletion),
+            Kind::Repost => Ok(Self::Repost),
+            Kind::Reaction => Ok(Self::Reaction(content.to_owned())),
+            kind => Ok(Self::Unknown(kind, content.to_owned())),
+        }
+    }
+
+    /// Serializes the content to string
+    ///
+    /// This should be used only when raw_content is not available. This should be
+    /// used once, before signing to populate raw_content in the Event struct
+    pub fn try_to_string(&self) -> Result<String, Error> {
+        match self {
+            Self::Metadata(p) => Ok(serde_json::to_string(&p)?),
+            Self::Contacts(p) => Ok(serde_json::to_string(&p)?),
+            Self::EncryptedDirectMessage(message) => {
+                let encrypted_message =
+                    general_purpose::STANDARD.encode(&message.encrypted_message);
+                let iv = general_purpose::STANDARD.encode(&message.iv);
+                let to_encode = format!("{}?iv={}", encrypted_message, iv);
+                Ok(serde_json::to_string(&to_encode)?)
+            }
+            Self::ShortTextNote(t)
+            | Self::RecommendRelayer(t)
+            | Self::Unknown(_, t)
+            | Self::Reaction(t) => Ok(t.to_owned()),
+            Self::EventDeletion | Self::Repost => Ok("".to_owned()),
+        }
+    }
+}

+ 33 - 0
crates/types/src/types/content/profile.rs

@@ -0,0 +1,33 @@
+//! Profile
+//!
+//! The profile are defined in the NIP-01 and NIP-05. The content, as many
+//! others, is encoded as JSON. Because the events are encoded as JSON, the
+//! content is a string, not a nested object, but instead a double encoding JSON
+//! object
+use serde::{Deserialize, Serialize};
+
+/// Profile as defined as NIP-01. NIP-05. Most properties are optional
+#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Default)]
+pub struct Profile {
+    /// Displaying name
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub display_name: Option<String>,
+    /// User biography
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub about: Option<String>,
+    /// User name
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub name: Option<String>,
+    /// Picture. It must be an external
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub picture: Option<String>,
+    /// Lightning payment address
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub lud06: Option<String>,
+    /// Website definition
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub website: Option<String>,
+    /// Where the NIP-05
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub nip05: Option<String>,
+}

+ 231 - 0
crates/types/src/types/event.rs

@@ -0,0 +1,231 @@
+//! Event type
+//!
+//! This is the core type of the protocol. All events must be signed to be valid
+//! and broadcasted by relayers
+use super::{content, Content, Id, Kind, Signature};
+use chrono::{DateTime, Utc};
+use secp256k1::{schnorr, Message, XOnlyPublicKey};
+use serde::{
+    de::{self, Deserializer},
+    Deserialize, Serialize,
+};
+use serde_json::json;
+use sha2::{Digest, Sha256};
+use thiserror::Error;
+
+/// Errors
+#[derive(Error, Debug)]
+pub enum Error {
+    /// JSON serialization errors
+    #[error("Error serializing object: {0}")]
+    Serialize(#[from] serde_json::Error),
+
+    /// Error serializing an ID
+    #[error("Error serializing id: {0}")]
+    Id(#[from] hex::FromHexError),
+
+    /// Error in the signature
+    #[error("Signature error: {0}")]
+    Signature(#[from] secp256k1::Error),
+
+    /// The provided ID is not valid, the ID is the hash of the content, as
+    /// described in NIP-01
+    #[error("Provided ID is not correct")]
+    InvalidProvidedId,
+
+    /// Internal Error
+    #[error("Internal error: {0}")]
+    Internal(#[from] content::Error),
+}
+
+/// This is the unsigned event.
+///
+/// The event must be signed, in the crate::types::Event, which wraps this
+/// struct, but all the content of the event is defined here, but the signature.
+#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
+pub struct UnsignedEvent {
+    /// The public key that signs this event
+    #[serde(rename = "pubkey")]
+    pub public_key: Id,
+    /// A timestamp when this event has been created
+    #[serde(with = "super::ts_seconds")]
+    pub created_at: DateTime<Utc>,
+    /// Content kind
+    pub kind: Kind,
+    /// Tags
+    pub tags: Vec<super::Tag>,
+    #[serde(skip_serializing)]
+    /// Content
+    ///
+    /// This is the parsed version of the raw_content. This is meant to be used
+    /// at rust's level to read the content easily.
+    pub content: Content,
+    /// Raw content
+    ///
+    /// The raw content that is serialized into the wire and signed.
+    #[serde(rename = "content")]
+    pub raw_content: String,
+}
+
+impl<'de> de::Deserialize<'de> for UnsignedEvent {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        #[derive(Deserialize)]
+        struct MetadataInternal {
+            #[serde(rename = "pubkey")]
+            pub public_key: Id,
+            #[serde(with = "super::ts_seconds")]
+            pub created_at: DateTime<Utc>,
+            pub kind: Kind,
+            pub tags: Vec<super::Tag>,
+            pub content: String,
+        }
+        let s: MetadataInternal = MetadataInternal::deserialize(deserializer)?;
+        let content = Content::deserialize(s.kind, &s.content)
+            .map_err(|d| de::Error::custom(d.to_string()))?;
+        Ok(Self {
+            public_key: s.public_key,
+            created_at: s.created_at,
+            kind: s.kind,
+            tags: s.tags,
+            raw_content: s.content,
+            content,
+        })
+    }
+}
+
+impl UnsignedEvent {
+    /// Creates a new unsigned event
+    pub fn new(
+        public_key: Id,
+        tags: Vec<super::Tag>,
+        content: Content,
+        created_at: Option<DateTime<Utc>>,
+    ) -> Result<Self, Error> {
+        Ok(Self {
+            public_key,
+            created_at: created_at.unwrap_or_else(Utc::now),
+            tags,
+            kind: content.kind(),
+            raw_content: content.try_to_string()?,
+            content,
+        })
+    }
+
+    /// Calculates the ID for this unsigned event
+    ///
+    /// This ID is then signed by the public key's counter part private key.
+    pub fn id(&self) -> Result<Id, Error> {
+        let data_to_hash = serde_json::to_string(&json!(vec![
+            json!(0),
+            json!(self.public_key.to_string()),
+            json!(self.created_at.timestamp()),
+            json!(self.content.kind()),
+            json!(self.tags),
+            json!(self.raw_content),
+        ]))?;
+        let mut hasher = Sha256::new();
+        hasher.update(&data_to_hash);
+        Ok(Id(hasher.finalize().into()))
+    }
+}
+
+/// Signed event
+///
+/// This is a wrap for UnsignedEvent but it will serializes the ID and the signature.
+///
+/// This is the struct that is published to relayers from clients
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Event {
+    /// Event Id
+    ///
+    /// This is calculated from the UnsignedEvent and later is used to be
+    /// signed. Any minor alteration to the UnsignedEvent will generate a
+    /// totally different Id and Signature, making the whole event invalid
+    pub id: Id,
+    /// Event content
+    #[serde(flatten)]
+    pub inner: UnsignedEvent,
+    /// The signature that makes the inner unsigned event's ID
+    #[serde(rename = "sig")]
+    pub signature: Signature,
+}
+
+impl Event {
+    /// Creates a new event
+    ///
+    /// At this stage user must provide the signature as well.
+    ///
+    /// TODO: Create a new function where the user provides the private key instead
+    pub fn new(data: UnsignedEvent, signature: Signature) -> Result<Self, Error> {
+        let id = data.id()?;
+        Self::verify_signature(&data.public_key, &signature, &id)?;
+
+        Ok(Self {
+            id,
+            inner: data,
+            signature,
+        })
+    }
+
+    /// Returns the kind of this event
+    pub fn kind(&self) -> Kind {
+        self.inner.kind
+    }
+
+    /// Returns the date when this event was created
+    pub fn created_at(&self) -> DateTime<Utc> {
+        self.inner.created_at
+    }
+
+    /// Returns the event author
+    pub fn author(&self) -> &Id {
+        &self.inner.public_key
+    }
+
+    /// Checks if the event is valid
+    pub fn is_valid(&self) -> Result<(), Error> {
+        let calculated_id = self.inner.id()?;
+        if calculated_id != self.id {
+            return Err(Error::InvalidProvidedId);
+        }
+        Self::verify_signature(&self.inner.public_key, &self.signature, &self.id)
+    }
+
+    /// Returns list of tags for this event
+    pub fn tags(&self) -> &[super::Tag] {
+        &self.inner.tags
+    }
+
+    /// Returns a reference to the inner content (the parsed content)
+    pub fn content(&self) -> &Content {
+        &self.inner.content
+    }
+
+    /// Verifies if the Id, Public Key and Signature are correct
+    fn verify_signature(
+        public_key: &Id,
+        signature: &Signature,
+        content_id: &Id,
+    ) -> Result<(), Error> {
+        let public_key = XOnlyPublicKey::from_slice(&**public_key)?;
+        let message = Message::from_slice(&**content_id)?;
+        Ok(schnorr::Signature::from_slice(&**signature)?.verify(&message, &public_key)?)
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn parse_event_content() {
+        let json = "{\"content\":\"{\\\"lud06\\\":\\\"lnbc1p3a4wxvpp5x0pa6gr55fq5s9d3dxs0vz77mqxgdw63hhtgtlfz5zvm65847vnqdqqcqpjsp5402c8rtqxd4j97rnvuejuwl4sg473g6wg08d67fvn7qc4gtpkfks9q7sqqqqqqqqqqqqqqqqqqqsqqqqqysgqmqz9gxqyjw5qrzjqwryaup9lh50kkranzgcdnn2fgvx390wgj5jd07rwr3vxeje0glclleasn65surjcsqqqqlgqqqqqeqqjqyxj968tem9ps6ttm9ukv6ag4yc6qmgj2svrccfgp4n83fpktr3dsx6fq7grfzlqt982aaemahg9q29vzl9f627kh4j8h8xc2z2mtpdqqjlekah\\\",\\\"website\\\":\\\"\\\",\\\"nip05\\\":\\\"cesar@cesar.com.py\\\",\\\"picture\\\":\\\"https://pbs.twimg.com/profile_images/1175432935337537536/_Peu9vuJ_400x400.jpg\\\",\\\"display_name\\\":\\\"C\\\",\\\"about\\\":\\\"Rust and PHP\\\",\\\"name\\\":\\\"c\\\"}\",\"created_at\":1678476588,\"id\":\"3800c787a23288641c0b96cbcc87c26cbd3ea7bee53b7748422fdb100fb7b9f0\",\"kind\":0,\"pubkey\":\"b2815682cfc83fcd2c3add05785cf4573dd388457069974cc6d8cca06b3c3b78\",\"sig\":\"c8a12ce96833e4cd67bce0e9e50f831262ef0f0c0cff5e56c38a0c90867ed1a6621e9692948ef5e85a7ca3726c3f0f43fa7e1992536bc457317123bca8784f5f\",\"tags\":[]}";
+
+        let obj: Event = serde_json::from_str(json).expect("valid");
+        obj.is_valid().unwrap();
+        assert!(obj.is_valid().is_ok());
+    }
+}

+ 80 - 0
crates/types/src/types/filter.rs

@@ -0,0 +1,80 @@
+//! Filters
+//!
+//! This is used for clients to requests for events and subscribe to new events.
+//!
+//! Each property is treated as an AND, but a vector of filters can be provided,
+//! and they will be treated as OR.
+use chrono::{DateTime, Utc};
+use serde::{Deserialize, Serialize};
+
+use super::Kind;
+
+/// Filter
+#[derive(Serialize, Deserialize, Default, Debug, Clone)]
+pub struct Filter {
+    /// Fetch events by their Id, or subscribe
+    ///
+    /// A full Id can be provided, or a prefix.
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub ids: Vec<super::Addr>,
+    /// Fetch events by a public key, or a prefix
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub authors: Vec<super::Addr>,
+    /// Fetch events by their kinds
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub kinds: Vec<Kind>,
+    /// Fetch events that has tag references to a given event id
+    #[serde(default, skip_serializing_if = "Vec::is_empty", rename = "#e")]
+    pub references_to_event: Vec<super::Addr>,
+    /// Fetches events that has a tag to a public key
+    #[serde(default, skip_serializing_if = "Vec::is_empty", rename = "#p")]
+    pub references_to_public_key: Vec<super::Addr>,
+    /// Fetch events newer to this given timestamp
+    #[serde(
+        default,
+        with = "super::option_ts_seconds",
+        skip_serializing_if = "Option::is_none"
+    )]
+    pub since: Option<DateTime<Utc>>,
+    /// Fetch events older to this timestamp
+    #[serde(
+        default,
+        with = "super::option_ts_seconds",
+        skip_serializing_if = "Option::is_none"
+    )]
+    pub until: Option<DateTime<Utc>>,
+    /// Has a limit to fetch stored events
+    #[serde(default, skip_serializing_if = "is_zero")]
+    pub limit: u64,
+}
+
+#[inline]
+fn is_zero(number: &u64) -> bool {
+    *number == 0
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn deserialize() {
+        let json = "{\"authors\":[\"c0f0f1\", \"1234567901\"]}";
+        let obj: Result<Filter, _> = serde_json::from_str(&json);
+        assert!(obj.is_ok());
+    }
+
+    #[test]
+    fn serialize() {
+        let now = Utc::now();
+        let x = Filter {
+            since: Some(now),
+            ..Default::default()
+        };
+
+        assert_eq!(
+            format!("{}\"since\":{:?}{}", "{", now.timestamp(), "}"),
+            serde_json::to_string(&x).expect("valid json")
+        );
+    }
+}

+ 63 - 0
crates/types/src/types/id.rs

@@ -0,0 +1,63 @@
+//! This mod wraps the event Ids
+use serde::{
+    de::{self, Deserializer},
+    ser::{self, Serializer},
+    Deserialize,
+};
+use std::ops::Deref;
+
+/// Event Id
+///
+/// Event Id are raw 32 bytes and 64-character length hex encoded to JSON
+#[derive(Debug, Clone, Hash, Eq, PartialEq)]
+pub struct Id(pub [u8; 32]);
+
+impl AsRef<[u8]> for Id {
+    fn as_ref(&self) -> &[u8] {
+        &self.0
+    }
+}
+
+impl Deref for Id {
+    type Target = [u8; 32];
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl ToString for Id {
+    fn to_string(&self) -> String {
+        hex::encode(self.0)
+    }
+}
+
+impl TryFrom<String> for Id {
+    type Error = String;
+    fn try_from(value: String) -> Result<Self, Self::Error> {
+        Ok(Self(
+            hex::decode(&value)
+                .map_err(|e| e.to_string())?
+                .try_into()
+                .map_err(|_| "Invalid length for Id".to_string())?,
+        ))
+    }
+}
+
+impl<'de> Deserialize<'de> for Id {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let s = <String>::deserialize(deserializer)?;
+        s.try_into().map_err(de::Error::custom)
+    }
+}
+
+impl ser::Serialize for Id {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        serializer.serialize_str(self.to_string().as_str())
+    }
+}

+ 234 - 0
crates/types/src/types/kind.rs

@@ -0,0 +1,234 @@
+//! Content Kind
+//!
+//! This is how the client should process the content.
+use super::Content;
+use serde::{
+    de::{self, Deserializer},
+    ser::{self, Serializer},
+    Deserialize,
+};
+
+/// Content Kind
+///
+/// Any unsupported Kind will be wrapped under the Unknown type
+///
+/// The Kind is represented as a u32 on the wire
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum Kind {
+    /// Metadata
+    ///
+    /// The metadata is the profile information
+    Metadata,
+    /// Short text notes
+    ShortTextNote,
+    /// Recommend relayers where a given public key or event publishes their content
+    RecommendRelayer,
+    /// List of contacts a public key has
+    ///
+    /// This can be interpreted as the list of accounts / public key a given
+    /// account follows
+    Contacts,
+    /// Encrypted messages
+    ///
+    /// The encrypted messages are encrypted with the user's public key. They
+    /// can only be decrypted with the counterpart private key
+    EncryptedDirectMessages,
+    //// An event that should be removed.
+    ///
+    /// A client can request relayers to remove their own contents. A relayer
+    /// may comply or not
+    EventDeletion,
+    /// Reposting content from another public key
+    Repost,
+    /// React to another event
+    Reaction,
+    /// Zap Request
+    ZapRequest,
+    /// Zap
+    Zap,
+    /// Unknown Kind
+    Unknown(u32),
+}
+
+impl PartialOrd for Kind {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        let kind_id: u32 = (*self).into();
+        let other_kind_id: u32 = (*other).into();
+        kind_id.partial_cmp(&other_kind_id)
+    }
+}
+
+impl Ord for Kind {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        let kind_id: u32 = (*self).into();
+        let other_kind_id: u32 = (*other).into();
+        kind_id.cmp(&other_kind_id)
+    }
+}
+
+impl Kind {
+    /// Is the Kind replaceable according to NIP-16
+    pub fn is_replaceable(&self) -> bool {
+        let kind_id: u32 = (*self).into();
+        (10_000..=19_999).contains(&kind_id)
+    }
+
+    /// Is the Kind ephemeral according to NIP-16
+    pub fn is_ephemeral(&self) -> bool {
+        let kind_id: u32 = (*self).into();
+        (20_000..=29_999).contains(&kind_id)
+    }
+}
+
+impl From<Kind> for u32 {
+    fn from(kind: Kind) -> Self {
+        match kind {
+            Kind::Metadata => 0,
+            Kind::ShortTextNote => 1,
+            Kind::RecommendRelayer => 2,
+            Kind::Contacts => 3,
+            Kind::EncryptedDirectMessages => 4,
+            Kind::EventDeletion => 5,
+            Kind::Repost => 6,
+            Kind::Reaction => 7,
+            Kind::ZapRequest => 9734,
+            Kind::Zap => 9735,
+            Kind::Unknown(t) => t,
+        }
+    }
+}
+
+impl From<u32> for Kind {
+    fn from(kind: u32) -> Self {
+        match kind {
+            0 => Kind::Metadata,
+            1 => Kind::ShortTextNote,
+            2 => Kind::RecommendRelayer,
+            3 => Kind::Contacts,
+            4 => Kind::EncryptedDirectMessages,
+            5 => Kind::EventDeletion,
+            6 => Kind::Repost,
+            7 => Kind::Reaction,
+            9734 => Kind::ZapRequest,
+            9735 => Kind::Zap,
+            any => Kind::Unknown(any),
+        }
+    }
+}
+
+impl ser::Serialize for Kind {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        serializer.serialize_u32((*self).into())
+    }
+}
+
+impl<'de> de::Deserialize<'de> for Kind {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let raw: u32 = Deserialize::deserialize(deserializer)?;
+        Ok(raw.into())
+    }
+}
+
+impl From<&Content> for Kind {
+    fn from(content: &Content) -> Self {
+        match content {
+            Content::Metadata(_) => Kind::Metadata,
+            Content::ShortTextNote(_) => Kind::ShortTextNote,
+            Content::RecommendRelayer(_) => Kind::RecommendRelayer,
+            Content::Contacts(_) => Kind::Contacts,
+            Content::EncryptedDirectMessage(_) => Kind::EncryptedDirectMessages,
+            Content::EventDeletion => Kind::EventDeletion,
+            Content::Repost => Kind::Repost,
+            Content::Reaction(_) => Kind::Reaction,
+            Content::Unknown(kind, _) => *kind,
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use crate::{
+        types::{Content, Kind},
+        Response,
+    };
+
+    #[test]
+    fn deletion() {
+        let json = r#"["EVENT","6414a63d7ddf1",{"content":"","created_at":1679052792,"id":"2f9b354e8ea7b28e1e33c585ceb19e0b6838e3580f2741fe5af6cbe0b6146566","kind":5,"pubkey":"bd4bdec9641883c492ca0062bb4d6aa8c756385980ec3e7fe2217b03184985c6","sig":"faa1c74d050f3d941049cdfb6c4e6865b90c11ce591fc2150d53be2acfc92e085dfe451d1bbcff3c88079034def131394b37448baa0a46c848017f09a00305a3","tags":[["e","fe722046568a80af561b23c27a381514575f028c908b428dd8ac065db8fcb9eb"]]}]"#;
+
+        let msg: Response = serde_json::from_str(json).expect("message");
+        assert_eq!(
+            Kind::EventDeletion,
+            msg.as_event().expect("event").event.inner.kind
+        );
+    }
+
+    #[test]
+    fn deletion_content_to_kind() {
+        let content = Content::EventDeletion;
+        let kind: Kind = (&content).into();
+        assert_eq!(kind, Kind::EventDeletion);
+    }
+
+    #[test]
+    fn reaction() {
+        let json = r#"["EVENT","6414a69109d48",{"content":"🤙","created_at":1679074282,"id":"303eb43cea76695f11d3ceae9e1e89797034d04bbad4b922099c0c4087f30c58","kind":7,"pubkey":"e64b77a57752e98e2fa23d16773a182c46d92108a30f85e445604804d8543526","sig":"b6f2f26a2eecf13c077185f32cbfb73e3916484ef5745fdf37bd1025784935db18345b39048470bab0448cd52bcc8c7c2c140f895aa38ec1d17f1350e9d328d9","tags":[["e","5c24cac9fcdcb894d18c33c059dcc0eba7a5124387d1dbb6e03d2dd3411c4d40"],["p","bd8aed58fee47e64520ec250b70efd264b496bc3f8c2563c26c0b843cb1cdb53"]]}]"#;
+
+        let msg: Response = serde_json::from_str(json).expect("message");
+        assert_eq!(
+            Kind::Reaction,
+            msg.as_event().expect("event").event.inner.kind
+        );
+    }
+
+    #[test]
+    fn reaction_content_to_kind() {
+        let content = Content::Reaction("🤙".to_owned());
+        let kind: Kind = (&content).into();
+        assert_eq!(kind, Kind::Reaction);
+    }
+
+    #[test]
+    fn repost() {
+        let json = r#"["EVENT","6414a4343e86b",{"content":"","created_at":1679068882,"id":"a08ec930d95860f0ffe53d95887ed30334562e31d137db0377828c1c1827245d","kind":6,"pubkey":"13bf6b8de4d3b8b0103529b4753c6dece8cc2cddc34a682dc6a8dbe6ab317876","sig":"2d1e4b83e2c0a498f14555e9bfbd2516294062079403bdda4db656d0edd57b428e948becb50ce044f28006899eb2fc444ce2e4e04bef0247f863bbabd26f87fe","tags":[["e","2aa7af268b11942c24016333a590aee738cf5bf28e4602421178bfb08759b13a"],["p","13bf6b8de4d3b8b0103529b4753c6dece8cc2cddc34a682dc6a8dbe6ab317876"]]}]"#;
+
+        let msg: Response = serde_json::from_str(json).expect("message");
+        assert_eq!(
+            Kind::Repost,
+            msg.as_event().expect("event").event.inner.kind
+        );
+    }
+
+    #[test]
+    fn repost_content_to_kind() {
+        let content = Content::Repost;
+        let kind: Kind = (&content).into();
+        assert_eq!(kind, Kind::Repost);
+    }
+
+    #[test]
+    fn is_replaceable() {
+        let repleaceable: Kind = 15005.into();
+        let not_repleaceable: Kind = 1.into();
+        let not_repleaceable_2: Kind = 20101.into();
+        assert!(repleaceable.is_replaceable());
+        assert!(!not_repleaceable.is_replaceable());
+        assert!(!not_repleaceable_2.is_replaceable());
+    }
+
+    #[test]
+    fn is_ephemeral() {
+        let ephemeral: Kind = 25005.into();
+        let not_ephemeral: Kind = 1.into();
+        let not_ephemeral_2: Kind = 10101.into();
+        assert!(ephemeral.is_ephemeral());
+        assert!(!not_ephemeral.is_ephemeral());
+        assert!(!not_ephemeral_2.is_ephemeral());
+    }
+}

+ 86 - 0
crates/types/src/types/mod.rs

@@ -0,0 +1,86 @@
+//! Common shared types between clients and relayers
+pub mod addr;
+pub mod content;
+pub mod event;
+pub mod filter;
+pub mod id;
+pub mod kind;
+pub mod signature;
+pub mod subscription_id;
+pub mod tag;
+
+pub(crate) mod option_ts_seconds {
+    use chrono::{DateTime, LocalResult, TimeZone, Utc};
+    use serde::{de, Deserialize, Deserializer, Serializer};
+
+    pub fn serialize<S>(date: &Option<DateTime<Utc>>, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        match date {
+            Some(date) => super::ts_seconds::serialize(date, serializer),
+            None => serializer.serialize_none(),
+        }
+    }
+
+    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let timestamp: Option<i64> = Deserialize::deserialize(deserializer)?;
+        let timestamp = if let Some(timestamp) = timestamp {
+            timestamp
+        } else {
+            return Ok(None);
+        };
+
+        match Utc.timestamp_opt(timestamp, 0) {
+            LocalResult::None => Err(de::Error::custom("Invalid timestamp")),
+            LocalResult::Ambiguous(min, max) => Err(de::Error::custom(format!(
+                "Timestamp {0} is ambiguous (min={1}, max={2})",
+                timestamp, min, max
+            ))),
+            LocalResult::Single(ts) => Ok(Some(ts)),
+        }
+    }
+}
+
+pub(crate) mod ts_seconds {
+    use chrono::{DateTime, LocalResult, TimeZone, Utc};
+    use serde::{de, Deserialize, Deserializer, Serializer};
+
+    pub fn serialize<S>(date: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        serializer.serialize_i64(date.timestamp())
+    }
+
+    pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let timestamp: i64 = Deserialize::deserialize(deserializer)?;
+
+        match Utc.timestamp_opt(timestamp, 0) {
+            LocalResult::None => Err(de::Error::custom("Invalid timestamp")),
+            LocalResult::Ambiguous(min, max) => Err(de::Error::custom(format!(
+                "Timestamp {0} is ambiguous (min={1}, max={2})",
+                timestamp, min, max
+            ))),
+            LocalResult::Single(ts) => Ok(ts),
+        }
+    }
+}
+
+pub use self::{
+    addr::Addr,
+    content::Content,
+    event::{Event, UnsignedEvent},
+    filter::Filter,
+    id::Id,
+    kind::Kind,
+    signature::Signature,
+    subscription_id::SubscriptionId,
+    tag::Tag,
+};

+ 89 - 0
crates/types/src/types/signature.rs

@@ -0,0 +1,89 @@
+//! Event signature
+use serde::{
+    de::{self, Deserialize, Deserializer},
+    ser::{self, Serializer},
+};
+use std::ops::Deref;
+use thiserror::Error;
+
+/// Errors
+#[derive(Error, Debug)]
+pub enum Error {
+    /// Invalid JSON encoding
+    ///
+    /// It must be encoded as a hex string with length 128
+    #[error("Error decoding: {0}")]
+    DecodingError(#[from] hex::FromHexError),
+
+    /// Invalid size of the signature
+    #[error("Expecting a 64 bytes signature")]
+    InvalidSize,
+}
+
+/// Wraps the signature as a 64-bytes vector of bytes
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Signature(pub [u8; 64]);
+
+impl AsRef<[u8]> for Signature {
+    fn as_ref(&self) -> &[u8] {
+        &self.0
+    }
+}
+
+impl Deref for Signature {
+    type Target = [u8; 64];
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl ToString for Signature {
+    fn to_string(&self) -> String {
+        hex::encode(self.0)
+    }
+}
+
+impl TryFrom<String> for Signature {
+    type Error = Error;
+
+    fn try_from(value: String) -> Result<Self, Self::Error> {
+        let bytes = hex::decode(&value)?;
+        if bytes.len() != 64 {
+            return Err(Error::InvalidSize);
+        }
+        Ok(Self(
+            bytes[0..64].try_into().map_err(|_| Error::InvalidSize)?,
+        ))
+    }
+}
+
+impl TryFrom<&str> for Signature {
+    type Error = Error;
+
+    fn try_from(value: &str) -> Result<Self, Self::Error> {
+        value.to_owned().try_into()
+    }
+}
+
+impl<'de> Deserialize<'de> for Signature {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let s = <String>::deserialize(deserializer)?;
+        let t = s
+            .try_into()
+            .map_err(|e: Error| de::Error::custom(e.to_string()))?;
+        Ok(t)
+    }
+}
+
+impl ser::Serialize for Signature {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let str = self.to_string();
+        serializer.serialize_str(&str)
+    }
+}

+ 79 - 0
crates/types/src/types/subscription_id.rs

@@ -0,0 +1,79 @@
+//! Subscription ID
+//!
+//! This mod abstract the subscription and makes sure it is valid
+use hex::ToHex;
+use rand::RngCore;
+use std::{convert::TryFrom, ops::Deref};
+use thiserror::Error;
+
+/// Error type
+#[derive(Error, Debug)]
+pub enum Error {
+    /// The subscription ID is too long
+    #[error("Subscription ID is limited to 64 characters")]
+    TooLong,
+}
+
+/// Subscription ID
+///
+/// The rules are simple, any UTF-8 valid string with fewer than 32 characters
+///
+/// By default a random ID will be created if needed.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct SubscriptionId(String);
+
+impl Deref for SubscriptionId {
+    type Target = str;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl TryFrom<String> for SubscriptionId {
+    type Error = Error;
+    fn try_from(s: String) -> Result<Self, Self::Error> {
+        if s.as_bytes().len() > 64 {
+            return Err(Error::TooLong);
+        }
+        Ok(SubscriptionId(s))
+    }
+}
+
+impl TryFrom<&str> for SubscriptionId {
+    type Error = Error;
+    fn try_from(s: &str) -> Result<Self, Self::Error> {
+        s.to_owned().try_into()
+    }
+}
+
+impl Default for SubscriptionId {
+    fn default() -> Self {
+        let mut data = [0u8; 32];
+        rand::thread_rng().fill_bytes(&mut data);
+        Self(data.encode_hex::<String>())
+    }
+}
+
+impl ToString for SubscriptionId {
+    fn to_string(&self) -> String {
+        self.0.clone()
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn default() {
+        assert_eq!(64, SubscriptionId::default().to_string().len());
+    }
+
+    #[test]
+    fn too_long() {
+        let f = format!("too_long:{}", SubscriptionId::default().to_string());
+        let r: Result<SubscriptionId, _> = f.as_str().try_into();
+        assert!(r.is_err());
+    }
+}

+ 50 - 0
crates/types/src/types/tag/event.rs

@@ -0,0 +1,50 @@
+//! Event tag
+use super::Addr;
+
+/// A tag that references another event
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct Event {
+    /// The other event to relate Id
+    pub id: Addr,
+    /// The relayer where the related content can be found
+    pub relayer_url: Option<String>,
+    /// <marker> is optional and if present is one of "reply", "root", or "mention".
+    pub marker: Option<Marker>,
+}
+
+/// Marker as defined NIP-10
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum Marker {
+    /// For top level replies (those replying directly to the root event), only
+    /// the "root" marker should be used
+    Root,
+    /// Denotes the id of the reply event being responded to
+    Reply,
+    /// Denotes a quoted or reposted event id.
+    Mention,
+    /// Unknown marker
+    Unknown(String),
+}
+
+impl ToString for Marker {
+    fn to_string(&self) -> String {
+        (match self {
+            Self::Root => "root",
+            Self::Reply => "reply",
+            Self::Mention => "mention",
+            Self::Unknown(x) => x,
+        })
+        .to_owned()
+    }
+}
+
+impl From<&str> for Marker {
+    fn from(marker: &str) -> Self {
+        match marker.to_ascii_lowercase().as_str() {
+            "root" => Self::Root,
+            "reply" => Self::Reply,
+            "mention" => Self::Mention,
+            _ => Self::Unknown(marker.to_owned()),
+        }
+    }
+}

+ 162 - 0
crates/types/src/types/tag/mod.rs

@@ -0,0 +1,162 @@
+//! Tag mod
+//!
+//! An event can tag/reference another public key, or another event.
+//!
+//! It can also use the tag to add all sort of metadata, useful for their own
+//! type
+use super::Addr;
+use serde::{
+    de::{self, Deserialize, Deserializer},
+    ser::{self, SerializeSeq, Serializer},
+};
+
+mod event;
+mod public_key;
+
+pub use self::{event::*, public_key::*};
+
+/// Tags are how events relates to each other.
+///
+/// So far, there are two standard tags, Events and Public Key. This an event
+/// can be related to another, with extra options (like a relayer url, and a pet
+/// name).
+///
+/// Any non standard tag will be parsed as Unknown with a vector of strings as
+/// their parameters
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum Tag {
+    /// Tag another event
+    Event(Event),
+    /// Tag another public key
+    PubKey(PubKey),
+    /// Any non standard tag
+    Unknown(String, Vec<String>),
+}
+
+impl Tag {
+    /// Is the tag a public key?
+    pub fn is_pubkey(&self) -> bool {
+        matches!(self, Self::PubKey(_))
+    }
+
+    /// Is the tag an event?
+    pub fn is_event(&self) -> bool {
+        matches!(self, Self::Event(_))
+    }
+}
+
+impl ser::Serialize for Tag {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let typ = match self {
+            Tag::Event(_) => "e",
+            Tag::PubKey(_) => "p",
+            Tag::Unknown(u, _) => u,
+        };
+
+        let mut seq = serializer.serialize_seq(Some(2))?;
+        seq.serialize_element(typ)?;
+
+        match self {
+            Tag::Event(event) => {
+                seq.serialize_element(&event.id.to_hex())?;
+                if let Some(relayer) = &event.relayer_url {
+                    seq.serialize_element(&relayer)?;
+                    if let Some(marker) = &event.marker {
+                        seq.serialize_element(&marker.to_string())?;
+                    }
+                }
+            }
+            Tag::PubKey(key) => {
+                seq.serialize_element(&key.id.to_hex())?;
+                if let Some(relayer) = &key.relayer_url {
+                    seq.serialize_element(relayer)?;
+                    if let Some(pet_name) = &key.pet_name {
+                        seq.serialize_element(pet_name)?;
+                    }
+                }
+            }
+            Tag::Unknown(_, extra) => {
+                for extra in extra.iter() {
+                    seq.serialize_element(extra)?;
+                }
+            }
+        }
+        seq.end()
+    }
+}
+
+impl<'de> Deserialize<'de> for Tag {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let parts: Vec<String> = Deserialize::deserialize(deserializer)?;
+        if parts.len() < 2 {
+            return Err(de::Error::custom("expected array of two strings"));
+        }
+
+        Ok(match parts[0].as_str() {
+            "e" | "p" => {
+                let id: Addr = parts[1]
+                    .as_str()
+                    .try_into()
+                    .map_err(|e: super::addr::Error| de::Error::custom(e.to_string()))?;
+
+                let relayer_url = parts.get(2).cloned();
+                let extra = parts.get(3).cloned();
+
+                if "e" == parts[0].as_str() {
+                    Tag::Event(Event {
+                        id,
+                        relayer_url,
+                        marker: extra.map(|x| x.as_str().into()),
+                    })
+                } else {
+                    Tag::PubKey(PubKey {
+                        id,
+                        relayer_url,
+                        pet_name: extra,
+                    })
+                }
+            }
+            extra => Tag::Unknown(extra.to_owned(), parts[1..].to_owned()),
+        })
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::Response;
+
+    #[test]
+    fn bug_01() {
+        let json = r#"["EVENT","d45a98f898820258a3313f5cb14f5fe8a9263437931ac6309f23ae0324833f39",{"content":"Will fight under the light of lightnings! No darkness on nostr! 🐶🐾😂","created_at":1678685477,"id":"5462822032c16d32267ba40536409fd51ea188b20e7dd5f9e7a0aa5561346f79","kind":1,"pubkey":"8fb140b4e8ddef97ce4b821d247278a1a4353362623f64021484b372f948000c","sig":"62c8e09a0fedd8096a313ef12436b0f0bcad56e9058d4bc12f61ae6094c099bb966dce47efa2d721c5bfdf923614db46d1d1c248105cb99c4ec495292cc875b1","tags":[["e","5f63f9e7d37673e76ddf7448cd67c3d74f9be96c240a40199b59a30db32d7f43"],["e","c70894331986c66a2baf6fc12dd5c86280e4616cee0e57bbee90972ebbb4b735"],["p","ac3fb436a663b25893657f4b6a3d9d2f02d1974bb5ced603f4d0c8ee32d7e0a2"]]}]"#;
+        let message: Result<Response, _> = serde_json::from_str(json);
+        assert!(message.is_ok());
+    }
+
+    #[test]
+    fn parse_unexpected_tag() {
+        let json = r#"["EVENT","f4e62282fda9c7d93a6e3b03fd1f1a3a34936f74584b0e021edf9659fd7da9d6",{"content":"1678682368","created_at":1678682368,"id":"7506ccd8ce4de835c61c04fd16f8489b2acb8cc052ed6730cba203188dfedf57","kind":30000,"pubkey":"228cc1e37a8fec2eee3dda3a3dbd04a60968086d8f42751a7632499d938eda8f","sig":"73ec91ee5d23a93287700f31274a57d84f46c0aa06523138c8f5b0cf2f20bd8a7db72346ecae9c717d9642cea81d72c35ea95ec615146b1640f62de5e3fbbb69","tags":[["d","chats/null/lastOpened"]]}]"#;
+        let message: Result<Response, _> = serde_json::from_str(json);
+        assert!(message.is_ok());
+    }
+
+    #[test]
+    fn serialize_deserialize() {
+        let json = "[\"p\",\"a0b0c0d0\"]";
+        let tag: Tag = serde_json::from_str(&json).expect("valid json");
+        assert_eq!(
+            Tag::PubKey(PubKey {
+                id: Addr::try_from_public_key_str("a0b0c0d0", None).expect("valid addr"),
+                relayer_url: None,
+                pet_name: None,
+            }),
+            tag
+        );
+    }
+}

+ 14 - 0
crates/types/src/types/tag/public_key.rs

@@ -0,0 +1,14 @@
+//! Public key tag
+use super::Addr;
+
+/// Public key tag
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct PubKey {
+    /// The public key or a prefix for more privacy
+    pub id: Addr,
+    /// Optionally a relayer to connect to
+    pub relayer_url: Option<String>,
+    /// A pet name, an internal name, that this public key gives to the
+    /// related/tagged public key
+    pub pet_name: Option<String>,
+}

+ 57 - 0
src/bin/dump.rs

@@ -0,0 +1,57 @@
+use futures::Future;
+use nostr_rs_client::{Error as ClientError, Relayers};
+use nostr_rs_storage::RocksDb;
+use nostr_rs_types::{client::Subscribe, Request, Response};
+use std::pin::Pin;
+use tokio::sync::mpsc;
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    #[error("Nostr: {0}")]
+    Addr(#[from] nostr_rs_types::types::addr::Error),
+
+    #[error("client: {0}")]
+    Client(#[from] ClientError),
+}
+
+fn on_connection(
+    sent_messages: Vec<Request>,
+    socket: mpsc::Sender<Request>,
+) -> Pin<Box<dyn Future<Output = ()> + Send>> {
+    println!("REconnecting with {:?}", sent_messages);
+    Box::pin(async move {
+        for m in sent_messages {
+            let _ = socket.send(m).await;
+        }
+    })
+}
+
+#[tokio::main]
+async fn main() {
+    env_logger::init();
+    let clients = Relayers::default();
+    clients.send(Subscribe::default().into()).await;
+
+    let _ = clients
+        .connect_to("wss://relay.damus.io/", 30, Some(on_connection))
+        .await;
+    let db = RocksDb::new("./db").expect("db");
+
+    loop {
+        println!("going into loop");
+        if let Some((msg, _relayed_by)) = clients.recv().await.expect("valid connection") {
+            match msg {
+                Response::Event(x) => {
+                    let event = x.event;
+
+                    if db.store(&event).expect("valid") {
+                        panic!("\tStored");
+                    } else {
+                        println!("\tSkip");
+                    }
+                }
+                _ => {}
+            }
+        }
+    }
+}

+ 7 - 0
src/main.rs

@@ -0,0 +1,7 @@
+use instant_acme::{
+    Account, AuthorizationStatus, ChallengeType, Identifier, KeyAuthorization, LetsEncrypt,
+    NewAccount, NewOrder, OrderStatus,
+};
+
+#[tokio::main]
+async fn main() {}

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.