diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000000000000000000000000000000000000..60337f19f07141c1b7e5278c3b64a9dd43775a3f
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,78 @@
+# Contributing
+
+For any addition of feature or modification of existing feature, please discuss it beforehand via an issue of this repository by tagging one or more maintainers.
+
+## Commit Message Guidelines
+
+We have very precise rules over how our git commit messages can be formatted.  This leads to **more
+readable messages** that are easy to follow when looking through the **project history**.
+
+### Commit Message Format
+
+Each commit message consists of a **header**, a **body** and a **footer**.  The header has a special
+format that includes a **type**, a **scope** and a **subject**:
+
+```txt
+<type>(<scope>): <subject>
+<BLANK LINE>
+<body>
+<BLANK LINE>
+<footer>
+```
+
+The **header** is mandatory and the **scope** of the header is optional.
+
+Any line of the commit message cannot be longer 100 characters! This allows the message to be easier
+to read on GitHub as well as in various git tools.
+
+The footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.
+
+```txt
+docs(changelog): update changelog to beta.5
+```
+
+```txt
+fix(release): need to depend on latest rxjs and zone.js
+
+The version in our package.json gets copied to the one we publish, and users need the latest of these.
+```
+
+### Revert
+
+If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.
+
+### Type
+
+Must be one of the following:
+
+* **build**: Changes that affect the build system or external dependencies (example scopes: crypto, wot)
+* **chore**: Modification of the repository architecture
+* **ci**: Changes to our CI configuration files and scripts (example scopes: Github Actions, Gitlab CI)
+* **docs**: Documentation only changes
+* **feat**: Add a new feature
+* **mod**: Modify an existing feature
+* **fix**: A bug fix
+* **perf**: A code change that improves performance
+* **refactor**: A code change that neither fixes a bug nor adds a feature nor modify an existing feature
+* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
+* **test**: Adding missing tests or correcting existing tests
+
+### Subject
+
+The subject contains a succinct description of the change:
+
+* use the imperative, present tense: "change" not "changed" nor "changes"
+* don't capitalize the first letter
+* no dot (.) at the end
+
+### Body
+
+Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes".
+The body should include the motivation for the change and contrast this with previous behavior.
+
+### Footer
+
+The footer should contain any information about **Breaking Changes** and is also the place to
+reference issues that this commit **Closes**.
+
+**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000000000000000000000000000000000000..35711874710eb6b50ec22bccb37e9646beceb6cd
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,2795 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "Inflector"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
+dependencies = [
+ "lazy_static",
+ "regex",
+]
+
+[[package]]
+name = "aes"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561"
+dependencies = [
+ "aes-soft",
+ "aesni",
+ "cipher",
+]
+
+[[package]]
+name = "aes-soft"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072"
+dependencies = [
+ "cipher",
+ "opaque-debug 0.3.0",
+]
+
+[[package]]
+name = "aesni"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce"
+dependencies = [
+ "cipher",
+ "opaque-debug 0.3.0",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b"
+
+[[package]]
+name = "arrayref"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
+
+[[package]]
+name = "arrayvec"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "async-bincode"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a31c08aa335b3ab414d29bdefe1f4353408abf93f3db1e3e2cc78d3ec4f0d43"
+dependencies = [
+ "bincode",
+ "byteorder",
+ "bytes 1.0.1",
+ "futures-core",
+ "futures-sink",
+ "serde",
+ "tokio",
+]
+
+[[package]]
+name = "async-graphql"
+version = "2.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb5e71af65ee4a367603829e92b26710caf4116d1eaca8953dedf1809b33694a"
+dependencies = [
+ "async-graphql-derive",
+ "async-graphql-parser",
+ "async-graphql-value",
+ "async-stream",
+ "async-trait",
+ "fnv",
+ "futures-util",
+ "http",
+ "indexmap",
+ "log",
+ "multer",
+ "once_cell",
+ "pin-project-lite",
+ "regex",
+ "serde",
+ "serde_json",
+ "static_assertions",
+ "tempfile",
+ "thiserror",
+]
+
+[[package]]
+name = "async-graphql-derive"
+version = "2.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fd4c2eb837e894909fe13509f2351fa3990c114426e41255936800892ccbe26"
+dependencies = [
+ "Inflector",
+ "async-graphql-parser",
+ "darling",
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "thiserror",
+]
+
+[[package]]
+name = "async-graphql-parser"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a8d8116f3015b7686ef98ffb70a74183c3c17bf45135993d3f095812e09e786"
+dependencies = [
+ "async-graphql-value",
+ "pest",
+ "pest_derive",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "async-graphql-value"
+version = "2.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8342ada84efe4b3d59e1313d1d2740a8ccfc76ddb57ccf55e45a6464dd7d0d3"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "async-mutex"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e"
+dependencies = [
+ "event-listener",
+]
+
+[[package]]
+name = "async-oneshot"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50f4770cbbff928c30a991de67fb3976f44d8e3e202f8c79ef91b47006e04904"
+dependencies = [
+ "futures-micro",
+]
+
+[[package]]
+name = "async-rwlock"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "261803dcc39ba9e72760ba6e16d0199b1eef9fc44e81bffabbebb9f5aea3906c"
+dependencies = [
+ "async-mutex",
+ "event-listener",
+]
+
+[[package]]
+name = "async-stream"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a26cb53174ddd320edfff199a853f93d571f48eeb4dde75e67a9a3dbb7b7e5e"
+dependencies = [
+ "async-stream-impl",
+ "futures-core",
+]
+
+[[package]]
+name = "async-stream-impl"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db134ba52475c060f3329a8ef0f8786d6b872ed01515d4b79c162e5798da1340"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "async-trait"
+version = "0.1.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "async_io_stream"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d5ad740b7193a31e80950ab7fece57c38d426fcd23a729d9d7f4cf15bb63f94"
+dependencies = [
+ "futures",
+ "rustc_version",
+ "tokio",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "beef"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6736e2428df2ca2848d846c43e88745121a6654696e349ce0054a420815a7409"
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
+[[package]]
+name = "blake3"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9ff35b701f3914bdb8fad3368d822c766ef2858b2583198e41639b936f09d3f"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "cc",
+ "cfg-if 0.1.10",
+ "constant_time_eq",
+ "crypto-mac",
+ "digest 0.9.0",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
+dependencies = [
+ "block-padding",
+ "byte-tools",
+ "byteorder",
+ "generic-array 0.12.4",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "generic-array 0.14.4",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
+dependencies = [
+ "byte-tools",
+]
+
+[[package]]
+name = "bs58"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
+
+[[package]]
+name = "buf_redux"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f"
+dependencies = [
+ "memchr",
+ "safemem",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
+
+[[package]]
+name = "byte-tools"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "bytes"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
+
+[[package]]
+name = "bytes"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
+
+[[package]]
+name = "cc"
+version = "1.0.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
+dependencies = [
+ "jobserver",
+]
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[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.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "time",
+ "winapi",
+]
+
+[[package]]
+name = "cipher"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801"
+dependencies = [
+ "generic-array 0.14.4",
+]
+
+[[package]]
+name = "cmake"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb6210b637171dfba4cda12e579ac6dc73f5165ad56133e5d72ef3131f320855"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "constant_time_eq"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
+
+[[package]]
+name = "convert_case"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+
+[[package]]
+name = "cpuid-bool"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
+
+[[package]]
+name = "crc32fast"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-utils",
+ "lazy_static",
+ "memoffset",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49"
+dependencies = [
+ "autocfg",
+ "cfg-if 1.0.0",
+ "lazy_static",
+]
+
+[[package]]
+name = "crypto-mac"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
+dependencies = [
+ "generic-array 0.14.4",
+ "subtle",
+]
+
+[[package]]
+name = "cryptoxide"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46212f5d1792f89c3e866fb10636139464060110c568edd7f73ab5e9f736c26d"
+
+[[package]]
+name = "ctor"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "darling"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "derive_more"
+version = "0.99.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f82b1b72f1263f214c0f823371768776c4f5841b942c9883aa8e5ec584fd0ba6"
+dependencies = [
+ "convert_case",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "diff"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
+
+[[package]]
+name = "difference"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
+
+[[package]]
+name = "digest"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
+dependencies = [
+ "generic-array 0.12.4",
+]
+
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array 0.14.4",
+]
+
+[[package]]
+name = "downcast"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bb454f0228b18c7f4c3b0ebbee346ed9c52e7443b0999cd543ff3571205701d"
+
+[[package]]
+name = "dubp"
+version = "0.51.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "914faa8052c72c8b2f513a44398123379d70a59dfedf0aa8dc7b581ee223fbfc"
+dependencies = [
+ "dubp-block",
+ "dubp-common",
+ "dubp-documents",
+ "dubp-documents-parser",
+ "dubp-wallet",
+ "dup-crypto",
+]
+
+[[package]]
+name = "dubp-block"
+version = "0.51.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15cc90473a86c4987ea34211829d491dfb56f7c09ba79ac3d57d9430782d038"
+dependencies = [
+ "dubp-documents",
+ "dubp-documents-parser",
+ "json-pest-parser",
+ "log",
+ "serde",
+ "serde_json",
+ "thiserror",
+]
+
+[[package]]
+name = "dubp-common"
+version = "0.51.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3a5a6cc11940e0a85f492325fec45c557c5f103c92ea445427b4272c1a12395"
+dependencies = [
+ "dup-crypto",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "zerocopy",
+]
+
+[[package]]
+name = "dubp-documents"
+version = "0.51.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85d43233426a5a24a5d22e98da2d8f0efab9739a58af15fa27e74a213b2d5bb9"
+dependencies = [
+ "beef",
+ "dubp-wallet",
+ "log",
+ "serde",
+ "serde_json",
+ "thiserror",
+]
+
+[[package]]
+name = "dubp-documents-parser"
+version = "0.51.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac382364d99af3c235530f9de41a1833d18a16dff8833a7b351e8946d378de18"
+dependencies = [
+ "dubp-documents",
+ "json-pest-parser",
+ "pest",
+ "pest_derive",
+ "serde_json",
+ "thiserror",
+]
+
+[[package]]
+name = "dubp-wallet"
+version = "0.51.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47cc059e6b139def809f9d0bf776a21f5c2d59fefc20ed30c7aceedfef8de703"
+dependencies = [
+ "byteorder",
+ "dubp-common",
+ "serde",
+ "smallvec",
+ "thiserror",
+ "zerocopy",
+]
+
+[[package]]
+name = "duniter-bc-reader"
+version = "0.1.0"
+source = "git+https://git.duniter.org/nodes/rust/duniter-core#2b71d3e8f0bd223f84a010e500e2d47a99c0c4eb"
+dependencies = [
+ "anyhow",
+ "dubp",
+ "duniter-dbs",
+ "resiter",
+]
+
+[[package]]
+name = "duniter-bca"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "arrayvec",
+ "async-bincode",
+ "async_io_stream",
+ "bincode",
+ "dubp",
+ "duniter-bca-types",
+ "duniter-core",
+ "duniter-gva-db",
+ "duniter-gva-dbs-reader",
+ "fast-threadpool",
+ "futures",
+ "mockall",
+ "once_cell",
+ "smallvec",
+ "tokio",
+ "uninit",
+]
+
+[[package]]
+name = "duniter-bca-types"
+version = "0.1.0"
+dependencies = [
+ "arrayvec",
+ "bincode",
+ "dubp",
+ "serde",
+ "smallvec",
+ "thiserror",
+]
+
+[[package]]
+name = "duniter-conf"
+version = "0.1.0"
+source = "git+https://git.duniter.org/nodes/rust/duniter-core#2b71d3e8f0bd223f84a010e500e2d47a99c0c4eb"
+dependencies = [
+ "dubp",
+ "serde",
+]
+
+[[package]]
+name = "duniter-core"
+version = "1.8.1"
+source = "git+https://git.duniter.org/nodes/rust/duniter-core#2b71d3e8f0bd223f84a010e500e2d47a99c0c4eb"
+dependencies = [
+ "duniter-bc-reader",
+ "duniter-conf",
+ "duniter-dbs",
+ "duniter-dbs-write-ops",
+ "duniter-global",
+ "duniter-mempools",
+ "duniter-module",
+]
+
+[[package]]
+name = "duniter-dbs"
+version = "0.1.0"
+source = "git+https://git.duniter.org/nodes/rust/duniter-core#2b71d3e8f0bd223f84a010e500e2d47a99c0c4eb"
+dependencies = [
+ "arrayvec",
+ "bincode",
+ "byteorder",
+ "chrono",
+ "dubp",
+ "kv_typed",
+ "log",
+ "parking_lot",
+ "paste",
+ "rand 0.7.3",
+ "serde",
+ "serde_json",
+ "smallvec",
+ "thiserror",
+ "uninit",
+ "zerocopy",
+]
+
+[[package]]
+name = "duniter-dbs-write-ops"
+version = "0.1.0"
+source = "git+https://git.duniter.org/nodes/rust/duniter-core#2b71d3e8f0bd223f84a010e500e2d47a99c0c4eb"
+dependencies = [
+ "chrono",
+ "dubp",
+ "duniter-dbs",
+ "duniter-global",
+ "fast-threadpool",
+ "flume",
+ "log",
+ "resiter",
+]
+
+[[package]]
+name = "duniter-global"
+version = "1.8.1"
+source = "git+https://git.duniter.org/nodes/rust/duniter-core#2b71d3e8f0bd223f84a010e500e2d47a99c0c4eb"
+dependencies = [
+ "async-rwlock",
+ "dubp",
+ "duniter-dbs",
+ "flume",
+ "mockall",
+ "once_cell",
+ "tokio",
+]
+
+[[package]]
+name = "duniter-gva"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "arrayvec",
+ "async-graphql",
+ "async-mutex",
+ "async-trait",
+ "bytes 1.0.1",
+ "dubp",
+ "duniter-bca",
+ "duniter-core",
+ "duniter-gva-db",
+ "duniter-gva-dbs-reader",
+ "duniter-gva-gql",
+ "duniter-gva-indexer",
+ "fast-threadpool",
+ "flume",
+ "futures",
+ "http",
+ "log",
+ "mockall",
+ "resiter",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "tokio",
+ "unwrap",
+ "warp",
+]
+
+[[package]]
+name = "duniter-gva-db"
+version = "0.1.0"
+dependencies = [
+ "bincode",
+ "chrono",
+ "dubp",
+ "duniter-core",
+ "kv_typed",
+ "parking_lot",
+ "paste",
+ "serde",
+ "serde_json",
+ "uninit",
+ "zerocopy",
+]
+
+[[package]]
+name = "duniter-gva-dbs-reader"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "arrayvec",
+ "dubp",
+ "duniter-bca-types",
+ "duniter-core",
+ "duniter-gva-db",
+ "maplit",
+ "mockall",
+ "resiter",
+ "smallvec",
+ "unwrap",
+]
+
+[[package]]
+name = "duniter-gva-gql"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "arrayvec",
+ "async-graphql",
+ "async-trait",
+ "dubp",
+ "duniter-core",
+ "duniter-gva-db",
+ "duniter-gva-dbs-reader",
+ "fast-threadpool",
+ "flume",
+ "futures",
+ "log",
+ "mockall",
+ "pretty_assertions",
+ "resiter",
+ "serde",
+ "serde_json",
+ "tokio",
+ "unwrap",
+]
+
+[[package]]
+name = "duniter-gva-indexer"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "dubp",
+ "duniter-core",
+ "duniter-gva-db",
+ "maplit",
+ "once_cell",
+ "resiter",
+ "smallvec",
+]
+
+[[package]]
+name = "duniter-mempools"
+version = "0.1.0"
+source = "git+https://git.duniter.org/nodes/rust/duniter-core#2b71d3e8f0bd223f84a010e500e2d47a99c0c4eb"
+dependencies = [
+ "dubp",
+ "duniter-bc-reader",
+ "duniter-dbs",
+ "duniter-dbs-write-ops",
+ "log",
+ "thiserror",
+]
+
+[[package]]
+name = "duniter-module"
+version = "0.1.0"
+source = "git+https://git.duniter.org/nodes/rust/duniter-core#2b71d3e8f0bd223f84a010e500e2d47a99c0c4eb"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "dubp",
+ "duniter-conf",
+ "duniter-dbs",
+ "duniter-global",
+ "duniter-mempools",
+ "fast-threadpool",
+ "log",
+]
+
+[[package]]
+name = "dup-crypto"
+version = "0.51.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3b8d3e1c65e3ed89db6973e807e9c355c8f9078866402e695a16683f1e226d2"
+dependencies = [
+ "aes",
+ "arrayvec",
+ "base64",
+ "blake3",
+ "bs58",
+ "byteorder",
+ "cryptoxide",
+ "ed25519-bip32",
+ "getrandom 0.2.2",
+ "once_cell",
+ "ring",
+ "serde",
+ "thiserror",
+ "zerocopy",
+ "zeroize",
+]
+
+[[package]]
+name = "ed25519-bip32"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8827180a2b511141fbe49141e50b31a8d542465e0fb572f81f36feea2addfe92"
+dependencies = [
+ "cryptoxide",
+]
+
+[[package]]
+name = "either"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "event-listener"
+version = "2.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
+
+[[package]]
+name = "fake-simd"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
+
+[[package]]
+name = "fast-threadpool"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0585e8f3a04d8c4a65927a5cb5e42c6ce641528b4fc294af9d7990fcd6c4b86a"
+dependencies = [
+ "async-oneshot",
+ "flume",
+ "num_cpus",
+]
+
+[[package]]
+name = "float-cmp"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "flume"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11fce69af4d4582ea989e6adfc5c9b81fd2071ff89234e5c14675c82a85217df"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+ "nanorand",
+ "pin-project",
+ "spinning_top",
+]
+
+[[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.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
+dependencies = [
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "fragile"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69a039c3498dc930fe810151a34ba0c1c70b02b8625035592e74432f678591f2"
+
+[[package]]
+name = "fs2"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9d5813545e459ad3ca1bff9915e9ad7f1a47dc6a91b627ce321d5863b7dd253"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10f6cb7042eda00f0049b1d2080aa4b93442997ee507eb3828e8bd7577f94c9d"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "668c6733a182cd7deb4f1de7ba3bf2120823835b3bcfbeacf7d2c4a773c1bb8b"
+dependencies = [
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-micro"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61e9325be55c5581082cd110294fa988c1f920bc573ec370ef201e33c469a95a"
+
+[[package]]
+name = "futures-sink"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23"
+
+[[package]]
+name = "futures-task"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc"
+
+[[package]]
+name = "futures-util"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "proc-macro-hack",
+ "proc-macro-nested",
+ "slab",
+]
+
+[[package]]
+name = "fxhash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
+dependencies = [
+ "cfg-if 1.0.0",
+ "js-sys",
+ "libc",
+ "wasi 0.10.0+wasi-snapshot-preview1",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
+
+[[package]]
+name = "h2"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc018e188373e2777d0ef2467ebff62a08e66c3f5857b23c8fbec3018210dc00"
+dependencies = [
+ "bytes 1.0.1",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
+
+[[package]]
+name = "headers"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0b7591fb62902706ae8e7aaff416b1b0fa2c0fd0878b46dc13baa3712d8a855"
+dependencies = [
+ "base64",
+ "bitflags",
+ "bytes 1.0.1",
+ "headers-core",
+ "http",
+ "mime",
+ "sha-1 0.9.4",
+ "time",
+]
+
+[[package]]
+name = "headers-core"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
+dependencies = [
+ "http",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "http"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11"
+dependencies = [
+ "bytes 1.0.1",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dfb77c123b4e2f72a2069aeae0b4b4949cc7e966df277813fc16347e7549737"
+dependencies = [
+ "bytes 1.0.1",
+ "http",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a1ce40d6fc9764887c2fdc7305c3dcc429ba11ff981c1509416afd5697e4437"
+
+[[package]]
+name = "httpdate"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05842d0d43232b23ccb7060ecb0f0626922c21f30012e97b767b30afd4a5d4b9"
+
+[[package]]
+name = "hyper"
+version = "0.14.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f006b8784cfb01fe7aa9c46f5f5cd4cf5c85a8c612a0653ec97642979062665"
+dependencies = [
+ "bytes 1.0.1",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "idna"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "input_buffer"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413"
+dependencies = [
+ "bytes 1.0.1",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "itertools"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
+
+[[package]]
+name = "jobserver"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "972f5ae5d1cb9c6ae417789196c803205313edde988685da5e3aae0827b9e7fd"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "json-pest-parser"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3bc5c84a2bceeda1ce3bd58497bde2d8cba61ca0b45873ef502401f0ff2ae8ed"
+dependencies = [
+ "pest",
+ "pest_derive",
+ "thiserror",
+ "unwrap",
+]
+
+[[package]]
+name = "kv_typed"
+version = "0.1.0"
+source = "git+https://git.duniter.org/nodes/rust/duniter-core#2b71d3e8f0bd223f84a010e500e2d47a99c0c4eb"
+dependencies = [
+ "byteorder",
+ "cfg-if 0.1.10",
+ "flume",
+ "leveldb_minimal",
+ "parking_lot",
+ "paste",
+ "rayon",
+ "regex",
+ "serde_json",
+ "sled",
+ "smallvec",
+ "thiserror",
+ "uninit",
+ "zerocopy",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "leveldb-sys"
+version = "2.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "618aee5ba3d32cb8456420a9a454aa71c1af5b3e9c7a2ec20a0f3cbbe47246cb"
+dependencies = [
+ "cmake",
+ "libc",
+ "num_cpus",
+]
+
+[[package]]
+name = "leveldb_minimal"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53b4cb22d7d3cce486fc6e2ef7cd25d38e118f525f79c4a946ac48d89c5d16b1"
+dependencies = [
+ "leveldb-sys",
+ "libc",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
+
+[[package]]
+name = "lock_api"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "maplit"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
+
+[[package]]
+name = "matches"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
+
+[[package]]
+name = "memchr"
+version = "2.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
+
+[[package]]
+name = "memoffset"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "mime"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+
+[[package]]
+name = "mime_guess"
+version = "2.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
+[[package]]
+name = "mio"
+version = "0.7.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956"
+dependencies = [
+ "libc",
+ "log",
+ "miow",
+ "ntapi",
+ "winapi",
+]
+
+[[package]]
+name = "miow"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "mockall"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18d614ad23f9bb59119b8b5670a85c7ba92c5e9adf4385c81ea00c51c8be33d5"
+dependencies = [
+ "cfg-if 1.0.0",
+ "downcast",
+ "fragile",
+ "lazy_static",
+ "mockall_derive",
+ "predicates",
+ "predicates-tree",
+]
+
+[[package]]
+name = "mockall_derive"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dd4234635bca06fc96c7368d038061e0aae1b00a764dc817e900dc974e3deea"
+dependencies = [
+ "cfg-if 1.0.0",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "multer"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99851e6ad01b0fbe086dda2dea00d68bb84fc7d7eae2c39ca7313da9197f4d31"
+dependencies = [
+ "bytes 0.5.6",
+ "derive_more",
+ "encoding_rs",
+ "futures",
+ "http",
+ "httparse",
+ "lazy_static",
+ "log",
+ "mime",
+ "regex",
+ "twoway 0.2.1",
+]
+
+[[package]]
+name = "multipart"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d050aeedc89243f5347c3e237e3e13dc76fbe4ae3742a57b94dc14f69acf76d4"
+dependencies = [
+ "buf_redux",
+ "httparse",
+ "log",
+ "mime",
+ "mime_guess",
+ "quick-error",
+ "rand 0.7.3",
+ "safemem",
+ "tempfile",
+ "twoway 0.1.8",
+]
+
+[[package]]
+name = "nanorand"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac1378b66f7c93a1c0f8464a19bf47df8795083842e5090f4b7305973d5a22d0"
+dependencies = [
+ "getrandom 0.2.2",
+]
+
+[[package]]
+name = "normalize-line-endings"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
+
+[[package]]
+name = "ntapi"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
+
+[[package]]
+name = "opaque-debug"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
+[[package]]
+name = "output_vt100"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
+dependencies = [
+ "cfg-if 1.0.0",
+ "instant",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "winapi",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58"
+
+[[package]]
+name = "percent-encoding"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+
+[[package]]
+name = "pest"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
+dependencies = [
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
+dependencies = [
+ "maplit",
+ "pest",
+ "sha-1 0.8.2",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
+
+[[package]]
+name = "predicates"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eeb433456c1a57cc93554dea3ce40b4c19c4057e41c55d4a0f3d84ea71c325aa"
+dependencies = [
+ "difference",
+ "float-cmp",
+ "normalize-line-endings",
+ "predicates-core",
+ "regex",
+]
+
+[[package]]
+name = "predicates-core"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451"
+
+[[package]]
+name = "predicates-tree"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15f553275e5721409451eb85e15fd9a860a6e5ab4496eb215987502b5f5391f2"
+dependencies = [
+ "predicates-core",
+ "treeline",
+]
+
+[[package]]
+name = "pretty_assertions"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b"
+dependencies = [
+ "ansi_term",
+ "ctor",
+ "diff",
+ "output_vt100",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41fdbd1df62156fbc5945f4762632564d7d038153091c3fcf1067f6aef7cff92"
+dependencies = [
+ "thiserror",
+ "toml",
+]
+
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
+
+[[package]]
+name = "proc-macro-nested"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quick-error"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+
+[[package]]
+name = "quote"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom 0.1.16",
+ "libc",
+ "rand_chacha 0.2.2",
+ "rand_core 0.5.1",
+ "rand_hc 0.2.0",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.0",
+ "rand_core 0.6.2",
+ "rand_hc 0.3.0",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.2",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom 0.1.16",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
+dependencies = [
+ "getrandom 0.2.2",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
+dependencies = [
+ "rand_core 0.6.2",
+]
+
+[[package]]
+name = "rayon"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674"
+dependencies = [
+ "autocfg",
+ "crossbeam-deque",
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-utils",
+ "lazy_static",
+ "num_cpus",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "regex"
+version = "1.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548"
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "resiter"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd69ab1e90258b7769f0b5c46bfd802b8206d0707ced4ca4b9d5681b744de1be"
+
+[[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 = "rustc_version"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
+
+[[package]]
+name = "safemem"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "semver"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
+dependencies = [
+ "semver-parser",
+]
+
+[[package]]
+name = "semver-parser"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
+dependencies = [
+ "pest",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.125"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.125"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha-1"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
+dependencies = [
+ "block-buffer 0.7.3",
+ "digest 0.8.1",
+ "fake-simd",
+ "opaque-debug 0.2.3",
+]
+
+[[package]]
+name = "sha-1"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfebf75d25bd900fd1e7d11501efab59bc846dbc76196839663e6637bba9f25f"
+dependencies = [
+ "block-buffer 0.9.0",
+ "cfg-if 1.0.0",
+ "cpuid-bool",
+ "digest 0.9.0",
+ "opaque-debug 0.3.0",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
+
+[[package]]
+name = "sled"
+version = "0.34.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d0132f3e393bcb7390c60bb45769498cf4550bcb7a21d7f95c02b69f6362cdc"
+dependencies = [
+ "crc32fast",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+ "fs2",
+ "fxhash",
+ "libc",
+ "log",
+ "parking_lot",
+ "zstd",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "socket2"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e3dfc207c526015c632472a77be09cf1b6e46866581aecae5cc38fb4235dea2"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
+[[package]]
+name = "spinning_top"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8bd0ab6b8c375d2d963503b90d3770010d95bc3b5f98036f948dee24bf4e8879"
+dependencies = [
+ "lock_api",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "subtle"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
+
+[[package]]
+name = "syn"
+version = "1.0.70"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9505f307c872bab8eb46f77ae357c8eba1fdacead58ee5a850116b1d7f82883"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "unicode-xid",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "rand 0.8.3",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "time"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
+dependencies = [
+ "libc",
+ "wasi 0.10.0+wasi-snapshot-preview1",
+ "winapi",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
+[[package]]
+name = "tokio"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5"
+dependencies = [
+ "autocfg",
+ "bytes 1.0.1",
+ "libc",
+ "memchr",
+ "mio",
+ "num_cpus",
+ "pin-project-lite",
+ "tokio-macros",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e177a5d8c3bf36de9ebe6d58537d8879e964332f93fb3339e43f618c81361af0"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-tungstenite"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1a5f475f1b9d077ea1017ecbc60890fda8e54942d680ca0b1d2b47cfa2d861b"
+dependencies = [
+ "futures-util",
+ "log",
+ "pin-project",
+ "tokio",
+ "tungstenite",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "940a12c99365c31ea8dd9ba04ec1be183ffe4920102bb7122c2f515437601e8e"
+dependencies = [
+ "bytes 1.0.1",
+ "futures-core",
+ "futures-sink",
+ "log",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "tower-service"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
+
+[[package]]
+name = "tracing"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01ebdc2bb4498ab1ab5f5b73c5803825e60199229ccba0698170e3be0e7f959f"
+dependencies = [
+ "cfg-if 1.0.0",
+ "log",
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "treeline"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41"
+
+[[package]]
+name = "try-lock"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
+
+[[package]]
+name = "tungstenite"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24"
+dependencies = [
+ "base64",
+ "byteorder",
+ "bytes 1.0.1",
+ "http",
+ "httparse",
+ "input_buffer",
+ "log",
+ "rand 0.8.3",
+ "sha-1 0.9.4",
+ "url",
+ "utf-8",
+]
+
+[[package]]
+name = "twoway"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "twoway"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b40075910de3a912adbd80b5d8bad6ad10a23eeb1f5bf9d4006839e899ba5bc"
+dependencies = [
+ "memchr",
+ "unchecked-index",
+]
+
+[[package]]
+name = "typenum"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
+
+[[package]]
+name = "unchecked-index"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c"
+
+[[package]]
+name = "unicase"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0"
+dependencies = [
+ "matches",
+]
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
+
+[[package]]
+name = "uninit"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce382f462302087c8effe69a6c9e84ae8ce6a9cc541d921d0bb5d1fd789cdbf"
+
+[[package]]
+name = "untrusted"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+
+[[package]]
+name = "unwrap"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e33648dd74328e622c7be51f3b40a303c63f93e6fa5f08778b6203a4c25c20f"
+
+[[package]]
+name = "url"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
+[[package]]
+name = "version_check"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
+
+[[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 = "warp"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "332d47745e9a0c38636dbd454729b147d16bd1ed08ae67b3ab281c4506771054"
+dependencies = [
+ "bytes 1.0.1",
+ "futures",
+ "headers",
+ "http",
+ "hyper",
+ "log",
+ "mime",
+ "mime_guess",
+ "multipart",
+ "percent-encoding",
+ "pin-project",
+ "scoped-tls",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "tokio",
+ "tokio-stream",
+ "tokio-tungstenite",
+ "tokio-util",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9"
+dependencies = [
+ "cfg-if 1.0.0",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae"
+dependencies = [
+ "bumpalo",
+ "lazy_static",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489"
+
+[[package]]
+name = "web-sys"
+version = "0.3.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[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-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "zerocopy"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6580539ad917b7c026220c4b3f2c08d52ce54d6ce0dc491e66002e35388fab46"
+dependencies = [
+ "byteorder",
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc9c39e6d503229ffa00cc2954af4a751e6bbedf2a2c18e856eb3ece93d32495"
+dependencies = [
+ "proc-macro2",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2c1e130bebaeab2f23886bf9acbaca14b092408c452543c857f66399cd6dab1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zstd"
+version = "0.5.4+zstd.1.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69996ebdb1ba8b1517f61387a883857818a66c8a295f487b1ffd8fd9d2c82910"
+dependencies = [
+ "zstd-safe",
+]
+
+[[package]]
+name = "zstd-safe"
+version = "2.0.6+zstd.1.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98aa931fb69ecee256d44589d19754e61851ae4769bf963b385119b1cc37a49e"
+dependencies = [
+ "libc",
+ "zstd-sys",
+]
+
+[[package]]
+name = "zstd-sys"
+version = "1.4.18+zstd.1.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1e6e8778706838f43f771d80d37787cb2fe06dafe89dd3aebaf6721b9eaec81"
+dependencies = [
+ "cc",
+ "glob",
+ "itertools",
+ "libc",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..dc661b030a5b69c7acfe0a9eab80c3c32059308c
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,47 @@
+[package]
+name = "duniter-gva"
+version = "0.1.0"
+authors = ["librelois <elois@duniter.org>"]
+license = "AGPL-3.0"
+edition = "2018"
+
+[dependencies]
+anyhow = "1.0.33"
+arrayvec = "0.5.1"
+async-graphql = { version = "2.8", features = ["log"] }
+async-mutex = "1.4.0"
+async-trait = "0.1.41"
+bytes = "1.0"
+dubp = { version = "0.51.0", features = ["duniter"] }
+duniter-bca = { path = "./bca" }
+duniter-core = { git = "https://git.duniter.org/nodes/rust/duniter-core" }
+duniter-gva-db = { path = "./db" }
+duniter-gva-dbs-reader = { path = "./dbs-reader" }
+duniter-gva-indexer = { path = "./indexer" }
+duniter-gva-gql = { path = "./gql" }
+fast-threadpool = "0.2.3"
+flume = "0.10.0"
+futures = "0.3.6"
+http = "0.2.1"
+log = "0.4.11"
+resiter = "0.4.0"
+serde = { version = "1.0.105", features = ["derive"] }
+serde_urlencoded = "0.7.0"
+tokio = { version = "1.2", features = ["io-util", "rt-multi-thread"] }
+warp = "0.3"
+
+[dev-dependencies]
+duniter-core = { git = "https://git.duniter.org/nodes/rust/duniter-core", features = ["mem"] }
+mockall = "0.9.1"
+serde_json = "1.0.53"
+tokio = { version = "1.2", features = ["macros", "rt-multi-thread", "time"] }
+unwrap = "1.2.1"
+
+[workspace]
+members = [
+    "bca",
+    "db",
+    "dbs-reader",
+    "gql",
+    "indexer",
+]
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..5902c3a1f074a8b132fadc72d489341c6692701f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,19 @@
+# Duniter GVA
+
+This repository contains the code of the GVA module.
+
+## Duniter repositories
+
+Duniter's code is separated into several git repositories:
+
+* **[dubp-rs-libs]** contains the logic common to Duniter and its customers.
+* **[duniter-core]** contains the core code of Duniter.
+* The gitlab subgroup **[nodes/rust/modules]** contains the main Duniter modules code (gva, admin, etc).
+* The **[duniter]** subgroup contains the "official" implementations of the "duniter-cli" and "duniter-desktop" programs with their default modules (also contains the historical implementation being migrated).
+
+[DuniterModule]: https://git.duniter.org/nodes/rust/duniter-core/blob/main/module/src/lib.rs#L41
+[duniter gitlab]: https://git.duniter.org
+[dubp-rs-libs]: https://git.duniter.org/libs/dubp-rs-libs
+[duniter-core]: https://git.duniter.org/nodes/rust/duniter-core
+[duniter]: https://git.duniter.org/nodes/typescript/duniter
+[nodes/rust/modules]: https://git.duniter.org/nodes/rust/modules
diff --git a/bca/Cargo.toml b/bca/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..d29a25dd9212ca469bc015ed5e185c1d6560fbaf
--- /dev/null
+++ b/bca/Cargo.toml
@@ -0,0 +1,30 @@
+[package]
+name = "duniter-bca"
+version = "0.1.0"
+authors = ["librelois <elois@duniter.org>"]
+license = "AGPL-3.0"
+edition = "2018"
+
+[dependencies]
+anyhow = "1.0.33"
+arrayvec = { version = "0.5.1", features = ["serde"] }
+async-bincode = "0.6.1"
+async_io_stream = { version = "0.3.1", features = [ "tokio_io"] }
+bincode = "1.3"
+dubp = { version = "0.51.0", features = ["duniter"] }
+duniter-bca-types = { path = "types", features = ["duniter"] }
+duniter-core = { git = "https://git.duniter.org/nodes/rust/duniter-core" }
+duniter-gva-db = { path = "../db" }
+duniter-gva-dbs-reader = { path = "../dbs-reader" }
+fast-threadpool = "0.2.3"
+futures = "0.3.6"
+once_cell = "1.5"
+smallvec = { version = "1.4.0", features = ["serde", "write"] }
+tokio = { version = "1.2", features = ["macros", "rt-multi-thread"] }
+uninit = "0.4.0"
+
+[dev-dependencies]
+duniter-core = { git = "https://git.duniter.org/nodes/rust/duniter-core", features = ["mem", "mock"] }
+duniter-gva-dbs-reader = { path = "../dbs-reader", features = ["mock"] }
+tokio = { version = "1.2", features = ["macros", "rt-multi-thread", "time"] }
+mockall = "0.9.1"
diff --git a/bca/src/exec_req_type.rs b/bca/src/exec_req_type.rs
new file mode 100644
index 0000000000000000000000000000000000000000..183a3e5e678fef8d20490c7f9c7558b1690a1a96
--- /dev/null
+++ b/bca/src/exec_req_type.rs
@@ -0,0 +1,99 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+mod balances;
+mod current_ud;
+mod last_blockstamp_out_of_fork_window;
+mod members_count;
+mod prepare_simple_payment;
+mod send_txs;
+mod utxos;
+
+use dubp::crypto::keys::KeyPair;
+
+use crate::*;
+
+#[derive(Debug, PartialEq)]
+pub(super) struct ExecReqTypeError(pub(super) String);
+
+impl<E> From<E> for ExecReqTypeError
+where
+    E: ToString,
+{
+    fn from(e: E) -> Self {
+        Self(e.to_string())
+    }
+}
+
+pub(super) async fn execute_req_type(
+    bca_executor: &BcaExecutor,
+    req_type: BcaReqTypeV0,
+    _is_whitelisted: bool,
+) -> Result<BcaRespTypeV0, ExecReqTypeError> {
+    match req_type {
+        BcaReqTypeV0::BalancesOfPubkeys(pubkeys) => {
+            balances::exec_req_balances_of_pubkeys(bca_executor, pubkeys).await
+        }
+        BcaReqTypeV0::FirstUtxosOfPubkeys {
+            amount_target_opt,
+            pubkeys,
+        } => utxos::exec_req_first_utxos_of_pubkeys(bca_executor, amount_target_opt, pubkeys).await,
+        BcaReqTypeV0::LastBlockstampOutOfForkWindow => {
+            last_blockstamp_out_of_fork_window::exec_req_last_blockstamp_out_of_fork_window(
+                bca_executor,
+            )
+            .await
+        }
+        BcaReqTypeV0::MembersCount => members_count::exec_req_members_count(bca_executor).await,
+        BcaReqTypeV0::PrepareSimplePayment(params) => {
+            prepare_simple_payment::exec_req_prepare_simple_payment(bca_executor, params).await
+        }
+        BcaReqTypeV0::ProofServerPubkey { challenge } => Ok(BcaRespTypeV0::ProofServerPubkey {
+            challenge,
+            server_pubkey: bca_executor.self_keypair.public_key(),
+            sig: bca_executor
+                .self_keypair
+                .generate_signator()
+                .sign(&challenge),
+        }),
+        BcaReqTypeV0::Ping => Ok(BcaRespTypeV0::Pong),
+        BcaReqTypeV0::SendTxs(txs) => send_txs::send_txs(bca_executor, txs).await,
+        BcaReqTypeV0::Identities(pubkeys) => {
+            let dbs_reader = bca_executor.dbs_reader();
+            Ok(BcaRespTypeV0::Identities(
+                bca_executor
+                    .dbs_pool
+                    .execute(move |dbs| {
+                        pubkeys
+                            .into_iter()
+                            .map(|pubkey| {
+                                dbs_reader.idty(&dbs.bc_db_ro, pubkey).map(|idty_opt| {
+                                    idty_opt.map(|idty| Identity {
+                                        is_member: idty.is_member,
+                                        username: idty.username,
+                                    })
+                                })
+                            })
+                            .collect::<KvResult<ArrayVec<_>>>()
+                    })
+                    .await??,
+            ))
+        }
+        BcaReqTypeV0::CurrentUd => current_ud::exec_req_current_ud(bca_executor).await,
+        BcaReqTypeV0::BalancesOfScripts(scripts) => {
+            balances::exec_req_balances_of_scripts(bca_executor, scripts).await
+        }
+    }
+}
diff --git a/bca/src/exec_req_type/balances.rs b/bca/src/exec_req_type/balances.rs
new file mode 100644
index 0000000000000000000000000000000000000000..6901a9d03e7f49015031fa2064acd9c18a1d28c0
--- /dev/null
+++ b/bca/src/exec_req_type/balances.rs
@@ -0,0 +1,61 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+use dubp::{crypto::keys::ed25519::PublicKey, wallet::prelude::WalletScriptV10};
+
+pub(super) async fn exec_req_balances_of_pubkeys(
+    bca_executor: &BcaExecutor,
+    pubkeys: ArrayVec<[PublicKey; 16]>,
+) -> Result<BcaRespTypeV0, ExecReqTypeError> {
+    let dbs_reader = bca_executor.dbs_reader();
+    Ok(BcaRespTypeV0::Balances(
+        bca_executor
+            .dbs_pool
+            .execute(move |_| {
+                pubkeys
+                    .into_iter()
+                    .map(|pubkey| {
+                        dbs_reader
+                            .get_account_balance(&WalletScriptV10::single_sig(pubkey))
+                            .map(|balance_opt| balance_opt.map(|balance| balance.0))
+                    })
+                    .collect::<Result<ArrayVec<_>, _>>()
+            })
+            .await??,
+    ))
+}
+
+pub(super) async fn exec_req_balances_of_scripts(
+    bca_executor: &BcaExecutor,
+    scripts: ArrayVec<[WalletScriptV10; 16]>,
+) -> Result<BcaRespTypeV0, ExecReqTypeError> {
+    let dbs_reader = bca_executor.dbs_reader();
+    Ok(BcaRespTypeV0::Balances(
+        bca_executor
+            .dbs_pool
+            .execute(move |_| {
+                scripts
+                    .into_iter()
+                    .map(|script| {
+                        dbs_reader
+                            .get_account_balance(&script)
+                            .map(|balance_opt| balance_opt.map(|balance| balance.0))
+                    })
+                    .collect::<Result<ArrayVec<_>, _>>()
+            })
+            .await??,
+    ))
+}
diff --git a/bca/src/exec_req_type/current_ud.rs b/bca/src/exec_req_type/current_ud.rs
new file mode 100644
index 0000000000000000000000000000000000000000..02823dbbaf3d8a6b07cc623198c03511389bf5c9
--- /dev/null
+++ b/bca/src/exec_req_type/current_ud.rs
@@ -0,0 +1,30 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+pub(super) async fn exec_req_current_ud(
+    bca_executor: &BcaExecutor,
+) -> Result<BcaRespTypeV0, ExecReqTypeError> {
+    if let Some(current_ud) = bca_executor
+        .cm_accessor
+        .get_current_meta(|cm| cm.current_ud)
+        .await
+    {
+        Ok(BcaRespTypeV0::CurrentUd(current_ud))
+    } else {
+        Err("no blockchain".into())
+    }
+}
diff --git a/bca/src/exec_req_type/last_blockstamp_out_of_fork_window.rs b/bca/src/exec_req_type/last_blockstamp_out_of_fork_window.rs
new file mode 100644
index 0000000000000000000000000000000000000000..41529563e624d149e869d0f5abf85d903cbde4fc
--- /dev/null
+++ b/bca/src/exec_req_type/last_blockstamp_out_of_fork_window.rs
@@ -0,0 +1,99 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+use dubp::common::prelude::*;
+
+pub(super) async fn exec_req_last_blockstamp_out_of_fork_window(
+    bca_executor: &BcaExecutor,
+) -> Result<BcaRespTypeV0, ExecReqTypeError> {
+    if let Some(current_block_number) = bca_executor
+        .cm_accessor
+        .get_current_meta(|cm| cm.current_block_meta.number)
+        .await
+    {
+        let dbs_reader = bca_executor.dbs_reader();
+        bca_executor
+            .dbs_pool
+            .execute(move |dbs| {
+                let block_ref_number = if current_block_number < 101 {
+                    0
+                } else {
+                    current_block_number - 101
+                };
+                let block_ref_hash = dbs_reader
+                    .block(&dbs.bc_db_ro, U32BE(block_ref_number))?
+                    .expect("unreachable")
+                    .hash;
+                Ok::<_, ExecReqTypeError>(BcaRespTypeV0::LastBlockstampOutOfForkWindow(
+                    Blockstamp {
+                        number: BlockNumber(block_ref_number),
+                        hash: BlockHash(block_ref_hash),
+                    },
+                ))
+            })
+            .await?
+    } else {
+        Err("no blockchain".into())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::tests::*;
+
+    #[tokio::test]
+    async fn test_exec_req_last_blockstamp_out_of_fork_window_no_blockchain() {
+        let mut cm_mock = MockAsyncAccessor::new();
+        cm_mock
+            .expect_get_current_meta::<u32>()
+            .times(1)
+            .returning(|_| None);
+        let dbs_reader = MockDbsReader::new();
+        let bca_executor =
+            create_bca_executor(cm_mock, dbs_reader).expect("fail to create bca executor");
+
+        let resp_res = exec_req_last_blockstamp_out_of_fork_window(&bca_executor).await;
+
+        assert_eq!(resp_res, Err(ExecReqTypeError("no blockchain".into())));
+    }
+
+    #[tokio::test]
+    async fn test_exec_req_last_blockstamp_out_of_fork_window_ok() -> Result<(), ExecReqTypeError> {
+        let mut cm_mock = MockAsyncAccessor::new();
+        cm_mock
+            .expect_get_current_meta::<u32>()
+            .times(1)
+            .returning(|f| Some(f(&CurrentMeta::default())));
+        let mut dbs_reader = MockDbsReader::new();
+        dbs_reader
+            .expect_block()
+            .times(1)
+            .returning(|_, _| Ok(Some(BlockMetaV2::default())));
+
+        let bca_executor =
+            create_bca_executor(cm_mock, dbs_reader).expect("fail to create bca executor");
+
+        let resp = exec_req_last_blockstamp_out_of_fork_window(&bca_executor).await?;
+
+        assert_eq!(
+            resp,
+            BcaRespTypeV0::LastBlockstampOutOfForkWindow(Blockstamp::default())
+        );
+
+        Ok(())
+    }
+}
diff --git a/bca/src/exec_req_type/members_count.rs b/bca/src/exec_req_type/members_count.rs
new file mode 100644
index 0000000000000000000000000000000000000000..71b85c6e3cadfc6fd22f3d9d90ec04c161d8c8ed
--- /dev/null
+++ b/bca/src/exec_req_type/members_count.rs
@@ -0,0 +1,52 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+pub(super) async fn exec_req_members_count(
+    bca_executor: &BcaExecutor,
+) -> Result<BcaRespTypeV0, ExecReqTypeError> {
+    if let Some(members_count) = bca_executor
+        .cm_accessor
+        .get_current_meta(|cm| cm.current_block_meta.members_count)
+        .await
+    {
+        Ok(BcaRespTypeV0::MembersCount(members_count))
+    } else {
+        Err("no blockchain".into())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::tests::*;
+
+    #[tokio::test]
+    async fn test_exec_req_members_count() {
+        let mut cm_mock = MockAsyncAccessor::new();
+        cm_mock
+            .expect_get_current_meta::<u64>()
+            .times(1)
+            .returning(|f| Some(f(&CurrentMeta::default())));
+        let dbs_reader = MockDbsReader::new();
+        let bca_executor =
+            create_bca_executor(cm_mock, dbs_reader).expect("fail to create bca executor");
+
+        let resp_res = exec_req_members_count(&bca_executor).await;
+
+        assert_eq!(resp_res, Ok(BcaRespTypeV0::MembersCount(0)));
+    }
+}
diff --git a/bca/src/exec_req_type/prepare_simple_payment.rs b/bca/src/exec_req_type/prepare_simple_payment.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a23a77650570d2ec5fcf652ff0ea00acf6519c44
--- /dev/null
+++ b/bca/src/exec_req_type/prepare_simple_payment.rs
@@ -0,0 +1,223 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software current_block_number: (), current_block_hash: (), inputs: (), inputs_sum: (): you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+use dubp::wallet::prelude::*;
+use duniter_bca_types::prepare_payment::{PrepareSimplePayment, PrepareSimplePaymentResp};
+
+pub(super) async fn exec_req_prepare_simple_payment(
+    bca_executor: &BcaExecutor,
+    params: PrepareSimplePayment,
+) -> Result<BcaRespTypeV0, ExecReqTypeError> {
+    let issuer = params.issuer;
+
+    if let Some(current_meta) = bca_executor.cm_accessor.get_current_meta(|cm| *cm).await {
+        let current_block_meta = current_meta.current_block_meta;
+        let current_ud = current_meta.current_ud;
+        let dbs_reader = bca_executor.dbs_reader();
+        let (amount, block_ref_number, block_ref_hash, (inputs, inputs_sum)) = bca_executor
+            .dbs_pool
+            .execute(move |dbs| {
+                let mut amount = params.amount.to_cents(current_ud);
+                let block_ref_number = if current_block_meta.number < 101 {
+                    0
+                } else {
+                    current_block_meta.number - 101
+                };
+                let block_ref_hash = dbs_reader
+                    .block(&dbs.bc_db_ro, U32BE(block_ref_number))?
+                    .expect("unreachable")
+                    .hash;
+                let current_base = current_block_meta.unit_base as i64;
+
+                if amount.base() > current_base {
+                    Err("too long base".into())
+                } else {
+                    while amount.base() < current_base {
+                        amount = amount.increment_base();
+                    }
+                    Ok::<_, ExecReqTypeError>((
+                        amount,
+                        block_ref_number,
+                        block_ref_hash,
+                        dbs_reader.find_inputs(
+                            &dbs.bc_db_ro,
+                            &dbs.txs_mp_db,
+                            amount,
+                            &WalletScriptV10::single(WalletConditionV10::Sig(issuer)),
+                            false,
+                        )?,
+                    ))
+                }
+            })
+            .await??;
+
+        if inputs_sum < amount {
+            return Err("insufficient balance".into());
+        }
+
+        Ok(BcaRespTypeV0::PrepareSimplePayment(
+            PrepareSimplePaymentResp {
+                current_block_number: block_ref_number,
+                current_block_hash: block_ref_hash,
+                current_ud,
+                inputs,
+                inputs_sum,
+            },
+        ))
+    } else {
+        Err("no blockchain".into())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::tests::*;
+
+    #[tokio::test]
+    async fn test_exec_req_prepare_simple_payment_no_blockchain() {
+        let mut mock_cm = MockAsyncAccessor::new();
+        mock_cm
+            .expect_get_current_meta::<CurrentMeta>()
+            .times(1)
+            .returning(|_| None);
+        let dbs_reader = MockDbsReader::new();
+        let bca_executor =
+            create_bca_executor(mock_cm, dbs_reader).expect("fail to create bca executor");
+
+        let resp_res = exec_req_prepare_simple_payment(
+            &bca_executor,
+            PrepareSimplePayment {
+                issuer: PublicKey::default(),
+                amount: Amount::Cents(SourceAmount::new(42, 0)),
+            },
+        )
+        .await;
+
+        assert_eq!(resp_res, Err(ExecReqTypeError("no blockchain".into())));
+    }
+
+    #[tokio::test]
+    async fn test_exec_req_prepare_simple_payment_too_long_base() {
+        let mut mock_cm = MockAsyncAccessor::new();
+        mock_cm
+            .expect_get_current_meta::<CurrentMeta>()
+            .times(1)
+            .returning(|f| Some(f(&CurrentMeta::default())));
+        let mut dbs_reader = MockDbsReader::new();
+        dbs_reader
+            .expect_block()
+            .times(1)
+            .returning(|_, _| Ok(Some(BlockMetaV2::default())));
+        let bca_executor =
+            create_bca_executor(mock_cm, dbs_reader).expect("fail to create bca executor");
+
+        let resp_res = exec_req_prepare_simple_payment(
+            &bca_executor,
+            PrepareSimplePayment {
+                issuer: PublicKey::default(),
+                amount: Amount::Cents(SourceAmount::new(42, 1)),
+            },
+        )
+        .await;
+
+        assert_eq!(resp_res, Err(ExecReqTypeError("too long base".into())));
+    }
+
+    #[tokio::test]
+    async fn test_exec_req_prepare_simple_payment_insufficient_balance() {
+        let mut mock_cm = MockAsyncAccessor::new();
+        mock_cm
+            .expect_get_current_meta::<CurrentMeta>()
+            .times(1)
+            .returning(|f| Some(f(&CurrentMeta::default())));
+        let mut dbs_reader = MockDbsReader::new();
+        dbs_reader
+            .expect_block()
+            .times(1)
+            .returning(|_, _| Ok(Some(BlockMetaV2::default())));
+        dbs_reader
+            .expect_find_inputs::<TxsMpV2Db<FileBackend>>()
+            .times(1)
+            .returning(|_, _, _, _, _| Ok((vec![], SourceAmount::default())));
+        let bca_executor =
+            create_bca_executor(mock_cm, dbs_reader).expect("fail to create bca executor");
+
+        let resp_res = exec_req_prepare_simple_payment(
+            &bca_executor,
+            PrepareSimplePayment {
+                issuer: PublicKey::default(),
+                amount: Amount::Cents(SourceAmount::new(42, 0)),
+            },
+        )
+        .await;
+
+        assert_eq!(
+            resp_res,
+            Err(ExecReqTypeError("insufficient balance".into()))
+        );
+    }
+
+    #[tokio::test]
+    async fn test_exec_req_prepare_simple_payment_ok() -> Result<(), ExecReqTypeError> {
+        let input = TransactionInputV10 {
+            amount: SourceAmount::with_base0(57),
+            id: SourceIdV10::Utxo(UtxoIdV10 {
+                tx_hash: Hash::default(),
+                output_index: 3,
+            }),
+        };
+
+        let mut mock_cm = MockAsyncAccessor::new();
+        mock_cm
+            .expect_get_current_meta::<CurrentMeta>()
+            .times(1)
+            .returning(|f| Some(f(&CurrentMeta::default())));
+        let mut dbs_reader = MockDbsReader::new();
+        dbs_reader
+            .expect_block()
+            .times(1)
+            .returning(|_, _| Ok(Some(BlockMetaV2::default())));
+        dbs_reader
+            .expect_find_inputs::<TxsMpV2Db<FileBackend>>()
+            .times(1)
+            .returning(move |_, _, _, _, _| Ok((vec![input], SourceAmount::with_base0(57))));
+        let bca_executor =
+            create_bca_executor(mock_cm, dbs_reader).expect("fail to create bca executor");
+
+        let resp = exec_req_prepare_simple_payment(
+            &bca_executor,
+            PrepareSimplePayment {
+                issuer: PublicKey::default(),
+                amount: Amount::Cents(SourceAmount::new(42, 0)),
+            },
+        )
+        .await?;
+
+        assert_eq!(
+            resp,
+            BcaRespTypeV0::PrepareSimplePayment(PrepareSimplePaymentResp {
+                current_block_number: 0,
+                current_block_hash: Hash::default(),
+                current_ud: SourceAmount::ZERO,
+                inputs: vec![input],
+                inputs_sum: SourceAmount::with_base0(57),
+            })
+        );
+
+        Ok(())
+    }
+}
diff --git a/bca/src/exec_req_type/send_txs.rs b/bca/src/exec_req_type/send_txs.rs
new file mode 100644
index 0000000000000000000000000000000000000000..bb1ede18352b99476d87c264381f66209aff81d1
--- /dev/null
+++ b/bca/src/exec_req_type/send_txs.rs
@@ -0,0 +1,65 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+use dubp::{crypto::keys::KeyPair, documents::transaction::TransactionDocumentTrait};
+use duniter_bca_types::{
+    rejected_tx::{RejectedTx, RejectedTxReason},
+    Txs,
+};
+
+pub(super) async fn send_txs(
+    bca_executor: &BcaExecutor,
+    txs: Txs,
+) -> Result<BcaRespTypeV0, ExecReqTypeError> {
+    let expected_currency = bca_executor.currency.clone();
+
+    let server_pubkey = bca_executor.self_keypair.public_key();
+    let txs_mempool = bca_executor.txs_mempool;
+
+    let mut rejected_txs = Vec::new();
+    for (i, tx) in txs.into_iter().enumerate() {
+        if let Err(e) = tx.verify(Some(&expected_currency)) {
+            rejected_txs.push(RejectedTx {
+                tx_index: i as u16,
+                reason: RejectedTxReason::InvalidTx(e.to_string()),
+            });
+        } else if let Err(rejected_tx) = bca_executor
+            .dbs_pool
+            .execute(move |dbs| {
+                txs_mempool
+                    .add_pending_tx(&dbs.bc_db_ro, server_pubkey, &dbs.txs_mp_db, &tx)
+                    .map_err(|e| RejectedTx {
+                        tx_index: i as u16,
+                        reason: match e {
+                            duniter_core::mempools::TxMpError::Db(e) => {
+                                RejectedTxReason::DbError(e.to_string())
+                            }
+                            duniter_core::mempools::TxMpError::Full => {
+                                RejectedTxReason::MempoolFull
+                            }
+                            duniter_core::mempools::TxMpError::TxAlreadyWritten => {
+                                RejectedTxReason::TxAlreadyWritten
+                            }
+                        },
+                    })
+            })
+            .await?
+        {
+            rejected_txs.push(rejected_tx);
+        }
+    }
+    Ok(BcaRespTypeV0::RejectedTxs(rejected_txs))
+}
diff --git a/bca/src/exec_req_type/utxos.rs b/bca/src/exec_req_type/utxos.rs
new file mode 100644
index 0000000000000000000000000000000000000000..249ff1f48d8662940cf18a6fe1be639d685c4753
--- /dev/null
+++ b/bca/src/exec_req_type/utxos.rs
@@ -0,0 +1,58 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+use dubp::{crypto::keys::ed25519::PublicKey, wallet::prelude::WalletScriptV10};
+
+pub(super) async fn exec_req_first_utxos_of_pubkeys(
+    bca_executor: &BcaExecutor,
+    amount_target_opt: Option<Amount>,
+    pubkeys: ArrayVec<[PublicKey; 16]>,
+) -> Result<BcaRespTypeV0, ExecReqTypeError> {
+    if let Some(current_ud) = bca_executor
+        .cm_accessor
+        .get_current_meta(|cm| cm.current_ud)
+        .await
+    {
+        let dbs_reader = bca_executor.dbs_reader();
+        let scripts: ArrayVec<[WalletScriptV10; 16]> = pubkeys
+            .into_iter()
+            .map(WalletScriptV10::single_sig)
+            .collect();
+        if let Some(amount_target) = amount_target_opt {
+            Ok(BcaRespTypeV0::FirstUtxosOfPubkeys(
+                bca_executor
+                    .dbs_pool
+                    .execute(move |_| {
+                        Ok::<_, ExecReqTypeError>(dbs_reader.first_scripts_utxos(
+                            Some(amount_target.to_cents(current_ud)),
+                            40,
+                            &scripts,
+                        )?)
+                    })
+                    .await??,
+            ))
+        } else {
+            Ok(BcaRespTypeV0::FirstUtxosOfPubkeys(
+                bca_executor
+                    .dbs_pool
+                    .execute(move |_| dbs_reader.first_scripts_utxos(None, 40, &scripts))
+                    .await??,
+            ))
+        }
+    } else {
+        Err("no blockchain".into())
+    }
+}
diff --git a/bca/src/lib.rs b/bca/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d07777961fecb6ae28b0d018294962d85f11833c
--- /dev/null
+++ b/bca/src/lib.rs
@@ -0,0 +1,412 @@
+//  Copyright (C) 2020 Éloïs  req_id: (), resp_type: ()SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+#![deny(
+    clippy::unwrap_used,
+    missing_copy_implementations,
+    trivial_casts,
+    trivial_numeric_casts,
+    unstable_features,
+    unused_import_braces
+)]
+
+mod exec_req_type;
+
+const MAX_BATCH_SIZE: usize = 10;
+const RESP_MIN_SIZE: usize = 64;
+type RespBytes = SmallVec<[u8; RESP_MIN_SIZE]>;
+
+use crate::exec_req_type::ExecReqTypeError;
+#[cfg(test)]
+use crate::tests::AsyncAccessor;
+use arrayvec::ArrayVec;
+use async_bincode::AsyncBincodeReader;
+use async_io_stream::IoStream;
+use bincode::Options as _;
+use dubp::crypto::keys::{ed25519::Ed25519KeyPair, Signator};
+use duniter_bca_types::{
+    amount::Amount, bincode_opts, identity::Identity, BcaReq, BcaReqExecError, BcaReqTypeV0,
+    BcaResp, BcaRespTypeV0, BcaRespV0,
+};
+pub use duniter_core::dbs::kv_typed::prelude::*;
+use duniter_core::dbs::{FileBackend, SharedDbs};
+#[cfg(not(test))]
+use duniter_core::global::AsyncAccessor;
+use duniter_gva_dbs_reader::DbsReader;
+
+use futures::{prelude::stream::FuturesUnordered, StreamExt, TryStream, TryStreamExt};
+use once_cell::sync::OnceCell;
+use smallvec::SmallVec;
+use tokio::task::JoinError;
+
+#[cfg(test)]
+use crate::tests::DbsReaderImpl;
+#[cfg(not(test))]
+use duniter_gva_dbs_reader::DbsReaderImpl;
+
+static BCA_EXECUTOR: OnceCell<BcaExecutor> = OnceCell::new();
+
+pub fn set_bca_executor(
+    currency: String,
+    cm_accessor: AsyncAccessor,
+    dbs_pool: fast_threadpool::ThreadPoolAsyncHandler<SharedDbs<FileBackend>>,
+    dbs_reader: DbsReaderImpl,
+    self_keypair: Ed25519KeyPair,
+    software_version: &'static str,
+    txs_mempool: duniter_core::mempools::TxsMempool,
+) {
+    BCA_EXECUTOR
+        .set(BcaExecutor {
+            currency,
+            cm_accessor,
+            dbs_pool,
+            dbs_reader,
+            self_keypair,
+            software_version,
+            txs_mempool,
+        })
+        .unwrap_or_else(|_| panic!("BCA_EXECUTOR already set !"))
+}
+
+#[cfg(not(test))]
+pub async fn execute<B, S>(query_body_stream: S, is_whitelisted: bool) -> Vec<u8>
+where
+    B: AsRef<[u8]>,
+    S: 'static + TryStream<Ok = B, Error = std::io::Error> + Send + Unpin,
+{
+    unsafe {
+        BCA_EXECUTOR
+            .get_unchecked()
+            .execute(query_body_stream, is_whitelisted)
+            .await
+    }
+}
+
+#[derive(Clone)]
+struct BcaExecutor {
+    cm_accessor: AsyncAccessor,
+    currency: String,
+    dbs_pool: fast_threadpool::ThreadPoolAsyncHandler<SharedDbs<FileBackend>>,
+    dbs_reader: DbsReaderImpl,
+    self_keypair: Ed25519KeyPair,
+    software_version: &'static str,
+    txs_mempool: duniter_core::mempools::TxsMempool,
+}
+use uninit::extension_traits::VecCapacity;
+impl BcaExecutor {
+    pub async fn execute<B, S>(&self, query_body_stream: S, is_whitelisted: bool) -> Vec<u8>
+    where
+        B: AsRef<[u8]>,
+        S: 'static + TryStream<Ok = B, Error = std::io::Error> + Send + Unpin,
+    {
+        let async_bincode_reader =
+            AsyncBincodeReader::<IoStream<S, B>, BcaReq>::from(IoStream::new(query_body_stream));
+        self.execute_inner(async_bincode_reader, is_whitelisted)
+            .await
+            .into_iter()
+            .fold(Vec::new(), |mut vec, elem| {
+                // Write resp len
+                let out = vec.reserve_uninit(4);
+                out.copy_from_slice(&u32::to_be_bytes(elem.len() as u32)[..]);
+                unsafe {
+                    // # Safety
+                    //
+                    //   - `.copy_from_slice()` contract guarantees initialization
+                    //     of `out`, which, in turn, from `reserve_uninit`'s contract,
+                    //     leads to the `vec` extra capacity having been initialized.
+                    vec.set_len(vec.len() + 4);
+                }
+
+                // Write resp content
+                let out = vec.reserve_uninit(elem.len());
+                out.copy_from_slice(&elem[..]);
+                unsafe {
+                    // # Safety
+                    //
+                    //   - `.copy_from_slice()` contract guarantees initialization
+                    //     of `out`, which, in turn, from `reserve_uninit`'s contract,
+                    //     leads to the `vec` extra capacity having been initialized.
+                    vec.set_len(vec.len() + elem.len());
+                }
+                vec
+            })
+    }
+
+    async fn execute_inner(
+        &self,
+        stream: impl TryStream<Ok = BcaReq, Error = bincode::Error>,
+        is_whitelisted: bool,
+    ) -> Vec<RespBytes> {
+        match stream
+            .map_ok(|req| {
+                let self_clone = self.clone();
+                tokio::spawn(async move { self_clone.execute_req(req, is_whitelisted).await })
+            })
+            .take(MAX_BATCH_SIZE)
+            .try_collect::<FuturesUnordered<_>>()
+            .await
+        {
+            Ok(futures_unordered) => {
+                futures_unordered
+                    .map(|req_res: Result<BcaResp, JoinError>| {
+                        let resp = match req_res {
+                            Ok(resp) => Ok(resp),
+                            Err(e) => Err(if e.is_cancelled() {
+                                BcaReqExecError::Cancelled
+                            } else if e.is_panic() {
+                                BcaReqExecError::Panic
+                            } else {
+                                BcaReqExecError::Unknown
+                            }),
+                        };
+                        let mut resp_buffer = RespBytes::new();
+                        bincode_opts()
+                            .serialize_into(&mut resp_buffer, &resp)
+                            .expect("unreachable");
+                        resp_buffer
+                    })
+                    .collect()
+                    .await
+            }
+            Err(e) => {
+                let req_res: Result<BcaResp, BcaReqExecError> =
+                    Err(BcaReqExecError::InvalidReq(e.to_string()));
+                let mut resp_buffer = RespBytes::new();
+                bincode_opts()
+                    .serialize_into(&mut resp_buffer, &req_res)
+                    .expect("unreachable");
+                vec![resp_buffer]
+            }
+        }
+    }
+
+    #[inline(always)]
+    async fn execute_req(self, req: BcaReq, is_whitelisted: bool) -> BcaResp {
+        match req {
+            BcaReq::V0(req) => BcaResp::V0(BcaRespV0 {
+                req_id: req.req_id,
+                resp_type: match crate::exec_req_type::execute_req_type(
+                    &self,
+                    req.req_type,
+                    is_whitelisted,
+                )
+                .await
+                {
+                    Ok(resp_type) => resp_type,
+                    Err(e) => BcaRespTypeV0::Error(e.0),
+                },
+            }),
+            _ => BcaResp::UnsupportedVersion,
+        }
+    }
+}
+
+#[cfg(not(test))]
+impl BcaExecutor {
+    #[inline(always)]
+    pub fn dbs_reader(&self) -> DbsReaderImpl {
+        self.dbs_reader
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    pub use dubp::{
+        block::prelude::*,
+        crypto::{
+            hashs::Hash,
+            keys::{ed25519::PublicKey, KeyPair, Seed32},
+        },
+        documents::transaction::TransactionInputV10,
+        wallet::prelude::*,
+    };
+    pub use duniter_bca_types::BcaReqV0;
+    pub use duniter_core::dbs::databases::bc_v2::{BcV2DbReadable, BcV2DbRo};
+    pub use duniter_core::dbs::databases::cm_v1::{CmV1Db, CmV1DbReadable};
+    pub use duniter_core::dbs::databases::txs_mp_v2::{TxsMpV2Db, TxsMpV2DbReadable};
+    pub use duniter_core::dbs::BlockMetaV2;
+    pub use duniter_core::global::{CurrentMeta, MockAsyncAccessor};
+    pub use duniter_gva_dbs_reader::MockDbsReader;
+    pub use futures::TryStreamExt;
+
+    pub type AsyncAccessor = duniter_core::dbs::kv_typed::prelude::Arc<MockAsyncAccessor>;
+    pub type DbsReaderImpl = duniter_core::dbs::kv_typed::prelude::Arc<MockDbsReader>;
+
+    impl BcaExecutor {
+        #[inline(always)]
+        pub fn dbs_reader(&self) -> DbsReaderImpl {
+            self.dbs_reader.clone()
+        }
+    }
+
+    pub(crate) fn create_bca_executor(
+        mock_cm: MockAsyncAccessor,
+        mock_dbs_reader: MockDbsReader,
+    ) -> KvResult<BcaExecutor> {
+        let dbs = SharedDbs::mem()?;
+        let threadpool =
+            fast_threadpool::ThreadPool::start(fast_threadpool::ThreadPoolConfig::low(), dbs);
+        Ok(BcaExecutor {
+            cm_accessor: duniter_core::dbs::kv_typed::prelude::Arc::new(mock_cm),
+            currency: "g1".to_owned(),
+            dbs_pool: threadpool.into_async_handler(),
+            dbs_reader: duniter_core::dbs::kv_typed::prelude::Arc::new(mock_dbs_reader),
+            self_keypair: Ed25519KeyPair::from_seed(
+                Seed32::random().expect("fail to gen random seed"),
+            ),
+            software_version: "test",
+            txs_mempool: duniter_core::mempools::TxsMempool::new(10),
+        })
+    }
+
+    pub(crate) fn io_stream<B: AsRef<[u8]>>(
+        bytes: B,
+    ) -> impl TryStream<Ok = B, Error = std::io::Error> {
+        futures::stream::iter(std::iter::once(Ok(bytes)))
+    }
+
+    #[tokio::test]
+    async fn test_one_req_ok() -> Result<(), bincode::Error> {
+        let req = BcaReq::V0(BcaReqV0 {
+            req_id: 42,
+            req_type: BcaReqTypeV0::MembersCount,
+        });
+        assert_eq!(bincode_opts().serialized_size(&req)?, 3);
+        let mut bytes = [0u8; 7];
+
+        bincode_opts().serialize_into(&mut bytes[4..], &req)?;
+        bytes[3] = 3;
+
+        use bincode::Options;
+        //println!("bytes_for_bincode={:?}", &bytes[4..]);
+        assert_eq!(req, bincode_opts().deserialize(&bytes[4..])?);
+
+        let mut mock_cm = MockAsyncAccessor::new();
+        mock_cm
+            .expect_get_current_meta::<u64>()
+            .times(1)
+            .returning(|f| Some(f(&CurrentMeta::default())));
+        let bca_executor = create_bca_executor(mock_cm, MockDbsReader::new())
+            .expect("fail to create bca executor");
+
+        //println!("bytes={:?}", bytes);
+        let bytes_res = bca_executor.execute(io_stream(bytes), false).await;
+        //println!("bytes_res={:?}", bytes_res);
+        let bca_res: Vec<Result<BcaResp, BcaReqExecError>> =
+            AsyncBincodeReader::<_, Result<BcaResp, BcaReqExecError>>::from(&bytes_res[..])
+                .try_collect::<Vec<_>>()
+                .await?;
+
+        assert_eq!(
+            bca_res,
+            vec![Ok(BcaResp::V0(BcaRespV0 {
+                req_id: 42,
+                resp_type: BcaRespTypeV0::MembersCount(0)
+            }))]
+        );
+
+        Ok(())
+    }
+
+    #[tokio::test]
+    async fn test_one_req_invalid() -> Result<(), bincode::Error> {
+        let req = BcaReq::V0(BcaReqV0 {
+            req_id: 42,
+            req_type: BcaReqTypeV0::MembersCount,
+        });
+        assert_eq!(bincode_opts().serialized_size(&req)?, 3);
+        let mut bytes = [0u8; 7];
+
+        bincode_opts().serialize_into(&mut bytes[4..], &req)?;
+        bytes[3] = 2;
+
+        use bincode::Options;
+        //println!("bytes_for_bincode={:?}", &bytes[4..]);
+        assert_eq!(req, bincode_opts().deserialize(&bytes[4..])?);
+
+        let bca_executor = create_bca_executor(MockAsyncAccessor::new(), MockDbsReader::new())
+            .expect("fail to create bca executor");
+
+        //println!("bytes={:?}", bytes);
+        let bytes_res = bca_executor.execute(io_stream(bytes), false).await;
+        //println!("bytes_res={:?}", bytes_res);
+        let bca_res: Vec<Result<BcaResp, BcaReqExecError>> =
+            AsyncBincodeReader::<_, Result<BcaResp, BcaReqExecError>>::from(&bytes_res[..])
+                .try_collect::<Vec<_>>()
+                .await?;
+
+        assert_eq!(
+            bca_res,
+            vec![Err(BcaReqExecError::InvalidReq(
+                "io error: unexpected end of file".to_owned()
+            ))]
+        );
+
+        Ok(())
+    }
+
+    #[tokio::test]
+    async fn test_two_reqs_ok() -> Result<(), bincode::Error> {
+        let req1 = BcaReq::V0(BcaReqV0 {
+            req_id: 42,
+            req_type: BcaReqTypeV0::Ping,
+        });
+        assert_eq!(bincode_opts().serialized_size(&req1)?, 3);
+        let req2 = BcaReq::V0(BcaReqV0 {
+            req_id: 57,
+            req_type: BcaReqTypeV0::MembersCount,
+        });
+        assert_eq!(bincode_opts().serialized_size(&req2)?, 3);
+
+        let mut bytes = [0u8; 14];
+        bincode_opts().serialize_into(&mut bytes[4..], &req1)?;
+        bytes[3] = 3;
+        bincode_opts().serialize_into(&mut bytes[11..], &req2)?;
+        bytes[10] = 3;
+
+        let mut mock_cm = MockAsyncAccessor::new();
+        mock_cm
+            .expect_get_current_meta::<u64>()
+            .times(1)
+            .returning(|f| Some(f(&CurrentMeta::default())));
+        let bca_executor = create_bca_executor(mock_cm, MockDbsReader::new())
+            .expect("fail to create bca executor");
+
+        //println!("bytes={:?}", bytes);
+        let bytes_res = bca_executor.execute(io_stream(bytes), false).await;
+        //println!("bytes_res={:?}", bytes_res);
+        let bca_res: Vec<Result<BcaResp, BcaReqExecError>> =
+            AsyncBincodeReader::<_, Result<BcaResp, BcaReqExecError>>::from(&bytes_res[..])
+                .try_collect::<Vec<_>>()
+                .await?;
+
+        assert_eq!(
+            bca_res,
+            vec![
+                Ok(BcaResp::V0(BcaRespV0 {
+                    req_id: 42,
+                    resp_type: BcaRespTypeV0::Pong
+                })),
+                Ok(BcaResp::V0(BcaRespV0 {
+                    req_id: 57,
+                    resp_type: BcaRespTypeV0::MembersCount(0)
+                }))
+            ]
+        );
+
+        Ok(())
+    }
+}
diff --git a/bca/types/Cargo.toml b/bca/types/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..ef4fc9023f20f9b92768a8f411ee1f404b6d9ecd
--- /dev/null
+++ b/bca/types/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "duniter-bca-types"
+version = "0.1.0"
+authors = ["librelois <elois@duniter.org>"]
+license = "AGPL-3.0"
+edition = "2018"
+
+[dependencies]
+arrayvec = { version = "0.5.1", features = ["serde"] }
+bincode = "1.3"
+dubp = { version = "0.51.0" }
+serde = { version = "1.0.105", features = ["derive"] }
+smallvec = { version = "1.4.0", features = ["serde"] }
+thiserror = "1.0.20"
+
+[features]
+default = ["duniter"]
+
+client = ["dubp/client"]
+duniter = ["dubp/duniter"]
diff --git a/bca/types/src/amount.rs b/bca/types/src/amount.rs
new file mode 100644
index 0000000000000000000000000000000000000000..1682a314190882c11e68425c33c087b509a0e49a
--- /dev/null
+++ b/bca/types/src/amount.rs
@@ -0,0 +1,46 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub enum Amount {
+    Cents(SourceAmount),
+    Uds(f64),
+}
+
+impl Default for Amount {
+    fn default() -> Self {
+        Self::Cents(SourceAmount::ZERO)
+    }
+}
+
+impl Amount {
+    pub fn to_cents(self, ud_amount: SourceAmount) -> SourceAmount {
+        match self {
+            Amount::Cents(sa) => sa,
+            Amount::Uds(f64_) => {
+                if !f64_.is_finite() || f64_ <= 0f64 {
+                    SourceAmount::ZERO
+                } else {
+                    SourceAmount::new(
+                        f64::round(ud_amount.amount() as f64 * f64_) as i64,
+                        ud_amount.base(),
+                    )
+                }
+            }
+        }
+    }
+}
diff --git a/bca/types/src/identity.rs b/bca/types/src/identity.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e2302a9bfa7d9520b4a5b41831ff987276776ed8
--- /dev/null
+++ b/bca/types/src/identity.rs
@@ -0,0 +1,22 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+pub struct Identity {
+    pub is_member: bool,
+    pub username: String,
+}
diff --git a/bca/types/src/lib.rs b/bca/types/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a9b987ba3fb79b635586838f529f12fae31f2497
--- /dev/null
+++ b/bca/types/src/lib.rs
@@ -0,0 +1,144 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+#![deny(
+    clippy::unwrap_used,
+    missing_copy_implementations,
+    trivial_casts,
+    trivial_numeric_casts,
+    unstable_features,
+    unused_import_braces
+)]
+
+pub mod amount;
+pub mod identity;
+pub mod prepare_payment;
+pub mod rejected_tx;
+pub mod utxo;
+
+use crate::amount::Amount;
+use crate::identity::Identity;
+use crate::prepare_payment::{PrepareSimplePayment, PrepareSimplePaymentResp};
+use crate::utxo::Utxo;
+
+use arrayvec::ArrayVec;
+use bincode::Options as _;
+use dubp::crypto::keys::ed25519::{PublicKey, Signature};
+use dubp::wallet::prelude::*;
+use dubp::{common::prelude::Blockstamp, crypto::hashs::Hash};
+use serde::{Deserialize, Serialize};
+use smallvec::SmallVec;
+use thiserror::Error;
+
+// Constants
+
+pub const MAX_FIRST_UTXOS: usize = 40;
+
+// Request
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub enum BcaReq {
+    V0(BcaReqV0),
+    _V1,
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub struct BcaReqV0 {
+    pub req_id: usize,
+    pub req_type: BcaReqTypeV0,
+}
+
+#[allow(clippy::large_enum_variant)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub enum BcaReqTypeV0 {
+    BalancesOfPubkeys(ArrayVec<[PublicKey; 16]>),
+    FirstUtxosOfPubkeys {
+        amount_target_opt: Option<Amount>,
+        pubkeys: ArrayVec<[PublicKey; 16]>,
+    },
+    LastBlockstampOutOfForkWindow,
+    MembersCount,
+    PrepareSimplePayment(PrepareSimplePayment),
+    ProofServerPubkey {
+        challenge: [u8; 16],
+    },
+    Ping,
+    SendTxs(Txs),
+    Identities(ArrayVec<[PublicKey; 16]>),
+    CurrentUd,
+    BalancesOfScripts(ArrayVec<[WalletScriptV10; 16]>),
+}
+
+// Request types helpers
+
+pub type Txs = SmallVec<[dubp::documents::transaction::TransactionDocumentV10; 1]>;
+
+// Response
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub enum BcaResp {
+    V0(BcaRespV0),
+    UnsupportedVersion,
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct BcaRespV0 {
+    pub req_id: usize,
+    pub resp_type: BcaRespTypeV0,
+}
+
+#[allow(clippy::large_enum_variant)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub enum BcaRespTypeV0 {
+    Error(String),
+    Balances(ArrayVec<[Option<SourceAmount>; 16]>),
+    FirstUtxosOfPubkeys(Vec<ArrayVec<[Utxo; MAX_FIRST_UTXOS]>>),
+    ProofServerPubkey {
+        challenge: [u8; 16],
+        server_pubkey: PublicKey,
+        sig: Signature,
+    },
+    LastBlockstampOutOfForkWindow(Blockstamp),
+    MembersCount(u64),
+    PrepareSimplePayment(PrepareSimplePaymentResp),
+    Pong,
+    RejectedTxs(Vec<rejected_tx::RejectedTx>),
+    Identities(ArrayVec<[Option<Identity>; 16]>),
+    CurrentUd(SourceAmount),
+}
+
+// Result and error
+
+pub type BcaResult = Result<BcaResp, BcaReqExecError>;
+
+#[derive(Clone, Debug, Deserialize, Error, PartialEq, Eq, Serialize)]
+pub enum BcaReqExecError {
+    #[error("task cancelled")]
+    Cancelled,
+    #[error("Invalid request: {0}")]
+    InvalidReq(String),
+    #[error("task panicked")]
+    Panic,
+    #[error("Unknown error")]
+    Unknown,
+}
+
+// Bincode configuration
+
+pub fn bincode_opts() -> impl bincode::Options {
+    bincode::options()
+        .with_limit(u32::max_value() as u64)
+        .allow_trailing_bytes()
+}
diff --git a/bca/types/src/prepare_payment.rs b/bca/types/src/prepare_payment.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2de5d621a5fb521be8a9304a9789602d6755523d
--- /dev/null
+++ b/bca/types/src/prepare_payment.rs
@@ -0,0 +1,32 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+use dubp::documents::transaction::TransactionInputV10;
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub struct PrepareSimplePayment {
+    pub issuer: PublicKey,
+    pub amount: Amount,
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct PrepareSimplePaymentResp {
+    pub current_block_number: u32,
+    pub current_block_hash: Hash,
+    pub current_ud: SourceAmount,
+    pub inputs: Vec<TransactionInputV10>,
+    pub inputs_sum: SourceAmount,
+}
diff --git a/bca/types/src/rejected_tx.rs b/bca/types/src/rejected_tx.rs
new file mode 100644
index 0000000000000000000000000000000000000000..14b06e52b76e39ed5830541a422329093151535c
--- /dev/null
+++ b/bca/types/src/rejected_tx.rs
@@ -0,0 +1,30 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct RejectedTx {
+    pub tx_index: u16,
+    pub reason: RejectedTxReason,
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub enum RejectedTxReason {
+    DbError(String),
+    InvalidTx(String),
+    MempoolFull,
+    TxAlreadyWritten,
+}
diff --git a/bca/types/src/utxo.rs b/bca/types/src/utxo.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d7716a808926b6f4fd449d5310ffdb60fb4d4fbb
--- /dev/null
+++ b/bca/types/src/utxo.rs
@@ -0,0 +1,23 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
+pub struct Utxo {
+    pub amount: SourceAmount,
+    pub tx_hash: Hash,
+    pub output_index: u8,
+}
diff --git a/db/Cargo.toml b/db/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..5296953084815395685b09e7ce1ede955cf37058
--- /dev/null
+++ b/db/Cargo.toml
@@ -0,0 +1,32 @@
+[package]
+name = "duniter-gva-db"
+version = "0.1.0"
+authors = ["elois <elois@duniter.org>"]
+description = "Duniter GVA DB"
+repository = "https://git.duniter.org/nodes/typescript/duniter"
+license = "AGPL-3.0"
+edition = "2018"
+
+[lib]
+path = "src/lib.rs"
+
+[dependencies]
+bincode = "1.2.1"
+chrono = { version = "0.4.15", optional = true }
+duniter-core = { git = "https://git.duniter.org/nodes/rust/duniter-core" }
+dubp = { version = "0.51.0", features = ["duniter"] }
+kv_typed = { git = "https://git.duniter.org/nodes/rust/duniter-core", default-features = false, features = ["sled_backend"] }
+parking_lot = "0.11.0"
+paste = "1.0.2"
+serde = { version = "1.0.105", features = ["derive"] }
+serde_json = "1.0.53"
+uninit = "0.4.0"
+zerocopy = "0.3.0"
+
+[dev-dependencies]
+
+[features]
+#default = ["explorer"]
+
+explorer = ["chrono", "duniter-core/explorer", "kv_typed/explorer"]
+leveldb_backend = ["kv_typed/leveldb_backend"]
diff --git a/db/src/keys.rs b/db/src/keys.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ced890746bed7f72d910bf66d2e3e0f26c3ea091
--- /dev/null
+++ b/db/src/keys.rs
@@ -0,0 +1,17 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+pub mod gva_utxo_id;
+pub mod wallet_hash_with_bn;
diff --git a/db/src/keys/gva_utxo_id.rs b/db/src/keys/gva_utxo_id.rs
new file mode 100644
index 0000000000000000000000000000000000000000..1f624a872a284fc84f87581a30af18aed9decfc4
--- /dev/null
+++ b/db/src/keys/gva_utxo_id.rs
@@ -0,0 +1,166 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+use uninit::prelude::*;
+
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
+pub struct GvaUtxoIdDbV1([u8; 69]); // script hash ++ block_number ++ tx_hash ++ output_index
+
+impl Default for GvaUtxoIdDbV1 {
+    fn default() -> Self {
+        GvaUtxoIdDbV1([0u8; 69])
+    }
+}
+
+impl std::fmt::Display for GvaUtxoIdDbV1 {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "{}:{}:{}:{}",
+            self.get_script_hash(),
+            self.get_block_number(),
+            self.get_tx_hash(),
+            self.get_output_index()
+        )
+    }
+}
+
+impl GvaUtxoIdDbV1 {
+    pub fn get_script_hash(&self) -> Hash {
+        let mut buffer = uninit_array![u8; 32];
+
+        buffer.as_out().copy_from_slice(&self.0[..32]);
+
+        Hash(unsafe { std::mem::transmute(buffer) })
+    }
+    pub fn get_block_number(&self) -> u32 {
+        let mut buffer = uninit_array![u8; 4];
+
+        buffer.as_out().copy_from_slice(&self.0[32..36]);
+
+        u32::from_be_bytes(unsafe { std::mem::transmute(buffer) })
+    }
+    pub fn get_tx_hash(&self) -> Hash {
+        let mut buffer = uninit_array![u8; 32];
+
+        buffer.as_out().copy_from_slice(&self.0[36..68]);
+
+        Hash(unsafe { std::mem::transmute(buffer) })
+    }
+    pub fn get_output_index(&self) -> u8 {
+        self.0[68]
+    }
+    pub fn new(
+        script: WalletScriptV10,
+        block_number: u32,
+        tx_hash: Hash,
+        output_index: u8,
+    ) -> Self {
+        let script_hash = Hash::compute(script.to_string().as_bytes());
+        Self::new_(script_hash, block_number, tx_hash, output_index)
+    }
+    pub fn new_(script_hash: Hash, block_number: u32, tx_hash: Hash, output_index: u8) -> Self {
+        // TODO uncomment when feature const_generics became stable !
+        /*let mut buffer = uninit_array![u8; 69];
+        let (hash_buffer, rest_buffer) = buffer.as_out().split_at_out(32);
+        let (bn_buffer, rest_buffer) = rest_buffer.split_at_out(4);
+        let (tx_hash_buffer, output_index_buffer) = rest_buffer.split_at_out(32);
+        hash_buffer.copy_from_slice(script_hash.as_ref());
+        bn_buffer.copy_from_slice(&block_number.to_be_bytes()[..]);
+        tx_hash_buffer.copy_from_slice(tx_hash.as_ref());
+        output_index_buffer.copy_from_slice(&[output_index]);
+
+        Self(unsafe { std::mem::transmute(buffer) })*/
+        let mut buffer = [0u8; 69];
+        buffer[..32].copy_from_slice(script_hash.as_ref());
+        buffer[32..36].copy_from_slice(&block_number.to_be_bytes()[..]);
+        buffer[36..68].copy_from_slice(tx_hash.as_ref());
+        buffer[68] = output_index;
+        Self(buffer)
+    }
+    pub fn script_interval(script_hash: Hash) -> (Self, Self) {
+        let mut buffer = [0; 69];
+        buffer[..32].copy_from_slice(script_hash.as_ref());
+        let min = Self(buffer);
+        let mut buffer = [255; 69];
+        buffer[..32].copy_from_slice(script_hash.as_ref());
+        let max = Self(buffer);
+
+        (min, max)
+    }
+    pub fn script_block_interval(
+        script_hash: Hash,
+        block_number_start: u32,
+        block_number_end: u32,
+    ) -> (Self, Self) {
+        (
+            Self::new_(script_hash, block_number_start, Hash::default(), 0),
+            Self::new_(script_hash, block_number_end, Hash::max(), u8::MAX),
+        )
+    }
+}
+
+impl AsBytes for GvaUtxoIdDbV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        f(&self.0[..])
+    }
+}
+
+impl FromBytes for GvaUtxoIdDbV1 {
+    type Err = CorruptedBytes;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        if bytes.len() == 69 {
+            // TODO uncomment when feature const_generics became stable !
+            /*let mut buffer = uninit_array![u8; 69];
+            buffer.as_out().copy_from_slice(bytes);
+            Ok(Self(unsafe { std::mem::transmute(buffer) }))*/
+            let mut buffer = [0u8; 69];
+            buffer.copy_from_slice(bytes);
+            Ok(Self(buffer))
+        } else {
+            Err(CorruptedBytes("db corrupted".to_owned()))
+        }
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableKey for GvaUtxoIdDbV1 {
+    fn from_explorer_str(_: &str) -> std::result::Result<Self, FromExplorerKeyErr> {
+        unimplemented!()
+    }
+    fn to_explorer_string(&self) -> KvResult<String> {
+        Ok(self.to_string())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn utxo_gva_id_new() {
+        let script = WalletScriptV10::single(WalletConditionV10::Csv(86_400));
+        let script_hash = Hash::compute(script.to_string().as_bytes());
+        let tx_hash = Hash::default();
+        let utxo_gva_id = GvaUtxoIdDbV1::new(script, 42, tx_hash, 3);
+
+        assert_eq!(utxo_gva_id.get_script_hash(), script_hash);
+        assert_eq!(utxo_gva_id.get_block_number(), 42);
+        assert_eq!(utxo_gva_id.get_tx_hash(), tx_hash);
+        assert_eq!(utxo_gva_id.get_output_index(), 3);
+    }
+}
diff --git a/db/src/keys/wallet_hash_with_bn.rs b/db/src/keys/wallet_hash_with_bn.rs
new file mode 100644
index 0000000000000000000000000000000000000000..bb5c767c1e0a12d63a760c97cdff072ea0f121ce
--- /dev/null
+++ b/db/src/keys/wallet_hash_with_bn.rs
@@ -0,0 +1,119 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+use std::fmt::Display;
+use uninit::prelude::*;
+
+#[derive(
+    Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, zerocopy::AsBytes, zerocopy::FromBytes,
+)]
+#[repr(transparent)]
+pub struct WalletHashWithBnV1Db([u8; 36]); // wallet_hash ++ block_number
+
+impl WalletHashWithBnV1Db {
+    pub fn new(hash: Hash, block_number: BlockNumber) -> Self {
+        let mut buffer = uninit_array![u8; 36];
+        let (hash_buffer, bn_buffer) = buffer.as_out().split_at_out(32);
+
+        hash_buffer.copy_from_slice(hash.as_ref());
+        bn_buffer.copy_from_slice(&block_number.0.to_be_bytes()[..]);
+
+        Self(unsafe { std::mem::transmute(buffer) })
+    }
+    pub fn get_wallet_hash(&self) -> Hash {
+        let mut buffer = uninit_array![u8; 32];
+
+        buffer.as_out().copy_from_slice(&self.0[..32]);
+        let bytes: [u8; 32] = unsafe { std::mem::transmute(buffer) };
+
+        Hash(bytes)
+    }
+    pub fn get_block_number(&self) -> u32 {
+        let mut buffer = uninit_array![u8; 4];
+
+        buffer.as_out().copy_from_slice(&self.0[32..]);
+
+        u32::from_be_bytes(unsafe { std::mem::transmute(buffer) })
+    }
+    pub fn wallet_hash_interval(wallet_hash: Hash) -> (Self, Self) {
+        (
+            Self::new(wallet_hash, BlockNumber(0)),
+            Self::new(wallet_hash, BlockNumber(u32::MAX)),
+        )
+    }
+}
+
+impl Default for WalletHashWithBnV1Db {
+    fn default() -> Self {
+        WalletHashWithBnV1Db([0u8; 36])
+    }
+}
+
+impl Display for WalletHashWithBnV1Db {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}:{}", self.get_wallet_hash(), self.get_block_number())
+    }
+}
+
+impl AsBytes for WalletHashWithBnV1Db {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        f(self.0.as_ref())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for WalletHashWithBnV1Db {
+    type Err = CorruptedBytes;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let layout = zerocopy::LayoutVerified::<_, WalletHashWithBnV1Db>::new(bytes)
+            .ok_or_else(|| CorruptedBytes("corrupted db".to_owned()))?;
+        Ok(*layout)
+    }
+}
+
+impl KeyZc for WalletHashWithBnV1Db {
+    type Ref = Self;
+}
+
+impl ToDumpString for WalletHashWithBnV1Db {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableKey for WalletHashWithBnV1Db {
+    fn from_explorer_str(source: &str) -> Result<Self, FromExplorerKeyErr> {
+        let mut source = source.split(':');
+        let hash_str = source
+            .next()
+            .ok_or_else(|| FromExplorerKeyErr("missing hash".into()))?;
+        let bn_str = source
+            .next()
+            .ok_or_else(|| FromExplorerKeyErr("missing block number".into()))?;
+
+        let hash = Hash::from_hex(hash_str).map_err(|e| FromExplorerKeyErr(e.into()))?;
+        let block_number = bn_str
+            .parse()
+            .map_err(|e: std::num::ParseIntError| FromExplorerKeyErr(e.into()))?;
+
+        Ok(WalletHashWithBnV1Db::new(hash, block_number))
+    }
+    fn to_explorer_string(&self) -> KvResult<String> {
+        Ok(self.to_string())
+    }
+}
diff --git a/db/src/lib.rs b/db/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d1113e6243e5d60a4017e8625d016db9051499c5
--- /dev/null
+++ b/db/src/lib.rs
@@ -0,0 +1,71 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+#![deny(
+    clippy::unwrap_used,
+    missing_copy_implementations,
+    trivial_casts,
+    trivial_numeric_casts,
+    unstable_features,
+    unused_import_braces
+)]
+
+mod keys;
+mod values;
+
+pub use keys::gva_utxo_id::GvaUtxoIdDbV1;
+pub use keys::wallet_hash_with_bn::WalletHashWithBnV1Db;
+pub use values::gva_idty_db::GvaIdtyDbV1;
+pub use values::gva_tx::GvaTxDbV1;
+pub use values::wallet_script_array::WalletScriptArrayV2;
+
+pub(crate) use dubp::common::prelude::*;
+pub(crate) use dubp::crypto::hashs::Hash;
+pub(crate) use dubp::wallet::prelude::*;
+pub(crate) use duniter_core::dbs::smallvec::SmallVec;
+pub(crate) use duniter_core::dbs::{
+    CorruptedBytes, HashKeyV2, PubKeyKeyV2, SourceAmountValV2, ToDumpString, WalletConditionsV2,
+};
+pub(crate) use kv_typed::db_schema;
+pub(crate) use kv_typed::prelude::*;
+pub(crate) use serde::{Deserialize, Serialize};
+pub(crate) use std::collections::BTreeSet;
+
+db_schema!(
+    GvaV1,
+    [
+        ["blocks_by_common_time", BlocksByCommonTime, U64BE, u32],
+        ["blocks_with_ud", BlocksWithUd, U32BE, ()],
+        ["blockchain_time", BlockchainTime, U32BE, u64],
+        ["txs", Txs, HashKeyV2, GvaTxDbV1],
+        ["txs_by_block", TxsByBlock, U32BE, Vec<Hash>],
+        ["txs_by_issuer", TxsByIssuer, WalletHashWithBnV1Db, BTreeSet<Hash>],
+        ["txs_by_recipient", TxsByRecipient, WalletHashWithBnV1Db, BTreeSet<Hash>],
+        [
+            "scripts_by_pubkey",
+            ScriptsByPubkey,
+            PubKeyKeyV2,
+            WalletScriptArrayV2
+        ],
+        [
+            "gva_utxos",
+            GvaUtxos,
+            GvaUtxoIdDbV1,
+            SourceAmountValV2
+        ],
+        ["balances", Balances, WalletConditionsV2, SourceAmountValV2],
+        ["gva_identities", GvaIdentities, PubKeyKeyV2, GvaIdtyDbV1],
+    ]
+);
diff --git a/db/src/values.rs b/db/src/values.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ed42095fa54010e5d37f4557d94d2f18d45df1d0
--- /dev/null
+++ b/db/src/values.rs
@@ -0,0 +1,18 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+pub mod gva_idty_db;
+pub mod gva_tx;
+pub mod wallet_script_array;
diff --git a/db/src/values/gva_idty_db.rs b/db/src/values/gva_idty_db.rs
new file mode 100644
index 0000000000000000000000000000000000000000..9bb918af86985f2ec7bd0d589404916cc01476be
--- /dev/null
+++ b/db/src/values/gva_idty_db.rs
@@ -0,0 +1,54 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
+pub struct GvaIdtyDbV1 {
+    pub is_member: bool,
+    pub joins: SmallVec<[BlockNumber; 2]>,
+    pub leaves: BTreeSet<BlockNumber>,
+    pub first_ud: Option<BlockNumber>,
+}
+
+impl AsBytes for GvaIdtyDbV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        f(&bincode::serialize(&self).unwrap_or_else(|_| unreachable!()))
+    }
+}
+
+impl kv_typed::prelude::FromBytes for GvaIdtyDbV1 {
+    type Err = bincode::Error;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        bincode::deserialize(bytes)
+    }
+}
+
+impl ToDumpString for GvaIdtyDbV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableValue for GvaIdtyDbV1 {
+    fn from_explorer_str(source: &str) -> Result<Self, FromExplorerValueErr> {
+        serde_json::from_str(source).map_err(|e| FromExplorerValueErr(e.into()))
+    }
+    fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
+        serde_json::to_value(&self).map_err(|e| KvError::DeserError(e.into()))
+    }
+}
diff --git a/db/src/values/gva_tx.rs b/db/src/values/gva_tx.rs
new file mode 100644
index 0000000000000000000000000000000000000000..258fae77c228769021c44f7c0dd1357ef4d33977
--- /dev/null
+++ b/db/src/values/gva_tx.rs
@@ -0,0 +1,56 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+use dubp::documents::transaction::TransactionDocumentV10;
+
+#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
+pub struct GvaTxDbV1 {
+    pub tx: TransactionDocumentV10,
+    pub written_block: Blockstamp,
+    pub written_time: i64,
+}
+
+impl AsBytes for GvaTxDbV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        let bytes = bincode::serialize(self).unwrap_or_else(|_| unreachable!());
+        f(bytes.as_ref())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for GvaTxDbV1 {
+    type Err = CorruptedBytes;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        bincode::deserialize(&bytes).map_err(|e| CorruptedBytes(format!("{}: '{:?}'", e, bytes)))
+    }
+}
+
+impl ToDumpString for GvaTxDbV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableValue for GvaTxDbV1 {
+    fn from_explorer_str(source: &str) -> Result<Self, FromExplorerValueErr> {
+        Self::from_bytes(source.as_bytes()).map_err(|e| FromExplorerValueErr(e.0.into()))
+    }
+    fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
+        serde_json::to_value(self).map_err(|e| KvError::DeserError(e.into()))
+    }
+}
diff --git a/db/src/values/wallet_script_array.rs b/db/src/values/wallet_script_array.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d0b4fa932549d1e14620c3274ced6295708d59c1
--- /dev/null
+++ b/db/src/values/wallet_script_array.rs
@@ -0,0 +1,53 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+#[derive(Debug, Default, PartialEq)]
+pub struct WalletScriptArrayV2(pub std::collections::HashSet<WalletScriptV10>);
+
+impl AsBytes for WalletScriptArrayV2 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        f(&bincode::serialize(&self.0).unwrap_or_else(|_| unreachable!()))
+    }
+}
+
+impl kv_typed::prelude::FromBytes for WalletScriptArrayV2 {
+    type Err = bincode::Error;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        Ok(Self(bincode::deserialize(bytes)?))
+    }
+}
+
+impl ToDumpString for WalletScriptArrayV2 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableValue for WalletScriptArrayV2 {
+    fn from_explorer_str(_: &str) -> std::result::Result<Self, FromExplorerValueErr> {
+        unimplemented!()
+    }
+    fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
+        Ok(serde_json::Value::Array(
+            self.0
+                .iter()
+                .map(|script| serde_json::Value::String(script.to_string()))
+                .collect(),
+        ))
+    }
+}
diff --git a/dbs-reader/Cargo.toml b/dbs-reader/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..7b95b394baadd66cfb07a99b7e7d2e869f2aa58a
--- /dev/null
+++ b/dbs-reader/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "duniter-gva-dbs-reader"
+version = "0.1.0"
+authors = ["elois <elois@duniter.org>"]
+description = "Duniter GVA DBs read operations"
+repository = "https://git.duniter.org/nodes/typescript/duniter"
+keywords = ["dubp", "duniter", "blockchain", "database"]
+license = "AGPL-3.0"
+edition = "2018"
+
+[lib]
+path = "src/lib.rs"
+
+[features]
+mock = ["mockall"]
+
+[dependencies]
+anyhow = "1.0.34"
+arrayvec = "0.5.1"
+duniter-bca-types = { path = "../bca/types" }
+duniter-core = { git = "https://git.duniter.org/nodes/rust/duniter-core" }
+duniter-gva-db = { path = "../db" }
+dubp = { version = "0.51.0", features = ["duniter"] }
+mockall = { version = "0.9.1", optional = true }
+resiter = "0.4.0"
+
+[dev-dependencies]
+duniter-core = { git = "https://git.duniter.org/nodes/rust/duniter-core", features = ["mem"] }
+maplit = "1.0.2"
+smallvec = { version = "1.4.0", features = ["serde", "write"] }
+unwrap = "1.2.1"
diff --git a/dbs-reader/src/block.rs b/dbs-reader/src/block.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4d7ce5d32e008b54d6d7fe6ac7728efefa40cdff
--- /dev/null
+++ b/dbs-reader/src/block.rs
@@ -0,0 +1,228 @@
+//  Copyright (C) 2021 Pascal Engélibert
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
+pub struct BlockCursor {
+    pub number: BlockNumber,
+}
+impl std::fmt::Display for BlockCursor {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.number)
+    }
+}
+
+impl FromStr for BlockCursor {
+    type Err = WrongCursor;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        Ok(Self {
+            number: s.parse().map_err(|_| WrongCursor)?,
+        })
+    }
+}
+
+impl DbsReaderImpl {
+    pub(super) fn block_(
+        &self,
+        bc_db: &BcV2DbRo<FileBackend>,
+        number: U32BE,
+    ) -> KvResult<Option<duniter_core::dbs::BlockMetaV2>> {
+        bc_db.blocks_meta().get(&number)
+    }
+
+    pub(super) fn blocks_(
+        &self,
+        bc_db: &BcV2DbRo<FileBackend>,
+        page_info: PageInfo<BlockCursor>,
+    ) -> KvResult<PagedData<Vec<(BlockCursor, duniter_core::dbs::BlockMetaV2)>>> {
+        let last_block_number = bc_db
+            .blocks_meta()
+            .iter_rev(.., |it| it.values().next_res())?
+            .ok_or_else(|| KvError::Custom("Empty blockchain".into()))?
+            .number;
+
+        let first_cursor_opt = if page_info.not_all() {
+            Some(BlockCursor {
+                number: BlockNumber(0),
+            })
+        } else {
+            None
+        };
+
+        let last_cursor_opt = if page_info.not_all() {
+            Some(BlockCursor {
+                number: BlockNumber(last_block_number),
+            })
+        } else {
+            None
+        };
+
+        let k_min = U32BE(if page_info.order {
+            page_info.pos.map_or_else(|| 0, |pos| pos.number.0)
+        } else {
+            page_info.limit_opt.map_or_else(
+                || 0,
+                |limit| {
+                    page_info
+                        .pos
+                        .map_or_else(|| last_block_number + 1, |pos| pos.number.0)
+                        .saturating_sub(limit.get() as u32 - 1)
+                },
+            )
+        });
+        let k_max = U32BE(if page_info.order {
+            page_info.limit_opt.map_or_else(
+                || last_block_number + 1,
+                |limit| {
+                    page_info.pos.map_or_else(
+                        || limit.get() as u32,
+                        |pos| pos.number.0.saturating_add(limit.get() as u32),
+                    )
+                },
+            )
+        } else {
+            page_info.pos.map_or_else(
+                || last_block_number + 1,
+                |pos| pos.number.0.saturating_add(1),
+            )
+        });
+
+        let blocks: Vec<(BlockCursor, duniter_core::dbs::BlockMetaV2)> = if page_info.order {
+            bc_db.blocks_meta().iter(k_min..k_max, blocks_inner)?
+        } else {
+            bc_db.blocks_meta().iter_rev(k_min..k_max, blocks_inner)?
+        };
+
+        Ok(PagedData {
+            has_next_page: has_next_page(
+                blocks
+                    .iter()
+                    .map(|(block_cursor, _block)| block_cursor.into()),
+                last_cursor_opt,
+                page_info,
+                page_info.order,
+            ),
+            has_previous_page: has_previous_page(
+                blocks
+                    .iter()
+                    .map(|(block_cursor, _block)| block_cursor.into()),
+                first_cursor_opt,
+                page_info,
+                page_info.order,
+            ),
+            data: blocks,
+        })
+    }
+}
+
+fn blocks_inner<I>(blocks_iter: I) -> KvResult<Vec<(BlockCursor, duniter_core::dbs::BlockMetaV2)>>
+where
+    I: Iterator<Item = KvResult<(U32BE, BlockMetaV2)>>,
+{
+    blocks_iter
+        .map(|block_res| {
+            block_res.map(|block| {
+                (
+                    BlockCursor {
+                        number: BlockNumber(block.0 .0),
+                    },
+                    block.1,
+                )
+            })
+        })
+        .collect()
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use duniter_core::dbs::databases::bc_v2::BcV2DbWritable;
+    use std::num::NonZeroUsize;
+
+    #[test]
+    fn test_block() -> KvResult<()> {
+        let bc_db = duniter_core::dbs::databases::bc_v2::BcV2Db::<Mem>::open(MemConf::default())?;
+        let bc_db_ro = bc_db.get_ro_handler();
+        let db_reader = DbsReaderImpl::mem();
+
+        bc_db
+            .blocks_meta_write()
+            .upsert(U32BE(0), duniter_core::dbs::BlockMetaV2::default())?;
+
+        assert_eq!(
+            db_reader.block(&bc_db_ro, U32BE(0))?,
+            Some(duniter_core::dbs::BlockMetaV2::default())
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_blocks() -> KvResult<()> {
+        let bc_db = duniter_core::dbs::databases::bc_v2::BcV2Db::<Mem>::open(MemConf::default())?;
+        let bc_db_ro = bc_db.get_ro_handler();
+        let db_reader = DbsReaderImpl::mem();
+
+        for i in 0..20 {
+            bc_db.blocks_meta_write().upsert(
+                U32BE(i),
+                duniter_core::dbs::BlockMetaV2 {
+                    number: i,
+                    ..Default::default()
+                },
+            )?;
+        }
+
+        let blocks = db_reader.blocks(
+            &bc_db_ro,
+            PageInfo {
+                pos: Some(BlockCursor {
+                    number: BlockNumber(10),
+                }),
+                order: true,
+                limit_opt: NonZeroUsize::new(3),
+            },
+        )?;
+
+        assert_eq!(blocks.data.len(), 3);
+        assert_eq!(blocks.data[0].1.number, 10);
+        assert_eq!(blocks.data[1].1.number, 11);
+        assert_eq!(blocks.data[2].1.number, 12);
+        assert!(blocks.has_previous_page);
+        assert!(blocks.has_next_page);
+
+        let blocks = db_reader.blocks(
+            &bc_db_ro,
+            PageInfo {
+                pos: Some(BlockCursor {
+                    number: BlockNumber(10),
+                }),
+                order: false,
+                limit_opt: NonZeroUsize::new(3),
+            },
+        )?;
+
+        assert_eq!(blocks.data.len(), 3);
+        assert_eq!(blocks.data[0].1.number, 10);
+        assert_eq!(blocks.data[1].1.number, 9);
+        assert_eq!(blocks.data[2].1.number, 8);
+        assert!(blocks.has_previous_page);
+        assert!(blocks.has_next_page);
+
+        Ok(())
+    }
+}
diff --git a/dbs-reader/src/current_frame.rs b/dbs-reader/src/current_frame.rs
new file mode 100644
index 0000000000000000000000000000000000000000..872f2e9f5669836f357b9ec21fe09a90c5ef8b74
--- /dev/null
+++ b/dbs-reader/src/current_frame.rs
@@ -0,0 +1,33 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use duniter_core::dbs::BlockMetaV2;
+
+use crate::*;
+
+impl DbsReaderImpl {
+    pub(super) fn get_current_frame_<BcDb: 'static + BcV2DbReadable>(
+        &self,
+        bc_db: &BcDb,
+        current_block_meta: &BlockMetaV2,
+    ) -> anyhow::Result<Vec<BlockMetaV2>> {
+        let issuers_frame = current_block_meta.issuers_frame;
+        let start = U32BE(current_block_meta.number + 1 - issuers_frame as u32);
+        bc_db
+            .blocks_meta()
+            .iter_rev(start.., |it| it.values().collect::<KvResult<_>>())
+            .map_err(Into::into)
+    }
+}
diff --git a/dbs-reader/src/find_inputs.rs b/dbs-reader/src/find_inputs.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ac628ac355b2741e92a7a3255e3cf13322a18ea9
--- /dev/null
+++ b/dbs-reader/src/find_inputs.rs
@@ -0,0 +1,244 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::{
+    uds_of_pubkey::UdsWithSum,
+    utxos::{UtxoCursor, UtxosWithSum},
+    *,
+};
+use dubp::{documents::transaction::TransactionInputV10, wallet::prelude::*};
+
+pub(super) const MIN_AMOUNT: i64 = 100;
+
+impl DbsReaderImpl {
+    pub(super) fn find_inputs_<TxsMpDb: 'static + TxsMpV2DbReadable>(
+        &self,
+        bc_db: &BcV2DbRo<FileBackend>,
+        txs_mp_db: &TxsMpDb,
+        amount: SourceAmount,
+        script: &WalletScriptV10,
+        use_mempool_sources: bool,
+    ) -> anyhow::Result<(Vec<TransactionInputV10>, SourceAmount)> {
+        // Pending UTXOs
+        let (mut inputs, mut inputs_sum) = if use_mempool_sources {
+            txs_mp_db
+                .outputs_by_script()
+                .get_ref_slice(
+                    duniter_core::dbs::WalletConditionsV2::from_ref(script),
+                    |utxos| {
+                        let mut sum = SourceAmount::ZERO;
+                        let inputs = utxos
+                            .iter()
+                            .filter(|utxo| {
+                                !txs_mp_db
+                                    .utxos_ids()
+                                    .contains_key(&UtxoIdDbV2(*utxo.tx_hash(), utxo.output_index()))
+                                    .unwrap_or(true)
+                            })
+                            .copied()
+                            .map(|utxo| {
+                                let amount = *utxo.amount();
+                                sum = sum + amount;
+                                TransactionInputV10 {
+                                    amount,
+                                    id: SourceIdV10::Utxo(UtxoIdV10 {
+                                        tx_hash: *utxo.tx_hash(),
+                                        output_index: utxo.output_index() as usize,
+                                    }),
+                                }
+                            })
+                            .collect();
+
+                        Ok((inputs, sum))
+                    },
+                )?
+                .unwrap_or((Vec::with_capacity(500), SourceAmount::ZERO))
+        } else {
+            (Vec::with_capacity(500), SourceAmount::ZERO)
+        };
+        // UDs
+        if script.nodes.is_empty() {
+            if let WalletSubScriptV10::Single(WalletConditionV10::Sig(issuer)) = script.root {
+                let pending_uds_bn = txs_mp_db.uds_ids().iter(.., |it| {
+                    it.keys()
+                        .map_ok(|duniter_core::dbs::UdIdV2(_pk, bn)| bn)
+                        .collect::<KvResult<_>>()
+                })?;
+
+                let PagedData {
+                    data: UdsWithSum { uds, sum: uds_sum },
+                    ..
+                } = self.unspent_uds_of_pubkey(
+                    bc_db,
+                    issuer,
+                    PageInfo::default(),
+                    Some(pending_uds_bn),
+                    Some(amount - inputs_sum),
+                )?;
+                inputs.extend(uds.into_iter().map(|(block_number, source_amount)| {
+                    TransactionInputV10 {
+                        amount: source_amount,
+                        id: SourceIdV10::Ud(UdSourceIdV10 {
+                            issuer,
+                            block_number,
+                        }),
+                    }
+                }));
+                inputs_sum = inputs_sum + uds_sum;
+            }
+        }
+        if inputs_sum < amount {
+            // Written UTXOs
+            let PagedData {
+                data:
+                    UtxosWithSum {
+                        utxos: written_utxos,
+                        sum: written_utxos_sum,
+                    },
+                ..
+            } = self.find_script_utxos(
+                txs_mp_db,
+                Some(amount - inputs_sum),
+                PageInfo::default(),
+                &script,
+            )?;
+            inputs.extend(written_utxos.into_iter().map(
+                |(
+                    UtxoCursor {
+                        tx_hash,
+                        output_index,
+                        ..
+                    },
+                    source_amount,
+                )| TransactionInputV10 {
+                    amount: source_amount,
+                    id: SourceIdV10::Utxo(UtxoIdV10 {
+                        tx_hash,
+                        output_index: output_index as usize,
+                    }),
+                },
+            ));
+
+            Ok((inputs, inputs_sum + written_utxos_sum))
+        } else {
+            Ok((inputs, inputs_sum))
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use duniter_core::dbs::{
+        databases::{bc_v2::BcV2DbWritable, txs_mp_v2::TxsMpV2DbWritable},
+        BlockMetaV2, SourceAmountValV2, UdIdV2, UtxoIdDbV2, UtxoValV2, WalletConditionsV2,
+    };
+    use duniter_gva_db::{GvaUtxoIdDbV1, GvaV1DbWritable};
+
+    const UD0: i64 = 100;
+
+    #[test]
+    fn test_find_inputs() -> anyhow::Result<()> {
+        let bc_db = duniter_core::dbs::databases::bc_v2::BcV2Db::<Mem>::open(MemConf::default())?;
+        let bc_db_ro = bc_db.get_ro_handler();
+        let gva_db = duniter_gva_db::GvaV1Db::<Mem>::open(MemConf::default())?;
+        let db_reader = create_dbs_reader(unsafe { std::mem::transmute(&gva_db.get_ro_handler()) });
+        let txs_mp_db =
+            duniter_core::dbs::databases::txs_mp_v2::TxsMpV2Db::<Mem>::open(MemConf::default())?;
+
+        let b0 = BlockMetaV2 {
+            dividend: Some(SourceAmount::with_base0(UD0)),
+            ..Default::default()
+        };
+        let pk = PublicKey::default();
+        let script = WalletScriptV10::single(WalletConditionV10::Sig(pk));
+        let mut pending_utxos = BTreeSet::new();
+        pending_utxos.insert(UtxoValV2::new(
+            SourceAmount::with_base0(900),
+            Hash::default(),
+            10,
+        ));
+
+        bc_db.blocks_meta_write().upsert(U32BE(0), b0)?;
+        bc_db
+            .uds_reval_write()
+            .upsert(U32BE(0), SourceAmountValV2(SourceAmount::with_base0(UD0)))?;
+        bc_db
+            .uds_write()
+            .upsert(UdIdV2(PublicKey::default(), BlockNumber(0)), ())?;
+        gva_db
+            .blockchain_time_write()
+            .upsert(U32BE(0), b0.median_time)?;
+        gva_db.gva_utxos_write().upsert(
+            GvaUtxoIdDbV1::new(script.clone(), 0, Hash::default(), 0),
+            SourceAmountValV2(SourceAmount::with_base0(500)),
+        )?;
+        gva_db.gva_utxos_write().upsert(
+            GvaUtxoIdDbV1::new(script.clone(), 0, Hash::default(), 1),
+            SourceAmountValV2(SourceAmount::with_base0(800)),
+        )?;
+        txs_mp_db
+            .outputs_by_script_write()
+            .upsert(WalletConditionsV2(script.clone()), pending_utxos)?;
+
+        // Gen tx1
+        let (inputs, inputs_sum) = db_reader.find_inputs(
+            &bc_db_ro,
+            &txs_mp_db,
+            SourceAmount::with_base0(550),
+            &script,
+            false,
+        )?;
+        assert_eq!(inputs.len(), 2);
+        assert_eq!(inputs_sum, SourceAmount::with_base0(600));
+
+        // Insert tx1 inputs in mempool
+        txs_mp_db
+            .uds_ids_write()
+            .upsert(UdIdV2(pk, BlockNumber(0)), ())?;
+        txs_mp_db
+            .utxos_ids_write()
+            .upsert(UtxoIdDbV2(Hash::default(), 0), ())?;
+
+        // Gen tx2
+        let (inputs, inputs_sum) = db_reader.find_inputs(
+            &bc_db_ro,
+            &txs_mp_db,
+            SourceAmount::with_base0(550),
+            &script,
+            false,
+        )?;
+        assert_eq!(inputs.len(), 1);
+        assert_eq!(inputs_sum, SourceAmount::with_base0(800));
+
+        // Insert tx2 inputs in mempool
+        txs_mp_db
+            .utxos_ids_write()
+            .upsert(UtxoIdDbV2(Hash::default(), 1), ())?;
+
+        // Gen tx3 (use pending utxo)
+        let (inputs, inputs_sum) = db_reader.find_inputs(
+            &bc_db_ro,
+            &txs_mp_db,
+            SourceAmount::with_base0(750),
+            &script,
+            true,
+        )?;
+        assert_eq!(inputs.len(), 1);
+        assert_eq!(inputs_sum, SourceAmount::with_base0(900));
+
+        Ok(())
+    }
+}
diff --git a/dbs-reader/src/idty.rs b/dbs-reader/src/idty.rs
new file mode 100644
index 0000000000000000000000000000000000000000..705bca3ed8cb026132ecefb110b1f3b8c4320646
--- /dev/null
+++ b/dbs-reader/src/idty.rs
@@ -0,0 +1,70 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+impl DbsReaderImpl {
+    pub(super) fn idty_(
+        &self,
+        bc_db: &BcV2DbRo<FileBackend>,
+        pubkey: PublicKey,
+    ) -> KvResult<Option<duniter_core::dbs::IdtyDbV2>> {
+        bc_db.identities().get(
+            &duniter_core::dbs::PubKeyKeyV2::from_bytes(pubkey.as_ref())
+                .map_err(|e| KvError::DeserError(Box::new(e)))?,
+        )
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use duniter_core::dbs::databases::bc_v2::BcV2DbWritable;
+
+    #[test]
+    fn test_idty() -> KvResult<()> {
+        let bc_db = duniter_core::dbs::databases::bc_v2::BcV2Db::<Mem>::open(MemConf::default())?;
+        let bc_db_ro = bc_db.get_ro_handler();
+        let db_reader = DbsReaderImpl::mem();
+        let pk = PublicKey::default();
+
+        bc_db
+            .identities_write()
+            .upsert(PubKeyKeyV2(pk), duniter_core::dbs::IdtyDbV2::default())?;
+
+        assert_eq!(
+            db_reader.idty(&bc_db_ro, pk)?,
+            Some(duniter_core::dbs::IdtyDbV2::default())
+        );
+
+        bc_db.identities_write().upsert(
+            PubKeyKeyV2(pk),
+            duniter_core::dbs::IdtyDbV2 {
+                is_member: true,
+                username: String::from("JohnDoe"),
+            },
+        )?;
+
+        assert_eq!(
+            db_reader.idty(&bc_db_ro, pk)?,
+            Some(duniter_core::dbs::IdtyDbV2 {
+                is_member: true,
+                username: String::from("JohnDoe"),
+            })
+        );
+
+        Ok(())
+    }
+}
diff --git a/dbs-reader/src/lib.rs b/dbs-reader/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..738795fe494b61da661099eb770d86860a8a0623
--- /dev/null
+++ b/dbs-reader/src/lib.rs
@@ -0,0 +1,355 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+#![deny(
+    clippy::unwrap_used,
+    missing_copy_implementations,
+    trivial_casts,
+    trivial_numeric_casts,
+    unstable_features,
+    unused_import_braces
+)]
+
+pub mod block;
+pub mod current_frame;
+pub mod find_inputs;
+pub mod idty;
+pub mod network;
+pub mod pagination;
+pub mod txs_history;
+pub mod uds_of_pubkey;
+pub mod utxos;
+
+pub use crate::pagination::{PageInfo, PagedData};
+pub use duniter_bca_types::MAX_FIRST_UTXOS;
+
+use crate::pagination::{has_next_page, has_previous_page};
+use arrayvec::ArrayVec;
+use dubp::common::crypto::keys::ed25519::PublicKey;
+use dubp::documents::transaction::TransactionDocumentV10;
+use dubp::{block::DubpBlockV10, common::crypto::hashs::Hash};
+use dubp::{common::prelude::BlockNumber, wallet::prelude::*};
+use duniter_bca_types::utxo::Utxo;
+use duniter_core::dbs::{databases::network_v1::NetworkV1DbReadable, FileBackend};
+use duniter_core::dbs::{
+    databases::{
+        bc_v2::{BcV2DbReadable, BcV2DbRo},
+        cm_v1::CmV1DbReadable,
+        txs_mp_v2::TxsMpV2DbReadable,
+    },
+    BlockMetaV2,
+};
+use duniter_core::dbs::{
+    kv_typed::prelude::*, HashKeyV2, PubKeyKeyV2, SourceAmountValV2, UtxoIdDbV2,
+};
+use duniter_gva_db::{GvaIdtyDbV1, GvaTxDbV1, GvaUtxoIdDbV1, GvaV1DbReadable, GvaV1DbRo};
+use resiter::filter::Filter;
+use resiter::filter_map::FilterMap;
+use resiter::flatten::Flatten;
+use resiter::map::Map;
+use std::{
+    collections::{BTreeSet, VecDeque},
+    num::NonZeroUsize,
+    str::FromStr,
+};
+
+#[derive(Clone, Copy, Debug)]
+pub struct WrongCursor;
+impl std::fmt::Display for WrongCursor {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "wrong cursor")
+    }
+}
+impl std::error::Error for WrongCursor {}
+
+#[cfg_attr(feature = "mock", mockall::automock)]
+pub trait DbsReader {
+    fn all_uds_of_pubkey(
+        &self,
+        bc_db: &BcV2DbRo<FileBackend>,
+        pubkey: PublicKey,
+        page_info: PageInfo<BlockNumber>,
+    ) -> KvResult<PagedData<uds_of_pubkey::UdsWithSum>>;
+    fn block(&self, bc_db: &BcV2DbRo<FileBackend>, number: U32BE) -> KvResult<Option<BlockMetaV2>>;
+    fn blocks(
+        &self,
+        bc_db: &BcV2DbRo<FileBackend>,
+        page_info: PageInfo<block::BlockCursor>,
+    ) -> KvResult<PagedData<Vec<(block::BlockCursor, BlockMetaV2)>>>;
+    fn endpoints<Db: 'static + NetworkV1DbReadable>(
+        &self,
+        network_db: &Db,
+        api_list: Vec<String>,
+    ) -> KvResult<Vec<String>>;
+    fn find_inputs<TxsMpDb: 'static + TxsMpV2DbReadable>(
+        &self,
+        bc_db: &BcV2DbRo<FileBackend>,
+        txs_mp_db: &TxsMpDb,
+        amount: SourceAmount,
+        script: &WalletScriptV10,
+        use_mempool_sources: bool,
+    ) -> anyhow::Result<(
+        Vec<dubp::documents::transaction::TransactionInputV10>,
+        SourceAmount,
+    )>;
+    fn find_script_utxos<TxsMpDb: 'static + TxsMpV2DbReadable>(
+        &self,
+        txs_mp_db_ro: &TxsMpDb,
+        amount_target_opt: Option<SourceAmount>,
+        page_info: PageInfo<utxos::UtxoCursor>,
+        script: &WalletScriptV10,
+    ) -> anyhow::Result<PagedData<utxos::UtxosWithSum>>;
+    fn first_scripts_utxos(
+        &self,
+        amount_target_opt: Option<SourceAmount>,
+        first: usize,
+        scripts: &[WalletScriptV10],
+    ) -> anyhow::Result<Vec<arrayvec::ArrayVec<[Utxo; MAX_FIRST_UTXOS]>>>;
+    fn get_account_balance(
+        &self,
+        account_script: &WalletScriptV10,
+    ) -> KvResult<Option<SourceAmountValV2>>;
+    fn get_blockchain_time(&self, block_number: BlockNumber) -> anyhow::Result<u64>;
+    fn get_current_block<CmDb: 'static + CmV1DbReadable>(
+        &self,
+        cm_db: &CmDb,
+    ) -> KvResult<Option<DubpBlockV10>>;
+    fn get_current_frame<BcDb: 'static + BcV2DbReadable>(
+        &self,
+        bc_db: &BcDb,
+        current_block_meta: &BlockMetaV2,
+    ) -> anyhow::Result<Vec<BlockMetaV2>>;
+    fn get_txs_history_bc_received(
+        &self,
+        from: Option<u64>,
+        page_info: PageInfo<txs_history::TxBcCursor>,
+        script_hash: Hash,
+        to: Option<u64>,
+    ) -> KvResult<PagedData<VecDeque<duniter_gva_db::GvaTxDbV1>>>;
+    fn get_txs_history_bc_sent(
+        &self,
+        from: Option<u64>,
+        page_info: PageInfo<txs_history::TxBcCursor>,
+        script_hash: Hash,
+        to: Option<u64>,
+    ) -> KvResult<PagedData<VecDeque<duniter_gva_db::GvaTxDbV1>>>;
+    fn get_txs_history_mempool<TxsMpDb: 'static + TxsMpV2DbReadable>(
+        &self,
+        txs_mp_db_ro: &TxsMpDb,
+        pubkey: PublicKey,
+    ) -> KvResult<(Vec<TransactionDocumentV10>, Vec<TransactionDocumentV10>)>;
+    fn idty(
+        &self,
+        bc_db: &BcV2DbRo<FileBackend>,
+        pubkey: PublicKey,
+    ) -> KvResult<Option<duniter_core::dbs::IdtyDbV2>>;
+    fn peers_and_heads<DB: 'static + NetworkV1DbReadable>(
+        &self,
+        dunp_db: &DB,
+    ) -> KvResult<
+        Vec<(
+            duniter_core::dbs::PeerCardDbV1,
+            Vec<duniter_core::dbs::DunpHeadDbV1>,
+        )>,
+    >;
+    fn unspent_uds_of_pubkey(
+        &self,
+        bc_db: &BcV2DbRo<FileBackend>,
+        pubkey: PublicKey,
+        page_info: PageInfo<BlockNumber>,
+        bn_to_exclude_opt: Option<std::collections::BTreeSet<BlockNumber>>,
+        amount_target_opt: Option<SourceAmount>,
+    ) -> KvResult<PagedData<uds_of_pubkey::UdsWithSum>>;
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct DbsReaderImpl(&'static GvaV1DbRo<FileBackend>);
+
+pub fn create_dbs_reader(gva_db_ro: &'static GvaV1DbRo<FileBackend>) -> DbsReaderImpl {
+    DbsReaderImpl(gva_db_ro)
+}
+
+impl DbsReader for DbsReaderImpl {
+    fn all_uds_of_pubkey(
+        &self,
+        bc_db: &BcV2DbRo<FileBackend>,
+        pubkey: PublicKey,
+        page_info: PageInfo<BlockNumber>,
+    ) -> KvResult<PagedData<uds_of_pubkey::UdsWithSum>> {
+        self.all_uds_of_pubkey_(bc_db, pubkey, page_info)
+    }
+
+    fn block(&self, bc_db: &BcV2DbRo<FileBackend>, number: U32BE) -> KvResult<Option<BlockMetaV2>> {
+        self.block_(bc_db, number)
+    }
+
+    fn blocks(
+        &self,
+        bc_db: &BcV2DbRo<FileBackend>,
+        page_info: PageInfo<block::BlockCursor>,
+    ) -> KvResult<PagedData<Vec<(block::BlockCursor, BlockMetaV2)>>> {
+        self.blocks_(bc_db, page_info)
+    }
+
+    fn endpoints<Db: 'static + NetworkV1DbReadable>(
+        &self,
+        network_db: &Db,
+        api_list: Vec<String>,
+    ) -> KvResult<Vec<String>> {
+        self.endpoints_(network_db, api_list)
+    }
+
+    fn find_inputs<TxsMpDb: 'static + TxsMpV2DbReadable>(
+        &self,
+        bc_db: &BcV2DbRo<FileBackend>,
+        txs_mp_db: &TxsMpDb,
+        amount: SourceAmount,
+        script: &WalletScriptV10,
+        use_mempool_sources: bool,
+    ) -> anyhow::Result<(
+        Vec<dubp::documents::transaction::TransactionInputV10>,
+        SourceAmount,
+    )> {
+        self.find_inputs_(bc_db, txs_mp_db, amount, script, use_mempool_sources)
+    }
+
+    fn find_script_utxos<TxsMpDb: 'static + TxsMpV2DbReadable>(
+        &self,
+        txs_mp_db_ro: &TxsMpDb,
+        amount_target_opt: Option<SourceAmount>,
+        page_info: PageInfo<utxos::UtxoCursor>,
+        script: &WalletScriptV10,
+    ) -> anyhow::Result<PagedData<utxos::UtxosWithSum>> {
+        self.find_script_utxos_(txs_mp_db_ro, amount_target_opt, page_info, script)
+    }
+
+    fn first_scripts_utxos(
+        &self,
+        amount_target_opt: Option<SourceAmount>,
+        first: usize,
+        scripts: &[WalletScriptV10],
+    ) -> anyhow::Result<Vec<ArrayVec<[Utxo; MAX_FIRST_UTXOS]>>> {
+        self.first_scripts_utxos_(amount_target_opt, first, scripts)
+    }
+
+    fn get_account_balance(
+        &self,
+        account_script: &WalletScriptV10,
+    ) -> KvResult<Option<SourceAmountValV2>> {
+        self.0
+            .balances()
+            .get(duniter_core::dbs::WalletConditionsV2::from_ref(
+                account_script,
+            ))
+    }
+
+    fn get_blockchain_time(&self, block_number: BlockNumber) -> anyhow::Result<u64> {
+        Ok(self
+            .0
+            .blockchain_time()
+            .get(&U32BE(block_number.0))?
+            .unwrap_or_else(|| unreachable!()))
+    }
+
+    fn get_current_block<CmDb: CmV1DbReadable>(
+        &self,
+        cm_db: &CmDb,
+    ) -> KvResult<Option<DubpBlockV10>> {
+        Ok(cm_db.current_block().get(&())?.map(|db_block| db_block.0))
+    }
+
+    fn get_current_frame<BcDb: 'static + BcV2DbReadable>(
+        &self,
+        bc_db: &BcDb,
+        current_block_meta: &BlockMetaV2,
+    ) -> anyhow::Result<Vec<BlockMetaV2>> {
+        self.get_current_frame_(bc_db, current_block_meta)
+    }
+
+    fn get_txs_history_bc_received(
+        &self,
+        from: Option<u64>,
+        page_info: PageInfo<txs_history::TxBcCursor>,
+        script_hash: Hash,
+        to: Option<u64>,
+    ) -> KvResult<PagedData<VecDeque<GvaTxDbV1>>> {
+        self.get_txs_history_bc_received_(from, page_info, script_hash, to)
+    }
+
+    fn get_txs_history_bc_sent(
+        &self,
+        from: Option<u64>,
+        page_info: PageInfo<txs_history::TxBcCursor>,
+        script_hash: Hash,
+        to: Option<u64>,
+    ) -> KvResult<PagedData<VecDeque<GvaTxDbV1>>> {
+        self.get_txs_history_bc_sent_(from, page_info, script_hash, to)
+    }
+
+    fn get_txs_history_mempool<TxsMpDb: 'static + TxsMpV2DbReadable>(
+        &self,
+        txs_mp_db_ro: &TxsMpDb,
+        pubkey: PublicKey,
+    ) -> KvResult<(Vec<TransactionDocumentV10>, Vec<TransactionDocumentV10>)> {
+        self.get_txs_history_mempool_(txs_mp_db_ro, pubkey)
+    }
+
+    fn idty(
+        &self,
+        bc_db: &BcV2DbRo<FileBackend>,
+        pubkey: PublicKey,
+    ) -> KvResult<Option<duniter_core::dbs::IdtyDbV2>> {
+        self.idty_(bc_db, pubkey)
+    }
+
+    fn peers_and_heads<DB: 'static + NetworkV1DbReadable>(
+        &self,
+        dunp_db: &DB,
+    ) -> KvResult<
+        Vec<(
+            duniter_core::dbs::PeerCardDbV1,
+            Vec<duniter_core::dbs::DunpHeadDbV1>,
+        )>,
+    > {
+        self.peers_and_heads_(dunp_db)
+    }
+
+    fn unspent_uds_of_pubkey(
+        &self,
+        bc_db: &BcV2DbRo<FileBackend>,
+        pubkey: PublicKey,
+        page_info: PageInfo<BlockNumber>,
+        bn_to_exclude_opt: Option<BTreeSet<BlockNumber>>,
+        amount_target_opt: Option<SourceAmount>,
+    ) -> KvResult<PagedData<uds_of_pubkey::UdsWithSum>> {
+        self.unspent_uds_of_pubkey_(
+            bc_db,
+            pubkey,
+            page_info,
+            bn_to_exclude_opt.as_ref(),
+            amount_target_opt,
+        )
+    }
+}
+
+#[cfg(test)]
+impl DbsReaderImpl {
+    pub(crate) fn mem() -> Self {
+        use duniter_gva_db::GvaV1DbWritable;
+        let gva_db = duniter_gva_db::GvaV1Db::<Mem>::open(MemConf::default())
+            .expect("fail to create memory gva db");
+        create_dbs_reader(unsafe { std::mem::transmute(&gva_db.get_ro_handler()) })
+    }
+}
diff --git a/dbs-reader/src/network.rs b/dbs-reader/src/network.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4ebe0aaf2a1dfa0f06f9a138d46bf999ea0689b5
--- /dev/null
+++ b/dbs-reader/src/network.rs
@@ -0,0 +1,200 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+use dubp::crypto::keys::PublicKey as _;
+use duniter_core::dbs::{databases::network_v1::NetworkV1DbReadable, DunpHeadDbV1, PeerCardDbV1};
+
+#[allow(clippy::unnecessary_wraps)]
+impl DbsReaderImpl {
+    pub(super) fn endpoints_<DB: NetworkV1DbReadable>(
+        &self,
+        network_db: &DB,
+        mut api_list: Vec<String>,
+    ) -> KvResult<Vec<String>> {
+        if api_list.is_empty() {
+            return Ok(vec![]);
+        }
+        for api in &mut api_list {
+            api.push(' ');
+        }
+        network_db.peers_old().iter(.., |it| {
+            it.values()
+                .map_ok(|peer| {
+                    peer.endpoints.into_iter().filter(|endpoint| {
+                        api_list
+                            .iter()
+                            .any(|api| endpoint.starts_with(api.as_str()))
+                    })
+                })
+                .flatten_ok()
+                .collect::<Result<Vec<String>, _>>()
+        })
+    }
+    pub(super) fn peers_and_heads_<DB: NetworkV1DbReadable>(
+        &self,
+        dunp_db: &DB,
+    ) -> KvResult<Vec<(PeerCardDbV1, Vec<DunpHeadDbV1>)>> {
+        Ok(dunp_db.peers_old().iter(.., |it| {
+            it.values()
+                .filter_map(|peer_res| {
+                    if let Ok(peer) = peer_res {
+                        if let Ok(pubkey) = PublicKey::from_base58(&peer.pubkey) {
+                            let k_min = duniter_core::dbs::DunpNodeIdV1Db::new(0, pubkey);
+                            let k_max = duniter_core::dbs::DunpNodeIdV1Db::new(u32::MAX, pubkey);
+                            Some((
+                                peer,
+                                dunp_db.heads_old().iter(k_min..k_max, |it| {
+                                    it.values().filter_map(|head| head.ok()).collect()
+                                }),
+                            ))
+                        } else {
+                            None
+                        }
+                    } else {
+                        None
+                    }
+                })
+                .collect()
+        }))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use duniter_core::dbs::databases::network_v1::NetworkV1DbWritable;
+    use duniter_core::dbs::PeerCardDbV1;
+
+    #[test]
+    fn test_empty_endpoints() -> KvResult<()> {
+        // Populate DB
+        let dunp_db =
+            duniter_core::dbs::databases::network_v1::NetworkV1Db::<Mem>::open(MemConf::default())?;
+        let db_reader = DbsReaderImpl::mem();
+        let pk = PublicKey::default();
+
+        dunp_db
+            .peers_old_write()
+            .upsert(PubKeyKeyV2(pk), PeerCardDbV1::default())?;
+
+        // Request Data
+        let api_list = vec!["GVA".to_owned()];
+        assert_eq!(
+            db_reader.endpoints_(&dunp_db, api_list)?,
+            Vec::<String>::new()
+        );
+
+        Ok(())
+    }
+    #[test]
+    fn test_endpoints_with_empty_api_list() -> KvResult<()> {
+        let dummy_endpoint = "GVA S domain.tld 443 gva";
+
+        // Populate DB
+        let dunp_db =
+            duniter_core::dbs::databases::network_v1::NetworkV1Db::<Mem>::open(MemConf::default())?;
+        let db_reader = DbsReaderImpl::mem();
+        let pk = PublicKey::default();
+        let peer = PeerCardDbV1 {
+            endpoints: vec![dummy_endpoint.to_owned()],
+            ..Default::default()
+        };
+
+        dunp_db.peers_old_write().upsert(PubKeyKeyV2(pk), peer)?;
+
+        // Request Data
+        let api_list = vec![];
+        assert_eq!(
+            db_reader.endpoints_(&dunp_db, api_list)?,
+            Vec::<String>::new()
+        );
+
+        Ok(())
+    }
+    #[test]
+    fn test_single_peer_endpoints() -> KvResult<()> {
+        let dummy_endpoint = "GVA S domain.tld 443 gva";
+
+        // Populate DB
+        let dunp_db =
+            duniter_core::dbs::databases::network_v1::NetworkV1Db::<Mem>::open(MemConf::default())?;
+        let db_reader = DbsReaderImpl::mem();
+        let pk = PublicKey::default();
+        let peer = PeerCardDbV1 {
+            endpoints: vec![dummy_endpoint.to_owned()],
+            ..Default::default()
+        };
+
+        dunp_db.peers_old_write().upsert(PubKeyKeyV2(pk), peer)?;
+
+        // Request Data
+        let api_list = vec!["GVA".to_owned()];
+        assert_eq!(
+            db_reader.endpoints_(&dunp_db, api_list)?,
+            vec![dummy_endpoint.to_owned()]
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_peers_and_heads() -> KvResult<()> {
+        let dunp_db =
+            duniter_core::dbs::databases::network_v1::NetworkV1Db::<Mem>::open(MemConf::default())?;
+        let db_reader = DbsReaderImpl::mem();
+        let pk = PublicKey::default();
+
+        dunp_db.peers_old_write().upsert(
+            PubKeyKeyV2(pk),
+            PeerCardDbV1 {
+                pubkey: pk.to_string(),
+                ..Default::default()
+            },
+        )?;
+        dunp_db.heads_old_write().upsert(
+            duniter_core::dbs::DunpNodeIdV1Db::new(42, pk),
+            DunpHeadDbV1::default(),
+        )?;
+        dunp_db.heads_old_write().upsert(
+            duniter_core::dbs::DunpNodeIdV1Db::new(43, pk),
+            DunpHeadDbV1 {
+                pubkey: PublicKey::from_base58("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+                    .expect("invalid pubkey"),
+                ..Default::default()
+            },
+        )?;
+
+        assert_eq!(
+            db_reader.peers_and_heads(&dunp_db)?,
+            vec![(
+                PeerCardDbV1 {
+                    pubkey: pk.to_string(),
+                    ..Default::default()
+                },
+                vec![
+                    DunpHeadDbV1::default(),
+                    DunpHeadDbV1 {
+                        pubkey: PublicKey::from_base58("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+                            .expect("invalid pubkey"),
+                        ..Default::default()
+                    }
+                ]
+            )]
+        );
+
+        Ok(())
+    }
+}
diff --git a/dbs-reader/src/pagination.rs b/dbs-reader/src/pagination.rs
new file mode 100644
index 0000000000000000000000000000000000000000..48d80bbc52110b02474c321f6acfa9477df5332c
--- /dev/null
+++ b/dbs-reader/src/pagination.rs
@@ -0,0 +1,144 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Debug)]
+pub struct PagedData<D: std::fmt::Debug> {
+    pub data: D,
+    pub has_previous_page: bool,
+    pub has_next_page: bool,
+}
+impl<D: std::fmt::Debug + Default> PagedData<D> {
+    pub fn empty() -> Self {
+        PagedData {
+            data: D::default(),
+            has_previous_page: false,
+            has_next_page: false,
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct PageInfo<T> {
+    pub(crate) pos: Option<T>,
+    /// Order: true for ASC, false for DESC
+    pub(crate) order: bool,
+    pub(crate) limit_opt: Option<NonZeroUsize>,
+}
+impl<T> PageInfo<T> {
+    pub fn new(pos: Option<T>, order: bool, limit_opt: Option<NonZeroUsize>) -> Self {
+        PageInfo {
+            pos,
+            order,
+            limit_opt,
+        }
+    }
+    pub fn limit_opt(&self) -> Option<NonZeroUsize> {
+        self.limit_opt
+    }
+    pub fn not_all(&self) -> bool {
+        self.limit_opt.is_some() || self.pos.is_some()
+    }
+    pub fn order(&self) -> bool {
+        self.order
+    }
+    pub fn pos(&self) -> Option<&T> {
+        self.pos.as_ref()
+    }
+}
+impl<T> Default for PageInfo<T> {
+    fn default() -> Self {
+        PageInfo {
+            pos: None,
+            order: true,
+            limit_opt: None,
+        }
+    }
+}
+impl<T> Clone for PageInfo<T>
+where
+    T: Clone,
+{
+    fn clone(&self) -> Self {
+        Self {
+            pos: self.pos.clone(),
+            order: self.order,
+            limit_opt: self.limit_opt,
+        }
+    }
+}
+impl<T> Copy for PageInfo<T> where T: Copy {}
+
+pub(crate) fn has_next_page<
+    'i,
+    C: 'static + std::fmt::Debug + Default + Ord,
+    I: DoubleEndedIterator<Item = OwnedOrRef<'i, C>>,
+>(
+    mut page_cursors: I,
+    last_cursor_opt: Option<C>,
+    page_info: PageInfo<C>,
+    page_not_reversed: bool,
+) -> bool {
+    if page_info.not_all() {
+        if let Some(last_cursor) = last_cursor_opt {
+            //println!("TMP last_cursor={:?}", last_cursor);
+            if let Some(page_end_cursor) = if page_not_reversed {
+                page_cursors.next_back()
+            } else {
+                page_cursors.next()
+            } {
+                //println!("TMP page_end_cursor={:?}", page_end_cursor);
+                page_end_cursor.as_ref() != &last_cursor
+            } else {
+                page_info.pos.unwrap_or_default() < last_cursor
+            }
+        } else {
+            false
+        }
+    } else {
+        false
+    }
+}
+
+pub(crate) fn has_previous_page<
+    'i,
+    C: 'static + std::fmt::Debug + Default + Ord,
+    I: DoubleEndedIterator<Item = OwnedOrRef<'i, C>>,
+>(
+    mut page_cursors: I,
+    first_cursor_opt: Option<C>,
+    page_info: PageInfo<C>,
+    page_not_reversed: bool,
+) -> bool {
+    if page_info.not_all() {
+        if let Some(first_cursor) = first_cursor_opt {
+            //println!("TMP first_cursor={:?}", first_cursor);
+            if let Some(page_start_cursor) = if page_not_reversed {
+                page_cursors.next()
+            } else {
+                page_cursors.next_back()
+            } {
+                page_start_cursor.as_ref() != &first_cursor
+            } else {
+                page_info.pos.unwrap_or_default() > first_cursor
+            }
+        } else {
+            false
+        }
+    } else {
+        false
+    }
+}
diff --git a/dbs-reader/src/txs_history.rs b/dbs-reader/src/txs_history.rs
new file mode 100644
index 0000000000000000000000000000000000000000..8182961290e809d64fc9979260315069652468b6
--- /dev/null
+++ b/dbs-reader/src/txs_history.rs
@@ -0,0 +1,836 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+use duniter_core::dbs::smallvec::SmallVec;
+use duniter_gva_db::WalletHashWithBnV1Db;
+
+#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
+pub struct TxBcCursor {
+    pub block_number: BlockNumber,
+    pub tx_hash: Hash,
+}
+impl std::fmt::Display for TxBcCursor {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}:{}", self.block_number, self.tx_hash,)
+    }
+}
+
+impl FromStr for TxBcCursor {
+    type Err = WrongCursor;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let mut s = s.split(':');
+        let block_number = s
+            .next()
+            .ok_or(WrongCursor)?
+            .parse()
+            .map_err(|_| WrongCursor)?;
+        let tx_hash = Hash::from_hex(s.next().ok_or(WrongCursor)?).map_err(|_| WrongCursor)?;
+        Ok(Self {
+            block_number,
+            tx_hash,
+        })
+    }
+}
+
+impl DbsReaderImpl {
+    pub(super) fn get_txs_history_bc_received_(
+        &self,
+        from: Option<u64>,
+        page_info: PageInfo<TxBcCursor>,
+        script_hash: Hash,
+        to: Option<u64>,
+    ) -> KvResult<PagedData<VecDeque<GvaTxDbV1>>> {
+        let mut start_k = WalletHashWithBnV1Db::new(
+            script_hash,
+            BlockNumber(if let Some(from) = from {
+                self.0
+                    .blocks_by_common_time()
+                    .iter(U64BE(from).., |it| it)
+                    .values()
+                    .next_res()?
+                    .unwrap_or(u32::MAX)
+            } else {
+                0
+            }),
+        );
+        let mut end_k = WalletHashWithBnV1Db::new(
+            script_hash,
+            BlockNumber(if let Some(to) = to {
+                self.0
+                    .blocks_by_common_time()
+                    .iter_rev(..U64BE(to), |it| it)
+                    .values()
+                    .next_res()?
+                    .unwrap_or(0)
+            } else {
+                u32::MAX
+            }),
+        );
+        let first_cursor_opt = if page_info.not_all() {
+            self.0
+                .txs_by_recipient()
+                .iter_ref_slice(start_k..=end_k, |k, hashs| {
+                    Ok(TxBcCursor {
+                        block_number: BlockNumber(k.get_block_number()),
+                        tx_hash: hashs[0],
+                    })
+                })
+                .next_res()?
+        } else {
+            None
+        };
+        let last_cursor_opt = if page_info.not_all() {
+            self.0
+                .txs_by_recipient()
+                .iter_ref_slice_rev(start_k..=end_k, |k, hashs| {
+                    Ok(TxBcCursor {
+                        block_number: BlockNumber(k.get_block_number()),
+                        tx_hash: hashs[hashs.len() - 1],
+                    })
+                })
+                .next_res()?
+        } else {
+            None
+        };
+        let first_hashs_opt = if let Some(TxBcCursor {
+            block_number,
+            tx_hash: hash_limit,
+        }) = page_info.pos
+        {
+            if page_info.order {
+                let hashs = self.0.txs_by_recipient().get_ref_slice(
+                    &WalletHashWithBnV1Db::new(script_hash, block_number),
+                    |hashs| {
+                        Ok(hashs
+                            .iter()
+                            .rev()
+                            .take_while(|hash| *hash != &hash_limit)
+                            .copied()
+                            .collect::<SmallVec<[Hash; 8]>>())
+                    },
+                )?;
+                start_k = WalletHashWithBnV1Db::new(script_hash, BlockNumber(block_number.0 + 1));
+                hashs
+            } else {
+                let hashs = self.0.txs_by_recipient().get_ref_slice(
+                    &WalletHashWithBnV1Db::new(script_hash, block_number),
+                    |hashs| {
+                        Ok(hashs
+                            .iter()
+                            .take_while(|hash| *hash != &hash_limit)
+                            .copied()
+                            .collect::<SmallVec<[Hash; 8]>>())
+                    },
+                )?;
+                if block_number == BlockNumber(0) {
+                    return Ok(PagedData::empty());
+                }
+                end_k = WalletHashWithBnV1Db::new(script_hash, BlockNumber(block_number.0 - 1));
+                hashs
+            }
+        } else {
+            None
+        };
+
+        if page_info.order {
+            let txs_iter = self
+                .0
+                .txs_by_recipient()
+                .iter_ref_slice(start_k..=end_k, |_k, hashs| {
+                    let mut sent = SmallVec::<[GvaTxDbV1; 8]>::new();
+                    for hash in hashs {
+                        if let Some(tx_db) = self.0.txs().get(HashKeyV2::from_ref(hash))? {
+                            sent.push(tx_db);
+                        }
+                    }
+                    Ok(sent)
+                })
+                .flatten_ok();
+            txs_history_bc_collect(
+                *self,
+                first_cursor_opt,
+                first_hashs_opt,
+                last_cursor_opt,
+                page_info,
+                txs_iter,
+            )
+        } else {
+            let txs_iter = self
+                .0
+                .txs_by_recipient()
+                .iter_ref_slice_rev(start_k..=end_k, |_k, hashs| {
+                    let mut sent = SmallVec::<[GvaTxDbV1; 8]>::new();
+                    for hash in hashs.iter().rev() {
+                        if let Some(tx_db) = self.0.txs().get(HashKeyV2::from_ref(hash))? {
+                            sent.push(tx_db);
+                        }
+                    }
+                    Ok(sent)
+                })
+                .flatten_ok();
+            txs_history_bc_collect(
+                *self,
+                first_cursor_opt,
+                first_hashs_opt,
+                last_cursor_opt,
+                page_info,
+                txs_iter,
+            )
+        }
+    }
+    pub(super) fn get_txs_history_bc_sent_(
+        &self,
+        from: Option<u64>,
+        page_info: PageInfo<TxBcCursor>,
+        script_hash: Hash,
+        to: Option<u64>,
+    ) -> KvResult<PagedData<VecDeque<GvaTxDbV1>>> {
+        let mut start_k = WalletHashWithBnV1Db::new(
+            script_hash,
+            BlockNumber(if let Some(from) = from {
+                self.0
+                    .blocks_by_common_time()
+                    .iter(U64BE(from).., |it| it)
+                    .values()
+                    .next_res()?
+                    .unwrap_or(u32::MAX)
+            } else {
+                0
+            }),
+        );
+        let mut end_k = WalletHashWithBnV1Db::new(
+            script_hash,
+            BlockNumber(if let Some(to) = to {
+                self.0
+                    .blocks_by_common_time()
+                    .iter_rev(..U64BE(to), |it| it)
+                    .values()
+                    .next_res()?
+                    .unwrap_or(0)
+            } else {
+                u32::MAX
+            }),
+        );
+        let first_cursor_opt = if page_info.not_all() {
+            self.0
+                .txs_by_issuer()
+                .iter_ref_slice(start_k..=end_k, |k, hashs| {
+                    Ok(TxBcCursor {
+                        block_number: BlockNumber(k.get_block_number()),
+                        tx_hash: hashs[0],
+                    })
+                })
+                .next_res()?
+        } else {
+            None
+        };
+        let last_cursor_opt = if page_info.not_all() {
+            self.0
+                .txs_by_issuer()
+                .iter_ref_slice_rev(start_k..=end_k, |k, hashs| {
+                    Ok(TxBcCursor {
+                        block_number: BlockNumber(k.get_block_number()),
+                        tx_hash: hashs[hashs.len() - 1],
+                    })
+                })
+                .next_res()?
+        } else {
+            None
+        };
+        let first_hashs_opt = if let Some(TxBcCursor {
+            block_number,
+            tx_hash: hash_limit,
+        }) = page_info.pos
+        {
+            if page_info.order {
+                let hashs = self.0.txs_by_issuer().get_ref_slice(
+                    &WalletHashWithBnV1Db::new(script_hash, block_number),
+                    |hashs| {
+                        Ok(hashs
+                            .iter()
+                            .rev()
+                            .take_while(|hash| *hash != &hash_limit)
+                            .copied()
+                            .collect::<SmallVec<[Hash; 8]>>())
+                    },
+                )?;
+                start_k = WalletHashWithBnV1Db::new(script_hash, BlockNumber(block_number.0 + 1));
+                hashs
+            } else {
+                let hashs = self.0.txs_by_issuer().get_ref_slice(
+                    &WalletHashWithBnV1Db::new(script_hash, block_number),
+                    |hashs| {
+                        Ok(hashs
+                            .iter()
+                            .take_while(|hash| *hash != &hash_limit)
+                            .copied()
+                            .collect::<SmallVec<[Hash; 8]>>())
+                    },
+                )?;
+                if block_number == BlockNumber(0) {
+                    return Ok(PagedData::empty());
+                }
+                end_k = WalletHashWithBnV1Db::new(script_hash, BlockNumber(block_number.0 - 1));
+                hashs
+            }
+        } else {
+            None
+        };
+
+        if page_info.order {
+            let txs_iter = self
+                .0
+                .txs_by_issuer()
+                .iter_ref_slice(start_k..=end_k, |_k, hashs| {
+                    let mut sent = SmallVec::<[GvaTxDbV1; 8]>::new();
+                    for hash in hashs {
+                        if let Some(tx_db) = self.0.txs().get(HashKeyV2::from_ref(hash))? {
+                            sent.push(tx_db);
+                        }
+                    }
+                    Ok(sent)
+                })
+                .flatten_ok();
+            txs_history_bc_collect(
+                *self,
+                first_cursor_opt,
+                first_hashs_opt,
+                last_cursor_opt,
+                page_info,
+                txs_iter,
+            )
+        } else {
+            let txs_iter = self
+                .0
+                .txs_by_issuer()
+                .iter_ref_slice_rev(start_k..=end_k, |_k, hashs| {
+                    let mut sent = SmallVec::<[GvaTxDbV1; 8]>::new();
+                    for hash in hashs.iter().rev() {
+                        if let Some(tx_db) = self.0.txs().get(HashKeyV2::from_ref(hash))? {
+                            sent.push(tx_db);
+                        }
+                    }
+                    Ok(sent)
+                })
+                .flatten_ok();
+            txs_history_bc_collect(
+                *self,
+                first_cursor_opt,
+                first_hashs_opt,
+                last_cursor_opt,
+                page_info,
+                txs_iter,
+            )
+        }
+    }
+    pub(super) fn get_txs_history_mempool_<TxsMpDb: 'static + TxsMpV2DbReadable>(
+        &self,
+        txs_mp_db_ro: &TxsMpDb,
+        pubkey: PublicKey,
+    ) -> KvResult<(Vec<TransactionDocumentV10>, Vec<TransactionDocumentV10>)> {
+        let sending = txs_mp_db_ro
+            .txs_by_issuer()
+            .get_ref_slice(&PubKeyKeyV2(pubkey), |hashs| {
+                let mut sent = Vec::with_capacity(hashs.len());
+                for hash in hashs {
+                    if let Some(tx_db) = txs_mp_db_ro.txs().get(HashKeyV2::from_ref(hash))? {
+                        sent.push(tx_db.0);
+                    }
+                }
+                Ok(sent)
+            })?
+            .unwrap_or_default();
+        let pending = txs_mp_db_ro
+            .txs_by_recipient()
+            .get_ref_slice(&PubKeyKeyV2(pubkey), |hashs| {
+                let mut pending = Vec::with_capacity(hashs.len());
+                for hash in hashs {
+                    if let Some(tx_db) = txs_mp_db_ro.txs().get(HashKeyV2::from_ref(hash))? {
+                        pending.push(tx_db.0);
+                    }
+                }
+                Ok(pending)
+            })?
+            .unwrap_or_default();
+        Ok((sending, pending))
+    }
+}
+
+fn txs_history_bc_collect<I: Iterator<Item = KvResult<GvaTxDbV1>>>(
+    dbs_reader: DbsReaderImpl,
+    first_cursor_opt: Option<TxBcCursor>,
+    first_hashs_opt: Option<SmallVec<[Hash; 8]>>,
+    last_cursor_opt: Option<TxBcCursor>,
+    page_info: PageInfo<TxBcCursor>,
+    txs_iter: I,
+) -> KvResult<PagedData<VecDeque<GvaTxDbV1>>> {
+    let mut txs = if let Some(limit) = page_info.limit_opt {
+        txs_iter
+            .take(limit.get())
+            .collect::<KvResult<VecDeque<_>>>()?
+    } else {
+        txs_iter.collect::<KvResult<VecDeque<_>>>()?
+    };
+
+    if let Some(first_hashs) = first_hashs_opt {
+        for hash in first_hashs.into_iter() {
+            if let Some(tx_db) = dbs_reader.0.txs().get(&HashKeyV2(hash))? {
+                txs.push_front(tx_db);
+            }
+        }
+    }
+
+    Ok(PagedData {
+        has_next_page: if page_info.order {
+            has_next_page(
+                txs.iter().map(|tx_db| {
+                    TxBcCursor {
+                        block_number: tx_db.written_block.number,
+                        tx_hash: tx_db.tx.get_hash(),
+                    }
+                    .into()
+                }),
+                last_cursor_opt,
+                page_info,
+                page_info.order,
+            )
+        } else {
+            // Server can't efficiently determine hasNextPage in DESC order
+            false
+        },
+        has_previous_page: if page_info.order {
+            // Server can't efficiently determine hasPreviousPage in ASC order
+            false
+        } else {
+            has_previous_page(
+                txs.iter().map(|tx_db| {
+                    TxBcCursor {
+                        block_number: tx_db.written_block.number,
+                        tx_hash: tx_db.tx.get_hash(),
+                    }
+                    .into()
+                }),
+                first_cursor_opt,
+                page_info,
+                page_info.order,
+            )
+        },
+        data: txs,
+    })
+}
+
+// Needed for BMA only
+pub struct TxsHistory {
+    pub sent: Vec<GvaTxDbV1>,
+    pub received: Vec<GvaTxDbV1>,
+    pub sending: Vec<TransactionDocumentV10>,
+    pub pending: Vec<TransactionDocumentV10>,
+}
+
+// Needed for BMA only
+pub fn get_transactions_history_for_bma<GvaDb: GvaV1DbReadable, TxsMpDb: TxsMpV2DbReadable>(
+    gva_db_ro: &GvaDb,
+    txs_mp_db_ro: &TxsMpDb,
+    pubkey: PublicKey,
+) -> KvResult<TxsHistory> {
+    let script_hash = Hash::compute(WalletScriptV10::single_sig(pubkey).to_string().as_bytes());
+    let start_k = WalletHashWithBnV1Db::new(script_hash, BlockNumber(0));
+    let end_k = WalletHashWithBnV1Db::new(script_hash, BlockNumber(u32::MAX));
+
+    let sent = gva_db_ro
+        .txs_by_issuer()
+        .iter_ref_slice(start_k..end_k, |_k, hashs| {
+            let mut sent = SmallVec::<[GvaTxDbV1; 2]>::new();
+            for hash in hashs {
+                if let Some(tx_db) = gva_db_ro.txs().get(HashKeyV2::from_ref(hash))? {
+                    sent.push(tx_db);
+                }
+            }
+            Ok(sent)
+        })
+        .flatten_ok()
+        .collect::<KvResult<Vec<_>>>()?;
+
+    let received = gva_db_ro
+        .txs_by_recipient()
+        .iter_ref_slice(start_k..end_k, |_k, hashs| {
+            let mut sent = SmallVec::<[GvaTxDbV1; 2]>::new();
+            for hash in hashs {
+                if let Some(tx_db) = gva_db_ro.txs().get(HashKeyV2::from_ref(hash))? {
+                    sent.push(tx_db);
+                }
+            }
+            Ok(sent)
+        })
+        .flatten_ok()
+        .collect::<KvResult<Vec<_>>>()?;
+    let sending = txs_mp_db_ro
+        .txs_by_issuer()
+        .get_ref_slice(&PubKeyKeyV2(pubkey), |hashs| {
+            let mut sent = Vec::with_capacity(hashs.len());
+            for hash in hashs {
+                if let Some(tx_db) = txs_mp_db_ro.txs().get(HashKeyV2::from_ref(hash))? {
+                    sent.push(tx_db.0);
+                }
+            }
+            Ok(sent)
+        })?
+        .unwrap_or_default();
+    let pending = txs_mp_db_ro
+        .txs_by_recipient()
+        .get_ref_slice(&PubKeyKeyV2(pubkey), |hashs| {
+            let mut pending = Vec::with_capacity(hashs.len());
+            for hash in hashs {
+                if let Some(tx_db) = txs_mp_db_ro.txs().get(HashKeyV2::from_ref(hash))? {
+                    pending.push(tx_db.0);
+                }
+            }
+            Ok(pending)
+        })?
+        .unwrap_or_default();
+    Ok(TxsHistory {
+        sent,
+        received,
+        sending,
+        pending,
+    })
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use dubp::{
+        common::prelude::{BlockHash, Blockstamp},
+        crypto::keys::ed25519::PublicKey,
+        documents::transaction::{TransactionDocumentV10, TransactionDocumentV10Stringified},
+        documents_parser::prelude::FromStringObject,
+    };
+    use duniter_gva_db::GvaV1DbWritable;
+    use maplit::btreeset;
+    use unwrap::unwrap;
+
+    fn gen_tx(hash: Hash, written_block_number: BlockNumber) -> GvaTxDbV1 {
+        GvaTxDbV1 {
+            tx: unwrap!(TransactionDocumentV10::from_string_object(
+                &TransactionDocumentV10Stringified {
+                    currency: "test".to_owned(),
+                    blockstamp:
+                        "1-0000000000000000000000000000000000000000000000000000000000000000"
+                            .to_owned(),
+                    locktime: 0,
+                    issuers: vec![],
+                    inputs: vec![],
+                    unlocks: vec![],
+                    outputs: vec![],
+                    comment: "".to_owned(),
+                    signatures: vec![],
+                    hash: Some(hash.to_hex()),
+                }
+            )),
+            written_block: Blockstamp {
+                number: written_block_number,
+                hash: BlockHash(Hash::default()),
+            },
+            written_time: 1,
+        }
+    }
+
+    #[test]
+    fn test_get_txs_history_bc_received() -> KvResult<()> {
+        let gva_db = duniter_gva_db::GvaV1Db::<Mem>::open(MemConf::default())?;
+        let db_reader = create_dbs_reader(unsafe { std::mem::transmute(&gva_db.get_ro_handler()) });
+
+        let s1 = WalletScriptV10::single_sig(PublicKey::default());
+        let s1_hash = Hash::compute(&s1.to_string().as_bytes());
+
+        gva_db.txs_write().upsert(
+            HashKeyV2(Hash::default()),
+            gen_tx(Hash::default(), BlockNumber(1)),
+        )?;
+        gva_db.txs_write().upsert(
+            HashKeyV2(Hash([1; 32])),
+            gen_tx(Hash([1; 32]), BlockNumber(1)),
+        )?;
+        gva_db.txs_write().upsert(
+            HashKeyV2(Hash([2; 32])),
+            gen_tx(Hash([2; 32]), BlockNumber(1)),
+        )?;
+        gva_db.txs_write().upsert(
+            HashKeyV2(Hash([3; 32])),
+            gen_tx(Hash([3; 32]), BlockNumber(1)),
+        )?;
+        gva_db.txs_by_recipient_write().upsert(
+            WalletHashWithBnV1Db::new(s1_hash, BlockNumber(1)),
+            btreeset![Hash::default(), Hash([1; 32]), Hash([2; 32]), Hash([3; 32])],
+        )?;
+        gva_db.blocks_by_common_time_write().upsert(U64BE(1), 1)?;
+        gva_db.txs_write().upsert(
+            HashKeyV2(Hash([4; 32])),
+            gen_tx(Hash([4; 32]), BlockNumber(2)),
+        )?;
+        gva_db.txs_write().upsert(
+            HashKeyV2(Hash([5; 32])),
+            gen_tx(Hash([5; 32]), BlockNumber(2)),
+        )?;
+        gva_db.txs_by_recipient_write().upsert(
+            WalletHashWithBnV1Db::new(s1_hash, BlockNumber(2)),
+            btreeset![Hash([4; 32]), Hash([5; 32])],
+        )?;
+        gva_db.blocks_by_common_time_write().upsert(U64BE(2), 2)?;
+        gva_db.txs_write().upsert(
+            HashKeyV2(Hash([6; 32])),
+            gen_tx(Hash([6; 32]), BlockNumber(3)),
+        )?;
+        gva_db.txs_by_recipient_write().upsert(
+            WalletHashWithBnV1Db::new(s1_hash, BlockNumber(3)),
+            btreeset![Hash([6; 32])],
+        )?;
+        gva_db.blocks_by_common_time_write().upsert(U64BE(3), 3)?;
+        gva_db.txs_write().upsert(
+            HashKeyV2(Hash([7; 32])),
+            gen_tx(Hash([7; 32]), BlockNumber(4)),
+        )?;
+        gva_db.txs_by_recipient_write().upsert(
+            WalletHashWithBnV1Db::new(s1_hash, BlockNumber(4)),
+            btreeset![Hash([7; 32])],
+        )?;
+        gva_db.blocks_by_common_time_write().upsert(U64BE(4), 4)?;
+        gva_db.txs_write().upsert(
+            HashKeyV2(Hash([8; 32])),
+            gen_tx(Hash([8; 32]), BlockNumber(5)),
+        )?;
+        gva_db.txs_by_recipient_write().upsert(
+            WalletHashWithBnV1Db::new(s1_hash, BlockNumber(5)),
+            btreeset![Hash([8; 32])],
+        )?;
+        gva_db.blocks_by_common_time_write().upsert(U64BE(5), 5)?;
+
+        /*let received = db_reader.get_txs_history_bc_received(
+            PageInfo {
+                order: true,
+                limit_opt: None,
+                pos: Some(TxBcCursor {
+                    tx_hash: Hash([1; 32]),
+                    block_number: BlockNumber(1),
+                }),
+            },
+            s1_hash,
+        )?;
+        assert_eq!(
+            received.data
+                .into_iter()
+                .map(|tx_db| tx_db.tx.get_hash())
+                .collect::<Vec<_>>(),
+            vec![Hash([2; 32]), Hash([3; 32]), Hash([4; 32]), Hash([5; 32])],
+        );
+        assert!(!received.has_next_page);
+        assert!(!received.has_previous_page);
+
+        let received = db_reader.get_txs_history_bc_received(
+            PageInfo {
+                order: false,
+                limit_opt: None,
+                pos: Some(TxBcCursor {
+                    tx_hash: Hash([1; 32]),
+                    block_number: BlockNumber(1),
+                }),
+            },
+            s1_hash,
+        )?;
+        assert_eq!(
+            received.data
+                .into_iter()
+                .map(|tx_db| tx_db.tx.get_hash())
+                .collect::<Vec<_>>(),
+            vec![Hash([0; 32])],
+        );
+        assert!(!received.has_next_page);
+        assert!(!received.has_previous_page);*/
+
+        let received = db_reader.get_txs_history_bc_received(
+            None,
+            PageInfo {
+                order: false,
+                limit_opt: None,
+                pos: Some(TxBcCursor {
+                    tx_hash: Hash([5; 32]),
+                    block_number: BlockNumber(2),
+                }),
+            },
+            s1_hash,
+            None,
+        )?;
+        assert_eq!(
+            received
+                .data
+                .into_iter()
+                .map(|tx_db| tx_db.tx.get_hash())
+                .collect::<Vec<_>>(),
+            vec![
+                Hash([4; 32]),
+                Hash([3; 32]),
+                Hash([2; 32]),
+                Hash([1; 32]),
+                Hash([0; 32]),
+            ],
+        );
+        assert!(!received.has_next_page);
+        assert!(!received.has_previous_page);
+
+        let received = db_reader.get_txs_history_bc_received(
+            Some(2),
+            PageInfo {
+                order: true,
+                limit_opt: None,
+                pos: None,
+            },
+            s1_hash,
+            Some(5),
+        )?;
+        assert_eq!(
+            received
+                .data
+                .into_iter()
+                .map(|tx_db| tx_db.tx.get_hash())
+                .collect::<Vec<_>>(),
+            vec![Hash([4; 32]), Hash([5; 32]), Hash([6; 32]), Hash([7; 32])],
+        );
+        assert!(!received.has_next_page);
+        assert!(!received.has_previous_page);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_get_txs_history_bc_sent() -> KvResult<()> {
+        let gva_db = duniter_gva_db::GvaV1Db::<Mem>::open(MemConf::default())?;
+        let db_reader = create_dbs_reader(unsafe { std::mem::transmute(&gva_db.get_ro_handler()) });
+
+        let s1 = WalletScriptV10::single_sig(PublicKey::default());
+        let s1_hash = Hash::compute(&s1.to_string().as_bytes());
+
+        gva_db.txs_write().upsert(
+            HashKeyV2(Hash::default()),
+            gen_tx(Hash::default(), BlockNumber(1)),
+        )?;
+        gva_db.txs_write().upsert(
+            HashKeyV2(Hash([1; 32])),
+            gen_tx(Hash([1; 32]), BlockNumber(1)),
+        )?;
+        gva_db.txs_write().upsert(
+            HashKeyV2(Hash([2; 32])),
+            gen_tx(Hash([2; 32]), BlockNumber(1)),
+        )?;
+        gva_db.txs_write().upsert(
+            HashKeyV2(Hash([3; 32])),
+            gen_tx(Hash([3; 32]), BlockNumber(1)),
+        )?;
+        gva_db.txs_by_issuer_write().upsert(
+            WalletHashWithBnV1Db::new(s1_hash, BlockNumber(1)),
+            btreeset![Hash::default(), Hash([1; 32]), Hash([2; 32]), Hash([3; 32])],
+        )?;
+        gva_db.txs_write().upsert(
+            HashKeyV2(Hash([4; 32])),
+            gen_tx(Hash([4; 32]), BlockNumber(2)),
+        )?;
+        gva_db.txs_write().upsert(
+            HashKeyV2(Hash([5; 32])),
+            gen_tx(Hash([5; 32]), BlockNumber(2)),
+        )?;
+        gva_db.txs_by_issuer_write().upsert(
+            WalletHashWithBnV1Db::new(s1_hash, BlockNumber(2)),
+            btreeset![Hash([4; 32]), Hash([5; 32])],
+        )?;
+
+        let sent = db_reader.get_txs_history_bc_sent(
+            None,
+            PageInfo {
+                order: true,
+                limit_opt: None,
+                pos: Some(TxBcCursor {
+                    tx_hash: Hash([1; 32]),
+                    block_number: BlockNumber(1),
+                }),
+            },
+            s1_hash,
+            None,
+        )?;
+        assert_eq!(
+            sent.data
+                .into_iter()
+                .map(|tx_db| tx_db.tx.get_hash())
+                .collect::<Vec<_>>(),
+            vec![Hash([2; 32]), Hash([3; 32]), Hash([4; 32]), Hash([5; 32])],
+        );
+        assert!(!sent.has_next_page);
+        assert!(!sent.has_previous_page);
+
+        let sent = db_reader.get_txs_history_bc_sent(
+            None,
+            PageInfo {
+                order: false,
+                limit_opt: None,
+                pos: Some(TxBcCursor {
+                    tx_hash: Hash([1; 32]),
+                    block_number: BlockNumber(1),
+                }),
+            },
+            s1_hash,
+            None,
+        )?;
+        assert_eq!(
+            sent.data
+                .into_iter()
+                .map(|tx_db| tx_db.tx.get_hash())
+                .collect::<Vec<_>>(),
+            vec![Hash([0; 32])],
+        );
+        assert!(!sent.has_next_page);
+        assert!(!sent.has_previous_page);
+
+        let sent = db_reader.get_txs_history_bc_sent(
+            None,
+            PageInfo {
+                order: false,
+                limit_opt: None,
+                pos: Some(TxBcCursor {
+                    tx_hash: Hash([5; 32]),
+                    block_number: BlockNumber(2),
+                }),
+            },
+            s1_hash,
+            None,
+        )?;
+        assert_eq!(
+            sent.data
+                .into_iter()
+                .map(|tx_db| tx_db.tx.get_hash())
+                .collect::<Vec<_>>(),
+            vec![
+                Hash([4; 32]),
+                Hash([3; 32]),
+                Hash([2; 32]),
+                Hash([1; 32]),
+                Hash([0; 32]),
+            ],
+        );
+        assert!(!sent.has_next_page);
+        assert!(!sent.has_previous_page);
+
+        Ok(())
+    }
+}
diff --git a/dbs-reader/src/uds_of_pubkey.rs b/dbs-reader/src/uds_of_pubkey.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e92d3411dab1f5be8bf19606ae0136eccb87996b
--- /dev/null
+++ b/dbs-reader/src/uds_of_pubkey.rs
@@ -0,0 +1,829 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+use duniter_core::dbs::smallvec::SmallVec;
+use duniter_core::dbs::{
+    databases::bc_v2::{UdsEvent, UdsRevalEvent},
+    UdIdV2,
+};
+
+#[derive(Debug, Default)]
+pub struct UdsWithSum {
+    pub uds: Vec<(BlockNumber, SourceAmount)>,
+    pub sum: SourceAmount,
+}
+
+impl DbsReaderImpl {
+    pub(super) fn all_uds_of_pubkey_(
+        &self,
+        bc_db: &BcV2DbRo<FileBackend>,
+        pubkey: PublicKey,
+        page_info: PageInfo<BlockNumber>,
+    ) -> KvResult<PagedData<UdsWithSum>> {
+        (
+            bc_db.uds_reval(),
+            self.0.blocks_with_ud(),
+            self.0.gva_identities(),
+        )
+            .read(|(uds_reval, blocks_with_ud, gva_identities)| {
+                if let Some(gva_idty) = gva_identities.get(&PubKeyKeyV2(pubkey))? {
+                    match page_info.pos {
+                        None => {
+                            if page_info.order {
+                                blocks_with_ud.iter(.., move |it| {
+                                    all_uds_of_pubkey_inner::<FileBackend, _>(
+                                        gva_idty,
+                                        page_info,
+                                        it.keys().map_ok(|bn| BlockNumber(bn.0)),
+                                        uds_reval,
+                                        None,
+                                    )
+                                })
+                            } else {
+                                let last_ud_opt =
+                                    blocks_with_ud.iter_rev(.., |it| it.keys().next_res())?;
+                                blocks_with_ud.iter_rev(.., move |it| {
+                                    all_uds_of_pubkey_inner::<FileBackend, _>(
+                                        gva_idty,
+                                        page_info,
+                                        it.keys().map_ok(|bn| BlockNumber(bn.0)),
+                                        uds_reval,
+                                        last_ud_opt.map(|bn| BlockNumber(bn.0)),
+                                    )
+                                })
+                            }
+                        }
+                        Some(pos) => {
+                            if page_info.order {
+                                blocks_with_ud.iter(U32BE(pos.0).., move |it| {
+                                    all_uds_of_pubkey_inner::<FileBackend, _>(
+                                        gva_idty,
+                                        page_info,
+                                        it.keys().map_ok(|bn| BlockNumber(bn.0)),
+                                        uds_reval,
+                                        None,
+                                    )
+                                })
+                            } else {
+                                let last_ud_opt =
+                                    blocks_with_ud.iter_rev(.., |it| it.keys().next_res())?;
+                                blocks_with_ud.iter_rev(..=U32BE(pos.0), move |it| {
+                                    all_uds_of_pubkey_inner::<FileBackend, _>(
+                                        gva_idty,
+                                        page_info,
+                                        it.keys().map_ok(|bn| BlockNumber(bn.0)),
+                                        uds_reval,
+                                        last_ud_opt.map(|bn| BlockNumber(bn.0)),
+                                    )
+                                })
+                            }
+                        }
+                    }
+                } else {
+                    Ok(PagedData::empty())
+                }
+            })
+    }
+
+    pub(super) fn unspent_uds_of_pubkey_(
+        &self,
+        bc_db: &BcV2DbRo<FileBackend>,
+        pubkey: PublicKey,
+        page_info: PageInfo<BlockNumber>,
+        bn_to_exclude_opt: Option<&BTreeSet<BlockNumber>>,
+        amount_target_opt: Option<SourceAmount>,
+    ) -> KvResult<PagedData<UdsWithSum>> {
+        (bc_db.uds(), bc_db.uds_reval()).read(|(uds, uds_reval)| {
+            let (first_ud_opt, last_ud_opt) = if page_info.not_all() {
+                get_first_and_last_unspent_ud(&uds, pubkey, bn_to_exclude_opt)?
+            } else {
+                (None, None)
+            };
+            let mut blocks_numbers = if let Some(pos) = page_info.pos {
+                if page_info.order {
+                    uds.iter(
+                        UdIdV2(pubkey, pos)..UdIdV2(pubkey, BlockNumber(u32::MAX)),
+                        |it| {
+                            let it = it.keys().map_ok(|UdIdV2(_p, bn)| bn);
+                            if let Some(bn_to_exclude) = bn_to_exclude_opt {
+                                it.filter_ok(|bn| !bn_to_exclude.contains(&bn))
+                                    .collect::<KvResult<Vec<_>>>()
+                            } else {
+                                it.collect::<KvResult<Vec<_>>>()
+                            }
+                        },
+                    )?
+                } else {
+                    uds.iter_rev(UdIdV2(pubkey, BlockNumber(0))..=UdIdV2(pubkey, pos), |it| {
+                        let it = it.keys().map_ok(|UdIdV2(_p, bn)| bn);
+                        if let Some(bn_to_exclude) = bn_to_exclude_opt {
+                            it.filter_ok(|bn| !bn_to_exclude.contains(&bn))
+                                .collect::<KvResult<Vec<_>>>()
+                        } else {
+                            it.collect::<KvResult<Vec<_>>>()
+                        }
+                    })?
+                }
+            } else if page_info.order {
+                uds.iter(
+                    UdIdV2(pubkey, BlockNumber(0))..UdIdV2(pubkey, BlockNumber(u32::MAX)),
+                    |it| {
+                        let it = it.keys().map_ok(|UdIdV2(_p, bn)| bn);
+                        if let Some(bn_to_exclude) = bn_to_exclude_opt {
+                            it.filter_ok(|bn| !bn_to_exclude.contains(&bn))
+                                .collect::<KvResult<Vec<_>>>()
+                        } else {
+                            it.collect::<KvResult<Vec<_>>>()
+                        }
+                    },
+                )?
+            } else {
+                uds.iter_rev(
+                    UdIdV2(pubkey, BlockNumber(0))..UdIdV2(pubkey, BlockNumber(u32::MAX)),
+                    |it| {
+                        let it = it.keys().map_ok(|UdIdV2(_p, bn)| bn);
+                        if let Some(bn_to_exclude) = bn_to_exclude_opt {
+                            it.filter_ok(|bn| !bn_to_exclude.contains(&bn))
+                                .collect::<KvResult<Vec<_>>>()
+                        } else {
+                            it.collect::<KvResult<Vec<_>>>()
+                        }
+                    },
+                )?
+            };
+
+            if blocks_numbers.is_empty() {
+                Ok(PagedData::empty())
+            } else {
+                if let Some(limit) = page_info.limit_opt {
+                    blocks_numbers.truncate(limit.get());
+                }
+                let first_block_number = if page_info.order {
+                    blocks_numbers[0]
+                } else {
+                    blocks_numbers[blocks_numbers.len() - 1]
+                };
+                let first_reval = uds_reval
+                    .iter_rev(..=U32BE(first_block_number.0), |it| it.keys().next_res())?
+                    .expect("corrupted db");
+                let blocks_numbers_len = blocks_numbers.len();
+                let blocks_numbers = blocks_numbers.into_iter();
+                let uds_with_sum = if page_info.order {
+                    collect_uds(
+                        blocks_numbers,
+                        blocks_numbers_len,
+                        first_reval,
+                        uds_reval,
+                        amount_target_opt,
+                    )?
+                } else {
+                    collect_uds(
+                        blocks_numbers.rev(),
+                        blocks_numbers_len,
+                        first_reval,
+                        uds_reval,
+                        amount_target_opt,
+                    )?
+                };
+                Ok(PagedData {
+                    has_previous_page: has_previous_page(
+                        uds_with_sum.uds.iter().map(|(bn, _sa)| bn.into()),
+                        first_ud_opt,
+                        page_info,
+                        true,
+                    ),
+                    has_next_page: has_next_page(
+                        uds_with_sum.uds.iter().map(|(bn, _sa)| bn.into()),
+                        last_ud_opt,
+                        page_info,
+                        true,
+                    ),
+                    data: uds_with_sum,
+                })
+            }
+        })
+    }
+}
+
+fn all_uds_of_pubkey_inner<B, I>(
+    gva_idty: GvaIdtyDbV1,
+    page_info: PageInfo<BlockNumber>,
+    blocks_with_ud: I,
+    uds_reval: TxColRo<B::Col, UdsRevalEvent>,
+    last_ud_opt: Option<BlockNumber>,
+) -> KvResult<PagedData<UdsWithSum>>
+where
+    B: Backend,
+    I: Iterator<Item = KvResult<BlockNumber>>,
+{
+    let first_ud = gva_idty.first_ud;
+    let mut blocks_numbers = filter_blocks_numbers(gva_idty, page_info, blocks_with_ud)?;
+
+    if blocks_numbers.is_empty() {
+        return Ok(PagedData::empty());
+    }
+
+    let not_reach_end = if page_info.order {
+        if let Some(limit) = page_info.limit_opt {
+            if blocks_numbers.len() <= limit.get() {
+                false
+            } else {
+                blocks_numbers.pop();
+                true
+            }
+        } else {
+            false
+        }
+    } else if let Some(last_ud) = last_ud_opt {
+        blocks_numbers[0] != last_ud
+    } else {
+        false
+    };
+    let blocks_numbers_len = blocks_numbers.len();
+
+    let first_block_number = if page_info.order {
+        blocks_numbers[0]
+    } else {
+        blocks_numbers[blocks_numbers_len - 1]
+    };
+
+    let first_reval = uds_reval
+        .iter_rev(..=U32BE(first_block_number.0), |it| it.keys().next_res())?
+        .expect("corrupted db");
+
+    let uds_with_sum = if page_info.order {
+        collect_uds(
+            blocks_numbers.into_iter(),
+            blocks_numbers_len,
+            first_reval,
+            uds_reval,
+            None,
+        )?
+    } else {
+        collect_uds(
+            blocks_numbers.into_iter().rev(),
+            blocks_numbers_len,
+            first_reval,
+            uds_reval,
+            None,
+        )?
+    };
+
+    Ok(PagedData {
+        has_previous_page: has_previous_page(
+            uds_with_sum.uds.iter().map(|(bn, _sa)| bn.into()),
+            first_ud,
+            page_info,
+            true,
+        ),
+        has_next_page: not_reach_end,
+        data: uds_with_sum,
+    })
+}
+
+fn filter_blocks_numbers<I: Iterator<Item = KvResult<BlockNumber>>>(
+    gva_idty: GvaIdtyDbV1,
+    page_info: PageInfo<BlockNumber>,
+    blocks_with_ud: I,
+) -> KvResult<Vec<BlockNumber>> {
+    let mut is_member_changes = SmallVec::<[BlockNumber; 4]>::new();
+    for (join, leave) in gva_idty.joins.iter().zip(gva_idty.leaves.iter()) {
+        is_member_changes.push(*join);
+        is_member_changes.push(*leave);
+    }
+    if gva_idty.joins.len() > gva_idty.leaves.len() {
+        is_member_changes.push(*gva_idty.joins.last().unwrap_or_else(|| unreachable!()));
+    }
+
+    if page_info.order {
+        let mut i = 0;
+        let mut is_member = false;
+        if let Some(limit) = page_info.limit_opt {
+            blocks_with_ud
+                .filter_ok(|bn| {
+                    while i < is_member_changes.len() && *bn >= is_member_changes[i] {
+                        is_member = !is_member;
+                        i += 1;
+                    }
+                    is_member
+                })
+                .take(limit.get() + 1)
+                .collect::<KvResult<Vec<_>>>()
+        } else {
+            blocks_with_ud
+                .filter_ok(|bn| {
+                    while i < is_member_changes.len() && *bn >= is_member_changes[i] {
+                        is_member = !is_member;
+                        i += 1;
+                    }
+                    is_member
+                })
+                .collect::<KvResult<Vec<_>>>()
+        }
+    } else {
+        let is_member_changes: SmallVec<[BlockNumber; 4]> =
+            is_member_changes.into_iter().rev().collect();
+        let mut i = 0;
+        let mut is_member = gva_idty.is_member;
+        if let Some(limit) = page_info.limit_opt {
+            blocks_with_ud
+                .filter_ok(|bn| {
+                    /*println!(
+                        "TMP (bn, is_member_changes[{}])=({}, {})",
+                        i, bn, is_member_changes[i]
+                    );*/
+                    while i < is_member_changes.len() && *bn < is_member_changes[i] {
+                        is_member = !is_member;
+                        i += 1;
+                    }
+                    is_member
+                })
+                .take(limit.get())
+                .collect::<KvResult<Vec<_>>>()
+        } else {
+            blocks_with_ud
+                .filter_ok(|bn| {
+                    while i < is_member_changes.len() && *bn < is_member_changes[i] {
+                        is_member = !is_member;
+                        i += 1;
+                    }
+                    is_member
+                })
+                .collect::<KvResult<Vec<_>>>()
+        }
+    }
+}
+
+fn get_first_and_last_unspent_ud<BC: BackendCol>(
+    uds: &TxColRo<BC, UdsEvent>,
+    pubkey: PublicKey,
+    bn_to_exclude_opt: Option<&BTreeSet<BlockNumber>>,
+) -> KvResult<(Option<BlockNumber>, Option<BlockNumber>)> {
+    if let Some(bn_to_exclude) = bn_to_exclude_opt {
+        Ok((
+            uds.iter(
+                UdIdV2(pubkey, BlockNumber(0))..UdIdV2(pubkey, BlockNumber(u32::MAX)),
+                |it| {
+                    it.keys()
+                        .filter_map_ok(|UdIdV2(_p, bn)| {
+                            if !bn_to_exclude.contains(&bn) {
+                                Some(bn)
+                            } else {
+                                None
+                            }
+                        })
+                        .next_res()
+                },
+            )?,
+            uds.iter_rev(
+                UdIdV2(pubkey, BlockNumber(0))..UdIdV2(pubkey, BlockNumber(u32::MAX)),
+                |it| {
+                    it.keys()
+                        .filter_map_ok(|UdIdV2(_p, bn)| {
+                            if !bn_to_exclude.contains(&bn) {
+                                Some(bn)
+                            } else {
+                                None
+                            }
+                        })
+                        .next_res()
+                },
+            )?,
+        ))
+    } else {
+        Ok((
+            uds.iter(
+                UdIdV2(pubkey, BlockNumber(0))..UdIdV2(pubkey, BlockNumber(u32::MAX)),
+                |it| it.keys().map_ok(|UdIdV2(_p, bn)| bn).next_res(),
+            )?,
+            uds.iter_rev(
+                UdIdV2(pubkey, BlockNumber(0))..UdIdV2(pubkey, BlockNumber(u32::MAX)),
+                |it| it.keys().map_ok(|UdIdV2(_p, bn)| bn).next_res(),
+            )?,
+        ))
+    }
+}
+
+macro_rules! collect_one_ud {
+    ($block_number:ident, $current_ud:ident, $uds:ident, $sum:ident, $amount_target_opt:ident) => {
+        $uds.push(($block_number, $current_ud));
+        $sum = $sum + $current_ud;
+        if let Some(amount_target) = $amount_target_opt {
+            if $sum >= amount_target {
+                return Ok(UdsWithSum { $uds, $sum });
+            }
+        }
+    };
+}
+
+fn collect_uds<BC: BackendCol, I: Iterator<Item = BlockNumber>>(
+    mut blocks_numbers: I,
+    blocks_numbers_len: usize,
+    first_reval: U32BE,
+    uds_reval: TxColRo<BC, UdsRevalEvent>,
+    amount_opt: Option<SourceAmount>,
+) -> KvResult<UdsWithSum> {
+    let uds_revals = uds_reval.iter(first_reval.., |it| it.collect::<KvResult<Vec<_>>>())?;
+
+    if uds_revals.is_empty() {
+        Ok(UdsWithSum::default())
+    } else {
+        let mut current_ud = (uds_revals[0].1).0;
+        let mut uds = Vec::with_capacity(blocks_numbers_len);
+        let mut sum = SourceAmount::ZERO;
+
+        // Uds before last reval
+        for (block_reval, amount_reval) in &uds_revals[1..] {
+            'blocks_numbers: while let Some(block_number) = blocks_numbers.next() {
+                if block_number.0 >= block_reval.0 {
+                    current_ud = amount_reval.0;
+                    collect_one_ud!(block_number, current_ud, uds, sum, amount_opt);
+                    break 'blocks_numbers;
+                } else {
+                    collect_one_ud!(block_number, current_ud, uds, sum, amount_opt);
+                }
+            }
+        }
+
+        // Uds after last reval
+        for block_number in blocks_numbers {
+            collect_one_ud!(block_number, current_ud, uds, sum, amount_opt);
+        }
+
+        Ok(UdsWithSum { uds, sum })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+    use duniter_core::dbs::smallvec::smallvec as svec;
+    use duniter_core::dbs::{databases::bc_v2::BcV2DbWritable, SourceAmountValV2, UdIdV2};
+    use duniter_gva_db::GvaV1DbWritable;
+
+    #[test]
+    fn test_filter_blocks_numbers() -> KvResult<()> {
+        let idty = GvaIdtyDbV1 {
+            is_member: true,
+            joins: svec![BlockNumber(26), BlockNumber(51)],
+            leaves: [BlockNumber(32)].iter().copied().collect(),
+            first_ud: Some(BlockNumber(29)),
+        };
+        let blocks_with_ud = vec![
+            BlockNumber(3),
+            BlockNumber(9),
+            BlockNumber(15),
+            BlockNumber(22),
+            BlockNumber(29),
+            BlockNumber(35),
+            BlockNumber(42),
+            BlockNumber(48),
+            BlockNumber(54),
+            BlockNumber(60),
+        ];
+
+        assert_eq!(
+            filter_blocks_numbers(
+                idty.clone(),
+                PageInfo {
+                    pos: None,
+                    order: true,
+                    limit_opt: NonZeroUsize::new(1),
+                },
+                blocks_with_ud.iter().copied().map(Ok),
+            )?,
+            vec![BlockNumber(29), BlockNumber(54)]
+        );
+        assert_eq!(
+            filter_blocks_numbers(
+                idty,
+                PageInfo {
+                    pos: None,
+                    order: false,
+                    limit_opt: None,
+                },
+                blocks_with_ud.into_iter().rev().map(Ok),
+            )?,
+            vec![BlockNumber(60), BlockNumber(54), BlockNumber(29)]
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_all_uds_of_pubkey() -> KvResult<()> {
+        let pk = PublicKey::default();
+        let idty = GvaIdtyDbV1 {
+            is_member: true,
+            joins: svec![BlockNumber(26), BlockNumber(51)],
+            leaves: [BlockNumber(32)].iter().copied().collect(),
+            first_ud: Some(BlockNumber(29)),
+        };
+
+        let bc_db = duniter_core::dbs::databases::bc_v2::BcV2Db::<Mem>::open(MemConf::default())?;
+        let bc_db_ro = bc_db.get_ro_handler();
+        let gva_db = duniter_gva_db::GvaV1Db::<Mem>::open(MemConf::default())?;
+        let db_reader = create_dbs_reader(unsafe { std::mem::transmute(&gva_db.get_ro_handler()) });
+        bc_db
+            .uds_reval_write()
+            .upsert(U32BE(0), SourceAmountValV2(SourceAmount::with_base0(10)))?;
+        bc_db
+            .uds_reval_write()
+            .upsert(U32BE(40), SourceAmountValV2(SourceAmount::with_base0(12)))?;
+        gva_db
+            .gva_identities_write()
+            .upsert(PubKeyKeyV2(pk), idty)?;
+        gva_db.blocks_with_ud_write().upsert(U32BE(22), ())?;
+        gva_db.blocks_with_ud_write().upsert(U32BE(29), ())?;
+        gva_db.blocks_with_ud_write().upsert(U32BE(35), ())?;
+        gva_db.blocks_with_ud_write().upsert(U32BE(42), ())?;
+        gva_db.blocks_with_ud_write().upsert(U32BE(48), ())?;
+        gva_db.blocks_with_ud_write().upsert(U32BE(54), ())?;
+        gva_db.blocks_with_ud_write().upsert(U32BE(60), ())?;
+
+        // Get all uds
+        let PagedData {
+            data: UdsWithSum { uds, sum },
+            has_previous_page,
+            has_next_page,
+        } = db_reader.all_uds_of_pubkey(&bc_db_ro, pk, PageInfo::default())?;
+        assert_eq!(
+            uds,
+            vec![
+                (BlockNumber(29), SourceAmount::with_base0(10)),
+                (BlockNumber(54), SourceAmount::with_base0(12)),
+                (BlockNumber(60), SourceAmount::with_base0(12)),
+            ]
+        );
+        assert_eq!(sum, SourceAmount::with_base0(34));
+        assert!(!has_previous_page);
+        assert!(!has_next_page);
+
+        // Get all uds with limit
+        let PagedData {
+            data: UdsWithSum { uds, sum },
+            has_previous_page,
+            has_next_page,
+        } = db_reader.all_uds_of_pubkey(
+            &bc_db_ro,
+            pk,
+            PageInfo {
+                limit_opt: NonZeroUsize::new(2),
+                ..Default::default()
+            },
+        )?;
+        assert_eq!(
+            uds,
+            vec![
+                (BlockNumber(29), SourceAmount::with_base0(10)),
+                (BlockNumber(54), SourceAmount::with_base0(12)),
+            ]
+        );
+        assert_eq!(sum, SourceAmount::with_base0(22));
+        assert!(!has_previous_page);
+        assert!(has_next_page);
+
+        // Get all uds from particular position
+        let PagedData {
+            data: UdsWithSum { uds, sum },
+            has_previous_page,
+            has_next_page,
+        } = db_reader.all_uds_of_pubkey(
+            &bc_db_ro,
+            pk,
+            PageInfo {
+                pos: Some(BlockNumber(50)),
+                ..Default::default()
+            },
+        )?;
+        assert_eq!(
+            uds,
+            vec![
+                (BlockNumber(54), SourceAmount::with_base0(12)),
+                (BlockNumber(60), SourceAmount::with_base0(12)),
+            ]
+        );
+        assert_eq!(sum, SourceAmount::with_base0(24));
+        assert!(has_previous_page);
+        assert!(!has_next_page);
+
+        // Get all uds on DESC order
+        let PagedData {
+            data: UdsWithSum { uds, sum },
+            has_previous_page,
+            has_next_page,
+        } = db_reader.all_uds_of_pubkey(
+            &bc_db_ro,
+            pk,
+            PageInfo {
+                order: false,
+                ..Default::default()
+            },
+        )?;
+        assert_eq!(
+            uds,
+            vec![
+                (BlockNumber(29), SourceAmount::with_base0(10)),
+                (BlockNumber(54), SourceAmount::with_base0(12)),
+                (BlockNumber(60), SourceAmount::with_base0(12)),
+            ]
+        );
+        assert_eq!(sum, SourceAmount::with_base0(34));
+        assert!(!has_previous_page);
+        assert!(!has_next_page);
+
+        // Get all uds on DESC order with limit
+        let PagedData {
+            data: UdsWithSum { uds, sum },
+            has_previous_page,
+            has_next_page,
+        } = db_reader.all_uds_of_pubkey(
+            &bc_db_ro,
+            pk,
+            PageInfo {
+                order: false,
+                limit_opt: NonZeroUsize::new(2),
+                ..Default::default()
+            },
+        )?;
+        assert_eq!(
+            uds,
+            vec![
+                (BlockNumber(54), SourceAmount::with_base0(12)),
+                (BlockNumber(60), SourceAmount::with_base0(12)),
+            ]
+        );
+        assert_eq!(sum, SourceAmount::with_base0(24));
+        assert!(has_previous_page);
+        assert!(!has_next_page);
+
+        // Get all uds on DESC order from particular position
+        let PagedData {
+            data: UdsWithSum { uds, sum },
+            has_previous_page,
+            has_next_page,
+        } = db_reader.all_uds_of_pubkey(
+            &bc_db_ro,
+            pk,
+            PageInfo {
+                pos: Some(BlockNumber(55)),
+                order: false,
+                ..Default::default()
+            },
+        )?;
+        assert_eq!(
+            uds,
+            vec![
+                (BlockNumber(29), SourceAmount::with_base0(10)),
+                (BlockNumber(54), SourceAmount::with_base0(12)),
+            ]
+        );
+        assert_eq!(sum, SourceAmount::with_base0(22));
+        assert!(!has_previous_page);
+        assert!(has_next_page);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_unspent_uds_of_pubkey() -> KvResult<()> {
+        let pk = PublicKey::default();
+        let bc_db = duniter_core::dbs::databases::bc_v2::BcV2Db::<Mem>::open(MemConf::default())?;
+        let bc_db_ro = bc_db.get_ro_handler();
+        let dbs_reader = DbsReaderImpl::mem();
+
+        bc_db
+            .uds_reval_write()
+            .upsert(U32BE(0), SourceAmountValV2(SourceAmount::with_base0(10)))?;
+        bc_db
+            .uds_reval_write()
+            .upsert(U32BE(40), SourceAmountValV2(SourceAmount::with_base0(12)))?;
+
+        bc_db.uds_write().upsert(UdIdV2(pk, BlockNumber(0)), ())?;
+        bc_db.uds_write().upsert(UdIdV2(pk, BlockNumber(10)), ())?;
+        bc_db.uds_write().upsert(UdIdV2(pk, BlockNumber(20)), ())?;
+        bc_db.uds_write().upsert(UdIdV2(pk, BlockNumber(30)), ())?;
+        bc_db.uds_write().upsert(UdIdV2(pk, BlockNumber(40)), ())?;
+        bc_db.uds_write().upsert(UdIdV2(pk, BlockNumber(50)), ())?;
+        bc_db.uds_write().upsert(UdIdV2(pk, BlockNumber(60)), ())?;
+
+        // Get unspent uds
+        let PagedData {
+            data: UdsWithSum { uds, sum },
+            has_previous_page,
+            has_next_page,
+        } = dbs_reader.unspent_uds_of_pubkey(&bc_db_ro, pk, PageInfo::default(), None, None)?;
+        assert_eq!(uds.len(), 7);
+        assert_eq!(
+            uds.first(),
+            Some(&(BlockNumber(0), SourceAmount::with_base0(10)))
+        );
+        assert_eq!(
+            uds.last(),
+            Some(&(BlockNumber(60), SourceAmount::with_base0(12)))
+        );
+        assert_eq!(sum, SourceAmount::with_base0(76));
+        assert!(!has_previous_page);
+        assert!(!has_next_page);
+
+        // Get unspent uds from particular position
+        let PagedData {
+            data: UdsWithSum { uds, sum },
+            has_previous_page,
+            has_next_page,
+        } = dbs_reader.unspent_uds_of_pubkey(
+            &bc_db_ro,
+            pk,
+            PageInfo {
+                pos: Some(BlockNumber(30)),
+                ..Default::default()
+            },
+            None,
+            None,
+        )?;
+        assert_eq!(uds.len(), 4);
+        assert_eq!(
+            uds.first(),
+            Some(&(BlockNumber(30), SourceAmount::with_base0(10)))
+        );
+        assert_eq!(
+            uds.last(),
+            Some(&(BlockNumber(60), SourceAmount::with_base0(12)))
+        );
+        assert_eq!(sum, SourceAmount::with_base0(46));
+        assert!(has_previous_page);
+        assert!(!has_next_page);
+
+        // Get unspent uds in order DESC
+        let PagedData {
+            data: UdsWithSum { uds, sum },
+            has_previous_page,
+            has_next_page,
+        } = dbs_reader.unspent_uds_of_pubkey(
+            &bc_db_ro,
+            pk,
+            PageInfo {
+                order: false,
+                ..Default::default()
+            },
+            None,
+            None,
+        )?;
+        assert_eq!(uds.len(), 7);
+        assert_eq!(
+            uds.first(),
+            Some(&(BlockNumber(0), SourceAmount::with_base0(10)))
+        );
+        assert_eq!(
+            uds.last(),
+            Some(&(BlockNumber(60), SourceAmount::with_base0(12)))
+        );
+        assert_eq!(sum, SourceAmount::with_base0(76));
+        assert!(!has_previous_page);
+        assert!(!has_next_page);
+
+        // Get unspent uds in order DESC from particular position
+        let PagedData {
+            data: UdsWithSum { uds, sum },
+            has_previous_page,
+            has_next_page,
+        } = dbs_reader.unspent_uds_of_pubkey(
+            &bc_db_ro,
+            pk,
+            PageInfo {
+                pos: Some(BlockNumber(40)),
+                order: false,
+                ..Default::default()
+            },
+            None,
+            None,
+        )?;
+        assert_eq!(uds.len(), 5);
+        assert_eq!(
+            uds.first(),
+            Some(&(BlockNumber(0), SourceAmount::with_base0(10)))
+        );
+        assert_eq!(
+            uds.last(),
+            Some(&(BlockNumber(40), SourceAmount::with_base0(12)))
+        );
+        assert_eq!(sum, SourceAmount::with_base0(52));
+        assert!(!has_previous_page);
+        assert!(has_next_page);
+
+        Ok(())
+    }
+}
diff --git a/dbs-reader/src/utxos.rs b/dbs-reader/src/utxos.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5c9dc75cf9de5ae0fc0c58ef1151b84bf4d6487b
--- /dev/null
+++ b/dbs-reader/src/utxos.rs
@@ -0,0 +1,500 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use dubp::documents::dubp_wallet::prelude::*;
+use duniter_core::dbs::SourceAmountValV2;
+
+use crate::*;
+
+#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
+pub struct UtxoCursor {
+    pub block_number: BlockNumber,
+    pub tx_hash: Hash,
+    pub output_index: u8,
+}
+impl std::fmt::Display for UtxoCursor {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "{}:{}:{}",
+            self.block_number, self.tx_hash, self.output_index,
+        )
+    }
+}
+
+impl FromStr for UtxoCursor {
+    type Err = WrongCursor;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let mut s = s.split(':');
+        let block_number = s
+            .next()
+            .ok_or(WrongCursor)?
+            .parse()
+            .map_err(|_| WrongCursor)?;
+        let tx_hash = Hash::from_hex(s.next().ok_or(WrongCursor)?).map_err(|_| WrongCursor)?;
+        let output_index = s
+            .next()
+            .ok_or(WrongCursor)?
+            .parse()
+            .map_err(|_| WrongCursor)?;
+        Ok(Self {
+            block_number,
+            tx_hash,
+            output_index,
+        })
+    }
+}
+
+#[derive(Debug, Default)]
+pub struct UtxosWithSum {
+    pub utxos: Vec<(UtxoCursor, SourceAmount)>,
+    pub sum: SourceAmount,
+}
+
+impl DbsReaderImpl {
+    pub(super) fn find_script_utxos_<TxsMpDb: 'static + TxsMpV2DbReadable>(
+        &self,
+        txs_mp_db_ro: &TxsMpDb,
+        amount_target_opt: Option<SourceAmount>,
+        page_info: PageInfo<UtxoCursor>,
+        script: &WalletScriptV10,
+    ) -> anyhow::Result<PagedData<UtxosWithSum>> {
+        let mempool_filter = |k_res: KvResult<GvaUtxoIdDbV1>| match k_res {
+            Ok(gva_utxo_id) => {
+                match txs_mp_db_ro.utxos_ids().contains_key(&UtxoIdDbV2(
+                    gva_utxo_id.get_tx_hash(),
+                    gva_utxo_id.get_output_index() as u32,
+                )) {
+                    Ok(false) => Some(Ok(gva_utxo_id)),
+                    Ok(true) => None,
+                    Err(e) => Some(Err(e)),
+                }
+            }
+            Err(e) => Some(Err(e)),
+        };
+
+        let script_hash = Hash::compute(script.to_string().as_bytes());
+        let (mut k_min, mut k_max) = GvaUtxoIdDbV1::script_interval(script_hash);
+        let first_cursor_opt = if page_info.not_all() {
+            self.0
+                .gva_utxos()
+                .iter(k_min..k_max, |it| {
+                    it.keys().filter_map(mempool_filter).next_res()
+                })?
+                .map(|gva_utxo_id| UtxoCursor {
+                    block_number: BlockNumber(gva_utxo_id.get_block_number()),
+                    tx_hash: gva_utxo_id.get_tx_hash(),
+                    output_index: gva_utxo_id.get_output_index(),
+                })
+        } else {
+            None
+        };
+        let last_cursor_opt = if page_info.not_all() {
+            self.0
+                .gva_utxos()
+                .iter_rev(k_min..k_max, |it| {
+                    it.keys().filter_map(mempool_filter).next_res()
+                })?
+                .map(|gva_utxo_id| UtxoCursor {
+                    block_number: BlockNumber(gva_utxo_id.get_block_number()),
+                    tx_hash: gva_utxo_id.get_tx_hash(),
+                    output_index: gva_utxo_id.get_output_index(),
+                })
+        } else {
+            None
+        };
+        if let Some(ref pos) = page_info.pos {
+            if page_info.order {
+                k_min = GvaUtxoIdDbV1::new_(
+                    script_hash,
+                    pos.block_number.0,
+                    pos.tx_hash,
+                    pos.output_index,
+                );
+            } else {
+                k_max = GvaUtxoIdDbV1::new_(
+                    script_hash,
+                    pos.block_number.0,
+                    pos.tx_hash,
+                    pos.output_index,
+                );
+            }
+        }
+        let UtxosWithSum { utxos, mut sum } = if page_info.order {
+            self.0.gva_utxos().iter(k_min..k_max, |it| {
+                find_script_utxos_inner(txs_mp_db_ro, amount_target_opt, page_info, it)
+            })?
+        } else {
+            self.0.gva_utxos().iter_rev(k_min..k_max, |it| {
+                find_script_utxos_inner(txs_mp_db_ro, amount_target_opt, page_info, it)
+            })?
+        };
+
+        if amount_target_opt.is_none() {
+            sum = utxos.iter().map(|(_utxo_id_with_bn, sa)| *sa).sum();
+        }
+
+        let order = page_info.order;
+
+        Ok(PagedData {
+            has_next_page: has_next_page(
+                utxos
+                    .iter()
+                    .map(|(utxo_id_with_bn, _sa)| utxo_id_with_bn.into()),
+                last_cursor_opt,
+                page_info,
+                order,
+            ),
+            has_previous_page: has_previous_page(
+                utxos
+                    .iter()
+                    .map(|(utxo_id_with_bn, _sa)| utxo_id_with_bn.into()),
+                first_cursor_opt,
+                page_info,
+                order,
+            ),
+            data: UtxosWithSum { utxos, sum },
+        })
+    }
+    pub(super) fn first_scripts_utxos_(
+        &self,
+        amount_target_opt: Option<SourceAmount>,
+        first: usize,
+        scripts: &[WalletScriptV10],
+    ) -> anyhow::Result<Vec<ArrayVec<[Utxo; MAX_FIRST_UTXOS]>>> {
+        let iter = scripts.iter().map(|script| {
+            let (k_min, k_max) =
+                GvaUtxoIdDbV1::script_interval(Hash::compute(script.to_string().as_bytes()));
+            self.0.gva_utxos().iter(k_min..k_max, |it| {
+                it.take(first)
+                    .map_ok(|(k, v)| Utxo {
+                        amount: v.0,
+                        tx_hash: k.get_tx_hash(),
+                        output_index: k.get_output_index(),
+                    })
+                    .collect::<KvResult<_>>()
+            })
+        });
+        if let Some(amount_target) = amount_target_opt {
+            let mut sum = SourceAmount::ZERO;
+            Ok(iter
+                .take_while(|utxos_res: &KvResult<ArrayVec<[Utxo; MAX_FIRST_UTXOS]>>| {
+                    if let Ok(utxos) = utxos_res {
+                        sum = sum + utxos.iter().map(|utxo| utxo.amount).sum();
+                        sum <= amount_target
+                    } else {
+                        true
+                    }
+                })
+                .collect::<KvResult<Vec<_>>>()?)
+        } else {
+            Ok(iter.collect::<KvResult<Vec<_>>>()?)
+        }
+    }
+}
+
+fn find_script_utxos_inner<TxsMpDb, I>(
+    txs_mp_db_ro: &TxsMpDb,
+    amount_target_opt: Option<SourceAmount>,
+    page_info: PageInfo<UtxoCursor>,
+    utxos_iter: I,
+) -> KvResult<UtxosWithSum>
+where
+    TxsMpDb: TxsMpV2DbReadable,
+    I: Iterator<Item = KvResult<(GvaUtxoIdDbV1, SourceAmountValV2)>>,
+{
+    let mut sum = SourceAmount::ZERO;
+
+    let it = utxos_iter.filter_map(|entry_res| match entry_res {
+        Ok((gva_utxo_id, SourceAmountValV2(utxo_amount))) => {
+            if utxo_amount.amount() < super::find_inputs::MIN_AMOUNT {
+                None
+            } else {
+                let tx_hash = gva_utxo_id.get_tx_hash();
+                let output_index = gva_utxo_id.get_output_index();
+                match txs_mp_db_ro
+                    .utxos_ids()
+                    .contains_key(&UtxoIdDbV2(tx_hash, output_index as u32))
+                {
+                    Ok(false) => Some(Ok((
+                        UtxoCursor {
+                            tx_hash,
+                            output_index,
+                            block_number: BlockNumber(gva_utxo_id.get_block_number()),
+                        },
+                        utxo_amount,
+                    ))),
+                    Ok(true) => None,
+                    Err(e) => Some(Err(e)),
+                }
+            }
+        }
+        Err(e) => Some(Err(e)),
+    });
+    let utxos = if let Some(limit) = page_info.limit_opt {
+        if let Some(total_target) = amount_target_opt {
+            it.take(limit.get())
+                .take_while(|res| match res {
+                    Ok((_, utxo_amount)) => {
+                        if sum < total_target {
+                            sum = sum + *utxo_amount;
+                            true
+                        } else {
+                            false
+                        }
+                    }
+                    Err(_) => true,
+                })
+                .collect::<KvResult<Vec<_>>>()?
+        } else {
+            it.take(limit.get()).collect::<KvResult<Vec<_>>>()?
+        }
+    } else if let Some(total_target) = amount_target_opt {
+        it.take_while(|res| match res {
+            Ok((_, utxo_amount)) => {
+                if sum < total_target {
+                    sum = sum + *utxo_amount;
+                    true
+                } else {
+                    false
+                }
+            }
+            Err(_) => true,
+        })
+        .collect::<KvResult<Vec<_>>>()?
+    } else {
+        it.collect::<KvResult<Vec<_>>>()?
+    };
+
+    Ok(UtxosWithSum { utxos, sum })
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+    use dubp::crypto::keys::PublicKey as _;
+    use duniter_core::dbs::databases::txs_mp_v2::TxsMpV2DbWritable;
+    use duniter_gva_db::GvaV1DbWritable;
+    use unwrap::unwrap;
+
+    #[test]
+    fn test_first_scripts_utxos() -> anyhow::Result<()> {
+        let script = WalletScriptV10::single_sig(PublicKey::default());
+        let script2 = WalletScriptV10::single_sig(unwrap!(PublicKey::from_base58(
+            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+        )));
+
+        let gva_db = duniter_gva_db::GvaV1Db::<Mem>::open(MemConf::default())?;
+        let db_reader = create_dbs_reader(unsafe { std::mem::transmute(&gva_db.get_ro_handler()) });
+
+        gva_db.gva_utxos_write().upsert(
+            GvaUtxoIdDbV1::new(script.clone(), 0, Hash::default(), 0),
+            SourceAmountValV2(SourceAmount::with_base0(500)),
+        )?;
+        gva_db.gva_utxos_write().upsert(
+            GvaUtxoIdDbV1::new(script.clone(), 1, Hash::default(), 1),
+            SourceAmountValV2(SourceAmount::with_base0(800)),
+        )?;
+        gva_db.gva_utxos_write().upsert(
+            GvaUtxoIdDbV1::new(script.clone(), 2, Hash::default(), 2),
+            SourceAmountValV2(SourceAmount::with_base0(1_200)),
+        )?;
+
+        gva_db.gva_utxos_write().upsert(
+            GvaUtxoIdDbV1::new(script2.clone(), 0, Hash::default(), 0),
+            SourceAmountValV2(SourceAmount::with_base0(400)),
+        )?;
+        gva_db.gva_utxos_write().upsert(
+            GvaUtxoIdDbV1::new(script2.clone(), 1, Hash::default(), 1),
+            SourceAmountValV2(SourceAmount::with_base0(700)),
+        )?;
+        gva_db.gva_utxos_write().upsert(
+            GvaUtxoIdDbV1::new(script2.clone(), 2, Hash::default(), 2),
+            SourceAmountValV2(SourceAmount::with_base0(1_100)),
+        )?;
+
+        assert_eq!(
+            db_reader.first_scripts_utxos(None, 2, &[script, script2])?,
+            vec![
+                [
+                    Utxo {
+                        amount: SourceAmount::with_base0(500),
+                        tx_hash: Hash::default(),
+                        output_index: 0,
+                    },
+                    Utxo {
+                        amount: SourceAmount::with_base0(800),
+                        tx_hash: Hash::default(),
+                        output_index: 1,
+                    },
+                ]
+                .iter()
+                .copied()
+                .collect::<ArrayVec<_>>(),
+                [
+                    Utxo {
+                        amount: SourceAmount::with_base0(400),
+                        tx_hash: Hash::default(),
+                        output_index: 0,
+                    },
+                    Utxo {
+                        amount: SourceAmount::with_base0(700),
+                        tx_hash: Hash::default(),
+                        output_index: 1,
+                    },
+                ]
+                .iter()
+                .copied()
+                .collect::<ArrayVec<_>>()
+            ]
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_find_script_utxos() -> anyhow::Result<()> {
+        let script = WalletScriptV10::single_sig(PublicKey::default());
+
+        let gva_db = duniter_gva_db::GvaV1Db::<Mem>::open(MemConf::default())?;
+        let db_reader = create_dbs_reader(unsafe { std::mem::transmute(&gva_db.get_ro_handler()) });
+        let txs_mp_db =
+            duniter_core::dbs::databases::txs_mp_v2::TxsMpV2Db::<Mem>::open(MemConf::default())?;
+
+        gva_db.gva_utxos_write().upsert(
+            GvaUtxoIdDbV1::new(script.clone(), 0, Hash::default(), 0),
+            SourceAmountValV2(SourceAmount::with_base0(500)),
+        )?;
+        gva_db.gva_utxos_write().upsert(
+            GvaUtxoIdDbV1::new(script.clone(), 0, Hash::default(), 1),
+            SourceAmountValV2(SourceAmount::with_base0(800)),
+        )?;
+        gva_db.gva_utxos_write().upsert(
+            GvaUtxoIdDbV1::new(script.clone(), 0, Hash::default(), 2),
+            SourceAmountValV2(SourceAmount::with_base0(1200)),
+        )?;
+
+        // Find utxos with amount target
+        let PagedData {
+            data: UtxosWithSum { utxos, sum },
+            has_next_page,
+            has_previous_page,
+        } = db_reader.find_script_utxos(
+            &txs_mp_db,
+            Some(SourceAmount::with_base0(550)),
+            PageInfo::default(),
+            &script,
+        )?;
+
+        assert_eq!(
+            utxos,
+            vec![
+                (
+                    UtxoCursor {
+                        block_number: BlockNumber(0),
+                        tx_hash: Hash::default(),
+                        output_index: 0,
+                    },
+                    SourceAmount::with_base0(500)
+                ),
+                (
+                    UtxoCursor {
+                        block_number: BlockNumber(0),
+                        tx_hash: Hash::default(),
+                        output_index: 1,
+                    },
+                    SourceAmount::with_base0(800)
+                ),
+            ]
+        );
+        assert_eq!(sum, SourceAmount::with_base0(1300));
+        assert!(!has_next_page);
+        assert!(!has_previous_page);
+
+        // Find utxos with amount target in DESC order
+        let PagedData {
+            data: UtxosWithSum { utxos, sum },
+            ..
+        } = db_reader.find_script_utxos(
+            &txs_mp_db,
+            Some(SourceAmount::with_base0(550)),
+            PageInfo {
+                order: false,
+                ..Default::default()
+            },
+            &script,
+        )?;
+
+        assert_eq!(
+            utxos,
+            vec![(
+                UtxoCursor {
+                    block_number: BlockNumber(0),
+                    tx_hash: Hash::default(),
+                    output_index: 2,
+                },
+                SourceAmount::with_base0(1200)
+            ),]
+        );
+        assert_eq!(sum, SourceAmount::with_base0(1200));
+        assert!(!has_next_page);
+        assert!(!has_previous_page);
+
+        // Find utxos with limit in DESC order
+        let PagedData {
+            data: UtxosWithSum { utxos, sum },
+            has_previous_page,
+            has_next_page,
+        } = db_reader.find_script_utxos(
+            &txs_mp_db,
+            None,
+            PageInfo {
+                order: false,
+                limit_opt: NonZeroUsize::new(2),
+                ..Default::default()
+            },
+            &script,
+        )?;
+
+        assert_eq!(
+            utxos,
+            vec![
+                (
+                    UtxoCursor {
+                        block_number: BlockNumber(0),
+                        tx_hash: Hash::default(),
+                        output_index: 2,
+                    },
+                    SourceAmount::with_base0(1200)
+                ),
+                (
+                    UtxoCursor {
+                        block_number: BlockNumber(0),
+                        tx_hash: Hash::default(),
+                        output_index: 1,
+                    },
+                    SourceAmount::with_base0(800)
+                )
+            ]
+        );
+        assert_eq!(sum, SourceAmount::with_base0(2000));
+        assert!(!has_next_page);
+        assert!(has_previous_page);
+
+        Ok(())
+    }
+}
diff --git a/gql/Cargo.toml b/gql/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..e78f22ef8fc18f57f4f372adbac0628a1551f406
--- /dev/null
+++ b/gql/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "duniter-gva-gql"
+version = "0.1.0"
+authors = ["librelois <elois@duniter.org>"]
+license = "AGPL-3.0"
+edition = "2018"
+
+[dependencies]
+anyhow = "1.0.33"
+arrayvec = "0.5.1"
+async-graphql = { version = "2.8", features = ["log"] }
+async-trait = "0.1.41"
+dubp = { version = "0.51.0", features = ["duniter"] }
+duniter-core = { git = "https://git.duniter.org/nodes/rust/duniter-core" }
+duniter-gva-db = { path = "../db" }
+duniter-gva-dbs-reader = { path = "../dbs-reader" }
+fast-threadpool = "0.2.3"
+flume = "0.10.0"
+futures = "0.3.6"
+log = "0.4.11"
+resiter = "0.4.0"
+serde = { version = "1.0.105", features = ["derive"] }
+
+[dev-dependencies]
+duniter-core = { git = "https://git.duniter.org/nodes/rust/duniter-core", features = ["mem", "mock"] }
+duniter-gva-dbs-reader = { path = "../dbs-reader", features = ["mock"] }
+mockall = "0.9.1"
+pretty_assertions = "0.7"
+serde_json = "1.0.53"
+tokio = { version = "1.2", features = ["macros", "rt-multi-thread", "time"] }
+unwrap = "1.2.1"
diff --git a/gql/src/entities.rs b/gql/src/entities.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c6ac1569f842a1cf73575aaf264dc65ca57e7a51
--- /dev/null
+++ b/gql/src/entities.rs
@@ -0,0 +1,112 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+pub mod block_gva;
+pub mod idty_gva;
+pub mod network;
+pub mod tx_gva;
+pub mod ud_gva;
+pub mod utxos_gva;
+
+use crate::*;
+
+#[derive(Default, async_graphql::SimpleObject)]
+pub(crate) struct AggregateSum {
+    pub(crate) aggregate: Sum,
+}
+
+#[derive(Default, async_graphql::SimpleObject)]
+pub(crate) struct AmountWithBase {
+    pub(crate) amount: i32,
+    pub(crate) base: i32,
+}
+
+#[derive(async_graphql::SimpleObject)]
+pub(crate) struct EdgeTx {
+    pub(crate) direction: TxDirection,
+}
+
+pub(crate) enum RawTxOrChanges {
+    FinalTx(String),
+    Changes(Vec<String>),
+}
+#[async_graphql::Object]
+impl RawTxOrChanges {
+    /// Intermediate transactions documents for compacting sources (`null` if not needed)
+    async fn changes(&self) -> Option<&Vec<String>> {
+        if let Self::Changes(changes) = self {
+            Some(changes)
+        } else {
+            None
+        }
+    }
+    /// Transaction document that carries out the requested transfer (`null` if the amount to be sent requires too many sources)
+    async fn tx(&self) -> Option<&str> {
+        if let Self::FinalTx(raw_tx) = self {
+            Some(raw_tx.as_str())
+        } else {
+            None
+        }
+    }
+}
+
+#[derive(Default, async_graphql::SimpleObject)]
+pub(crate) struct Sum {
+    pub(crate) sum: AmountWithBase,
+}
+
+#[derive(Clone, Copy, Eq, PartialEq, async_graphql::Enum)]
+pub(crate) enum TxDirection {
+    /// Received
+    Received,
+    /// Sent
+    Sent,
+}
+
+#[derive(async_graphql::SimpleObject)]
+pub(crate) struct TxsHistoryMempool {
+    /// Transactions sending
+    pub(crate) sending: Vec<TxGva>,
+    /// Transactions receiving
+    pub(crate) receiving: Vec<TxGva>,
+}
+
+#[derive(Clone, async_graphql::SimpleObject)]
+pub(crate) struct UtxoGva {
+    /// Source amount
+    pub(crate) amount: i64,
+    /// Source base
+    pub(crate) base: i64,
+    /// Hash of origin transaction
+    pub(crate) tx_hash: String,
+    /// Index of output in origin transaction
+    pub(crate) output_index: u32,
+}
+
+#[derive(Clone, async_graphql::SimpleObject)]
+pub(crate) struct UtxoTimedGva {
+    /// Source amount
+    pub(crate) amount: i64,
+    /// Source base
+    pub(crate) base: i64,
+    /// Hash of origin transaction
+    pub(crate) tx_hash: String,
+    /// Index of output in origin transaction
+    pub(crate) output_index: u32,
+    /// Written block
+    pub(crate) written_block: u32,
+    /// Written time
+    pub(crate) written_time: u64,
+}
diff --git a/gql/src/entities/block_gva.rs b/gql/src/entities/block_gva.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c12b8c2f15df10f4cf48befcd83f87099c6a1566
--- /dev/null
+++ b/gql/src/entities/block_gva.rs
@@ -0,0 +1,176 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use super::tx_gva::TxGva;
+use crate::*;
+use dubp::block::DubpBlockV10;
+use duniter_core::dbs::BlockMetaV2;
+
+#[derive(async_graphql::SimpleObject)]
+pub(crate) struct BlockMeta {
+    pub version: u64,
+    pub number: u32,
+    pub hash: String,
+    pub signature: String,
+    pub inner_hash: String,
+    pub previous_hash: String,
+    pub issuer: String,
+    pub time: u64,
+    pub pow_min: u32,
+    pub members_count: u64,
+    pub issuers_count: u32,
+    pub issuers_frame: u64,
+    pub median_time: u64,
+    pub nonce: u64,
+    pub monetary_mass: u64,
+    pub unit_base: u32,
+    pub dividend: Option<u32>,
+}
+
+impl From<BlockMetaV2> for BlockMeta {
+    fn from(block_db: BlockMetaV2) -> Self {
+        Self::from(&block_db)
+    }
+}
+impl From<&BlockMetaV2> for BlockMeta {
+    fn from(block_db: &BlockMetaV2) -> Self {
+        BlockMeta {
+            version: block_db.version,
+            number: block_db.number,
+            hash: block_db.hash.to_string(),
+            signature: block_db.signature.to_string(),
+            inner_hash: block_db.inner_hash.to_string(),
+            previous_hash: block_db.previous_hash.to_string(),
+            issuer: block_db.issuer.to_string(),
+            time: block_db.time,
+            pow_min: block_db.pow_min,
+            members_count: block_db.members_count,
+            issuers_count: block_db.issuers_count,
+            issuers_frame: block_db.issuers_frame,
+            median_time: block_db.median_time,
+            nonce: block_db.nonce,
+            monetary_mass: block_db.monetary_mass,
+            unit_base: block_db.unit_base,
+            dividend: block_db.dividend.map(|sa| sa.amount() as u32),
+        }
+    }
+}
+
+#[derive(async_graphql::SimpleObject)]
+pub(crate) struct Block {
+    // Meta
+    pub version: u64,
+    pub number: u32,
+    pub hash: String,
+    pub signature: String,
+    pub inner_hash: String,
+    pub previous_hash: Option<String>,
+    pub issuer: String,
+    pub time: u64,
+    pub pow_min: u32,
+    pub members_count: u64,
+    pub issuers_count: u32,
+    pub issuers_frame: u64,
+    pub median_time: u64,
+    pub nonce: u64,
+    pub monetary_mass: u64,
+    pub unit_base: u32,
+    pub dividend: Option<u32>,
+    // Payload
+    /// Identities
+    pub identities: Vec<String>,
+    /// joiners
+    pub joiners: Vec<String>,
+    /// Actives (=renewals)
+    pub actives: Vec<String>,
+    /// Leavers
+    pub leavers: Vec<String>,
+    /// Revokeds
+    pub revoked: Vec<String>,
+    /// Excludeds
+    pub excluded: Vec<String>,
+    /// Certifications
+    pub certifications: Vec<String>,
+    pub transactions: Vec<TxGva>,
+}
+
+impl From<&DubpBlockV10> for Block {
+    fn from(block: &DubpBlockV10) -> Self {
+        let block = block.to_string_object();
+        Block {
+            // Meta
+            version: block.version,
+            number: block.number as u32,
+            hash: block.hash.unwrap_or_default(),
+            signature: block.signature,
+            inner_hash: block.inner_hash.unwrap_or_default(),
+            previous_hash: block.previous_hash,
+            issuer: block.issuer,
+            time: block.time,
+            pow_min: block.pow_min as u32,
+            members_count: block.members_count,
+            issuers_count: block.issuers_count as u32,
+            issuers_frame: block.issuers_frame,
+            median_time: block.median_time,
+            nonce: block.nonce,
+            monetary_mass: block.monetary_mass,
+            unit_base: block.unit_base as u32,
+            dividend: block.dividend.map(|amount| amount as u32),
+            // Payload
+            identities: block.identities,
+            joiners: block.joiners,
+            actives: block.actives,
+            leavers: block.leavers,
+            revoked: block.revoked,
+            excluded: block.excluded,
+            certifications: block.certifications,
+            transactions: block.transactions.into_iter().map(Into::into).collect(),
+        }
+    }
+}
+
+impl From<&BlockMetaV2> for Block {
+    fn from(block: &BlockMetaV2) -> Self {
+        Block {
+            // Meta
+            version: block.version,
+            number: block.number,
+            hash: block.hash.to_string(),
+            signature: block.signature.to_string(),
+            inner_hash: block.inner_hash.to_string(),
+            previous_hash: Some(block.previous_hash.to_string()),
+            issuer: block.issuer.to_string(),
+            time: block.time,
+            pow_min: block.pow_min,
+            members_count: block.members_count,
+            issuers_count: block.issuers_count,
+            issuers_frame: block.issuers_frame,
+            median_time: block.median_time,
+            nonce: block.nonce,
+            monetary_mass: block.monetary_mass,
+            unit_base: block.unit_base,
+            dividend: block.dividend.map(|sa| sa.amount() as u32),
+            // Payload
+            identities: vec![],
+            joiners: vec![],
+            actives: vec![],
+            leavers: vec![],
+            revoked: vec![],
+            excluded: vec![],
+            certifications: vec![],
+            transactions: vec![],
+        }
+    }
+}
diff --git a/gql/src/entities/idty_gva.rs b/gql/src/entities/idty_gva.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ee77b0e4db267306971fa64f4e28f460d42f1b2c
--- /dev/null
+++ b/gql/src/entities/idty_gva.rs
@@ -0,0 +1,20 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+#[derive(async_graphql::SimpleObject)]
+pub(crate) struct Identity {
+    pub is_member: bool,
+    pub username: String,
+}
diff --git a/gql/src/entities/network.rs b/gql/src/entities/network.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4e065909c8cb5431c1b9858fbfed4780ec5478b1
--- /dev/null
+++ b/gql/src/entities/network.rs
@@ -0,0 +1,74 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+#[derive(Default, async_graphql::SimpleObject)]
+#[graphql(name = "Peer")]
+pub struct PeerCardGva {
+    pub version: u32,
+    pub currency: String,
+    pub pubkey: String,
+    pub blockstamp: String,
+    pub endpoints: Vec<String>,
+    pub status: String,
+    pub signature: String,
+}
+impl From<duniter_core::dbs::PeerCardDbV1> for PeerCardGva {
+    fn from(peer: duniter_core::dbs::PeerCardDbV1) -> Self {
+        Self {
+            version: peer.version,
+            currency: peer.currency,
+            pubkey: peer.pubkey,
+            blockstamp: peer.blockstamp,
+            endpoints: peer.endpoints,
+            status: peer.status,
+            signature: peer.signature,
+        }
+    }
+}
+
+#[derive(Default, async_graphql::SimpleObject)]
+#[graphql(name = "Head")]
+pub struct HeadGva {
+    pub api: String,
+    pub pubkey: String,
+    pub blockstamp: String,
+    pub software: String,
+    pub software_version: String,
+    pub pow_prefix: u32,
+    pub free_member_room: u32,
+    pub free_mirror_room: u32,
+    pub signature: String,
+}
+impl From<duniter_core::dbs::DunpHeadDbV1> for HeadGva {
+    fn from(head: duniter_core::dbs::DunpHeadDbV1) -> Self {
+        Self {
+            api: head.api,
+            pubkey: head.pubkey.to_string(),
+            blockstamp: head.blockstamp.to_string(),
+            software: head.software,
+            software_version: head.software_version,
+            pow_prefix: head.pow_prefix,
+            free_member_room: head.free_member_room,
+            free_mirror_room: head.free_member_room,
+            signature: head.signature.to_string(),
+        }
+    }
+}
+
+#[derive(async_graphql::SimpleObject)]
+pub(crate) struct PeerWithHeads {
+    pub peer: PeerCardGva,
+    pub heads: Vec<HeadGva>,
+}
diff --git a/gql/src/entities/tx_gva.rs b/gql/src/entities/tx_gva.rs
new file mode 100644
index 0000000000000000000000000000000000000000..6fd3764338d17b728705c301a3383c08f243b6c1
--- /dev/null
+++ b/gql/src/entities/tx_gva.rs
@@ -0,0 +1,84 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+use dubp::documents::transaction::TransactionDocumentV10Stringified;
+use duniter_gva_db::GvaTxDbV1;
+
+#[derive(async_graphql::SimpleObject)]
+pub(crate) struct TxGva {
+    /// Version.
+    pub version: i32,
+    /// Currency.
+    pub currency: String,
+    /// Blockstamp
+    pub blockstamp: String,
+    /// Locktime
+    pub locktime: u64,
+    /// Document issuers.
+    pub issuers: Vec<String>,
+    /// Transaction inputs.
+    pub inputs: Vec<String>,
+    /// Inputs unlocks.
+    pub unlocks: Vec<String>,
+    /// Transaction outputs.
+    pub outputs: Vec<String>,
+    /// Transaction comment
+    pub comment: String,
+    /// Document signatures
+    pub signatures: Vec<String>,
+    /// Transaction hash
+    pub hash: String,
+    /// Written block
+    pub written_block: Option<String>,
+    /// Written Time
+    pub written_time: Option<i64>,
+}
+
+impl From<GvaTxDbV1> for TxGva {
+    fn from(db_tx: GvaTxDbV1) -> Self {
+        let mut self_: TxGva = (&db_tx.tx).into();
+        self_.written_block = Some(db_tx.written_block.to_string());
+        self_.written_time = Some(db_tx.written_time);
+        self_
+    }
+}
+
+impl From<&TransactionDocumentV10> for TxGva {
+    fn from(tx: &TransactionDocumentV10) -> Self {
+        let tx_stringified = tx.to_string_object();
+        Self::from(tx_stringified)
+    }
+}
+
+impl From<TransactionDocumentV10Stringified> for TxGva {
+    fn from(tx_stringified: TransactionDocumentV10Stringified) -> Self {
+        Self {
+            version: 10,
+            currency: tx_stringified.currency,
+            blockstamp: tx_stringified.blockstamp,
+            locktime: tx_stringified.locktime,
+            issuers: tx_stringified.issuers,
+            inputs: tx_stringified.inputs,
+            unlocks: tx_stringified.unlocks,
+            outputs: tx_stringified.outputs,
+            comment: tx_stringified.comment,
+            signatures: tx_stringified.signatures,
+            hash: tx_stringified.hash.unwrap_or_default(),
+            written_block: None,
+            written_time: None,
+        }
+    }
+}
diff --git a/gql/src/entities/ud_gva.rs b/gql/src/entities/ud_gva.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e268f5b67803645fd602666938ce14d00d8618cd
--- /dev/null
+++ b/gql/src/entities/ud_gva.rs
@@ -0,0 +1,48 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Clone, async_graphql::SimpleObject)]
+pub(crate) struct CurrentUdGva {
+    /// Ud amount
+    pub(crate) amount: i64,
+    /// Ud base
+    pub(crate) base: i64,
+}
+
+#[derive(Clone, async_graphql::SimpleObject)]
+pub(crate) struct RevalUdGva {
+    /// Ud amount
+    pub(crate) amount: i64,
+    /// Ud base
+    pub(crate) base: i64,
+    /// Number of the block that revaluate ud amount
+    pub(crate) block_number: u32,
+}
+
+#[derive(Clone, async_graphql::SimpleObject)]
+pub(crate) struct UdGva {
+    /// Ud amount
+    pub(crate) amount: i64,
+    /// Ud base
+    pub(crate) base: i64,
+    /// Issuer of this universal dividend
+    pub(crate) issuer: PubKeyGva,
+    /// Number of the block that created this UD
+    pub(crate) block_number: u32,
+    /// Blockchain time of the block that created this UD
+    pub(crate) blockchain_time: u64,
+}
diff --git a/gql/src/entities/utxos_gva.rs b/gql/src/entities/utxos_gva.rs
new file mode 100644
index 0000000000000000000000000000000000000000..9bd3e79906e1e8b1289e62f71d85dc3b6aeacadd
--- /dev/null
+++ b/gql/src/entities/utxos_gva.rs
@@ -0,0 +1,42 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+pub(crate) struct UtxosGva(pub arrayvec::ArrayVec<[UtxoGva; 40]>);
+impl async_graphql::Type for UtxosGva {
+    fn type_name() -> Cow<'static, str> {
+        Cow::Owned(format!("[{}]", UtxoGva::qualified_type_name()))
+    }
+
+    fn qualified_type_name() -> String {
+        format!("[{}]!", UtxoGva::qualified_type_name())
+    }
+
+    fn create_type_info(registry: &mut async_graphql::registry::Registry) -> String {
+        UtxoGva::create_type_info(registry);
+        Self::qualified_type_name()
+    }
+}
+#[async_trait::async_trait]
+impl async_graphql::OutputType for UtxosGva {
+    async fn resolve(
+        &self,
+        ctx: &async_graphql::ContextSelectionSet<'_>,
+        field: &async_graphql::Positioned<async_graphql::parser::types::Field>,
+    ) -> async_graphql::ServerResult<async_graphql::Value> {
+        async_graphql::resolver_utils::resolve_list(ctx, field, &self.0, Some(self.0.len())).await
+    }
+}
diff --git a/gql/src/inputs.rs b/gql/src/inputs.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b904690941b7a6c4deac1cd019404f9cf3de96f6
--- /dev/null
+++ b/gql/src/inputs.rs
@@ -0,0 +1,57 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(async_graphql::InputObject, Clone, Copy, Default)]
+pub(crate) struct TimeInterval {
+    pub(crate) from: Option<u64>,
+    pub(crate) to: Option<u64>,
+}
+
+#[derive(async_graphql::InputObject)]
+pub(crate) struct TxIssuer {
+    /// Account script (default is a script needed all provided signers)
+    pub(crate) script: Option<String>,
+    /// Signers
+    #[graphql(validator(ListMinLength(length = "1")))]
+    pub(crate) signers: Vec<String>,
+    /// XHX codes needed to unlock funds
+    #[graphql(validator(ListMinLength(length = "1")))]
+    pub(crate) codes: Option<Vec<String>>,
+    /// Amount
+    #[graphql(validator(IntGreaterThan(value = "0")))]
+    pub(crate) amount: i32,
+}
+
+#[derive(async_graphql::InputObject)]
+pub(crate) struct TxRecipient {
+    /// Amount
+    #[graphql(validator(IntGreaterThan(value = "0")))]
+    pub(crate) amount: i32,
+    /// Account script
+    pub(crate) script: String,
+}
+
+#[derive(Clone, Copy, async_graphql::Enum, Eq, PartialEq)]
+pub(crate) enum UdsFilter {
+    All,
+    Unspent,
+}
+impl Default for UdsFilter {
+    fn default() -> Self {
+        UdsFilter::All
+    }
+}
diff --git a/gql/src/inputs_validators.rs b/gql/src/inputs_validators.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b2578c22bb8eceb0cea3726729baee9fd66eaba0
--- /dev/null
+++ b/gql/src/inputs_validators.rs
@@ -0,0 +1,36 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+pub(crate) struct TxCommentValidator;
+
+impl async_graphql::validators::InputValueValidator for TxCommentValidator {
+    fn is_valid(&self, value: &async_graphql::Value) -> Result<(), String> {
+        if let async_graphql::Value::String(comment) = value {
+            if !TransactionDocumentV10::verify_comment(&comment) {
+                // Validation failed
+                Err("invalid comment".to_owned())
+            } else {
+                // Validation succeeded
+                Ok(())
+            }
+        } else {
+            // If the type does not match we can return None and built-in validations
+            // will pick up on the error
+            Ok(())
+        }
+    }
+}
diff --git a/gql/src/lib.rs b/gql/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..64a48b51ccaab5abd1e74dd8549e6b616ac341b2
--- /dev/null
+++ b/gql/src/lib.rs
@@ -0,0 +1,157 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+#![deny(
+    clippy::unwrap_used,
+    missing_copy_implementations,
+    trivial_casts,
+    trivial_numeric_casts,
+    unstable_features,
+    unused_import_braces
+)]
+
+mod entities;
+mod inputs;
+mod inputs_validators;
+mod mutations;
+mod pagination;
+mod queries;
+mod scalars;
+mod schema;
+mod subscriptions;
+
+pub use schema::{build_schema_with_data, get_schema_definition, GvaSchema, GvaSchemaData};
+
+use crate::entities::{
+    block_gva::{Block, BlockMeta},
+    idty_gva::Identity,
+    network::{HeadGva, PeerCardGva, PeerWithHeads},
+    tx_gva::TxGva,
+    ud_gva::{CurrentUdGva, RevalUdGva, UdGva},
+    utxos_gva::UtxosGva,
+    AggregateSum, AmountWithBase, EdgeTx, RawTxOrChanges, Sum, TxDirection, TxsHistoryMempool,
+    UtxoGva, UtxoTimedGva,
+};
+use crate::inputs::{TxIssuer, TxRecipient, UdsFilter};
+use crate::inputs_validators::TxCommentValidator;
+use crate::pagination::Pagination;
+use crate::scalars::{PkOrScriptGva, PubKeyGva};
+#[cfg(test)]
+use crate::tests::AsyncAccessor;
+#[cfg(test)]
+use crate::tests::DbsReaderImpl;
+use async_graphql::connection::{Connection, Edge, EmptyFields};
+use async_graphql::validators::{IntGreaterThan, IntRange, ListMaxLength, ListMinLength};
+use dubp::common::crypto::keys::{ed25519::PublicKey, PublicKey as _};
+use dubp::common::prelude::*;
+use dubp::crypto::hashs::Hash;
+use dubp::documents::prelude::*;
+use dubp::documents::transaction::{TransactionDocumentTrait, TransactionDocumentV10};
+use dubp::documents_parser::prelude::*;
+use dubp::wallet::prelude::*;
+use duniter_core::dbs::databases::txs_mp_v2::TxsMpV2DbReadable;
+use duniter_core::dbs::prelude::*;
+use duniter_core::dbs::{kv_typed::prelude::*, FileBackend};
+#[cfg(not(test))]
+use duniter_core::global::AsyncAccessor;
+use duniter_core::mempools::TxsMempool;
+use duniter_gva_dbs_reader::pagination::PageInfo;
+use duniter_gva_dbs_reader::DbsReader;
+#[cfg(not(test))]
+use duniter_gva_dbs_reader::DbsReaderImpl;
+use futures::{Stream, StreamExt};
+use resiter::map::Map;
+use std::{borrow::Cow, convert::TryFrom, num::NonZeroUsize, ops::Deref};
+
+#[derive(Clone, Copy, Debug, Default)]
+pub struct QueryContext {
+    pub is_whitelisted: bool,
+}
+
+#[derive(Debug, Default)]
+pub struct ServerMetaData {
+    pub currency: String,
+    pub self_pubkey: PublicKey,
+    pub software_version: &'static str,
+}
+
+#[cfg(test)]
+mod tests {
+    pub use duniter_core::global::{CurrentMeta, MockAsyncAccessor};
+    pub use duniter_gva_dbs_reader::MockDbsReader;
+
+    use super::*;
+    use fast_threadpool::ThreadPoolConfig;
+
+    pub type AsyncAccessor = duniter_core::dbs::kv_typed::prelude::Arc<MockAsyncAccessor>;
+    pub type DbsReaderImpl = duniter_core::dbs::kv_typed::prelude::Arc<MockDbsReader>;
+
+    pub(crate) fn create_schema(
+        mock_cm: MockAsyncAccessor,
+        dbs_ops: MockDbsReader,
+    ) -> KvResult<GvaSchema> {
+        let dbs = SharedDbs::mem()?;
+        let threadpool = fast_threadpool::ThreadPool::start(ThreadPoolConfig::default(), dbs);
+        Ok(schema::build_schema_with_data(
+            schema::GvaSchemaData {
+                cm_accessor: Arc::new(mock_cm),
+                dbs_pool: threadpool.into_async_handler(),
+                dbs_reader: Arc::new(dbs_ops),
+                server_meta_data: ServerMetaData {
+                    currency: "test_currency".to_owned(),
+                    self_pubkey: PublicKey::default(),
+                    software_version: "test",
+                },
+                txs_mempool: TxsMempool::new(10),
+            },
+            true,
+        ))
+    }
+
+    pub(crate) async fn exec_graphql_request(
+        schema: &GvaSchema,
+        request: &str,
+    ) -> anyhow::Result<serde_json::Value> {
+        Ok(serde_json::to_value(
+            schema
+                .execute(async_graphql::Request::new(request).data(QueryContext::default()))
+                .await,
+        )?)
+    }
+
+    /*pub(crate) fn create_schema_sub(dbs: SharedDbs<FileBackend>) -> KvResult<GvaSchema> {
+        let threadpool = fast_threadpool::ThreadPool::start(ThreadPoolConfig::default(), dbs);
+        Ok(schema::build_schema_with_data(
+            schema::GvaSchemaData {
+                dbs_pool: threadpool.into_async_handler(),
+                dbs_reader: Arc::new(MockDbsReader::new()),
+                server_meta_data: ServerMetaData {
+                    currency: "test_currency".to_owned(),
+                    self_pubkey: PublicKey::default(),
+                    software_version: "test",
+                },
+                txs_mempool: TxsMempool::new(10),
+            },
+            true,
+        ))
+    }
+
+    pub(crate) fn exec_graphql_subscription(
+        schema: &GvaSchema,
+        request: String,
+    ) -> impl Stream<Item = serde_json::Result<serde_json::Value>> + Send {
+        schema.execute_stream(request).map(serde_json::to_value)
+    }*/
+}
diff --git a/gql/src/mutations.rs b/gql/src/mutations.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3d258e9ae7c83142ab3d4d166cf647590d59ce38
--- /dev/null
+++ b/gql/src/mutations.rs
@@ -0,0 +1,86 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Clone, Copy, Default)]
+pub struct MutationRoot;
+
+#[async_graphql::Object]
+impl MutationRoot {
+    /// Process a transaction
+    /// Return the transaction if it successfully inserted
+    async fn tx(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+        raw_tx: String,
+    ) -> async_graphql::Result<TxGva> {
+        let tx = TransactionDocumentV10::parse_from_raw_text(&raw_tx)?;
+
+        let data = ctx.data::<GvaSchemaData>()?;
+        let expected_currency = data.server_meta_data.currency.clone();
+
+        tx.verify(Some(&expected_currency))?;
+
+        let server_pubkey = data.server_meta_data.self_pubkey;
+        let txs_mempool = data.txs_mempool;
+
+        let tx = data
+            .dbs_pool
+            .execute(move |dbs| {
+                txs_mempool
+                    .add_pending_tx(&dbs.bc_db_ro, server_pubkey, &dbs.txs_mp_db, &tx)
+                    .map(|()| tx)
+            })
+            .await??;
+
+        Ok(TxGva::from(&tx))
+    }
+
+    /// Process several transactions
+    /// Return an array of successfully inserted transactions
+    async fn txs(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+        raw_txs: Vec<String>,
+    ) -> async_graphql::Result<Vec<TxGva>> {
+        let txs = raw_txs
+            .iter()
+            .map(|raw_tx| TransactionDocumentV10::parse_from_raw_text(&raw_tx))
+            .collect::<Result<Vec<TransactionDocumentV10>, _>>()?;
+
+        let data = ctx.data::<GvaSchemaData>()?;
+        let expected_currency = data.server_meta_data.currency.clone();
+
+        let server_pubkey = data.server_meta_data.self_pubkey;
+        let txs_mempool = data.txs_mempool;
+
+        let mut processed_txs = Vec::with_capacity(txs.len());
+        for tx in txs {
+            tx.verify(Some(&expected_currency))?;
+            let tx = data
+                .dbs_pool
+                .execute(move |dbs| {
+                    txs_mempool
+                        .add_pending_tx(&dbs.bc_db_ro, server_pubkey, &dbs.txs_mp_db, &tx)
+                        .map(|()| tx)
+                })
+                .await??;
+            processed_txs.push(TxGva::from(&tx));
+        }
+
+        Ok(processed_txs)
+    }
+}
diff --git a/gql/src/pagination.rs b/gql/src/pagination.rs
new file mode 100644
index 0000000000000000000000000000000000000000..98db4e9ab7c1644168deaedd7f37ca001f099e15
--- /dev/null
+++ b/gql/src/pagination.rs
@@ -0,0 +1,73 @@
+use std::str::FromStr;
+
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+const MAX_PAGE_SIZE: u32 = 1_000;
+
+#[derive(Clone, Copy, async_graphql::Enum, Eq, PartialEq)]
+pub(crate) enum Order {
+    /// Ascending order
+    Asc,
+    /// Decreasing order
+    Desc,
+}
+impl Default for Order {
+    fn default() -> Self {
+        Order::Asc
+    }
+}
+
+#[derive(async_graphql::InputObject)]
+pub(crate) struct Pagination {
+    /// Identifier of the 1st desired element (of the last one in descending order)
+    cursor: Option<String>,
+    ord: Order,
+    page_size: u32,
+}
+
+impl Default for Pagination {
+    fn default() -> Self {
+        Pagination {
+            cursor: None,
+            ord: Order::default(),
+            page_size: 10,
+        }
+    }
+}
+
+impl Pagination {
+    pub(crate) fn convert_to_page_info<
+        E: 'static + std::error::Error + Send + Sync,
+        T: FromStr<Err = E>,
+    >(
+        self,
+        is_whitelisted: bool,
+    ) -> anyhow::Result<duniter_gva_dbs_reader::PageInfo<T>> {
+        let page_size = if is_whitelisted || (self.page_size > 0 && self.page_size < MAX_PAGE_SIZE)
+        {
+            NonZeroUsize::new(self.page_size as usize)
+        } else {
+            return Err(anyhow::Error::msg("pageSize must be between 1 and 1000."));
+        };
+        Ok(duniter_gva_dbs_reader::PageInfo::new(
+            self.cursor.map(|c| T::from_str(&c)).transpose()?,
+            self.ord == Order::Asc,
+            page_size,
+        ))
+    }
+}
diff --git a/gql/src/queries.rs b/gql/src/queries.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ad733e3a557962eb6fa5217c523d734e45266b36
--- /dev/null
+++ b/gql/src/queries.rs
@@ -0,0 +1,86 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+pub mod account_balance;
+pub mod block;
+pub mod current_block;
+pub mod current_frame;
+pub mod first_utxos_of_scripts;
+pub mod gen_tx;
+pub mod idty;
+pub mod network;
+pub mod txs_history;
+pub mod uds;
+pub mod utxos_of_script;
+
+use crate::*;
+
+#[derive(async_graphql::MergedObject, Default)]
+pub struct QueryRoot(
+    queries::NodeQuery,
+    queries::account_balance::AccountBalanceQuery,
+    queries::block::BlockQuery,
+    queries::current_block::CurrentBlockQuery,
+    queries::current_frame::CurrentFrameQuery,
+    queries::first_utxos_of_scripts::FirstUtxosQuery,
+    queries::gen_tx::GenTxsQuery,
+    queries::idty::IdtyQuery,
+    queries::network::NetworkQuery,
+    queries::txs_history::TxsHistoryBlockchainQuery,
+    queries::txs_history::TxsHistoryMempoolQuery,
+    queries::uds::UdsQuery,
+    queries::utxos_of_script::UtxosQuery,
+);
+
+#[derive(Default, async_graphql::SimpleObject)]
+struct NodeQuery {
+    node: Node,
+}
+
+#[derive(Default)]
+struct Node;
+
+#[async_graphql::Object]
+impl Node {
+    /// Peer card
+    async fn peer(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+    ) -> async_graphql::Result<Option<PeerCardGva>> {
+        let data = ctx.data::<GvaSchemaData>()?;
+
+        if let Some(self_peer_old) = data
+            .cm_accessor()
+            .get_self_peer_old(|self_peer_old| self_peer_old.clone())
+            .await
+        {
+            Ok(Some(PeerCardGva::from(self_peer_old)))
+        } else {
+            Ok(None)
+        }
+    }
+    /// Software
+    async fn software(&self) -> &'static str {
+        duniter_core::module::SOFTWARE_NAME
+    }
+    /// Software version
+    async fn version(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+    ) -> async_graphql::Result<&'static str> {
+        let data = ctx.data::<GvaSchemaData>()?;
+        Ok(data.server_meta_data.software_version)
+    }
+}
diff --git a/gql/src/queries/account_balance.rs b/gql/src/queries/account_balance.rs
new file mode 100644
index 0000000000000000000000000000000000000000..81d72677f0a85a5be868dc512787d8e992f6147c
--- /dev/null
+++ b/gql/src/queries/account_balance.rs
@@ -0,0 +1,143 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Default)]
+pub(crate) struct AccountBalanceQuery;
+#[async_graphql::Object]
+impl AccountBalanceQuery {
+    /// Get the balance of an account identified by a public key or a script.
+    ///
+    /// If the balance is null, the account has never been used. If the balance is zero, the account has already transited money in the past.
+    async fn balance(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+        #[graphql(desc = "Account script or public key")] script: PkOrScriptGva,
+    ) -> async_graphql::Result<Option<AmountWithBase>> {
+        let data = ctx.data::<GvaSchemaData>()?;
+        let dbs_reader = data.dbs_reader();
+
+        Ok(data
+            .dbs_pool
+            .execute(move |_| dbs_reader.get_account_balance(&script.0))
+            .await??
+            .map(|balance| AmountWithBase {
+                amount: balance.0.amount() as i32,
+                base: balance.0.base() as i32,
+            }))
+    }
+    /// Get the balance of several accounts in a single request
+    ///
+    /// Each account can be identified by a public key or a script. It is possible to mix the two in the same request.
+    /// The balances are returned in the order of the accounts provided as input. Each account has a balance,
+    /// which is null if the account does not exist.
+    async fn balances(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+        #[graphql(desc = "Accounts scripts or publics keys")] scripts: Vec<PkOrScriptGva>,
+    ) -> async_graphql::Result<Vec<Option<AmountWithBase>>> {
+        let data = ctx.data::<GvaSchemaData>()?;
+        let dbs_reader = data.dbs_reader();
+
+        Ok(data
+            .dbs_pool
+            .execute(move |_| {
+                scripts
+                    .iter()
+                    .map(|account_script| {
+                        dbs_reader
+                            .get_account_balance(&account_script.0)
+                            .map(|balance_opt| {
+                                balance_opt.map(|balance| AmountWithBase {
+                                    amount: balance.0.amount() as i32,
+                                    base: balance.0.base() as i32,
+                                })
+                            })
+                    })
+                    .collect::<Result<Vec<_>, _>>()
+            })
+            .await??)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::tests::*;
+    use duniter_core::dbs::SourceAmountValV2;
+
+    #[tokio::test]
+    async fn query_balance() -> anyhow::Result<()> {
+        let mut dbs_reader = MockDbsReader::new();
+        dbs_reader
+            .expect_get_account_balance()
+            .withf(|s| {
+                s == &WalletScriptV10::single_sig(
+                    PublicKey::from_base58("DnjL6hYA1k7FavGHbbir79PKQbmzw63d6bsamBBdUULP")
+                        .expect("wrong pubkey"),
+                )
+            })
+            .times(1)
+            .returning(|_| Ok(Some(SourceAmountValV2(SourceAmount::with_base0(38)))));
+        let schema = create_schema(MockAsyncAccessor::new(), dbs_reader)?;
+        assert_eq!(
+            exec_graphql_request(
+                &schema,
+                r#"{ balance(script: "DnjL6hYA1k7FavGHbbir79PKQbmzw63d6bsamBBdUULP") {amount} }"#
+            )
+            .await?,
+            serde_json::json!({
+                "data": {
+                    "balance": {
+                      "amount": 38
+                    }
+                }
+            })
+        );
+        Ok(())
+    }
+
+    #[tokio::test]
+    async fn query_balances() -> anyhow::Result<()> {
+        let mut dbs_reader = MockDbsReader::new();
+        dbs_reader
+            .expect_get_account_balance()
+            .withf(|s| {
+                s == &WalletScriptV10::single_sig(
+                    PublicKey::from_base58("DnjL6hYA1k7FavGHbbir79PKQbmzw63d6bsamBBdUULP")
+                        .expect("wrong pubkey"),
+                )
+            })
+            .times(1)
+            .returning(|_| Ok(Some(SourceAmountValV2(SourceAmount::with_base0(38)))));
+        let schema = create_schema(MockAsyncAccessor::new(), dbs_reader)?;
+        assert_eq!(
+            exec_graphql_request(
+                &schema,
+                r#"{ balances(scripts: ["DnjL6hYA1k7FavGHbbir79PKQbmzw63d6bsamBBdUULP"]) {amount} }"#
+            )
+            .await?,
+            serde_json::json!({
+                "data": {
+                    "balances": [{
+                      "amount": 38
+                    }]
+                }
+            })
+        );
+        Ok(())
+    }
+}
diff --git a/gql/src/queries/block.rs b/gql/src/queries/block.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f5cf8ea492e1bd960f076c2863bbed8f0618dd49
--- /dev/null
+++ b/gql/src/queries/block.rs
@@ -0,0 +1,142 @@
+//  Copyright (C) 2021 Pascal Engélibert
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+use duniter_gva_dbs_reader::PagedData;
+
+#[derive(Default)]
+pub(crate) struct BlockQuery;
+#[async_graphql::Object]
+impl BlockQuery {
+    /// Get block by number
+    async fn block_by_number(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+        #[graphql(desc = "block number")] number: u32,
+    ) -> async_graphql::Result<Option<BlockMeta>> {
+        let data = ctx.data::<GvaSchemaData>()?;
+        let dbs_reader = data.dbs_reader();
+
+        let block = data
+            .dbs_pool
+            .execute(move |dbs| dbs_reader.block(&dbs.bc_db_ro, U32BE(number)))
+            .await??;
+
+        Ok(block.map(|block| BlockMeta::from(&block)))
+    }
+
+    /// Get blocks
+    async fn blocks(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+        #[graphql(desc = "pagination", default)] pagination: Pagination,
+    ) -> async_graphql::Result<Connection<String, BlockMeta, EmptyFields, EmptyFields>> {
+        let QueryContext { is_whitelisted } = ctx.data::<QueryContext>()?;
+        let page_info = Pagination::convert_to_page_info(pagination, *is_whitelisted)?;
+
+        let data = ctx.data::<GvaSchemaData>()?;
+        let dbs_reader = data.dbs_reader();
+
+        let PagedData {
+            data: blocks,
+            has_next_page,
+            has_previous_page,
+        } = data
+            .dbs_pool
+            .execute(move |dbs| dbs_reader.blocks(&dbs.bc_db_ro, page_info))
+            .await??;
+
+        let mut conn = Connection::new(has_previous_page, has_next_page);
+
+        conn.append(blocks.into_iter().map(|(block_cursor, block)| {
+            Edge::new(block_cursor.to_string(), BlockMeta::from(block))
+        }));
+
+        Ok(conn)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::BlockNumber;
+    use crate::tests::*;
+    use duniter_core::dbs::BlockMetaV2;
+    use duniter_gva_dbs_reader::{block::BlockCursor, PagedData};
+
+    #[tokio::test]
+    async fn test_block_by_number() -> anyhow::Result<()> {
+        let mut dbs_reader = MockDbsReader::new();
+        dbs_reader
+            .expect_block()
+            .withf(|_, s| s.0 == 0)
+            .times(1)
+            .returning(|_, _| Ok(Some(BlockMetaV2::default())));
+        let schema = create_schema(MockAsyncAccessor::new(), dbs_reader)?;
+        assert_eq!(
+            exec_graphql_request(&schema, r#"{ blockByNumber(number: 0) {number} }"#).await?,
+            serde_json::json!({
+                "data": {
+                    "blockByNumber": {
+                        "number": BlockMetaV2::default().number,
+                    }
+                }
+            })
+        );
+        Ok(())
+    }
+
+    #[tokio::test]
+    async fn test_blocks() -> anyhow::Result<()> {
+        let mut dbs_reader = MockDbsReader::new();
+        dbs_reader.expect_blocks().times(1).returning(|_, _| {
+            Ok(PagedData {
+                data: vec![(
+                    BlockCursor {
+                        number: BlockNumber(0),
+                    },
+                    BlockMetaV2::default(),
+                )],
+                has_next_page: false,
+                has_previous_page: false,
+            })
+        });
+        let schema = create_schema(MockAsyncAccessor::new(), dbs_reader)?;
+        assert_eq!(
+            exec_graphql_request(
+                &schema,
+                r#"{ blocks{pageInfo{startCursor,endCursor},edges{node{number}}} }"#
+            )
+            .await?,
+            serde_json::json!({
+                "data": {
+                    "blocks": {
+                        "edges": [
+                            {
+                                "node": {
+                                    "number": BlockMetaV2::default().number,
+                                }
+                            }
+                        ],
+                        "pageInfo": {
+                            "endCursor": BlockMetaV2::default().number.to_string(),
+                            "startCursor": BlockMetaV2::default().number.to_string(),
+                        }
+                    }
+                }
+            })
+        );
+        Ok(())
+    }
+}
diff --git a/gql/src/queries/current_block.rs b/gql/src/queries/current_block.rs
new file mode 100644
index 0000000000000000000000000000000000000000..7d5ed16dc73bba99012bec34059229e73545c320
--- /dev/null
+++ b/gql/src/queries/current_block.rs
@@ -0,0 +1,65 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Default)]
+pub(crate) struct CurrentBlockQuery;
+#[async_graphql::Object]
+impl CurrentBlockQuery {
+    /// Get current block
+    async fn current_block(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+    ) -> async_graphql::Result<BlockMeta> {
+        let data = ctx.data::<GvaSchemaData>()?;
+
+        if let Some(current_block_meta) = data
+            .cm_accessor()
+            .get_current_meta(|cm| cm.current_block_meta)
+            .await
+        {
+            Ok(current_block_meta.into())
+        } else {
+            Err(async_graphql::Error::new("no blockchain"))
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::tests::*;
+
+    #[tokio::test]
+    async fn query_current_block() -> anyhow::Result<()> {
+        let mut mock_cm = MockAsyncAccessor::new();
+        mock_cm
+            .expect_get_current_meta::<duniter_core::dbs::BlockMetaV2>()
+            .times(1)
+            .returning(|f| Some(f(&CurrentMeta::default())));
+        let schema = create_schema(mock_cm, MockDbsReader::new())?;
+        assert_eq!(
+            exec_graphql_request(&schema, r#"{ currentBlock {nonce} }"#).await?,
+            serde_json::json!({
+                "data": {
+                    "currentBlock": {
+                      "nonce": 0
+                    }
+                }
+            })
+        );
+        Ok(())
+    }
+}
diff --git a/gql/src/queries/current_frame.rs b/gql/src/queries/current_frame.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5abca6b36790b815b8236f19add00d5a2564ee3a
--- /dev/null
+++ b/gql/src/queries/current_frame.rs
@@ -0,0 +1,94 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Default)]
+pub(crate) struct CurrentFrameQuery;
+#[async_graphql::Object]
+impl CurrentFrameQuery {
+    /// Get blocks in current frame
+    async fn current_frame(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+    ) -> async_graphql::Result<Vec<BlockMeta>> {
+        let data = ctx.data::<GvaSchemaData>()?;
+        let dbs_reader = data.dbs_reader();
+
+        if let Some(current_block_meta) = data
+            .cm_accessor()
+            .get_current_meta(|cm| cm.current_block_meta)
+            .await
+        {
+            Ok(data
+                .dbs_pool
+                .execute(move |dbs| {
+                    dbs_reader.get_current_frame(&dbs.bc_db_ro, &current_block_meta)
+                })
+                .await??
+                .into_iter()
+                .map(Into::into)
+                .collect())
+        } else {
+            Ok(vec![])
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::tests::*;
+    use duniter_core::dbs::databases::bc_v2::BcV2DbRo;
+    use duniter_core::dbs::BlockMetaV2;
+
+    #[tokio::test]
+    async fn query_current_frame() -> anyhow::Result<()> {
+        let mut mock_cm = MockAsyncAccessor::new();
+        mock_cm
+            .expect_get_current_meta::<BlockMetaV2>()
+            .times(1)
+            .returning(|f| {
+                Some(f(&CurrentMeta {
+                    current_block_meta: BlockMetaV2 {
+                        issuers_frame: 1,
+                        ..Default::default()
+                    },
+                    ..Default::default()
+                }))
+            });
+        let mut dbs_reader = MockDbsReader::new();
+        dbs_reader
+            .expect_get_current_frame::<BcV2DbRo<FileBackend>>()
+            .times(1)
+            .returning(|_, _| {
+                Ok(vec![BlockMetaV2 {
+                    ..Default::default()
+                }])
+            });
+        let schema = create_schema(mock_cm, dbs_reader)?;
+        assert_eq!(
+            exec_graphql_request(&schema, r#"{ currentFrame {nonce} }"#).await?,
+            serde_json::json!({
+                "data": {
+                    "currentFrame": [{
+                      "nonce": 0
+                    }]
+                }
+            })
+        );
+        Ok(())
+    }
+}
diff --git a/gql/src/queries/first_utxos_of_scripts.rs b/gql/src/queries/first_utxos_of_scripts.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a8d3c1bd20f33cd0f593548b2cce539bdd9595f9
--- /dev/null
+++ b/gql/src/queries/first_utxos_of_scripts.rs
@@ -0,0 +1,65 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Default)]
+pub(crate) struct FirstUtxosQuery;
+#[async_graphql::Object]
+impl FirstUtxosQuery {
+    /// First utxos of scripts
+    async fn first_utxos_of_scripts(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+        #[graphql(
+            desc = "DUBP wallets scripts",
+            validator(ListMaxLength(length = "100"))
+        )]
+        scripts: Vec<PkOrScriptGva>,
+        #[graphql(
+            desc = "Number of first utxos to get ",
+            default = 10,
+            validator(IntRange(min = "1", max = "40"))
+        )]
+        first: i32,
+    ) -> async_graphql::Result<Vec<UtxosGva>> {
+        let scripts: Vec<WalletScriptV10> = scripts.into_iter().map(|script| script.0).collect();
+
+        let data = ctx.data::<GvaSchemaData>()?;
+        let db_reader = data.dbs_reader();
+
+        let utxos_matrice: Vec<arrayvec::ArrayVec<_>> = data
+            .dbs_pool
+            .execute(move |_| db_reader.first_scripts_utxos(None, first as usize, &scripts))
+            .await??;
+
+        Ok(utxos_matrice
+            .into_iter()
+            .map(|utxos| {
+                UtxosGva(
+                    utxos
+                        .into_iter()
+                        .map(|utxo| UtxoGva {
+                            amount: utxo.amount.amount(),
+                            base: utxo.amount.base(),
+                            tx_hash: utxo.tx_hash.to_hex(),
+                            output_index: utxo.output_index as u32,
+                        })
+                        .collect(),
+                )
+            })
+            .collect())
+    }
+}
diff --git a/gql/src/queries/gen_tx.rs b/gql/src/queries/gen_tx.rs
new file mode 100644
index 0000000000000000000000000000000000000000..7f4a1b981ad287327517c7274b11117e9dece5ef
--- /dev/null
+++ b/gql/src/queries/gen_tx.rs
@@ -0,0 +1,277 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+use dubp::{
+    crypto::bases::BaseConversionError,
+    documents::transaction::v10_gen::{TransactionDocV10ComplexGen, TxV10ComplexIssuer},
+    documents::transaction::UnsignedTransactionDocumentTrait,
+};
+use duniter_core::dbs::smallvec::SmallVec;
+
+struct TxIssuerTyped {
+    script: WalletScriptV10,
+    signers: SmallVec<[PublicKey; 1]>,
+    codes: SmallVec<[String; 1]>,
+    amount: i32,
+}
+impl TryFrom<TxIssuer> for TxIssuerTyped {
+    type Error = async_graphql::Error;
+
+    fn try_from(input: TxIssuer) -> async_graphql::Result<Self> {
+        let codes = if let Some(codes) = input.codes {
+            codes.into_iter().collect()
+        } else {
+            SmallVec::new()
+        };
+        let signers: SmallVec<[PublicKey; 1]> = input
+            .signers
+            .iter()
+            .map(|s| PublicKey::from_base58(s))
+            .collect::<Result<_, BaseConversionError>>()?;
+        let script = if let Some(ref script_str) = input.script {
+            dubp::documents_parser::wallet_script_from_str(script_str)?
+        } else if signers.len() <= 3 && codes.is_empty() {
+            match signers.len() {
+                1 => WalletScriptV10::single(WalletConditionV10::Sig(signers[0])),
+                2 => WalletScriptV10::and(
+                    WalletConditionV10::Sig(signers[0]),
+                    WalletConditionV10::Sig(signers[1]),
+                ),
+                3 => WalletScriptV10::and_and(
+                    WalletConditionV10::Sig(signers[0]),
+                    WalletConditionV10::Sig(signers[1]),
+                    WalletConditionV10::Sig(signers[2]),
+                ),
+                _ => unreachable!(),
+            }
+        } else {
+            return Err(async_graphql::Error::new("missing a issuer script"));
+        };
+        Ok(Self {
+            script,
+            signers,
+            codes,
+            amount: input.amount,
+        })
+    }
+}
+struct TxRecipientTyped {
+    amount: i32,
+    script: WalletScriptV10,
+}
+impl TryFrom<TxRecipient> for TxRecipientTyped {
+    type Error = async_graphql::Error;
+
+    fn try_from(input: TxRecipient) -> async_graphql::Result<Self> {
+        let script = dubp::documents_parser::wallet_script_from_str(&input.script)?;
+        Ok(Self {
+            amount: input.amount,
+            script,
+        })
+    }
+}
+
+#[derive(Default)]
+pub(crate) struct GenTxsQuery;
+#[async_graphql::Object]
+impl GenTxsQuery {
+    #[allow(clippy::too_many_arguments)]
+    /// Generate simple transaction document
+    async fn gen_tx(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+        #[graphql(desc = "Transaction amount", validator(IntGreaterThan(value = "0")))] amount: i32,
+        #[graphql(
+            desc = "Cash back address, equal to issuer address by default (Ed25519 public key on base 58 representation)"
+        )]
+        cash_back_address: Option<PubKeyGva>,
+        #[graphql(desc = "Transaction comment", validator(TxCommentValidator))] comment: Option<
+            String,
+        >,
+        issuer: PubKeyGva,
+        #[graphql(desc = "Recipient address")] recipient: PkOrScriptGva,
+        #[graphql(desc = "Use mempool sources", default = false)] use_mempool_sources: bool,
+    ) -> async_graphql::Result<Vec<String>> {
+        let comment = comment.unwrap_or_default();
+        let issuer = issuer.0;
+        let recipient = recipient.0;
+
+        let data = ctx.data::<GvaSchemaData>()?;
+        let db_reader = data.dbs_reader();
+        let currency = data.server_meta_data.currency.clone();
+
+        if let Some(current_block_meta) = data
+            .cm_accessor
+            .get_current_meta(|cm| cm.current_block_meta)
+            .await
+        {
+            let (inputs, inputs_sum) = data
+                .dbs_pool
+                .execute(move |dbs| {
+                    db_reader.find_inputs(
+                        &dbs.bc_db_ro,
+                        &dbs.txs_mp_db,
+                        SourceAmount::new(amount as i64, current_block_meta.unit_base as i64),
+                        &WalletScriptV10::single(WalletConditionV10::Sig(issuer)),
+                        use_mempool_sources,
+                    )
+                })
+                .await??;
+
+            let amount = SourceAmount::new(amount as i64, current_block_meta.unit_base as i64);
+
+            if inputs_sum < amount {
+                return Err(async_graphql::Error::new("insufficient balance"));
+            }
+
+            let current_blockstamp = Blockstamp {
+                number: BlockNumber(current_block_meta.number),
+                hash: BlockHash(current_block_meta.hash),
+            };
+
+            Ok(TransactionDocumentV10::generate_simple_txs(
+                current_blockstamp,
+                currency,
+                (inputs, inputs_sum),
+                issuer,
+                recipient,
+                (amount, comment),
+                cash_back_address.map(|pubkey_gva| pubkey_gva.0),
+            )
+            .into_iter()
+            .map(|tx| tx.as_text().to_owned())
+            .collect())
+        } else {
+            Err(async_graphql::Error::new("no blockchain"))
+        }
+    }
+    /// Generate complex transaction document
+    async fn gen_complex_tx(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+        #[graphql(desc = "Transaction issuers")] issuers: Vec<TxIssuer>,
+        #[graphql(desc = "Transaction recipients")] recipients: Vec<TxRecipient>,
+        #[graphql(desc = "Transaction comment", validator(TxCommentValidator))] comment: Option<
+            String,
+        >,
+        #[graphql(desc = "Use mempool sources", default = false)] use_mempool_sources: bool,
+    ) -> async_graphql::Result<RawTxOrChanges> {
+        let comment = comment.unwrap_or_default();
+        let issuers = issuers
+            .into_iter()
+            .map(TryFrom::try_from)
+            .collect::<async_graphql::Result<Vec<TxIssuerTyped>>>()?;
+        let recipients = recipients
+            .into_iter()
+            .map(TryFrom::try_from)
+            .collect::<async_graphql::Result<Vec<TxRecipientTyped>>>()?;
+
+        let issuers_sum: i32 = issuers.iter().map(|issuer| issuer.amount).sum();
+        let recipients_sum: i32 = recipients.iter().map(|recipient| recipient.amount).sum();
+        if issuers_sum != recipients_sum {
+            return Err(async_graphql::Error::new(
+            "The sum of the amounts of the issuers must be equal to the sum of the amounts of the recipients.",
+        ));
+        }
+
+        let data = ctx.data::<GvaSchemaData>()?;
+        let db_reader = data.dbs_reader();
+        let currency = data.server_meta_data.currency.clone();
+
+        if let Some(current_block_meta) = data
+            .cm_accessor
+            .get_current_meta(|cm| cm.current_block_meta)
+            .await
+        {
+            let issuers_inputs_with_sum = data
+                .dbs_pool
+                .execute(move |dbs| {
+                    let mut issuers_inputs_with_sum = Vec::new();
+                    for issuer in issuers {
+                        issuers_inputs_with_sum.push((
+                            db_reader.find_inputs(
+                                &dbs.bc_db_ro,
+                                &dbs.txs_mp_db,
+                                SourceAmount::new(
+                                    issuer.amount as i64,
+                                    current_block_meta.unit_base as i64,
+                                ),
+                                &issuer.script,
+                                use_mempool_sources,
+                            )?,
+                            issuer,
+                        ));
+                    }
+                    Ok::<_, anyhow::Error>(issuers_inputs_with_sum)
+                })
+                .await??;
+
+            for ((_inputs, inputs_sum), issuer) in &issuers_inputs_with_sum {
+                let amount =
+                    SourceAmount::new(issuer.amount as i64, current_block_meta.unit_base as i64);
+                if *inputs_sum < amount {
+                    return Err(async_graphql::Error::new(format!(
+                        "Insufficient balance for issuer {}",
+                        issuer.script.to_string()
+                    )));
+                }
+            }
+
+            let current_blockstamp = Blockstamp {
+                number: BlockNumber(current_block_meta.number),
+                hash: BlockHash(current_block_meta.hash),
+            };
+            let base = current_block_meta.unit_base as i64;
+
+            let (final_tx_opt, changes_txs) = TransactionDocV10ComplexGen {
+                blockstamp: current_blockstamp,
+                currency,
+                issuers: issuers_inputs_with_sum
+                    .into_iter()
+                    .map(|((inputs, inputs_sum), issuer)| TxV10ComplexIssuer {
+                        amount: SourceAmount::new(issuer.amount as i64, base),
+                        codes: issuer.codes,
+                        inputs,
+                        inputs_sum,
+                        script: issuer.script,
+                        signers: issuer.signers,
+                    })
+                    .collect(),
+                recipients: recipients
+                    .into_iter()
+                    .map(|TxRecipientTyped { amount, script }| {
+                        (SourceAmount::new(amount as i64, base), script)
+                    })
+                    .collect(),
+                user_comment: comment,
+            }
+            .gen()?;
+
+            if let Some(final_tx) = final_tx_opt {
+                Ok(RawTxOrChanges::FinalTx(final_tx.as_text().to_owned()))
+            } else {
+                Ok(RawTxOrChanges::Changes(
+                    changes_txs
+                        .into_iter()
+                        .map(|tx| tx.as_text().to_owned())
+                        .collect(),
+                ))
+            }
+        } else {
+            Err(async_graphql::Error::new("no blockchain"))
+        }
+    }
+}
diff --git a/gql/src/queries/idty.rs b/gql/src/queries/idty.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b2117f6895378917a30b2f1bfddbac55ed4ac5bc
--- /dev/null
+++ b/gql/src/queries/idty.rs
@@ -0,0 +1,81 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Default)]
+pub(crate) struct IdtyQuery;
+#[async_graphql::Object]
+impl IdtyQuery {
+    /// Get identity by public key
+    async fn idty(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+        #[graphql(desc = "public key")] pubkey: PubKeyGva,
+    ) -> async_graphql::Result<Option<Identity>> {
+        let data = ctx.data::<GvaSchemaData>()?;
+        let dbs_reader = data.dbs_reader();
+
+        Ok(data
+            .dbs_pool
+            .execute(move |dbs| dbs_reader.idty(&dbs.bc_db_ro, pubkey.0))
+            .await??
+            .map(|idty| Identity {
+                is_member: idty.is_member,
+                username: idty.username,
+            }))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::tests::*;
+
+    #[tokio::test]
+    async fn test_idty() -> anyhow::Result<()> {
+        let mut dbs_reader = MockDbsReader::new();
+        dbs_reader
+            .expect_idty()
+            .withf(|_, s| {
+                s == &PublicKey::from_base58("DnjL6hYA1k7FavGHbbir79PKQbmzw63d6bsamBBdUULP")
+                    .expect("wrong pubkey")
+            })
+            .times(1)
+            .returning(|_, _| {
+                Ok(Some(duniter_core::dbs::IdtyDbV2 {
+                    is_member: true,
+                    username: String::from("JohnDoe"),
+                }))
+            });
+        let schema = create_schema(MockAsyncAccessor::new(), dbs_reader)?;
+        assert_eq!(
+            exec_graphql_request(
+                &schema,
+                r#"{ idty(pubkey: "DnjL6hYA1k7FavGHbbir79PKQbmzw63d6bsamBBdUULP") {isMember, username} }"#
+            )
+            .await?,
+            serde_json::json!({
+                "data": {
+                    "idty": {
+                        "isMember": true,
+                        "username": "JohnDoe"
+                    }
+                }
+            })
+        );
+        Ok(())
+    }
+}
diff --git a/gql/src/queries/network.rs b/gql/src/queries/network.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5b4d1a0b04bc074fa26f2a3c85dceff52b0719f2
--- /dev/null
+++ b/gql/src/queries/network.rs
@@ -0,0 +1,144 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Default, async_graphql::SimpleObject)]
+pub(crate) struct NetworkQuery {
+    network: NetworkQueryInner,
+}
+
+#[derive(Default)]
+pub(crate) struct NetworkQueryInner;
+
+#[async_graphql::Object]
+impl NetworkQueryInner {
+    /// Get endpoints known by the node
+    async fn endpoints(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+        #[graphql(
+            desc = "filter endpoints by api (exact match endpoint first word, case sensitive)"
+        )]
+        api_list: Vec<String>,
+    ) -> async_graphql::Result<Vec<String>> {
+        let data = ctx.data::<GvaSchemaData>()?;
+        let dbs_reader = data.dbs_reader();
+
+        Ok(data
+            .dbs_pool
+            .execute(move |dbs| dbs_reader.endpoints(&dbs.dunp_db, api_list))
+            .await??)
+    }
+    /// Get peers and heads
+    async fn nodes(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+    ) -> async_graphql::Result<Vec<PeerWithHeads>> {
+        let data = ctx.data::<GvaSchemaData>()?;
+
+        let db_reader = data.dbs_reader();
+
+        Ok(data
+            .dbs_pool
+            .execute(move |dbs| db_reader.peers_and_heads(&dbs.dunp_db))
+            .await??
+            .into_iter()
+            .map(|(peer, heads)| PeerWithHeads {
+                peer: PeerCardGva::from(peer),
+                heads: heads.into_iter().map(HeadGva::from).collect(),
+            })
+            .collect())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::tests::*;
+    use duniter_core::dbs::databases::network_v1::NetworkV1Db;
+    use pretty_assertions::assert_eq;
+
+    #[tokio::test]
+    async fn test_endpoints() -> anyhow::Result<()> {
+        let mock_cm = MockAsyncAccessor::new();
+        let mut mock_dbs_reader = MockDbsReader::new();
+        mock_dbs_reader
+            .expect_endpoints::<NetworkV1Db<FileBackend>>()
+            .times(1)
+            .returning(|_, _| {
+                Ok(vec![
+                    "GVA S g1.librelois.fr 443 gva".to_owned(),
+                    "GVA S domain.tld 443 gva".to_owned(),
+                ])
+            });
+        let schema = create_schema(mock_cm, mock_dbs_reader)?;
+        assert_eq!(
+            exec_graphql_request(&schema, r#"{ network { endpoints(apiList:["GVA"]) } }"#).await?,
+            serde_json::json!({
+                "data": {
+                    "network": {
+                        "endpoints": [
+                            "GVA S g1.librelois.fr 443 gva",
+                            "GVA S domain.tld 443 gva"
+                        ]
+                    }
+                }
+            })
+        );
+        Ok(())
+    }
+
+    #[tokio::test]
+    async fn test_peers_and_heads() -> anyhow::Result<()> {
+        let mut dbs_reader = MockDbsReader::new();
+        dbs_reader
+            .expect_peers_and_heads::<NetworkV1Db<FileBackend>>()
+            .times(1)
+            .returning(|_| {
+                Ok(vec![(
+                    duniter_core::dbs::PeerCardDbV1::default(),
+                    vec![duniter_core::dbs::DunpHeadDbV1::default()],
+                )])
+            });
+        let schema = create_schema(MockAsyncAccessor::new(), dbs_reader)?;
+        assert_eq!(
+            exec_graphql_request(
+                &schema,
+                r#"{ network { nodes { peer { blockstamp }, heads { blockstamp } } } }"#
+            )
+            .await?,
+            serde_json::json!({
+                "data": {
+                    "network": {
+                        "nodes": [
+                            {
+                                "heads": [
+                                    {
+                                        "blockstamp": "0-0000000000000000000000000000000000000000000000000000000000000000"
+                                    }
+                                ],
+                                "peer": {
+                                    "blockstamp": ""
+                                }
+                            }
+                        ],
+                    }
+                }
+            })
+        );
+        Ok(())
+    }
+}
diff --git a/gql/src/queries/txs_history.rs b/gql/src/queries/txs_history.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3cf5bfe93b2b0941d84dc27897555dceb4f7b3ed
--- /dev/null
+++ b/gql/src/queries/txs_history.rs
@@ -0,0 +1,345 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::inputs::TimeInterval;
+use crate::*;
+use duniter_gva_db::GvaTxDbV1;
+use duniter_gva_dbs_reader::txs_history::TxBcCursor;
+use futures::future::join;
+
+#[derive(Default)]
+pub(crate) struct TxsHistoryBlockchainQuery;
+
+#[async_graphql::Object]
+impl TxsHistoryBlockchainQuery {
+    /// Transactions history (written in blockchain)
+    async fn txs_history_bc(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+        #[graphql(desc = "pagination", default)] pagination: Pagination,
+        script: PkOrScriptGva,
+        #[graphql(default)] time_interval: TimeInterval,
+    ) -> async_graphql::Result<TxsHistoryBlockchainQueryInner> {
+        let QueryContext { is_whitelisted } = ctx.data::<QueryContext>()?;
+        let pagination = Pagination::convert_to_page_info(pagination, *is_whitelisted)?;
+        let script_hash = Hash::compute(script.0.to_string().as_bytes());
+        Ok(TxsHistoryBlockchainQueryInner {
+            pagination,
+            script_hash,
+            time_interval,
+        })
+    }
+}
+
+pub(crate) struct TxsHistoryBlockchainQueryInner {
+    pub(crate) pagination: PageInfo<TxBcCursor>,
+    pub(crate) script_hash: Hash,
+    pub(crate) time_interval: TimeInterval,
+}
+
+#[async_graphql::Object]
+impl TxsHistoryBlockchainQueryInner {
+    /// Transactions history (written in blockchain)
+    async fn both(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+    ) -> async_graphql::Result<Connection<String, TxGva, EmptyFields, EdgeTx>> {
+        let start_time = std::time::Instant::now();
+
+        let data = ctx.data::<GvaSchemaData>()?;
+
+        let db_reader = data.dbs_reader();
+        let pagination = self.pagination;
+        let script_hash = self.script_hash;
+        let time_interval = self.time_interval;
+        let sent_fut = data.dbs_pool.execute(move |_| {
+            db_reader.get_txs_history_bc_sent(
+                time_interval.from,
+                pagination,
+                script_hash,
+                time_interval.to,
+            )
+        });
+        let db_reader = data.dbs_reader();
+        let script_hash = self.script_hash;
+        let received_fut = data.dbs_pool.execute(move |_| {
+            db_reader.get_txs_history_bc_received(
+                time_interval.from,
+                pagination,
+                script_hash,
+                time_interval.to,
+            )
+        });
+        let (sent_res, received_res) = join(sent_fut, received_fut).await;
+        let (sent, received) = (sent_res??, received_res??);
+
+        let mut both_txs = sent
+            .data
+            .into_iter()
+            .map(|db_tx| (TxDirection::Sent, db_tx))
+            .chain(
+                received
+                    .data
+                    .into_iter()
+                    .map(|db_tx| (TxDirection::Received, db_tx)),
+            )
+            .collect::<Vec<(TxDirection, GvaTxDbV1)>>();
+        /*if let Some(TxBcCursor { tx_hash, .. }) = pagination.pos() {
+            while both.txs
+        }*/
+        if self.pagination.order() {
+            both_txs.sort_unstable_by(|(_, db_tx1), (_, db_tx2)| {
+                db_tx1
+                    .written_block
+                    .number
+                    .cmp(&db_tx2.written_block.number)
+            });
+        } else {
+            both_txs.sort_unstable_by(|(_, db_tx1), (_, db_tx2)| {
+                db_tx2
+                    .written_block
+                    .number
+                    .cmp(&db_tx1.written_block.number)
+            });
+        }
+        if let Some(limit) = self.pagination.limit_opt() {
+            both_txs.truncate(limit.get());
+        }
+        let mut conn = Connection::new(
+            sent.has_previous_page || received.has_previous_page,
+            sent.has_next_page || received.has_next_page,
+        );
+        conn.append(both_txs.into_iter().map(|(tx_direction, db_tx)| {
+            Edge::with_additional_fields(
+                TxBcCursor {
+                    block_number: db_tx.written_block.number,
+                    tx_hash: db_tx.tx.get_hash(),
+                }
+                .to_string(),
+                db_tx.into(),
+                EdgeTx {
+                    direction: tx_direction,
+                },
+            )
+        }));
+
+        println!(
+            "txs_history_bc::both duration: {}ms",
+            start_time.elapsed().as_millis()
+        );
+
+        Ok(conn)
+    }
+    /// Received transactions history (written in blockchain)
+    async fn received(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+    ) -> async_graphql::Result<Connection<String, TxGva, EmptyFields, EmptyFields>> {
+        let data = ctx.data::<GvaSchemaData>()?;
+        let db_reader = data.dbs_reader();
+        let pagination = self.pagination;
+        let script_hash = self.script_hash;
+        let time_interval = self.time_interval;
+        let received = data
+            .dbs_pool
+            .execute(move |_| {
+                db_reader.get_txs_history_bc_received(
+                    time_interval.from,
+                    pagination,
+                    script_hash,
+                    time_interval.to,
+                )
+            })
+            .await??;
+        let mut conn = Connection::new(received.has_previous_page, received.has_next_page);
+        conn.append(received.data.into_iter().map(|db_tx| {
+            Edge::new(
+                TxBcCursor {
+                    block_number: db_tx.written_block.number,
+                    tx_hash: db_tx.tx.get_hash(),
+                }
+                .to_string(),
+                db_tx.into(),
+            )
+        }));
+
+        Ok(conn)
+    }
+    /// Sent transactions history (written in blockchain)
+    async fn sent(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+    ) -> async_graphql::Result<Connection<String, TxGva, EmptyFields, EmptyFields>> {
+        let data = ctx.data::<GvaSchemaData>()?;
+        let db_reader = data.dbs_reader();
+        let pagination = self.pagination;
+        let script_hash = self.script_hash;
+        let time_interval = self.time_interval;
+        let sent = data
+            .dbs_pool
+            .execute(move |_| {
+                db_reader.get_txs_history_bc_sent(
+                    time_interval.from,
+                    pagination,
+                    script_hash,
+                    time_interval.to,
+                )
+            })
+            .await??;
+        let mut conn = Connection::new(sent.has_previous_page, sent.has_next_page);
+        conn.append(sent.data.into_iter().map(|db_tx| {
+            Edge::new(
+                TxBcCursor {
+                    block_number: db_tx.written_block.number,
+                    tx_hash: db_tx.tx.get_hash(),
+                }
+                .to_string(),
+                db_tx.into(),
+            )
+        }));
+
+        Ok(conn)
+    }
+}
+
+#[derive(Default)]
+pub(crate) struct TxsHistoryMempoolQuery;
+
+#[async_graphql::Object]
+impl TxsHistoryMempoolQuery {
+    /// Transactions waiting on mempool
+    async fn txs_history_mp(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+        #[graphql(desc = "Ed25519 public key on base 58 representation")] pubkey: PubKeyGva,
+    ) -> async_graphql::Result<TxsHistoryMempool> {
+        let data = ctx.data::<GvaSchemaData>()?;
+        let db_reader = data.dbs_reader();
+
+        let (sending, pending) = data
+            .dbs_pool
+            .execute(move |dbs| db_reader.get_txs_history_mempool(&dbs.txs_mp_db, pubkey.0))
+            .await??;
+
+        Ok(TxsHistoryMempool {
+            sending: sending
+                .into_iter()
+                .map(|db_tx| TxGva::from(&db_tx))
+                .collect(),
+            receiving: pending
+                .into_iter()
+                .map(|db_tx| TxGva::from(&db_tx))
+                .collect(),
+        })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::collections::VecDeque;
+
+    use crate::tests::*;
+    use dubp::documents::transaction::TransactionDocumentV10;
+    use dubp::documents::transaction::TransactionDocumentV10Stringified;
+    use dubp::documents_parser::prelude::FromStringObject;
+    use duniter_gva_db::GvaTxDbV1;
+    use duniter_gva_dbs_reader::pagination::PagedData;
+
+    #[tokio::test]
+    async fn test_txs_history_blockchain() -> anyhow::Result<()> {
+        let mut dbs_reader = MockDbsReader::new();
+        dbs_reader
+            .expect_get_txs_history_bc_received()
+            .times(1)
+            .returning(|_, _, _, _| Ok(PagedData::empty()));
+        dbs_reader
+            .expect_get_txs_history_bc_sent()
+            .times(1)
+            .returning(|_, _, _, _| {
+                let tx = TransactionDocumentV10::from_string_object(
+                    &TransactionDocumentV10Stringified {
+                        currency: "test".to_owned(),
+                        blockstamp:
+                            "0-0000000000000000000000000000000000000000000000000000000000000000"
+                                .to_owned(),
+                        locktime: 0,
+                        issuers: vec![],
+                        inputs: vec![],
+                        unlocks: vec![],
+                        outputs: vec![],
+                        comment: "".to_owned(),
+                        signatures: vec![],
+                        hash: Some(
+                            "0000000000000000000000000000000000000000000000000000000000000000"
+                                .to_owned(),
+                        ),
+                    },
+                )
+                .expect("wrong tx");
+                let mut expected_data = VecDeque::new();
+                expected_data.push_back(GvaTxDbV1 {
+                    tx,
+                    ..Default::default()
+                });
+                Ok(PagedData {
+                    data: expected_data,
+                    has_previous_page: false,
+                    has_next_page: false,
+                })
+            });
+        let schema = create_schema(MockAsyncAccessor::new(), dbs_reader)?;
+        assert_eq!(
+            exec_graphql_request(
+                &schema,
+                r#"{
+                txsHistoryBc(script: "D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx") {
+                    sent {
+                        edges {
+                            node {
+                                blockstamp
+                            }
+                        }
+                    }
+                    received {
+                        edges {
+                            node {
+                                blockstamp
+                            }
+                        }
+                    }
+                }
+              }"#
+            )
+            .await?,
+            serde_json::json!({
+                "data": {
+                    "txsHistoryBc": {
+                        "received": {
+                            "edges": []
+                        },
+                        "sent": {
+                            "edges": [{
+                                "node": {
+                                    "blockstamp": "0-0000000000000000000000000000000000000000000000000000000000000000",
+                                }
+                            }]
+                        }
+                    }
+                  }
+            })
+        );
+        Ok(())
+    }
+}
diff --git a/gql/src/queries/uds.rs b/gql/src/queries/uds.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b24f90c8682cc777108d22f37cd55e4c1b91e2af
--- /dev/null
+++ b/gql/src/queries/uds.rs
@@ -0,0 +1,185 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+use async_graphql::connection::*;
+use duniter_core::dbs::databases::bc_v2::BcV2DbReadable;
+use duniter_gva_dbs_reader::{uds_of_pubkey::UdsWithSum, PagedData};
+
+#[derive(Default)]
+pub(crate) struct UdsQuery;
+#[async_graphql::Object]
+impl UdsQuery {
+    /// Current universal dividends amount
+    async fn current_ud(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+    ) -> async_graphql::Result<Option<CurrentUdGva>> {
+        let data = ctx.data::<GvaSchemaData>()?;
+
+        Ok(
+            if let Some(current_ud) = data.cm_accessor.get_current_meta(|cm| cm.current_ud).await {
+                Some(CurrentUdGva {
+                    amount: current_ud.amount(),
+                    base: current_ud.base(),
+                })
+            } else {
+                None
+            },
+        )
+    }
+    /// Universal dividends issued by a public key
+    #[allow(clippy::clippy::too_many_arguments)]
+    async fn uds_of_pubkey(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+        #[graphql(desc = "Ed25519 public key on base 58 representation")] pubkey: PubKeyGva,
+        #[graphql(default)] filter: UdsFilter,
+        #[graphql(desc = "pagination", default)] pagination: Pagination,
+        #[graphql(desc = "Amount needed")] amount: Option<i64>,
+    ) -> async_graphql::Result<Connection<String, UdGva, AggregateSum, EmptyFields>> {
+        let QueryContext { is_whitelisted } = ctx.data::<QueryContext>()?;
+        let pagination = Pagination::convert_to_page_info(pagination, *is_whitelisted)?;
+
+        let data = ctx.data::<GvaSchemaData>()?;
+        let dbs_reader = data.dbs_reader();
+
+        if let Some(current_base) = data
+            .cm_accessor
+            .get_current_meta(|cm| cm.current_block_meta.unit_base)
+            .await
+        {
+            let (
+                PagedData {
+                    data: UdsWithSum { uds, sum },
+                    has_previous_page,
+                    has_next_page,
+                },
+                times,
+            ) = data
+                .dbs_pool
+                .execute(move |dbs| {
+                    let paged_data = match filter {
+                        UdsFilter::All => {
+                            dbs_reader.all_uds_of_pubkey(&dbs.bc_db_ro, pubkey.0, pagination)
+                        }
+                        UdsFilter::Unspent => dbs_reader.unspent_uds_of_pubkey(
+                            &dbs.bc_db_ro,
+                            pubkey.0,
+                            pagination,
+                            None,
+                            amount.map(|amount| SourceAmount::new(amount, current_base as i64)),
+                        ),
+                    }?;
+
+                    let mut times = Vec::with_capacity(paged_data.data.uds.len());
+                    for (bn, _sa) in &paged_data.data.uds {
+                        times.push(dbs_reader.get_blockchain_time(*bn)?);
+                    }
+                    Ok::<_, anyhow::Error>((paged_data, times))
+                })
+                .await??;
+
+            let mut conn = Connection::with_additional_fields(
+                has_previous_page,
+                has_next_page,
+                AggregateSum {
+                    aggregate: Sum {
+                        sum: AmountWithBase {
+                            amount: sum.amount() as i32,
+                            base: sum.base() as i32,
+                        },
+                    },
+                },
+            );
+            let uds_timed =
+                uds.into_iter()
+                    .zip(times.into_iter())
+                    .map(|((bn, sa), blockchain_time)| {
+                        Edge::new(
+                            bn.0.to_string(),
+                            UdGva {
+                                amount: sa.amount(),
+                                base: sa.base(),
+                                issuer: pubkey,
+                                block_number: bn.0,
+                                blockchain_time,
+                            },
+                        )
+                    });
+            if pagination.order() {
+                conn.append(uds_timed);
+            } else {
+                conn.append(uds_timed.rev());
+            }
+            Ok(conn)
+        } else {
+            Err(async_graphql::Error::new("no blockchain"))
+        }
+    }
+    /// Universal dividends revaluations
+    async fn uds_reval(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+    ) -> async_graphql::Result<Vec<RevalUdGva>> {
+        let data = ctx.data::<GvaSchemaData>()?;
+
+        Ok(data
+            .dbs_pool
+            .execute(move |dbs| {
+                dbs.bc_db_ro.uds_reval().iter(.., |it| {
+                    it.map_ok(|(block_number, sa)| RevalUdGva {
+                        amount: sa.0.amount(),
+                        base: sa.0.base(),
+                        block_number: block_number.0,
+                    })
+                    .collect::<KvResult<Vec<_>>>()
+                })
+            })
+            .await??)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::tests::*;
+
+    #[tokio::test]
+    async fn query_current_ud() -> anyhow::Result<()> {
+        let mut mock_cm = MockAsyncAccessor::new();
+        mock_cm
+            .expect_get_current_meta::<SourceAmount>()
+            .times(1)
+            .returning(|f| {
+                Some(f(&CurrentMeta {
+                    current_ud: SourceAmount::with_base0(100),
+                    ..Default::default()
+                }))
+            });
+        let schema = create_schema(mock_cm, MockDbsReader::new())?;
+        assert_eq!(
+            exec_graphql_request(&schema, r#"{ currentUd {amount} }"#).await?,
+            serde_json::json!({
+                "data": {
+                    "currentUd": {
+                      "amount": 100
+                    }
+                }
+            })
+        );
+        Ok(())
+    }
+}
diff --git a/gql/src/queries/utxos_of_script.rs b/gql/src/queries/utxos_of_script.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c3a3039e5f374ab070eb51642aba24f2bff37f1e
--- /dev/null
+++ b/gql/src/queries/utxos_of_script.rs
@@ -0,0 +1,103 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+use async_graphql::connection::*;
+use duniter_gva_dbs_reader::{
+    utxos::{UtxoCursor, UtxosWithSum},
+    PagedData,
+};
+
+#[derive(Default)]
+pub(crate) struct UtxosQuery;
+#[async_graphql::Object]
+impl UtxosQuery {
+    /// Utxos of script
+    async fn utxos_of_script(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+        #[graphql(desc = "DUBP wallet script")] script: PkOrScriptGva,
+        #[graphql(desc = "pagination", default)] pagination: Pagination,
+        #[graphql(desc = "Amount needed")] amount: Option<i64>,
+    ) -> async_graphql::Result<Connection<String, UtxoTimedGva, AggregateSum, EmptyFields>> {
+        let QueryContext { is_whitelisted } = ctx.data::<QueryContext>()?;
+        log::info!("is_whitelisted={}", is_whitelisted);
+        let pagination = Pagination::convert_to_page_info(pagination, *is_whitelisted)?;
+
+        let data = ctx.data::<GvaSchemaData>()?;
+        let cm_accessor = data.cm_accessor();
+        let db_reader = data.dbs_reader();
+
+        if let Some(current_base) = cm_accessor
+            .get_current_meta(|cm| cm.current_block_meta.unit_base)
+            .await
+        {
+            let (
+                PagedData {
+                    data: UtxosWithSum { utxos, sum },
+                    has_previous_page,
+                    has_next_page,
+                },
+                times,
+            ) = data
+                .dbs_pool
+                .execute(move |dbs| {
+                    let paged_data = db_reader.find_script_utxos(
+                        &dbs.txs_mp_db,
+                        amount.map(|amount| SourceAmount::new(amount, current_base as i64)),
+                        pagination,
+                        &script.0,
+                    )?;
+                    let mut times = Vec::with_capacity(paged_data.data.utxos.len());
+                    for (UtxoCursor { block_number, .. }, _sa) in &paged_data.data.utxos {
+                        times.push(db_reader.get_blockchain_time(*block_number)?);
+                    }
+                    Ok::<_, anyhow::Error>((paged_data, times))
+                })
+                .await??;
+
+            let mut conn = Connection::with_additional_fields(
+                has_previous_page,
+                has_next_page,
+                AggregateSum {
+                    aggregate: Sum {
+                        sum: AmountWithBase {
+                            amount: sum.amount() as i32,
+                            base: sum.base() as i32,
+                        },
+                    },
+                },
+            );
+            conn.append(utxos.into_iter().zip(times.into_iter()).map(
+                |((utxo_cursor, source_amount), blockchain_time)| {
+                    Edge::new(
+                        utxo_cursor.to_string(),
+                        UtxoTimedGva {
+                            amount: source_amount.amount(),
+                            base: source_amount.base(),
+                            tx_hash: utxo_cursor.tx_hash.to_hex(),
+                            output_index: utxo_cursor.output_index as u32,
+                            written_block: utxo_cursor.block_number.0,
+                            written_time: blockchain_time,
+                        },
+                    )
+                },
+            ));
+            Ok(conn)
+        } else {
+            Err(async_graphql::Error::new("no blockchain"))
+        }
+    }
+}
diff --git a/gql/src/scalars.rs b/gql/src/scalars.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f58a79ba540f72b6b54648e70aa92799a1e14cec
--- /dev/null
+++ b/gql/src/scalars.rs
@@ -0,0 +1,83 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+use async_graphql::{InputValueError, InputValueResult, Scalar, ScalarType};
+use dubp::crypto::bases::b58::ToBase58;
+
+#[derive(Clone, Copy, Debug)]
+pub(crate) struct PubKeyGva(pub(crate) PublicKey);
+
+impl async_graphql::Description for PubKeyGva {
+    fn description() -> &'static str {
+        "Public key on base 58 representation"
+    }
+}
+
+#[Scalar(use_type_description = true)]
+impl ScalarType for PubKeyGva {
+    fn parse(value: async_graphql::Value) -> InputValueResult<Self> {
+        if let async_graphql::Value::String(value_str) = &value {
+            if value_str.len() < 40 {
+                Err(InputValueError::custom("too short public key"))
+            } else if value_str.len() > 44 {
+                Err(InputValueError::custom("too long public key"))
+            } else {
+                Ok(PublicKey::from_base58(value_str).map(PubKeyGva)?)
+            }
+        } else {
+            // If the type does not match
+            Err(InputValueError::expected_type(value))
+        }
+    }
+
+    fn to_value(&self) -> async_graphql::Value {
+        async_graphql::Value::String(self.0.to_base58())
+    }
+}
+
+pub(crate) struct PkOrScriptGva(pub(crate) WalletScriptV10);
+
+impl async_graphql::Description for PkOrScriptGva {
+    fn description() -> &'static str {
+        "Public key on base 58 representation or complex DUBP script"
+    }
+}
+
+#[Scalar(use_type_description = true)]
+impl ScalarType for PkOrScriptGva {
+    fn parse(value: async_graphql::Value) -> InputValueResult<Self> {
+        if let async_graphql::Value::String(value_str) = &value {
+            Ok(PkOrScriptGva(
+                if value_str.len() >= 40 || value_str.len() <= 44 {
+                    if let Ok(pubkey) = PublicKey::from_base58(&value_str) {
+                        WalletScriptV10::single_sig(pubkey)
+                    } else {
+                        dubp::documents_parser::wallet_script_from_str(&value_str)?
+                    }
+                } else {
+                    dubp::documents_parser::wallet_script_from_str(&value_str)?
+                },
+            ))
+        } else {
+            // If the type does not match
+            Err(InputValueError::expected_type(value))
+        }
+    }
+
+    fn to_value(&self) -> async_graphql::Value {
+        async_graphql::Value::String(self.0.to_string())
+    }
+}
diff --git a/gql/src/schema.rs b/gql/src/schema.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c5ab4f6de9ad96279bb7019c3747e68a08a1e44a
--- /dev/null
+++ b/gql/src/schema.rs
@@ -0,0 +1,74 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+pub type GvaSchema = async_graphql::Schema<
+    crate::queries::QueryRoot,
+    crate::mutations::MutationRoot,
+    crate::subscriptions::SubscriptionRoot,
+>;
+
+pub fn get_schema_definition() -> String {
+    async_graphql::Schema::build(
+        queries::QueryRoot::default(),
+        mutations::MutationRoot::default(),
+        subscriptions::SubscriptionRoot::default(),
+    )
+    .finish()
+    .sdl()
+}
+
+pub fn build_schema_with_data(data: GvaSchemaData, logger: bool) -> GvaSchema {
+    let mut builder = async_graphql::Schema::build(
+        queries::QueryRoot::default(),
+        mutations::MutationRoot::default(),
+        subscriptions::SubscriptionRoot::default(),
+    )
+    .data(data);
+    if logger {
+        builder = builder.extension(async_graphql::extensions::Logger);
+    }
+    builder.finish()
+}
+
+pub struct GvaSchemaData {
+    pub cm_accessor: AsyncAccessor,
+    pub dbs_pool: fast_threadpool::ThreadPoolAsyncHandler<SharedDbs<FileBackend>>,
+    pub dbs_reader: DbsReaderImpl,
+    pub server_meta_data: ServerMetaData,
+    pub txs_mempool: TxsMempool,
+}
+
+#[cfg(not(test))]
+impl GvaSchemaData {
+    #[inline(always)]
+    pub fn cm_accessor(&self) -> AsyncAccessor {
+        self.cm_accessor
+    }
+    #[inline(always)]
+    pub fn dbs_reader(&self) -> DbsReaderImpl {
+        self.dbs_reader
+    }
+}
+#[cfg(test)]
+impl GvaSchemaData {
+    pub fn cm_accessor(&self) -> AsyncAccessor {
+        self.cm_accessor.clone()
+    }
+    pub fn dbs_reader(&self) -> DbsReaderImpl {
+        self.dbs_reader.clone()
+    }
+}
diff --git a/gql/src/subscriptions.rs b/gql/src/subscriptions.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4a5dafaa66de4908d35155e8b553c5709dc79f9e
--- /dev/null
+++ b/gql/src/subscriptions.rs
@@ -0,0 +1,62 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+mod new_blocks;
+mod receive_pending_txs;
+
+use crate::*;
+use futures::future::Either;
+
+#[derive(Clone, Copy, Default, async_graphql::MergedSubscription)]
+pub struct SubscriptionRoot(
+    new_blocks::NewBlocksSubscription,
+    receive_pending_txs::PendingTxsSubscription,
+);
+
+pub(crate) async fn create_subscription<C, D, E, F, FC, FUT>(
+    ctx: &async_graphql::Context<'_>,
+    select_col: FC,
+    f: F,
+) -> impl Stream<Item = async_graphql::Result<D>>
+where
+    C: DbCollectionRo<Event = E, K = E::K, V = E::V>,
+    E: EventTrait,
+    F: FnMut(Arc<Events<E>>) -> FUT,
+    FUT: std::future::Future<Output = Option<async_graphql::Result<D>>>,
+    FC: 'static + Send + FnOnce(&SharedDbs<FileBackend>) -> &C,
+{
+    match subscribe_to_col(ctx, select_col).await {
+        Ok(r) => Either::Left(r.into_stream().filter_map(f)),
+        Err(e) => {
+            use futures::FutureExt;
+            Either::Right(futures::future::ready(Err(e)).into_stream())
+        }
+    }
+}
+
+async fn subscribe_to_col<C, E, F>(
+    ctx: &async_graphql::Context<'_>,
+    f: F,
+) -> async_graphql::Result<flume::Receiver<Arc<Events<E>>>>
+where
+    C: DbCollectionRo<Event = E, K = E::K, V = E::V>,
+    E: EventTrait,
+    F: 'static + Send + FnOnce(&SharedDbs<FileBackend>) -> &C,
+{
+    let data = ctx.data::<GvaSchemaData>()?;
+    let (s, r) = flume::unbounded();
+    data.dbs_pool.execute(|dbs| f(dbs).subscribe(s)).await??;
+    Ok(r)
+}
diff --git a/gql/src/subscriptions/new_blocks.rs b/gql/src/subscriptions/new_blocks.rs
new file mode 100644
index 0000000000000000000000000000000000000000..dbbec972b8509509c45db340a54044addcd14e02
--- /dev/null
+++ b/gql/src/subscriptions/new_blocks.rs
@@ -0,0 +1,51 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use super::create_subscription;
+use crate::*;
+use duniter_core::dbs::databases::cm_v1::{CmV1DbReadable, CurrentBlockEvent};
+
+#[derive(Clone, Copy, Default)]
+pub struct NewBlocksSubscription;
+
+#[async_graphql::Subscription]
+impl NewBlocksSubscription {
+    async fn new_blocks(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+    ) -> impl Stream<Item = async_graphql::Result<Vec<Block>>> {
+        create_subscription(
+            ctx,
+            |dbs| dbs.cm_db.current_block(),
+            |events| {
+                let mut blocks = Vec::new();
+                for event in events.deref() {
+                    if let CurrentBlockEvent::Upsert {
+                        value: ref block, ..
+                    } = event
+                    {
+                        blocks.push(Block::from(&block.0));
+                    }
+                }
+                if blocks.is_empty() {
+                    futures::future::ready(None)
+                } else {
+                    futures::future::ready(Some(Ok(blocks)))
+                }
+            },
+        )
+        .await
+    }
+}
diff --git a/gql/src/subscriptions/receive_pending_txs.rs b/gql/src/subscriptions/receive_pending_txs.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d223f7c65f6daa9d4c8f4e89e6d13f96c5b84a30
--- /dev/null
+++ b/gql/src/subscriptions/receive_pending_txs.rs
@@ -0,0 +1,52 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use super::create_subscription;
+use crate::*;
+use duniter_core::dbs::databases::txs_mp_v2::TxsEvent;
+
+#[derive(Clone, Copy, Default)]
+pub struct PendingTxsSubscription;
+
+#[async_graphql::Subscription]
+impl PendingTxsSubscription {
+    async fn receive_pending_txs(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+    ) -> impl Stream<Item = async_graphql::Result<Vec<TxGva>>> {
+        create_subscription(
+            ctx,
+            |dbs| dbs.txs_mp_db.txs(),
+            |events| {
+                let mut txs = Vec::new();
+                for event in events.deref() {
+                    if let TxsEvent::Upsert {
+                        value: ref pending_tx,
+                        ..
+                    } = event
+                    {
+                        txs.push(TxGva::from(&pending_tx.0));
+                    }
+                }
+                if txs.is_empty() {
+                    futures::future::ready(None)
+                } else {
+                    futures::future::ready(Some(Ok(txs)))
+                }
+            },
+        )
+        .await
+    }
+}
diff --git a/indexer/Cargo.toml b/indexer/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..59a390a1f1b030ff10f3d34716143e7d04c44cf4
--- /dev/null
+++ b/indexer/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "duniter-gva-indexer"
+version = "0.1.0"
+authors = ["elois <elois@duniter.org>"]
+description = "Duniter GVA DB writer"
+repository = "https://git.duniter.org/nodes/typescript/duniter"
+keywords = ["dubp", "duniter", "blockchain", "database"]
+license = "AGPL-3.0"
+edition = "2018"
+
+[lib]
+path = "src/lib.rs"
+
+[dependencies]
+anyhow = "1.0.34"
+duniter-core = { git = "https://git.duniter.org/nodes/rust/duniter-core" }
+duniter-gva-db = { path = "../db" }
+dubp = { version = "0.51.0", features = ["duniter"] }
+once_cell = "1.5.2"
+resiter = "0.4.0"
+
+[dev-dependencies]
+duniter-core = { git = "https://git.duniter.org/nodes/rust/duniter-core", features = ["mem"] }
+maplit = "1.0.2"
+smallvec = { version = "1.4.0", features = ["serde", "write"] }
diff --git a/indexer/src/identities.rs b/indexer/src/identities.rs
new file mode 100644
index 0000000000000000000000000000000000000000..0a33b376b0113412977700cc337c5eda5b586b5b
--- /dev/null
+++ b/indexer/src/identities.rs
@@ -0,0 +1,80 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+pub(crate) fn update_identities<B: Backend>(
+    block: &DubpBlockV10,
+    identities: &mut TxColRw<B::Col, GvaIdentitiesEvent>,
+) -> KvResult<()> {
+    for mb in block.joiners() {
+        let pubkey = mb.issuers()[0];
+
+        let mut idty = identities.get(&PubKeyKeyV2(pubkey))?.unwrap_or_default();
+        idty.is_member = true;
+        idty.joins.push(block.number());
+        identities.upsert(PubKeyKeyV2(pubkey), idty);
+    }
+    for revo in block.revoked() {
+        let pubkey = revo.issuer;
+        if let Some(mut idty) = identities.get(&PubKeyKeyV2(pubkey))? {
+            idty.is_member = false;
+            idty.leaves.insert(block.number());
+            identities.upsert(PubKeyKeyV2(pubkey), idty)
+        }
+    }
+    for pubkey in block.excluded().iter().copied() {
+        if let Some(mut idty) = identities.get(&PubKeyKeyV2(pubkey))? {
+            idty.is_member = false;
+            idty.leaves.insert(block.number());
+            identities.upsert(PubKeyKeyV2(pubkey), idty)
+        }
+    }
+    Ok(())
+}
+
+pub(crate) fn revert_identities<B: Backend>(
+    block: &DubpBlockV10,
+    identities: &mut TxColRw<B::Col, GvaIdentitiesEvent>,
+) -> KvResult<()> {
+    for mb in block.joiners() {
+        let pubkey = mb.issuers()[0];
+
+        let mut idty = identities.get(&PubKeyKeyV2(pubkey))?.unwrap_or_default();
+        idty.is_member = false;
+        idty.joins.pop();
+        identities.upsert(PubKeyKeyV2(pubkey), idty);
+    }
+    for idty in block.identities() {
+        let pubkey = idty.issuers()[0];
+        identities.remove(PubKeyKeyV2(pubkey));
+    }
+    for revo in block.revoked() {
+        let pubkey = revo.issuer;
+        if let Some(mut idty) = identities.get(&PubKeyKeyV2(pubkey))? {
+            idty.is_member = true;
+            idty.leaves.remove(&block.number());
+            identities.upsert(PubKeyKeyV2(pubkey), idty)
+        }
+    }
+    for pubkey in block.excluded().iter().copied() {
+        if let Some(mut idty) = identities.get(&PubKeyKeyV2(pubkey))? {
+            idty.is_member = true;
+            idty.leaves.remove(&block.number());
+            identities.upsert(PubKeyKeyV2(pubkey), idty)
+        }
+    }
+    Ok(())
+}
diff --git a/indexer/src/lib.rs b/indexer/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..8578580faf16a4a1d3e60e16b3624128207dba88
--- /dev/null
+++ b/indexer/src/lib.rs
@@ -0,0 +1,593 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+#![deny(
+    clippy::unwrap_used,
+    missing_copy_implementations,
+    trivial_casts,
+    trivial_numeric_casts,
+    unstable_features,
+    unused_import_braces
+)]
+
+mod identities;
+mod tx;
+mod utxos;
+
+use dubp::block::prelude::*;
+use dubp::common::crypto::hashs::Hash;
+use dubp::common::prelude::*;
+use dubp::documents::{
+    prelude::*, transaction::TransactionDocumentTrait, transaction::TransactionDocumentV10,
+};
+use dubp::wallet::prelude::*;
+use duniter_core::dbs::{
+    kv_typed::prelude::*, prelude::*, FileBackend, HashKeyV2, PubKeyKeyV2, SourceAmountValV2,
+    WalletConditionsV2,
+};
+use duniter_gva_db::*;
+use resiter::filter::Filter;
+use std::{
+    collections::{BTreeSet, HashMap},
+    path::Path,
+};
+
+static GVA_DB_RO: once_cell::sync::OnceCell<GvaV1DbRo<FileBackend>> =
+    once_cell::sync::OnceCell::new();
+static GVA_DB_RW: once_cell::sync::OnceCell<GvaV1Db<FileBackend>> =
+    once_cell::sync::OnceCell::new();
+
+pub fn get_gva_db_ro(profile_path_opt: Option<&Path>) -> &'static GvaV1DbRo<FileBackend> {
+    GVA_DB_RO.get_or_init(|| get_gva_db_rw(profile_path_opt).get_ro_handler())
+}
+pub fn get_gva_db_rw(profile_path_opt: Option<&Path>) -> &'static GvaV1Db<FileBackend> {
+    GVA_DB_RW.get_or_init(|| {
+        duniter_gva_db::GvaV1Db::<FileBackend>::open(FileBackend::gen_backend_conf(
+            "gva_v1",
+            profile_path_opt,
+        ))
+        .expect("Fail to open GVA DB")
+    })
+}
+
+pub struct UtxoV10<'s> {
+    pub id: UtxoIdV10,
+    pub amount: SourceAmount,
+    pub script: &'s WalletScriptV10,
+    pub written_block: BlockNumber,
+}
+
+pub fn apply_block<B: Backend>(block: &DubpBlockV10, gva_db: &GvaV1Db<B>) -> KvResult<()> {
+    let blockstamp = Blockstamp {
+        number: block.number(),
+        hash: block.hash(),
+    };
+    gva_db.write(|mut db| {
+        db.blocks_by_common_time
+            .upsert(U64BE(block.common_time()), block.number().0);
+        db.blockchain_time
+            .upsert(U32BE(block.number().0), block.common_time());
+        identities::update_identities::<B>(&block, &mut db.gva_identities)?;
+        if let Some(divident_amount) = block.dividend() {
+            db.blocks_with_ud.upsert(U32BE(blockstamp.number.0), ());
+            apply_ud::<B>(
+                blockstamp.number,
+                divident_amount,
+                &mut db.balances,
+                &mut db.gva_identities,
+            )?;
+        }
+        apply_block_txs::<B>(
+            &mut db,
+            blockstamp,
+            block.common_time() as i64,
+            block.transactions(),
+        )
+    })?;
+
+    Ok(())
+}
+
+pub fn revert_block<B: Backend>(block: &DubpBlockV10, gva_db: &GvaV1Db<B>) -> KvResult<()> {
+    gva_db.write(|mut db| {
+        db.blocks_by_common_time.remove(U64BE(block.common_time()));
+        db.blockchain_time.remove(U32BE(block.number().0));
+        identities::revert_identities::<B>(&block, &mut db.gva_identities)?;
+        if let Some(divident_amount) = block.dividend() {
+            db.blocks_with_ud.remove(U32BE(block.number().0));
+            revert_ud::<B>(
+                block.number(),
+                divident_amount,
+                &mut db.balances,
+                &mut db.gva_identities,
+            )?;
+        }
+
+        let mut scripts_hash = HashMap::with_capacity(block.transactions().len() * 3);
+        for tx in block.transactions() {
+            let tx_hash = tx.get_hash();
+            tx::revert_tx::<B>(block.number(), &mut db, &mut scripts_hash, &tx_hash)?.ok_or_else(
+                || {
+                    KvError::DbCorrupted(format!(
+                        "GVA: tx '{}' dont exist on txs history.",
+                        tx_hash,
+                    ))
+                },
+            )?;
+        }
+        db.txs_by_block.remove(U32BE(block.number().0));
+        Ok(())
+    })?;
+
+    Ok(())
+}
+
+fn apply_ud<B: Backend>(
+    block_number: BlockNumber,
+    divident_amount: SourceAmount,
+    balances: &mut TxColRw<B::Col, BalancesEvent>,
+    identities: &mut TxColRw<B::Col, GvaIdentitiesEvent>,
+) -> KvResult<()> {
+    let members = identities.iter(.., |it| {
+        it.filter_ok(|(_pk, idty)| idty.is_member)
+            .collect::<KvResult<Vec<_>>>()
+    })?;
+    for (pk, mut idty) in members {
+        if idty.first_ud.is_none() {
+            idty.first_ud = Some(block_number);
+            identities.upsert(pk, idty);
+        }
+
+        // Increase account balance
+        let account_script = WalletScriptV10::single_sig(pk.0);
+        let balance = balances
+            .get(WalletConditionsV2::from_ref(&account_script))?
+            .unwrap_or_default();
+        balances.upsert(
+            WalletConditionsV2(account_script),
+            SourceAmountValV2(balance.0 + divident_amount),
+        );
+    }
+    Ok(())
+}
+
+fn revert_ud<B: Backend>(
+    block_number: BlockNumber,
+    divident_amount: SourceAmount,
+    balances: &mut TxColRw<B::Col, BalancesEvent>,
+    identities: &mut TxColRw<B::Col, GvaIdentitiesEvent>,
+) -> KvResult<()> {
+    let members = identities.iter(.., |it| {
+        it.filter_ok(|(_pk, idty)| idty.is_member)
+            .collect::<KvResult<Vec<_>>>()
+    })?;
+    for (pk, mut idty) in members {
+        if let Some(first_ud) = idty.first_ud {
+            if first_ud == block_number {
+                idty.first_ud = None;
+                identities.upsert(pk, idty);
+            }
+        }
+
+        // Increase account balance
+        let account_script = WalletScriptV10::single_sig(pk.0);
+        if let Some(SourceAmountValV2(balance)) =
+            balances.get(WalletConditionsV2::from_ref(&account_script))?
+        {
+            balances.upsert(
+                WalletConditionsV2(account_script),
+                SourceAmountValV2(balance - divident_amount),
+            );
+        }
+    }
+    Ok(())
+}
+
+fn apply_block_txs<B: Backend>(
+    gva_db: &mut GvaV1DbTxRw<B::Col>,
+    current_blockstamp: Blockstamp,
+    current_time: i64,
+    txs: &[TransactionDocumentV10],
+) -> KvResult<()> {
+    let mut scripts_index = HashMap::new();
+    let mut txs_by_issuer_mem = HashMap::new();
+    let mut txs_by_recipient_mem = HashMap::new();
+    let mut txs_hashes = Vec::with_capacity(txs.len());
+    for tx in txs {
+        let tx_hash = tx.get_hash();
+        txs_hashes.push(tx_hash);
+        // Write tx and update sources
+        tx::apply_tx::<B>(
+            current_blockstamp,
+            current_time,
+            gva_db,
+            &mut scripts_index,
+            tx_hash,
+            tx,
+            &mut txs_by_issuer_mem,
+            &mut txs_by_recipient_mem,
+        )?;
+    }
+
+    if !txs_hashes.is_empty() {
+        gva_db
+            .txs_by_block
+            .upsert(U32BE(current_blockstamp.number.0), txs_hashes);
+    }
+    for (k, v) in txs_by_issuer_mem {
+        gva_db.txs_by_issuer.upsert(k, v);
+    }
+    for (k, v) in txs_by_recipient_mem {
+        gva_db.txs_by_recipient.upsert(k, v);
+    }
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use dubp::{
+        crypto::keys::{ed25519::PublicKey, PublicKey as _},
+        documents::transaction::TransactionDocumentV10Stringified,
+        documents_parser::prelude::FromStringObject,
+    };
+
+    #[test]
+    fn test_gva_apply_block() -> anyhow::Result<()> {
+        let gva_db = GvaV1Db::<Mem>::open(MemConf::default())?;
+
+        let s1 = WalletScriptV10::single_sig(PublicKey::from_base58(
+            "D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx",
+        )?);
+        let s2 = WalletScriptV10::single_sig(PublicKey::from_base58(
+            "4fHMTFBMo5sTQEc5p1CNWz28S4mnnqdUBmECq1zt4n2m",
+        )?);
+
+        let b0 = DubpBlockV10::from_string_object(&DubpBlockV10Stringified {
+            version: 10,
+            median_time: 5_243,
+            dividend: Some(1000),
+            joiners: vec!["D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:FFeyrvYio9uYwY5aMcDGswZPNjGLrl8THn9l3EPKSNySD3SDSHjCljSfFEwb87sroyzJQoVzPwER0sW/cbZMDg==:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:elois".to_owned()],
+            inner_hash: Some("0000000A65A12DB95B3153BCD05DB4D5C30CC7F0B1292D9FFBC3DE67F72F6040".to_owned()),
+            signature: "7B0hvcfajE2G8nBLp0vLVaQcQdQIyli21Gu8F2l+nimKHRe+fUNi+MWd1e/u29BYZa+RZ1yxhbHIbFzytg7fAA==".to_owned(),
+            hash: Some("0000000000000000000000000000000000000000000000000000000000000000".to_owned()),
+            ..Default::default()
+        })?;
+
+        apply_block(&b0, &gva_db)?;
+
+        assert_eq!(gva_db.blocks_by_common_time().count()?, 1);
+        assert_eq!(gva_db.blocks_by_common_time().get(&U64BE(5_243))?, Some(0));
+        assert_eq!(gva_db.blockchain_time().count()?, 1);
+        assert_eq!(gva_db.blockchain_time().get(&U32BE(0))?, Some(5_243));
+        assert_eq!(gva_db.balances().count()?, 1);
+        assert_eq!(
+            gva_db.balances().get(&WalletConditionsV2(s1.clone()))?,
+            Some(SourceAmountValV2(SourceAmount::with_base0(1000)))
+        );
+        assert_eq!(gva_db.txs_by_block().count()?, 0);
+
+        let b1 = DubpBlockV10::from_string_object(&DubpBlockV10Stringified {
+            number: 1,
+            version: 10,
+            median_time: 5_245,
+            transactions: vec![TransactionDocumentV10Stringified {
+                currency: "test".to_owned(),
+                blockstamp: "0-0000000000000000000000000000000000000000000000000000000000000000".to_owned(),
+                locktime: 0,
+                issuers: vec!["D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx".to_owned()],
+                inputs: vec!["1000:0:D:D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:0".to_owned()],
+                unlocks: vec![],
+                outputs: vec![
+                    "600:0:SIG(4fHMTFBMo5sTQEc5p1CNWz28S4mnnqdUBmECq1zt4n2m)".to_owned(),
+                    "400:0:SIG(D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx)".to_owned(),
+                ],
+                comment: "".to_owned(),
+                signatures: vec![],
+                hash: Some("0000000000000000000000000000000000000000000000000000000000000000".to_owned()),
+            }],
+            inner_hash: Some("0000000A65A12DB95B3153BCD05DB4D5C30CC7F0B1292D9FFBC3DE67F72F6040".to_owned()),
+            signature: "7B0hvcfajE2G8nBLp0vLVaQcQdQIyli21Gu8F2l+nimKHRe+fUNi+MWd1e/u29BYZa+RZ1yxhbHIbFzytg7fAA==".to_owned(),
+            hash: Some("0000000000000000000000000000000000000000000000000000000000000000".to_owned()),
+            ..Default::default()
+        })?;
+
+        apply_block(&b1, &gva_db)?;
+
+        assert_eq!(gva_db.blocks_by_common_time().count()?, 2);
+        assert_eq!(gva_db.blocks_by_common_time().get(&U64BE(5_245))?, Some(1));
+        assert_eq!(gva_db.blockchain_time().count()?, 2);
+        assert_eq!(gva_db.blockchain_time().get(&U32BE(1))?, Some(5_245));
+        assert_eq!(gva_db.balances().count()?, 2);
+        assert_eq!(
+            gva_db.balances().get(&WalletConditionsV2(s2.clone()))?,
+            Some(SourceAmountValV2(SourceAmount::with_base0(600)))
+        );
+        assert_eq!(
+            gva_db.balances().get(&WalletConditionsV2(s1.clone()))?,
+            Some(SourceAmountValV2(SourceAmount::with_base0(400)))
+        );
+        assert_eq!(gva_db.gva_utxos().count()?, 2);
+        assert_eq!(
+            gva_db
+                .gva_utxos()
+                .iter(.., |it| it.collect::<KvResult<Vec<_>>>())?,
+            vec![
+                (
+                    GvaUtxoIdDbV1::new(s1.clone(), 1, Hash::default(), 1),
+                    SourceAmountValV2(SourceAmount::with_base0(400))
+                ),
+                (
+                    GvaUtxoIdDbV1::new(s2.clone(), 1, Hash::default(), 0),
+                    SourceAmountValV2(SourceAmount::with_base0(600))
+                ),
+            ]
+        );
+        assert_eq!(gva_db.txs_by_block().count()?, 1);
+        assert_eq!(
+            gva_db.txs_by_block().get(&U32BE(1))?,
+            Some(vec![Hash::from_hex(
+                "0000000000000000000000000000000000000000000000000000000000000000"
+            )?])
+        );
+
+        let b2 = DubpBlockV10::from_string_object(&DubpBlockV10Stringified {
+            number: 2,
+            version: 10,
+            median_time: 5_247,
+            transactions: vec![TransactionDocumentV10Stringified {
+                currency: "test".to_owned(),
+                blockstamp: "0-0000000000000000000000000000000000000000000000000000000000000000".to_owned(),
+                locktime: 0,
+                issuers: vec!["D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx".to_owned()],
+                inputs: vec!["400:0:T:0000000000000000000000000000000000000000000000000000000000000000:1".to_owned()],
+                unlocks: vec![],
+                outputs: vec![
+                    "300:0:SIG(D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx)".to_owned(),
+                    "100:0:SIG(4fHMTFBMo5sTQEc5p1CNWz28S4mnnqdUBmECq1zt4n2m)".to_owned(),
+                ],
+                comment: "".to_owned(),
+                signatures: vec![],
+                hash: Some("0101010101010101010101010101010101010101010101010101010101010101".to_owned()),
+            }],
+            inner_hash: Some("0000000A65A12DB95B3153BCD05DB4D5C30CC7F0B1292D9FFBC3DE67F72F6040".to_owned()),
+            signature: "7B0hvcfajE2G8nBLp0vLVaQcQdQIyli21Gu8F2l+nimKHRe+fUNi+MWd1e/u29BYZa+RZ1yxhbHIbFzytg7fAA==".to_owned(),
+            hash: Some("0000000000000000000000000000000000000000000000000000000000000000".to_owned()),
+            ..Default::default()
+        })?;
+
+        apply_block(&b2, &gva_db)?;
+
+        assert_eq!(gva_db.blocks_by_common_time().count()?, 3);
+        assert_eq!(gva_db.blocks_by_common_time().get(&U64BE(5_247))?, Some(2));
+        assert_eq!(gva_db.blockchain_time().count()?, 3);
+        assert_eq!(gva_db.blockchain_time().get(&U32BE(2))?, Some(5_247));
+        assert_eq!(gva_db.balances().count()?, 2);
+        assert_eq!(
+            gva_db.balances().get(&WalletConditionsV2(s2.clone()))?,
+            Some(SourceAmountValV2(SourceAmount::with_base0(700)))
+        );
+        assert_eq!(
+            gva_db.balances().get(&WalletConditionsV2(s1.clone()))?,
+            Some(SourceAmountValV2(SourceAmount::with_base0(300)))
+        );
+        assert_eq!(gva_db.gva_utxos().count()?, 3);
+        assert_eq!(
+            gva_db
+                .gva_utxos()
+                .iter(.., |it| it.collect::<KvResult<Vec<_>>>())?,
+            vec![
+                (
+                    GvaUtxoIdDbV1::new(s1, 2, Hash([1; 32]), 0),
+                    SourceAmountValV2(SourceAmount::with_base0(300))
+                ),
+                (
+                    GvaUtxoIdDbV1::new(s2.clone(), 1, Hash::default(), 0),
+                    SourceAmountValV2(SourceAmount::with_base0(600))
+                ),
+                (
+                    GvaUtxoIdDbV1::new(s2, 2, Hash([1; 32]), 1),
+                    SourceAmountValV2(SourceAmount::with_base0(100))
+                ),
+            ]
+        );
+        assert_eq!(gva_db.txs_by_block().count()?, 2);
+        assert_eq!(
+            gva_db.txs_by_block().get(&U32BE(2))?,
+            Some(vec![Hash::from_hex(
+                "0101010101010101010101010101010101010101010101010101010101010101"
+            )?])
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_gva_revert_block() -> anyhow::Result<()> {
+        let gva_db = GvaV1Db::<Mem>::open(MemConf::default())?;
+
+        let s1 = WalletScriptV10::single_sig(PublicKey::from_base58(
+            "D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx",
+        )?);
+        let s2 = WalletScriptV10::single_sig(PublicKey::from_base58(
+            "4fHMTFBMo5sTQEc5p1CNWz28S4mnnqdUBmECq1zt4n2m",
+        )?);
+
+        let b0 = DubpBlockV10::from_string_object(&DubpBlockV10Stringified {
+            version: 10,
+            median_time: 5_243,
+            dividend: Some(1000),
+            joiners: vec!["D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:FFeyrvYio9uYwY5aMcDGswZPNjGLrl8THn9l3EPKSNySD3SDSHjCljSfFEwb87sroyzJQoVzPwER0sW/cbZMDg==:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:elois".to_owned()],
+            inner_hash: Some("0000000A65A12DB95B3153BCD05DB4D5C30CC7F0B1292D9FFBC3DE67F72F6040".to_owned()),
+            signature: "7B0hvcfajE2G8nBLp0vLVaQcQdQIyli21Gu8F2l+nimKHRe+fUNi+MWd1e/u29BYZa+RZ1yxhbHIbFzytg7fAA==".to_owned(),
+            hash: Some("0000000000000000000000000000000000000000000000000000000000000000".to_owned()),
+            ..Default::default()
+        })?;
+
+        apply_block(&b0, &gva_db)?;
+
+        let b1 = DubpBlockV10::from_string_object(&DubpBlockV10Stringified {
+            number: 1,
+            version: 10,
+            median_time: 5_245,
+            transactions: vec![TransactionDocumentV10Stringified {
+                currency: "test".to_owned(),
+                blockstamp: "0-0000000000000000000000000000000000000000000000000000000000000000".to_owned(),
+                locktime: 0,
+                issuers: vec!["D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx".to_owned()],
+                inputs: vec!["1000:0:D:D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:0".to_owned()],
+                unlocks: vec![],
+                outputs: vec![
+                    "600:0:SIG(4fHMTFBMo5sTQEc5p1CNWz28S4mnnqdUBmECq1zt4n2m)".to_owned(),
+                    "400:0:SIG(D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx)".to_owned(),
+                ],
+                comment: "".to_owned(),
+                signatures: vec![],
+                hash: Some("0000000000000000000000000000000000000000000000000000000000000000".to_owned()),
+            }],
+            inner_hash: Some("0000000A65A12DB95B3153BCD05DB4D5C30CC7F0B1292D9FFBC3DE67F72F6040".to_owned()),
+            signature: "7B0hvcfajE2G8nBLp0vLVaQcQdQIyli21Gu8F2l+nimKHRe+fUNi+MWd1e/u29BYZa+RZ1yxhbHIbFzytg7fAA==".to_owned(),
+            hash: Some("0000000000000000000000000000000000000000000000000000000000000000".to_owned()),
+            ..Default::default()
+        })?;
+
+        apply_block(&b1, &gva_db)?;
+
+        let b2 = DubpBlockV10::from_string_object(&DubpBlockV10Stringified {
+            number: 2,
+            version: 10,
+            median_time: 5_247,
+            transactions: vec![TransactionDocumentV10Stringified {
+                currency: "test".to_owned(),
+                blockstamp: "0-0000000000000000000000000000000000000000000000000000000000000000".to_owned(),
+                locktime: 0,
+                issuers: vec!["D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx".to_owned()],
+                inputs: vec!["400:0:T:0000000000000000000000000000000000000000000000000000000000000000:1".to_owned()],
+                unlocks: vec![],
+                outputs: vec![
+                    "400:0:SIG(4fHMTFBMo5sTQEc5p1CNWz28S4mnnqdUBmECq1zt4n2m)".to_owned(),
+                ],
+                comment: "".to_owned(),
+                signatures: vec![],
+                hash: Some("0101010101010101010101010101010101010101010101010101010101010101".to_owned()),
+            }],
+            inner_hash: Some("0000000A65A12DB95B3153BCD05DB4D5C30CC7F0B1292D9FFBC3DE67F72F6040".to_owned()),
+            signature: "7B0hvcfajE2G8nBLp0vLVaQcQdQIyli21Gu8F2l+nimKHRe+fUNi+MWd1e/u29BYZa+RZ1yxhbHIbFzytg7fAA==".to_owned(),
+            hash: Some("0000000000000000000000000000000000000000000000000000000000000000".to_owned()),
+            ..Default::default()
+        })?;
+
+        apply_block(&b2, &gva_db)?;
+
+        let b3 = DubpBlockV10::from_string_object(&DubpBlockV10Stringified {
+            number: 3,
+            version: 10,
+            median_time: 5_249,
+            transactions: vec![TransactionDocumentV10Stringified {
+                currency: "test".to_owned(),
+                blockstamp: "0-0000000000000000000000000000000000000000000000000000000000000000".to_owned(),
+                locktime: 0,
+                issuers: vec!["4fHMTFBMo5sTQEc5p1CNWz28S4mnnqdUBmECq1zt4n2m".to_owned()],
+                inputs: vec!["400:0:T:0101010101010101010101010101010101010101010101010101010101010101:0".to_owned()],
+                unlocks: vec![],
+                outputs: vec![
+                    "400:0:SIG(D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx)".to_owned(),
+                ],
+                comment: "".to_owned(),
+                signatures: vec![],
+                hash: Some("0202020202020202020202020202020202020202020202020202020202020202".to_owned()),
+            }],
+            inner_hash: Some("0000000A65A12DB95B3153BCD05DB4D5C30CC7F0B1292D9FFBC3DE67F72F6040".to_owned()),
+            signature: "7B0hvcfajE2G8nBLp0vLVaQcQdQIyli21Gu8F2l+nimKHRe+fUNi+MWd1e/u29BYZa+RZ1yxhbHIbFzytg7fAA==".to_owned(),
+            hash: Some("0000000000000000000000000000000000000000000000000000000000000000".to_owned()),
+            ..Default::default()
+        })?;
+
+        apply_block(&b3, &gva_db)?;
+
+        revert_block(&b3, &gva_db)?;
+
+        assert_eq!(gva_db.blockchain_time().count()?, 3);
+        assert_eq!(gva_db.blockchain_time().get(&U32BE(2))?, Some(5_247));
+        assert_eq!(gva_db.balances().count()?, 2);
+        assert_eq!(
+            gva_db.balances().get(&WalletConditionsV2(s1.clone()))?,
+            Some(SourceAmountValV2(SourceAmount::ZERO))
+        );
+        assert_eq!(
+            gva_db.balances().get(&WalletConditionsV2(s2.clone()))?,
+            Some(SourceAmountValV2(SourceAmount::with_base0(1_000)))
+        );
+        assert_eq!(gva_db.gva_utxos().count()?, 2);
+        assert_eq!(
+            gva_db
+                .gva_utxos()
+                .iter(.., |it| it.collect::<KvResult<Vec<_>>>())?,
+            vec![
+                (
+                    GvaUtxoIdDbV1::new(s2.clone(), 1, Hash::default(), 0),
+                    SourceAmountValV2(SourceAmount::with_base0(600))
+                ),
+                (
+                    GvaUtxoIdDbV1::new(s2.clone(), 2, Hash([1u8; 32]), 0),
+                    SourceAmountValV2(SourceAmount::with_base0(400))
+                ),
+            ]
+        );
+
+        revert_block(&b2, &gva_db)?;
+
+        assert_eq!(gva_db.blockchain_time().count()?, 2);
+        assert_eq!(gva_db.blockchain_time().get(&U32BE(1))?, Some(5_245));
+        assert_eq!(gva_db.balances().count()?, 2);
+        assert_eq!(
+            gva_db.balances().get(&WalletConditionsV2(s2.clone()))?,
+            Some(SourceAmountValV2(SourceAmount::with_base0(600)))
+        );
+        assert_eq!(
+            gva_db.balances().get(&WalletConditionsV2(s1.clone()))?,
+            Some(SourceAmountValV2(SourceAmount::with_base0(400)))
+        );
+        assert_eq!(gva_db.gva_utxos().count()?, 2);
+        assert_eq!(
+            gva_db
+                .gva_utxos()
+                .iter(.., |it| it.collect::<KvResult<Vec<_>>>())?,
+            vec![
+                (
+                    GvaUtxoIdDbV1::new(s1.clone(), 1, Hash::default(), 1),
+                    SourceAmountValV2(SourceAmount::with_base0(400))
+                ),
+                (
+                    GvaUtxoIdDbV1::new(s2.clone(), 1, Hash::default(), 0),
+                    SourceAmountValV2(SourceAmount::with_base0(600))
+                ),
+            ]
+        );
+
+        revert_block(&b1, &gva_db)?;
+
+        assert_eq!(gva_db.blockchain_time().count()?, 1);
+        assert_eq!(gva_db.blockchain_time().get(&U32BE(0))?, Some(5_243));
+        assert_eq!(gva_db.balances().count()?, 1);
+        assert_eq!(
+            gva_db.balances().get(&WalletConditionsV2(s1))?,
+            Some(SourceAmountValV2(SourceAmount::with_base0(1000)))
+        );
+        assert_eq!(gva_db.balances().get(&WalletConditionsV2(s2))?, None);
+
+        Ok(())
+    }
+}
diff --git a/indexer/src/tx.rs b/indexer/src/tx.rs
new file mode 100644
index 0000000000000000000000000000000000000000..627d8cb2191423a781828d573c73a8183a138e08
--- /dev/null
+++ b/indexer/src/tx.rs
@@ -0,0 +1,514 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+pub(crate) type ScriptsHash = HashMap<WalletScriptV10, Hash>;
+
+fn get_script_hash(script: &WalletScriptV10, scripts_hash: &mut ScriptsHash) -> Hash {
+    if let Some(script_hash) = scripts_hash.get(script) {
+        *script_hash
+    } else {
+        let script_hash = Hash::compute(script.to_string().as_bytes());
+        scripts_hash.insert(script.clone(), script_hash);
+        script_hash
+    }
+}
+
+#[allow(clippy::too_many_arguments)]
+pub(crate) fn apply_tx<B: Backend>(
+    current_blockstamp: Blockstamp,
+    current_time: i64,
+    gva_db: &mut GvaV1DbTxRw<B::Col>,
+    scripts_hash: &mut ScriptsHash,
+    tx_hash: Hash,
+    tx: &TransactionDocumentV10,
+    txs_by_issuer_mem: &mut HashMap<WalletHashWithBnV1Db, BTreeSet<Hash>>,
+    txs_by_recipient_mem: &mut HashMap<WalletHashWithBnV1Db, BTreeSet<Hash>>,
+) -> KvResult<()> {
+    let mut issuers_scripts_hashs = BTreeSet::new();
+    for input in tx.get_inputs() {
+        let (account_script_hash, account_script) = match input.id {
+            SourceIdV10::Utxo(utxo_id) => {
+                // Get issuer script & written block
+                let db_tx_origin = gva_db
+                    .txs
+                    .get(&HashKeyV2::from_ref(&utxo_id.tx_hash))?
+                    .ok_or_else(|| {
+                        KvError::DbCorrupted(format!("Not found origin tx of uxto {}", utxo_id))
+                    })?;
+                let utxo_script = db_tx_origin.tx.get_outputs()[utxo_id.output_index]
+                    .conditions
+                    .script
+                    .clone();
+                let utxo_script_hash = get_script_hash(&utxo_script, scripts_hash);
+
+                // Remove consumed UTXOs
+                super::utxos::remove_utxo_v10::<B>(
+                    &mut gva_db.scripts_by_pubkey,
+                    &mut gva_db.gva_utxos,
+                    utxo_id,
+                    &utxo_script,
+                    utxo_script_hash,
+                    db_tx_origin.written_block.number.0,
+                )?;
+
+                // Return utxo_script with hash
+                (utxo_script_hash, utxo_script)
+            }
+            SourceIdV10::Ud(UdSourceIdV10 { issuer, .. }) => {
+                let script = WalletScriptV10::single_sig(issuer);
+                (Hash::compute(script.to_string().as_bytes()), script)
+            }
+        };
+        issuers_scripts_hashs.insert(account_script_hash);
+        // Insert on col `txs_by_issuer`
+        txs_by_issuer_mem
+            .entry(WalletHashWithBnV1Db::new(
+                account_script_hash,
+                current_blockstamp.number,
+            ))
+            .or_default()
+            .insert(tx_hash);
+        // Decrease account balance
+        decrease_account_balance::<B>(
+            account_script,
+            account_script_hash,
+            &mut gva_db.balances,
+            input.amount,
+            &mut gva_db.gva_identities,
+            false,
+            &mut gva_db.txs_by_recipient,
+        )?;
+    }
+
+    for (output_index, output) in tx.get_outputs().iter().enumerate() {
+        let utxo_script_hash = get_script_hash(&output.conditions.script, scripts_hash);
+        // Insert created UTXOs
+        super::utxos::write_utxo_v10::<B>(
+            &mut gva_db.scripts_by_pubkey,
+            &mut gva_db.gva_utxos,
+            UtxoV10 {
+                id: UtxoIdV10 {
+                    tx_hash,
+                    output_index,
+                },
+                amount: output.amount,
+                script: &output.conditions.script,
+                written_block: current_blockstamp.number,
+            },
+            utxo_script_hash,
+        )?;
+
+        // Insert on col `txs_by_recipient`
+        if !issuers_scripts_hashs.contains(&utxo_script_hash) {
+            txs_by_recipient_mem
+                .entry(WalletHashWithBnV1Db::new(
+                    utxo_script_hash,
+                    current_blockstamp.number,
+                ))
+                .or_default()
+                .insert(tx_hash);
+        }
+
+        // Increase account balance
+        let balance = gva_db
+            .balances
+            .get(WalletConditionsV2::from_ref(&output.conditions.script))?
+            .unwrap_or_default();
+        gva_db.balances.upsert(
+            WalletConditionsV2(output.conditions.script.clone()),
+            SourceAmountValV2(balance.0 + output.amount),
+        );
+    }
+
+    // Insert tx itself
+    gva_db.txs.upsert(
+        HashKeyV2(tx_hash),
+        GvaTxDbV1 {
+            tx: tx.clone(),
+            written_block: current_blockstamp,
+            written_time: current_time,
+        },
+    );
+
+    Ok(())
+}
+
+pub(crate) fn revert_tx<B: Backend>(
+    block_number: BlockNumber,
+    gva_db: &mut GvaV1DbTxRw<B::Col>,
+    scripts_hash: &mut ScriptsHash,
+    tx_hash: &Hash,
+) -> KvResult<Option<TransactionDocumentV10>> {
+    if let Some(tx_db) = gva_db.txs.get(&HashKeyV2::from_ref(tx_hash))? {
+        use dubp::documents::transaction::TransactionDocumentTrait as _;
+        for (output_index, output) in tx_db.tx.get_outputs().iter().enumerate() {
+            let script = &output.conditions.script;
+            let utxo_script_hash = get_script_hash(&script, scripts_hash);
+
+            // Remove UTXOs created by this tx
+            super::utxos::remove_utxo_v10::<B>(
+                &mut gva_db.scripts_by_pubkey,
+                &mut gva_db.gva_utxos,
+                UtxoIdV10 {
+                    tx_hash: *tx_hash,
+                    output_index,
+                },
+                script,
+                utxo_script_hash,
+                block_number.0,
+            )?;
+
+            // Remove on col `txs_by_recipient`
+            let k = WalletHashWithBnV1Db::new(utxo_script_hash, block_number);
+            gva_db.txs_by_recipient.remove(k);
+
+            // Decrease account balance
+            decrease_account_balance::<B>(
+                script.clone(),
+                utxo_script_hash,
+                &mut gva_db.balances,
+                output.amount,
+                &mut gva_db.gva_identities,
+                true,
+                &mut gva_db.txs_by_recipient,
+            )?;
+        }
+        // Recreate UTXOs consumed by this tx (and update balance)
+        for input in tx_db.tx.get_inputs() {
+            let (account_script_hash, account_script) = match input.id {
+                SourceIdV10::Utxo(utxo_id) => {
+                    let db_tx_origin = gva_db
+                        .txs
+                        .get(&HashKeyV2::from_ref(&utxo_id.tx_hash))?
+                        .ok_or_else(|| {
+                            KvError::DbCorrupted(format!("Not found origin tx of uxto {}", utxo_id))
+                        })?;
+                    let utxo_script = db_tx_origin.tx.get_outputs()[utxo_id.output_index]
+                        .conditions
+                        .script
+                        .clone();
+                    let utxo_script_hash = get_script_hash(&utxo_script, scripts_hash);
+                    super::utxos::write_utxo_v10::<B>(
+                        &mut gva_db.scripts_by_pubkey,
+                        &mut gva_db.gva_utxos,
+                        UtxoV10 {
+                            id: utxo_id,
+                            amount: input.amount,
+                            script: &utxo_script,
+                            written_block: db_tx_origin.written_block.number,
+                        },
+                        utxo_script_hash,
+                    )?;
+
+                    // Return utxo_script
+                    (utxo_script_hash, utxo_script)
+                }
+                SourceIdV10::Ud(UdSourceIdV10 { issuer, .. }) => {
+                    let script = WalletScriptV10::single_sig(issuer);
+                    (Hash::compute(script.to_string().as_bytes()), script)
+                }
+            };
+            // Remove on col `txs_by_issuer`
+            gva_db
+                .txs_by_issuer
+                .remove(WalletHashWithBnV1Db::new(account_script_hash, block_number));
+            // Increase account balance
+            let balance = gva_db
+                .balances
+                .get(WalletConditionsV2::from_ref(&account_script))?
+                .unwrap_or_default();
+
+            gva_db.balances.upsert(
+                WalletConditionsV2(account_script),
+                SourceAmountValV2(balance.0 + input.amount),
+            );
+        }
+
+        // Remove tx itself
+        gva_db.txs.remove(HashKeyV2(*tx_hash));
+
+        Ok(Some(tx_db.tx))
+    } else {
+        Ok(None)
+    }
+}
+
+fn decrease_account_balance<B: Backend>(
+    account_script: WalletScriptV10,
+    account_script_hash: Hash,
+    balances: &mut TxColRw<B::Col, BalancesEvent>,
+    decrease_amount: SourceAmount,
+    identities: &mut TxColRw<B::Col, GvaIdentitiesEvent>,
+    revert: bool,
+    txs_by_recipients: &mut TxColRw<B::Col, TxsByRecipientEvent>,
+) -> KvResult<()> {
+    if let Some(SourceAmountValV2(balance)) =
+        balances.get(WalletConditionsV2::from_ref(&account_script))?
+    {
+        let new_balance = balance - decrease_amount;
+        let remove_balance = if revert && new_balance == SourceAmount::ZERO {
+            let (k_min, k_max) = WalletHashWithBnV1Db::wallet_hash_interval(account_script_hash);
+            if txs_by_recipients
+                .iter(k_min..k_max, |it| it.keys().next_res())?
+                .is_some()
+            {
+                false
+            } else if let Some(pubkey) = account_script.as_single_sig() {
+                if let Some(idty) = identities.get(&PubKeyKeyV2(pubkey))? {
+                    idty.first_ud.is_none()
+                } else {
+                    true
+                }
+            } else {
+                true
+            }
+        } else {
+            false
+        };
+        if remove_balance {
+            balances.remove(WalletConditionsV2(account_script));
+        } else {
+            balances.upsert(
+                WalletConditionsV2(account_script),
+                SourceAmountValV2(new_balance),
+            );
+        }
+    }
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+    use dubp::{
+        crypto::keys::ed25519::Ed25519KeyPair, crypto::keys::KeyPair as _,
+        documents::smallvec::smallvec as svec, documents::transaction::v10::*,
+        documents::transaction::UTXOConditions,
+    };
+    use duniter_core::dbs::BlockMetaV2;
+    use maplit::btreeset;
+
+    #[test]
+    fn test_apply_tx() -> KvResult<()> {
+        let kp = Ed25519KeyPair::generate_random().expect("gen rand kp");
+        let kp2 = Ed25519KeyPair::generate_random().expect("gen rand kp");
+
+        let ud0_amount = SourceAmount::with_base0(1000);
+        let o1_amount = ud0_amount - SourceAmount::with_base0(600);
+        let o2_amount = ud0_amount - SourceAmount::with_base0(400);
+
+        let gva_db = GvaV1Db::<Mem>::open(MemConf::default())?;
+
+        let b0 = BlockMetaV2 {
+            dividend: Some(ud0_amount),
+            ..Default::default()
+        };
+        let current_blockstamp = b0.blockstamp();
+        let pk = kp.public_key();
+        //println!("TMP pk1={}", pk);
+        let pk2 = kp2.public_key();
+        //println!("TMP pk2={}", pk2);
+        let script = WalletScriptV10::single_sig(pk);
+        let script2 = WalletScriptV10::single_sig(pk2);
+        let script_hash = Hash::compute(script.to_string().as_bytes());
+        let script2_hash = Hash::compute(script2.to_string().as_bytes());
+
+        gva_db.balances_write().upsert(
+            WalletConditionsV2(script.clone()),
+            SourceAmountValV2(ud0_amount),
+        )?;
+
+        let tx1 = TransactionDocumentV10Builder {
+            currency: "test",
+            blockstamp: current_blockstamp,
+            locktime: 0,
+            issuers: svec![pk],
+            inputs: &[TransactionInputV10 {
+                amount: ud0_amount,
+                id: SourceIdV10::Ud(UdSourceIdV10 {
+                    issuer: pk,
+                    block_number: BlockNumber(0),
+                }),
+            }],
+            unlocks: &[TransactionInputUnlocksV10::default()],
+            outputs: svec![
+                TransactionOutputV10 {
+                    amount: o1_amount,
+                    conditions: UTXOConditions::from(script2.clone()),
+                },
+                TransactionOutputV10 {
+                    amount: o2_amount,
+                    conditions: UTXOConditions::from(script.clone()),
+                }
+            ],
+            comment: "",
+            hash: None,
+        }
+        .build_and_sign(vec![kp.generate_signator()]);
+        let tx1_hash = tx1.get_hash();
+
+        let mut scripts_hash = HashMap::new();
+
+        let mut txs_by_issuer_mem = HashMap::new();
+        let mut txs_by_recipient_mem = HashMap::new();
+        (&gva_db).write(|mut db| {
+            apply_tx::<Mem>(
+                current_blockstamp,
+                b0.median_time as i64,
+                &mut db,
+                &mut scripts_hash,
+                tx1_hash,
+                &tx1,
+                &mut txs_by_issuer_mem,
+                &mut txs_by_recipient_mem,
+            )
+        })?;
+
+        assert_eq!(txs_by_issuer_mem.len(), 1);
+        assert_eq!(
+            txs_by_issuer_mem.get(&WalletHashWithBnV1Db::new(script_hash, BlockNumber(0))),
+            Some(&btreeset![tx1_hash])
+        );
+        assert_eq!(txs_by_recipient_mem.len(), 1);
+        assert_eq!(
+            txs_by_recipient_mem.get(&WalletHashWithBnV1Db::new(script2_hash, BlockNumber(0))),
+            Some(&btreeset![tx1_hash])
+        );
+
+        assert_eq!(
+            gva_db
+                .balances()
+                .get(WalletConditionsV2::from_ref(&script2))?,
+            Some(SourceAmountValV2(o1_amount))
+        );
+        assert_eq!(
+            gva_db
+                .balances()
+                .get(WalletConditionsV2::from_ref(&script))?,
+            Some(SourceAmountValV2(o2_amount))
+        );
+
+        let tx2 = TransactionDocumentV10Builder {
+            currency: "test",
+            blockstamp: current_blockstamp,
+            locktime: 0,
+            issuers: svec![pk2],
+            inputs: &[TransactionInputV10 {
+                amount: o1_amount,
+                id: SourceIdV10::Utxo(UtxoIdV10 {
+                    tx_hash: tx1_hash,
+                    output_index: 0,
+                }),
+            }],
+            unlocks: &[TransactionInputUnlocksV10::default()],
+            outputs: svec![TransactionOutputV10 {
+                amount: o1_amount,
+                conditions: UTXOConditions::from(script.clone()),
+            },],
+            comment: "",
+            hash: None,
+        }
+        .build_and_sign(vec![kp.generate_signator()]);
+        let tx2_hash = tx2.get_hash();
+
+        let mut txs_by_issuer_mem = HashMap::new();
+        let mut txs_by_recipient_mem = HashMap::new();
+        (&gva_db).write(|mut db| {
+            apply_tx::<Mem>(
+                current_blockstamp,
+                b0.median_time as i64,
+                &mut db,
+                &mut scripts_hash,
+                tx2_hash,
+                &tx2,
+                &mut txs_by_issuer_mem,
+                &mut txs_by_recipient_mem,
+            )
+        })?;
+
+        assert_eq!(txs_by_issuer_mem.len(), 1);
+        assert_eq!(
+            txs_by_issuer_mem.get(&WalletHashWithBnV1Db::new(script2_hash, BlockNumber(0))),
+            Some(&btreeset![tx2_hash])
+        );
+        assert_eq!(txs_by_recipient_mem.len(), 1);
+        assert_eq!(
+            txs_by_recipient_mem.get(&WalletHashWithBnV1Db::new(script_hash, BlockNumber(0))),
+            Some(&btreeset![tx2_hash])
+        );
+
+        assert_eq!(
+            gva_db
+                .balances()
+                .get(WalletConditionsV2::from_ref(&script2))?,
+            Some(SourceAmountValV2(SourceAmount::ZERO))
+        );
+        assert_eq!(
+            gva_db
+                .balances()
+                .get(WalletConditionsV2::from_ref(&script))?,
+            Some(SourceAmountValV2(ud0_amount))
+        );
+
+        (&gva_db).write(|mut db| {
+            revert_tx::<Mem>(
+                current_blockstamp.number,
+                &mut db,
+                &mut scripts_hash,
+                &tx2_hash,
+            )
+        })?;
+
+        assert_eq!(
+            gva_db
+                .balances()
+                .get(WalletConditionsV2::from_ref(&script2))?,
+            Some(SourceAmountValV2(o1_amount))
+        );
+        assert_eq!(
+            gva_db
+                .balances()
+                .get(WalletConditionsV2::from_ref(&script))?,
+            Some(SourceAmountValV2(o2_amount))
+        );
+
+        (&gva_db).write(|mut db| {
+            revert_tx::<Mem>(
+                current_blockstamp.number,
+                &mut db,
+                &mut scripts_hash,
+                &tx1_hash,
+            )
+        })?;
+
+        assert_eq!(
+            gva_db
+                .balances()
+                .get(WalletConditionsV2::from_ref(&script2))?,
+            None
+        );
+        assert_eq!(
+            gva_db
+                .balances()
+                .get(WalletConditionsV2::from_ref(&script))?,
+            Some(SourceAmountValV2(ud0_amount))
+        );
+
+        Ok(())
+    }
+}
diff --git a/indexer/src/utxos.rs b/indexer/src/utxos.rs
new file mode 100644
index 0000000000000000000000000000000000000000..91ec82723ebb0c51605b05cd710739df339a15fd
--- /dev/null
+++ b/indexer/src/utxos.rs
@@ -0,0 +1,86 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+pub(crate) fn write_utxo_v10<'s, B: Backend>(
+    scripts_by_pubkey: &mut TxColRw<B::Col, ScriptsByPubkeyEvent>,
+    gva_utxos: &mut TxColRw<B::Col, GvaUtxosEvent>,
+    utxo: UtxoV10<'s>,
+    utxo_script_hash: Hash,
+) -> KvResult<()> {
+    for pubkey in utxo.script.pubkeys() {
+        let mut pubkey_scripts = scripts_by_pubkey
+            .get(&PubKeyKeyV2(pubkey))?
+            .unwrap_or_default();
+        if !pubkey_scripts.0.contains(&utxo.script) {
+            pubkey_scripts.0.insert(utxo.script.clone());
+            scripts_by_pubkey.upsert(PubKeyKeyV2(pubkey), pubkey_scripts);
+        }
+    }
+
+    let block_number = utxo.written_block.0;
+    let utxo_amount = utxo.amount;
+    let utxo_id = utxo.id;
+    gva_utxos.upsert(
+        GvaUtxoIdDbV1::new_(
+            utxo_script_hash,
+            block_number,
+            utxo_id.tx_hash,
+            utxo_id.output_index as u8,
+        ),
+        SourceAmountValV2(utxo_amount),
+    );
+
+    Ok(())
+}
+
+pub(crate) fn remove_utxo_v10<B: Backend>(
+    scripts_by_pubkey: &mut TxColRw<B::Col, ScriptsByPubkeyEvent>,
+    gva_utxos: &mut TxColRw<B::Col, GvaUtxosEvent>,
+    utxo_id: UtxoIdV10,
+    utxo_script: &WalletScriptV10,
+    utxo_script_hash: Hash,
+    written_block_number: u32,
+) -> KvResult<()> {
+    gva_utxos.remove(GvaUtxoIdDbV1::new_(
+        utxo_script_hash,
+        written_block_number,
+        utxo_id.tx_hash,
+        utxo_id.output_index as u8,
+    ));
+
+    let (k_min, k_max) = GvaUtxoIdDbV1::script_interval(utxo_script_hash);
+    if gva_utxos
+        .iter(k_min..k_max, |it| it.keys().next_res())?
+        .is_none()
+    {
+        let pubkeys = utxo_script.pubkeys();
+        for pubkey in pubkeys {
+            let mut pubkey_scripts =
+                scripts_by_pubkey
+                    .get(&PubKeyKeyV2(pubkey))?
+                    .ok_or_else(|| {
+                        KvError::DbCorrupted(format!(
+                            "GVA: key {} dont exist on col `scripts_by_pubkey`.",
+                            pubkey,
+                        ))
+                    })?;
+            pubkey_scripts.0.remove(utxo_script);
+            scripts_by_pubkey.upsert(PubKeyKeyV2(pubkey), pubkey_scripts);
+        }
+    }
+    Ok(())
+}
diff --git a/src/anti_spam.rs b/src/anti_spam.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b7609d1740f062d6dcf32608ab822c22b83ba290
--- /dev/null
+++ b/src/anti_spam.rs
@@ -0,0 +1,201 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+use async_mutex::Mutex;
+use duniter_core::dbs::kv_typed::prelude::Arc;
+use std::{
+    collections::{HashMap, HashSet},
+    net::IpAddr,
+    time::Duration,
+    time::Instant,
+};
+
+pub(super) const MAX_BATCH_SIZE: usize = 5;
+
+const COUNT_INTERVAL: usize = 10;
+const MIN_DURATION_INTERVAL: Duration = Duration::from_secs(20);
+const LARGE_DURATION_INTERVAL: Duration = Duration::from_secs(180);
+const REDUCED_COUNT_INTERVAL: usize = COUNT_INTERVAL / 2;
+const MAX_BAN_COUNT: usize = 16;
+const BAN_FORGET_MIN_DURATION: Duration = Duration::from_secs(180);
+
+#[derive(Clone)]
+pub(crate) struct AntiSpam {
+    state: Arc<Mutex<AntiSpamInner>>,
+    whitelist: HashSet<IpAddr>,
+}
+
+#[derive(Clone)]
+pub(crate) struct AntiSpamResponse {
+    pub is_whitelisted: bool,
+    pub is_ok: bool,
+}
+
+impl AntiSpamResponse {
+    fn ban() -> Self {
+        AntiSpamResponse {
+            is_whitelisted: false,
+            is_ok: false,
+        }
+    }
+    fn ok() -> Self {
+        AntiSpamResponse {
+            is_whitelisted: false,
+            is_ok: true,
+        }
+    }
+    fn whitelisted() -> Self {
+        AntiSpamResponse {
+            is_whitelisted: true,
+            is_ok: true,
+        }
+    }
+}
+
+struct AntiSpamInner {
+    ban: HashMap<IpAddr, (bool, usize, Instant)>,
+    ips_time: HashMap<IpAddr, (usize, Instant)>,
+}
+
+impl From<&GvaConf> for AntiSpam {
+    fn from(conf: &GvaConf) -> Self {
+        AntiSpam {
+            state: Arc::new(Mutex::new(AntiSpamInner {
+                ban: HashMap::with_capacity(10),
+                ips_time: HashMap::with_capacity(10),
+            })),
+            whitelist: conf.get_whitelist().iter().copied().collect(),
+        }
+    }
+}
+
+impl AntiSpam {
+    pub(crate) async fn verify(
+        &self,
+        remote_addr_opt: Option<std::net::IpAddr>,
+    ) -> AntiSpamResponse {
+        if let Some(ip) = remote_addr_opt {
+            log::trace!("GVA: receive request from {}", ip);
+            if self.whitelist.contains(&ip) {
+                AntiSpamResponse::whitelisted()
+            } else {
+                let mut guard = self.state.lock().await;
+                if let Some((is_banned, ban_count, instant)) = guard.ban.get(&ip).copied() {
+                    let ban_duration =
+                        Duration::from_secs(1 << std::cmp::min(ban_count, MAX_BAN_COUNT));
+                    if is_banned {
+                        if Instant::now().duration_since(instant) > ban_duration {
+                            guard.ban.insert(ip, (false, ban_count + 1, Instant::now()));
+                            guard.ips_time.insert(ip, (1, Instant::now()));
+                            AntiSpamResponse::ok()
+                        } else {
+                            guard.ban.insert(ip, (true, ban_count + 1, Instant::now()));
+                            AntiSpamResponse::ban()
+                        }
+                    } else if Instant::now().duration_since(instant)
+                        > std::cmp::max(ban_duration, BAN_FORGET_MIN_DURATION)
+                    {
+                        guard.ban.remove(&ip);
+                        guard.ips_time.insert(ip, (1, Instant::now()));
+                        AntiSpamResponse::ok()
+                    } else {
+                        Self::verify_interval(ip, &mut guard, ban_count)
+                    }
+                } else {
+                    Self::verify_interval(ip, &mut guard, 0)
+                }
+            }
+        } else {
+            AntiSpamResponse::ban()
+        }
+    }
+    fn verify_interval(
+        ip: IpAddr,
+        state: &mut AntiSpamInner,
+        ban_count: usize,
+    ) -> AntiSpamResponse {
+        if let Some((count, instant)) = state.ips_time.get(&ip).copied() {
+            if count == COUNT_INTERVAL {
+                let duration = Instant::now().duration_since(instant);
+                if duration > MIN_DURATION_INTERVAL {
+                    if duration > LARGE_DURATION_INTERVAL {
+                        state.ips_time.insert(ip, (1, Instant::now()));
+                        AntiSpamResponse::ok()
+                    } else {
+                        state
+                            .ips_time
+                            .insert(ip, (REDUCED_COUNT_INTERVAL, Instant::now()));
+                        AntiSpamResponse::ok()
+                    }
+                } else {
+                    state.ban.insert(ip, (true, ban_count, Instant::now()));
+                    AntiSpamResponse::ban()
+                }
+            } else {
+                state.ips_time.insert(ip, (count + 1, instant));
+                AntiSpamResponse::ok()
+            }
+        } else {
+            state.ips_time.insert(ip, (1, Instant::now()));
+            AntiSpamResponse::ok()
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::net::{Ipv4Addr, Ipv6Addr};
+
+    const LOCAL_IP4: IpAddr = IpAddr::V4(Ipv4Addr::LOCALHOST);
+    const LOCAL_IP6: IpAddr = IpAddr::V6(Ipv6Addr::LOCALHOST);
+
+    #[tokio::test]
+    async fn test_anti_spam() {
+        let anti_spam = AntiSpam::from(&GvaConf::default());
+        assert!(!anti_spam.verify(None).await.is_ok);
+
+        for _ in 0..(COUNT_INTERVAL * 2) {
+            assert!(anti_spam.verify(Some(LOCAL_IP4)).await.is_ok);
+            assert!(anti_spam.verify(Some(LOCAL_IP6)).await.is_ok);
+        }
+
+        let extern_ip = IpAddr::V4(Ipv4Addr::UNSPECIFIED);
+
+        // Consume max queries
+        for _ in 0..COUNT_INTERVAL {
+            assert!(anti_spam.verify(Some(extern_ip)).await.is_ok);
+        }
+        // Should be banned
+        assert!(!anti_spam.verify(Some(extern_ip)).await.is_ok);
+
+        // Should be un-banned after one second
+        tokio::time::sleep(Duration::from_millis(1_100)).await;
+        // Re-consume max queries
+        for _ in 0..COUNT_INTERVAL {
+            assert!(anti_spam.verify(Some(extern_ip)).await.is_ok);
+        }
+        // Should be banned for 2 seconds this time
+        tokio::time::sleep(Duration::from_millis(1_100)).await;
+        // Attempting a request when I'm banned must be twice my banning time
+        assert!(!anti_spam.verify(Some(extern_ip)).await.is_ok);
+        tokio::time::sleep(Duration::from_millis(4_100)).await;
+        // Re-consume max queries
+        for _ in 0..COUNT_INTERVAL {
+            assert!(anti_spam.verify(Some(extern_ip)).await.is_ok);
+        }
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2c560453bbce36eb242766a58ef1e184a75b345f
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,371 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+#![deny(
+    clippy::unwrap_used,
+    missing_copy_implementations,
+    trivial_casts,
+    trivial_numeric_casts,
+    unstable_features,
+    unused_import_braces
+)]
+
+mod anti_spam;
+mod warp_;
+
+pub use duniter_core::conf::gva_conf::GvaConf;
+
+use async_graphql::http::GraphQLPlaygroundConfig;
+use dubp::common::prelude::*;
+use dubp::documents::transaction::TransactionDocumentV10;
+use dubp::{block::DubpBlockV10, crypto::hashs::Hash};
+use dubp::{
+    common::crypto::keys::{ed25519::PublicKey, KeyPair as _},
+    crypto::keys::ed25519::Ed25519KeyPair,
+};
+use duniter_core::conf::DuniterMode;
+use duniter_core::dbs::databases::txs_mp_v2::TxsMpV2DbReadable;
+use duniter_core::dbs::prelude::*;
+use duniter_core::dbs::{kv_typed::prelude::*, FileBackend};
+use duniter_core::global::AsyncAccessor;
+use duniter_core::mempools::Mempools;
+use duniter_gva_db::*;
+use duniter_gva_gql::{GvaSchema, QueryContext};
+use duniter_gva_indexer::{get_gva_db_ro, get_gva_db_rw};
+use futures::{StreamExt, TryStreamExt};
+use std::{convert::Infallible, path::Path};
+use warp::{http::Response as HttpResponse, Filter as _, Rejection};
+
+#[derive(Debug)]
+pub struct GvaModule {
+    conf: Option<GvaConf>,
+    currency: String,
+    dbs_pool: fast_threadpool::ThreadPoolAsyncHandler<SharedDbs<FileBackend>>,
+    gva_db_ro: &'static GvaV1DbRo<FileBackend>,
+    mempools: Mempools,
+    mode: DuniterMode,
+    self_keypair: Ed25519KeyPair,
+    software_version: &'static str,
+}
+
+#[async_trait::async_trait]
+impl duniter_core::module::DuniterModule for GvaModule {
+    const INDEX_BLOCKS: bool = true;
+
+    fn apply_block(
+        block: &DubpBlockV10,
+        _conf: &duniter_core::conf::DuniterConf,
+        profile_path_opt: Option<&Path>,
+    ) -> KvResult<()> {
+        let gva_db = get_gva_db_rw(profile_path_opt);
+        duniter_gva_indexer::apply_block(&block, gva_db)
+    }
+    fn revert_block(
+        block: &DubpBlockV10,
+        _conf: &duniter_core::conf::DuniterConf,
+        profile_path_opt: Option<&Path>,
+    ) -> KvResult<()> {
+        let gva_db = get_gva_db_rw(profile_path_opt);
+        duniter_gva_indexer::revert_block(&block, gva_db)
+    }
+    fn init(
+        conf: &duniter_core::conf::DuniterConf,
+        currency: &str,
+        dbs_pool: &fast_threadpool::ThreadPoolAsyncHandler<SharedDbs<FileBackend>>,
+        mempools: Mempools,
+        mode: duniter_core::conf::DuniterMode,
+        profile_path_opt: Option<&Path>,
+        software_version: &'static str,
+    ) -> anyhow::Result<(Self, Vec<duniter_core::module::Endpoint>)> {
+        let mut endpoints = Vec::new();
+        if let Some(conf) = conf.gva.clone() {
+            let remote_port = conf.get_remote_port();
+            endpoints.push(format!(
+                "GVA {}{} {} {}",
+                if remote_port == 443 || conf.get_remote_tls() {
+                    "S "
+                } else {
+                    ""
+                },
+                conf.get_remote_host(),
+                remote_port,
+                conf.get_remote_path(),
+            ));
+            endpoints.push(format!(
+                "GVASUB {}{} {} {}",
+                if remote_port == 443 || conf.get_remote_tls() {
+                    "S "
+                } else {
+                    ""
+                },
+                conf.get_remote_host(),
+                remote_port,
+                conf.get_remote_subscriptions_path(),
+            ));
+        };
+        Ok((
+            GvaModule {
+                conf: conf.gva.to_owned(),
+                currency: currency.to_owned(),
+                dbs_pool: dbs_pool.to_owned(),
+                gva_db_ro: get_gva_db_ro(profile_path_opt),
+                mempools,
+                mode,
+                self_keypair: conf.self_key_pair.clone(),
+                software_version,
+            },
+            endpoints,
+        ))
+    }
+
+    async fn start(self) -> anyhow::Result<()> {
+        // Do not start GVA server on js tests
+        if std::env::var_os("DUNITER_JS_TESTS") != Some("yes".into()) {
+            let GvaModule {
+                conf,
+                currency,
+                dbs_pool,
+                gva_db_ro,
+                mempools,
+                mode,
+                self_keypair,
+                software_version,
+            } = self;
+
+            if let DuniterMode::Start = mode {
+                if let Some(conf) = conf {
+                    GvaModule::start_inner(
+                        conf,
+                        currency,
+                        dbs_pool,
+                        gva_db_ro,
+                        mempools,
+                        self_keypair,
+                        software_version,
+                    )
+                    .await
+                }
+            }
+        }
+        Ok(())
+    }
+    // Needed for BMA only, will be removed when the migration is complete.
+    fn get_transactions_history_for_bma(
+        dbs_pool: &fast_threadpool::ThreadPoolSyncHandler<SharedDbs<FileBackend>>,
+        profile_path_opt: Option<&Path>,
+        pubkey: PublicKey,
+    ) -> KvResult<Option<duniter_core::module::TxsHistoryForBma>> {
+        let gva_db = get_gva_db_ro(profile_path_opt);
+        let duniter_gva_dbs_reader::txs_history::TxsHistory {
+            sent,
+            received,
+            sending,
+            pending,
+        } = dbs_pool
+            .execute(move |dbs| {
+                duniter_gva_dbs_reader::txs_history::get_transactions_history_for_bma(
+                    gva_db,
+                    &dbs.txs_mp_db,
+                    pubkey,
+                )
+            })
+            .expect("dbs pool disconnected")?;
+        Ok(Some(duniter_core::module::TxsHistoryForBma {
+            sent: sent
+                .into_iter()
+                .map(
+                    |GvaTxDbV1 {
+                         tx,
+                         written_block,
+                         written_time,
+                     }| (tx, written_block, written_time),
+                )
+                .collect(),
+            received: received
+                .into_iter()
+                .map(
+                    |GvaTxDbV1 {
+                         tx,
+                         written_block,
+                         written_time,
+                     }| (tx, written_block, written_time),
+                )
+                .collect(),
+            sending,
+            pending,
+        }))
+    }
+    // Needed for BMA only, will be removed when the migration is complete.
+    fn get_tx_by_hash(
+        dbs_pool: &fast_threadpool::ThreadPoolSyncHandler<SharedDbs<FileBackend>>,
+        hash: Hash,
+        profile_path_opt: Option<&Path>,
+    ) -> KvResult<Option<(TransactionDocumentV10, Option<BlockNumber>)>> {
+        let gva_db = get_gva_db_ro(profile_path_opt);
+        dbs_pool
+            .execute(move |dbs| {
+                if let Some(tx) = dbs
+                    .txs_mp_db
+                    .txs()
+                    .get(&duniter_core::dbs::HashKeyV2(hash))?
+                {
+                    Ok(Some((tx.0, None)))
+                } else if let Some(tx_db) = gva_db.txs().get(&duniter_core::dbs::HashKeyV2(hash))? {
+                    Ok(Some((tx_db.tx, Some(tx_db.written_block.number))))
+                } else {
+                    Ok(None)
+                }
+            })
+            .expect("dbs pool disconnected")
+    }
+}
+
+impl GvaModule {
+    async fn start_inner(
+        conf: GvaConf,
+        currency: String,
+        dbs_pool: fast_threadpool::ThreadPoolAsyncHandler<SharedDbs<FileBackend>>,
+        gva_db_ro: &'static GvaV1DbRo<FileBackend>,
+        mempools: Mempools,
+        self_keypair: Ed25519KeyPair,
+        software_version: &'static str,
+    ) {
+        log::info!("GvaServer::start: conf={:?}", conf);
+
+        // Create BcaExecutor and GvaSchema
+        let self_pubkey = self_keypair.public_key();
+        duniter_bca::set_bca_executor(
+            currency.clone(),
+            AsyncAccessor::new(),
+            dbs_pool.clone(),
+            duniter_gva_dbs_reader::create_dbs_reader(gva_db_ro),
+            self_keypair,
+            software_version,
+            mempools.txs,
+        );
+        let gva_schema = duniter_gva_gql::build_schema_with_data(
+            duniter_gva_gql::GvaSchemaData {
+                cm_accessor: AsyncAccessor::new(),
+                dbs_reader: duniter_gva_dbs_reader::create_dbs_reader(gva_db_ro),
+                dbs_pool,
+                server_meta_data: duniter_gva_gql::ServerMetaData {
+                    currency,
+                    self_pubkey,
+                    software_version,
+                },
+                txs_mempool: mempools.txs,
+            },
+            true,
+        );
+
+        // Create warp server routes
+        let graphql_post = warp_::graphql(
+            &conf,
+            gva_schema.clone(),
+            async_graphql::http::MultipartOptions::default(),
+        );
+
+        let conf_clone = conf.clone();
+        let graphql_playground =
+            warp::path::path(conf.get_path())
+                .and(warp::get())
+                .map(move || {
+                    HttpResponse::builder()
+                        .header("content-type", "text/html")
+                        .body(async_graphql::http::playground_source(
+                            GraphQLPlaygroundConfig::new(&format!("/{}", &conf_clone.get_path()))
+                                .subscription_endpoint(&format!(
+                                    "/{}",
+                                    &conf_clone.get_subscriptions_path(),
+                                )),
+                        ))
+                });
+
+        let routes = graphql_playground
+            .or(graphql_post)
+            .or(warp_::graphql_ws(&conf, gva_schema.clone()))
+            .recover(|err: Rejection| async move {
+                if let Some(warp_::BadRequest(err)) = err.find() {
+                    return Ok::<_, Infallible>(warp::reply::with_status(
+                        err.to_string(),
+                        http::StatusCode::BAD_REQUEST,
+                    ));
+                }
+
+                Ok(warp::reply::with_status(
+                    "INTERNAL_SERVER_ERROR".to_string(),
+                    http::StatusCode::INTERNAL_SERVER_ERROR,
+                ))
+            });
+
+        // Start warp server
+        log::info!(
+            "GVA server listen on http://{}:{}/{}",
+            conf.get_ip4(),
+            conf.get_port(),
+            &conf.get_path()
+        );
+        if let Some(ip6) = conf.get_ip6() {
+            log::info!(
+                "GVA server listen on http://{}:{}/{}",
+                ip6,
+                conf.get_port(),
+                &conf.get_path()
+            );
+            futures::future::join(
+                warp::serve(routes.clone()).run((conf.get_ip4(), conf.get_port())),
+                warp::serve(routes).run((ip6, conf.get_port())),
+            )
+            .await;
+        } else {
+            warp::serve(routes)
+                .run((conf.get_ip4(), conf.get_port()))
+                .await;
+        }
+        log::warn!("GVA server stopped");
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use duniter_core::conf::DuniterConf;
+    use duniter_core::mempools::Mempools;
+    use duniter_core::module::DuniterModule;
+    use fast_threadpool::{ThreadPool, ThreadPoolConfig};
+    use unwrap::unwrap;
+
+    #[tokio::test]
+    #[ignore]
+    async fn launch_mem_gva() -> anyhow::Result<()> {
+        let dbs = unwrap!(SharedDbs::mem());
+        let threadpool = ThreadPool::start(ThreadPoolConfig::default(), dbs);
+
+        GvaModule::init(
+            &DuniterConf::default(),
+            "",
+            &threadpool.into_async_handler(),
+            Mempools::default(),
+            duniter_core::conf::DuniterMode::Start,
+            None,
+            "test",
+        )?
+        .0
+        .start()
+        .await?;
+
+        Ok(())
+    }
+}
diff --git a/src/warp_.rs b/src/warp_.rs
new file mode 100644
index 0000000000000000000000000000000000000000..83a1ba0aa10d3a8eecf4f789b08f1403a1b908de
--- /dev/null
+++ b/src/warp_.rs
@@ -0,0 +1,328 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use std::{
+    net::{IpAddr, SocketAddr},
+    time::Duration,
+};
+
+use bytes::Bytes;
+
+use crate::anti_spam::{AntiSpam, AntiSpamResponse};
+use crate::*;
+
+const MAX_BATCH_REQ_PROCESS_DURATION_IN_MILLIS: u64 = 5_000;
+
+pub struct BadRequest(pub anyhow::Error);
+
+impl std::fmt::Debug for BadRequest {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl warp::reject::Reject for BadRequest {}
+
+pub struct ReqExecTooLong;
+
+impl std::fmt::Debug for ReqExecTooLong {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "server error: request execution too long")
+    }
+}
+
+impl warp::reject::Reject for ReqExecTooLong {}
+
+struct GraphQlRequest {
+    inner: async_graphql::BatchRequest,
+}
+
+impl GraphQlRequest {
+    fn data<D: std::any::Any + Copy + Send + Sync>(self, data: D) -> Self {
+        match self.inner {
+            async_graphql::BatchRequest::Single(request) => {
+                Self::new(async_graphql::BatchRequest::Single(request.data(data)))
+            }
+            async_graphql::BatchRequest::Batch(requests) => {
+                Self::new(async_graphql::BatchRequest::Batch(
+                    requests.into_iter().map(|req| req.data(data)).collect(),
+                ))
+            }
+        }
+    }
+    #[allow(clippy::from_iter_instead_of_collect)]
+    async fn execute(self, schema: GvaSchema) -> async_graphql::BatchResponse {
+        use std::iter::FromIterator as _;
+        match self.inner {
+            async_graphql::BatchRequest::Single(request) => {
+                async_graphql::BatchResponse::Single(schema.execute(request).await)
+            }
+            async_graphql::BatchRequest::Batch(requests) => async_graphql::BatchResponse::Batch(
+                futures::stream::FuturesOrdered::from_iter(
+                    requests
+                        .into_iter()
+                        .zip(std::iter::repeat(schema))
+                        .map(|(request, schema)| async move { schema.execute(request).await }),
+                )
+                .collect()
+                .await,
+            ),
+        }
+    }
+    fn len(&self) -> usize {
+        match &self.inner {
+            async_graphql::BatchRequest::Single(_) => 1,
+            async_graphql::BatchRequest::Batch(requests) => requests.len(),
+        }
+    }
+    fn new(inner: async_graphql::BatchRequest) -> Self {
+        Self { inner }
+    }
+    fn single(request: async_graphql::Request) -> Self {
+        Self::new(async_graphql::BatchRequest::Single(request))
+    }
+}
+
+enum ServerResponse {
+    Bincode(Vec<u8>),
+    GraphQl(async_graphql::BatchResponse),
+}
+
+impl warp::reply::Reply for ServerResponse {
+    fn into_response(self) -> warp::reply::Response {
+        match self {
+            ServerResponse::Bincode(bytes) => bytes.into_response(),
+            ServerResponse::GraphQl(gql_batch_resp) => {
+                let mut resp = warp::reply::with_header(
+                    warp::reply::json(&gql_batch_resp),
+                    "content-type",
+                    "application/json",
+                )
+                .into_response();
+                add_cache_control_batch(&mut resp, &gql_batch_resp);
+                resp
+            }
+        }
+    }
+}
+
+fn add_cache_control_batch(
+    http_resp: &mut warp::reply::Response,
+    batch_resp: &async_graphql::BatchResponse,
+) {
+    match batch_resp {
+        async_graphql::BatchResponse::Single(resp) => add_cache_control(http_resp, resp),
+        async_graphql::BatchResponse::Batch(resps) => {
+            for resp in resps {
+                add_cache_control(http_resp, resp)
+            }
+        }
+    }
+}
+
+fn add_cache_control(http_resp: &mut warp::reply::Response, resp: &async_graphql::Response) {
+    if resp.is_ok() {
+        if let Some(cache_control) = resp.cache_control.value() {
+            if let Ok(value) = cache_control.parse() {
+                http_resp.headers_mut().insert("cache-control", value);
+            }
+        }
+    }
+}
+
+pub(crate) fn graphql(
+    conf: &GvaConf,
+    gva_schema: GvaSchema,
+    opts: async_graphql::http::MultipartOptions,
+) -> impl warp::Filter<Extract = (impl warp::Reply,), Error = Rejection> + Clone {
+    let anti_spam = AntiSpam::from(conf);
+    let opts = Arc::new(opts);
+    warp::path::path(conf.get_path())
+        .and(warp::method())
+        .and(warp::query::raw().or(warp::any().map(String::new)).unify())
+        .and(warp::addr::remote())
+        .and(warp::header::optional::<IpAddr>("X-Real-IP"))
+        .and(warp::header::optional::<String>("content-type"))
+        .and(warp::body::stream())
+        .and(warp::any().map(move || anti_spam.clone()))
+        .and(warp::any().map(move || gva_schema.clone()))
+        .and(warp::any().map(move || opts.clone()))
+        .and_then(
+            |method,
+             query: String,
+             remote_addr: Option<SocketAddr>,
+             x_real_ip: Option<IpAddr>,
+             content_type: Option<String>,
+             body,
+             anti_spam: AntiSpam,
+             gva_schema: GvaSchema,
+             opts: Arc<async_graphql::http::MultipartOptions>| async move {
+                let AntiSpamResponse {
+                    is_whitelisted,
+                    is_ok,
+                } = anti_spam
+                    .verify(x_real_ip.or_else(|| remote_addr.map(|ra| ra.ip())))
+                    .await;
+                if is_ok {
+                    if method == http::Method::GET {
+                        let request: async_graphql::Request = serde_urlencoded::from_str(&query)
+                            .map_err(|err| warp::reject::custom(BadRequest(err.into())))?;
+                        Ok(ServerResponse::GraphQl(
+                            GraphQlRequest::single(request.data(QueryContext { is_whitelisted }))
+                                .execute(gva_schema)
+                                .await,
+                        ))
+                    } else {
+                        let body_stream = futures::TryStreamExt::map_err(body, |err| {
+                            std::io::Error::new(std::io::ErrorKind::Other, err)
+                        })
+                        .map_ok(|mut buf| {
+                            let remaining = warp::Buf::remaining(&buf);
+                            warp::Buf::copy_to_bytes(&mut buf, remaining)
+                        });
+                        if content_type.as_deref() == Some("application/bincode") {
+                            tokio::time::timeout(
+                                Duration::from_millis(MAX_BATCH_REQ_PROCESS_DURATION_IN_MILLIS),
+                                process_bincode_batch_queries(body_stream, is_whitelisted),
+                            )
+                            .await
+                            .map_err(|_| warp::reject::custom(ReqExecTooLong))?
+                        } else {
+                            tokio::time::timeout(
+                                Duration::from_millis(MAX_BATCH_REQ_PROCESS_DURATION_IN_MILLIS),
+                                process_json_batch_queries(
+                                    body_stream.into_async_read(),
+                                    content_type,
+                                    gva_schema,
+                                    is_whitelisted,
+                                    *opts,
+                                ),
+                            )
+                            .await
+                            .map_err(|_| warp::reject::custom(ReqExecTooLong))?
+                        }
+                    }
+                } else {
+                    Err(warp::reject::custom(BadRequest(anyhow::Error::msg(
+                        r#"{ "error": "too many requests" }"#,
+                    ))))
+                }
+            },
+        )
+}
+
+async fn process_bincode_batch_queries(
+    body_reader: impl 'static + futures::TryStream<Ok = Bytes, Error = std::io::Error> + Send + Unpin,
+    is_whitelisted: bool,
+) -> Result<ServerResponse, warp::Rejection> {
+    Ok(ServerResponse::Bincode(
+        duniter_bca::execute(body_reader, is_whitelisted).await,
+    ))
+}
+
+async fn process_json_batch_queries(
+    body_reader: impl 'static + futures::AsyncRead + Send + Unpin,
+    content_type: Option<String>,
+    gva_schema: GvaSchema,
+    is_whitelisted: bool,
+    opts: async_graphql::http::MultipartOptions,
+) -> Result<ServerResponse, warp::Rejection> {
+    let batch_request = GraphQlRequest::new(
+        async_graphql::http::receive_batch_body(
+            content_type,
+            body_reader,
+            async_graphql::http::MultipartOptions::clone(&opts),
+        )
+        .await
+        .map_err(|err| warp::reject::custom(BadRequest(err.into())))?,
+    );
+    if is_whitelisted || batch_request.len() <= anti_spam::MAX_BATCH_SIZE {
+        Ok(ServerResponse::GraphQl(
+            batch_request
+                .data(QueryContext { is_whitelisted })
+                .execute(gva_schema)
+                .await,
+        ))
+    } else {
+        Err(warp::reject::custom(BadRequest(anyhow::Error::msg(
+            r#"{ "error": "The batch contains too many requests" }"#,
+        ))))
+    }
+}
+
+pub(crate) fn graphql_ws(
+    conf: &GvaConf,
+    schema: GvaSchema,
+) -> impl warp::Filter<Extract = (impl warp::Reply,), Error = Rejection> + Clone {
+    let anti_spam = AntiSpam::from(conf);
+    warp::path::path(conf.get_subscriptions_path())
+        .and(warp::addr::remote())
+        .and(warp::header::optional::<IpAddr>("X-Real-IP"))
+        .and(warp::ws())
+        .and(warp::any().map(move || schema.clone()))
+        .and(warp::any().map(move || anti_spam.clone()))
+        .and_then(
+            |remote_addr: Option<SocketAddr>,
+             x_real_ip: Option<IpAddr>,
+             ws: warp::ws::Ws,
+             schema: GvaSchema,
+             anti_spam: AntiSpam| async move {
+                let AntiSpamResponse {
+                    is_whitelisted: _,
+                    is_ok,
+                } = anti_spam
+                    .verify(x_real_ip.or_else(|| remote_addr.map(|ra| ra.ip())))
+                    .await;
+                if is_ok {
+                    Ok((ws, schema))
+                } else {
+                    Err(warp::reject::custom(BadRequest(anyhow::Error::msg(
+                        r#"{ "error": "too many requests" }"#,
+                    ))))
+                }
+            },
+        )
+        .and_then(|(ws, schema): (warp::ws::Ws, GvaSchema)| {
+            let reply = ws.on_upgrade(move |websocket| {
+                let (ws_sender, ws_receiver) = websocket.split();
+
+                async move {
+                    let _ = async_graphql::http::WebSocket::new(
+                        schema,
+                        ws_receiver
+                            .take_while(|msg| futures::future::ready(msg.is_ok()))
+                            .map(Result::unwrap)
+                            .map(warp::ws::Message::into_bytes),
+                        async_graphql::http::WebSocketProtocols::SubscriptionsTransportWS,
+                    )
+                    .map(|ws_msg| match ws_msg {
+                        async_graphql::http::WsMessage::Text(s) => warp::ws::Message::text(s),
+                        async_graphql::http::WsMessage::Close(code, reason) => {
+                            warp::ws::Message::close_with(code, reason)
+                        }
+                    })
+                    .map(Ok)
+                    .forward(ws_sender)
+                    .await;
+                }
+            });
+
+            futures::future::ready(Ok::<_, Rejection>(warp::reply::with_header(
+                reply,
+                "Sec-WebSocket-Protocol",
+                "graphql-ws",
+            )))
+        })
+}