diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000000000000000000000000000000000000..a6b5f55ce37307cf7ce08edf1fddef0ff766b434
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,21 @@
+# EditorConfig helps developers define and maintain consistent
+# coding styles between different editors and IDEs
+# editorconfig.org
+
+root = true
+
+[*]
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+
+[*.md]
+# double whitespace at end of line
+# denotes a line break in Markdown
+trim_trailing_whitespace = false
+
+[*.yml]
+indent_size = 2
\ No newline at end of file
diff --git a/.env.example b/.env.example
deleted file mode 100644
index ea8377ce969f474b8f1b927f8d3bf5f88aa70b18..0000000000000000000000000000000000000000
--- a/.env.example
+++ /dev/null
@@ -1,6 +0,0 @@
-SIGNING_KEY=
-RPC_URL=http://127.0.0.1:8545
-
-CHAIN_ID=636363
-
-AUTO_SEND_PERIOD=30
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index ec47942092de7bc76c4196700eabd10c573bc900..889edcbe58be2b13dab6075145c089d87430bff9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
 /target
 
-.env
\ No newline at end of file
+.env
+.cargo/config.toml
+*.db
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 07c1cf500ae889333734e0b95848802b40b018c9..8b3a74cba7aa160a3211586bfb781f1850861513 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,8 +1,23 @@
-stages:
-  - build
+image: gitlab.limos.fr:5005/ecomobicoin/infra/rust-builder-image:1.71
+
+cache:
+  paths:
+    - target/
+
+variables:
+  PACKAGE_REGISTRY_URL: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic
 
 before_script:
-  - rustc --version && cargo --version
+  - | # skip if not using custom Rust image
+    if hash rustc 2>/dev/null; then
+      rustc --version
+      cargo --version
+    fi
+
+stages:
+  - build
+  - release-build
+  - release
 
 package-docker:
   image: docker:latest
@@ -30,23 +45,47 @@ package-docker:
     - if: $CI_COMMIT_BRANCH
       exists:
         - Dockerfile
-# release:
-#   stage: release
-#   image: registry.gitlab.com/gitlab-org/release-cli:latest
-#   variables:
-#     LINUX_ASSET_LINK: '{"name":"gitlab-downloader-linux-86_64-${CI_COMMIT_TAG}","url":"${PACKAGE_REGISTRY_URL}/${CI_COMMIT_TAG}/linux-x86_64/gitlab-downloader","type":"package"}'
-#   script:
-#     - echo "Releasing..."
-#     - echo "Tag = ${CI_COMMIT_TAG}"
-#     - echo "URL = ${PACKAGE_REGISTRY_URL}/${CI_COMMIT_TAG}"
-#     - echo "LINUX = ${LINUX_ASSET_LINK}"
-#     - echo "WIN = ${WIN_ASSET_LINK}"
-#     - |
-#       release-cli --debug create --name "Release ${CI_COMMIT_TAG}" --tag-name "${CI_COMMIT_TAG}" --assets-link "${LINUX_ASSET_LINK}" --assets-link "${WIN_ASSET_LINK}"
-#     - echo "Done!"
-#   needs:
-#     - job: build
-#       artifacts: true
-#   rules:
-#     - if: '$CI_COMMIT_TAG && $CI_COMMIT_TAG =~ /^v[0-9]\.[0-9]\.[0-9](-rc.+)?/'
-#       when: always
+
+release-build-windows:
+  stage: release-build
+  script:
+    - cargo build --target x86_64-pc-windows-gnu --release
+    - |
+      curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file "target/x86_64-pc-windows-gnu/release/testnet-injector.exe" "${PACKAGE_REGISTRY_URL}/${CI_COMMIT_TAG}/win-x86_64/testnet-injector.exe"
+  rules:
+    - if: "$CI_COMMIT_TAG && $CI_COMMIT_TAG =~ /^v/"
+      when: always
+
+release-build-linux:
+  stage: release-build
+  script:
+    - cargo build --target x86_64-unknown-linux-gnu --release
+    - |
+      curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file "target/x86_64-unknown-linux-gnu/release/testnet-injector" "${PACKAGE_REGISTRY_URL}/${CI_COMMIT_TAG}/linux-x86_64/testnet-injector"
+  rules:
+    - if: "$CI_COMMIT_TAG && $CI_COMMIT_TAG =~ /^v/"
+      when: always
+
+release:
+  stage: release
+  image: registry.gitlab.com/gitlab-org/release-cli:latest
+  variables:
+    LINUX_ASSET_LINK: '{"name":"testnet-injector-linux-86_64-${CI_COMMIT_TAG}","url":"${PACKAGE_REGISTRY_URL}/${CI_COMMIT_TAG}/linux-x86_64/testnet-injector","type":"package"}'
+    WIN_ASSET_LINK: '{"name":"testnet-injector-win-86_64-${CI_COMMIT_TAG}","url":"${PACKAGE_REGISTRY_URL}/${CI_COMMIT_TAG}/win-x86_64/testnet-injector.exe","type":"package"}'
+  script:
+    - echo "Releasing..."
+    - echo "Tag = ${CI_COMMIT_TAG}"
+    - echo "URL = ${PACKAGE_REGISTRY_URL}/${CI_COMMIT_TAG}"
+    - echo "LINUX = ${LINUX_ASSET_LINK}"
+    - echo "WIN = ${WIN_ASSET_LINK}"
+    - |
+      release-cli --debug create --name "Release ${CI_COMMIT_TAG}" --tag-name "${CI_COMMIT_TAG}" --assets-link "${LINUX_ASSET_LINK}" --assets-link "${WIN_ASSET_LINK}"
+    - echo "Done!"
+  needs:
+    - job: release-build-windows
+      artifacts: true
+    - job: release-build-linux
+      artifacts: true
+  rules:
+    - if: "$CI_COMMIT_TAG && $CI_COMMIT_TAG =~ /^v/"
+      when: always
diff --git a/Cargo.lock b/Cargo.lock
index 603e5b666e5885b8bd65567d2e0d6fb1acadaca7..958a6473a547ac6d0b9077bc9ed3f88f97584919 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -12,187 +12,6 @@ dependencies = [
  "regex",
 ]
 
-[[package]]
-name = "actix-codec"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8"
-dependencies = [
- "bitflags 1.3.2",
- "bytes",
- "futures-core",
- "futures-sink",
- "memchr",
- "pin-project-lite",
- "tokio",
- "tokio-util",
- "tracing",
-]
-
-[[package]]
-name = "actix-http"
-version = "3.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2079246596c18b4a33e274ae10c0e50613f4d32a4198e09c7b93771013fed74"
-dependencies = [
- "actix-codec",
- "actix-rt",
- "actix-service",
- "actix-utils",
- "ahash 0.8.3",
- "base64 0.21.2",
- "bitflags 1.3.2",
- "brotli",
- "bytes",
- "bytestring",
- "derive_more",
- "encoding_rs",
- "flate2",
- "futures-core",
- "h2",
- "http",
- "httparse",
- "httpdate",
- "itoa",
- "language-tags",
- "local-channel",
- "mime",
- "percent-encoding",
- "pin-project-lite",
- "rand",
- "sha1",
- "smallvec",
- "tokio",
- "tokio-util",
- "tracing",
- "zstd 0.12.3+zstd.1.5.2",
-]
-
-[[package]]
-name = "actix-macros"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6"
-dependencies = [
- "quote",
- "syn 1.0.109",
-]
-
-[[package]]
-name = "actix-router"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d66ff4d247d2b160861fa2866457e85706833527840e4133f8f49aa423a38799"
-dependencies = [
- "bytestring",
- "http",
- "regex",
- "serde",
- "tracing",
-]
-
-[[package]]
-name = "actix-rt"
-version = "2.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15265b6b8e2347670eb363c47fc8c75208b4a4994b27192f345fcbe707804f3e"
-dependencies = [
- "futures-core",
- "tokio",
-]
-
-[[package]]
-name = "actix-server"
-version = "2.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e8613a75dd50cc45f473cee3c34d59ed677c0f7b44480ce3b8247d7dc519327"
-dependencies = [
- "actix-rt",
- "actix-service",
- "actix-utils",
- "futures-core",
- "futures-util",
- "mio",
- "num_cpus",
- "socket2",
- "tokio",
- "tracing",
-]
-
-[[package]]
-name = "actix-service"
-version = "2.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a"
-dependencies = [
- "futures-core",
- "paste",
- "pin-project-lite",
-]
-
-[[package]]
-name = "actix-utils"
-version = "3.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8"
-dependencies = [
- "local-waker",
- "pin-project-lite",
-]
-
-[[package]]
-name = "actix-web"
-version = "4.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd3cb42f9566ab176e1ef0b8b3a896529062b4efc6be0123046095914c4c1c96"
-dependencies = [
- "actix-codec",
- "actix-http",
- "actix-macros",
- "actix-router",
- "actix-rt",
- "actix-server",
- "actix-service",
- "actix-utils",
- "actix-web-codegen",
- "ahash 0.7.6",
- "bytes",
- "bytestring",
- "cfg-if",
- "cookie",
- "derive_more",
- "encoding_rs",
- "futures-core",
- "futures-util",
- "http",
- "itoa",
- "language-tags",
- "log",
- "mime",
- "once_cell",
- "pin-project-lite",
- "regex",
- "serde",
- "serde_json",
- "serde_urlencoded",
- "smallvec",
- "socket2",
- "time 0.3.22",
- "url",
-]
-
-[[package]]
-name = "actix-web-codegen"
-version = "4.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2262160a7ae29e3415554a3f1fc04c764b1540c116aa524683208078b7a75bc9"
-dependencies = [
- "actix-router",
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
 [[package]]
 name = "addr2line"
 version = "0.20.0"
@@ -219,29 +38,6 @@ dependencies = [
  "cpufeatures",
 ]
 
-[[package]]
-name = "ahash"
-version = "0.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
-dependencies = [
- "getrandom",
- "once_cell",
- "version_check",
-]
-
-[[package]]
-name = "ahash"
-version = "0.8.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
-dependencies = [
- "cfg-if",
- "getrandom",
- "once_cell",
- "version_check",
-]
-
 [[package]]
 name = "aho-corasick"
 version = "0.7.20"
@@ -260,21 +56,6 @@ dependencies = [
  "memchr",
 ]
 
-[[package]]
-name = "alloc-no-stdlib"
-version = "2.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
-
-[[package]]
-name = "alloc-stdlib"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
-dependencies = [
- "alloc-no-stdlib",
-]
-
 [[package]]
 name = "android-tzdata"
 version = "0.1.1"
@@ -292,16 +73,15 @@ dependencies = [
 
 [[package]]
 name = "anstream"
-version = "0.3.2"
+version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
+checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
 dependencies = [
  "anstyle",
  "anstyle-parse",
  "anstyle-query",
  "anstyle-wincon",
  "colorchoice",
- "is-terminal",
  "utf8parse",
 ]
 
@@ -331,9 +111,9 @@ dependencies = [
 
 [[package]]
 name = "anstyle-wincon"
-version = "1.0.1"
+version = "3.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
+checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
 dependencies = [
  "anstyle",
  "windows-sys",
@@ -341,9 +121,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.71"
+version = "1.0.75"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
+checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
 
 [[package]]
 name = "arrayref"
@@ -388,7 +168,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.23",
+ "syn 2.0.32",
 ]
 
 [[package]]
@@ -399,7 +179,7 @@ checksum = "7b2d0f03b3640e3a630367e40c468cb7f309529c708ed1d88597047b0e7c6ef7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.23",
+ "syn 2.0.32",
 ]
 
 [[package]]
@@ -615,27 +395,6 @@ dependencies = [
  "generic-array",
 ]
 
-[[package]]
-name = "brotli"
-version = "3.3.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68"
-dependencies = [
- "alloc-no-stdlib",
- "alloc-stdlib",
- "brotli-decompressor",
-]
-
-[[package]]
-name = "brotli-decompressor"
-version = "2.3.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744"
-dependencies = [
- "alloc-no-stdlib",
- "alloc-stdlib",
-]
-
 [[package]]
 name = "bs58"
 version = "0.4.0"
@@ -682,15 +441,6 @@ dependencies = [
  "serde",
 ]
 
-[[package]]
-name = "bytestring"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "238e4886760d98c4f899360c834fa93e62cf7f721ac3c2da375cbdf4b8679aae"
-dependencies = [
- "bytes",
-]
-
 [[package]]
 name = "bzip2"
 version = "0.4.4"
@@ -767,11 +517,8 @@ checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
 dependencies = [
  "android-tzdata",
  "iana-time-zone",
- "js-sys",
  "num-traits",
  "serde",
- "time 0.1.45",
- "wasm-bindgen",
  "winapi",
 ]
 
@@ -787,20 +534,19 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.3.10"
+version = "4.4.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "384e169cc618c613d5e3ca6404dda77a8685a63e08660dcc64abaf7da7cb0c7a"
+checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64"
 dependencies = [
  "clap_builder",
  "clap_derive",
- "once_cell",
 ]
 
 [[package]]
 name = "clap_builder"
-version = "4.3.10"
+version = "4.4.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef137bbe35aab78bdb468ccfba75a5f4d8321ae011d34063770780545176af2d"
+checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc"
 dependencies = [
  "anstream",
  "anstyle",
@@ -810,21 +556,21 @@ dependencies = [
 
 [[package]]
 name = "clap_derive"
-version = "4.3.2"
+version = "4.4.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f"
+checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
 dependencies = [
  "heck",
  "proc-macro2",
  "quote",
- "syn 2.0.23",
+ "syn 2.0.32",
 ]
 
 [[package]]
 name = "clap_lex"
-version = "0.5.0"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
+checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
 
 [[package]]
 name = "coins-bip32"
@@ -900,23 +646,6 @@ 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 = "cookie"
-version = "0.16.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
-dependencies = [
- "percent-encoding",
- "time 0.3.22",
- "version_check",
-]
-
 [[package]]
 name = "core-foundation-sys"
 version = "0.8.4"
@@ -1012,6 +741,27 @@ dependencies = [
  "typenum",
 ]
 
+[[package]]
+name = "csv"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe"
+dependencies = [
+ "csv-core",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "csv-core"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70"
+dependencies = [
+ "memchr",
+]
+
 [[package]]
 name = "ctr"
 version = "0.9.2"
@@ -1042,7 +792,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "strsim",
- "syn 2.0.23",
+ "syn 2.0.32",
 ]
 
 [[package]]
@@ -1053,7 +803,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a"
 dependencies = [
  "darling_core",
  "quote",
- "syn 2.0.23",
+ "syn 2.0.32",
 ]
 
 [[package]]
@@ -1072,16 +822,24 @@ dependencies = [
  "zeroize",
 ]
 
+[[package]]
+name = "deranged"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3"
+dependencies = [
+ "powerfmt",
+ "serde",
+]
+
 [[package]]
 name = "derive_more"
 version = "0.99.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
 dependencies = [
- "convert_case",
  "proc-macro2",
  "quote",
- "rustc_version",
  "syn 1.0.109",
 ]
 
@@ -1133,12 +891,6 @@ dependencies = [
  "winapi",
 ]
 
-[[package]]
-name = "dotenvy"
-version = "0.15.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
-
 [[package]]
 name = "dunce"
 version = "1.0.4"
@@ -1162,7 +914,7 @@ dependencies = [
 [[package]]
 name = "ecomobicoin-jsonrpc"
 version = "0.1.0"
-source = "git+https://gitlab.limos.fr/ecomobicoin/ecomobicoin-jsonrpc.git#20df825e93327e0cf691918d64321960b0b4d92f"
+source = "git+https://gitlab.limos.fr/ecomobicoin/ecomobicoin-jsonrpc.git#1a7e75657d759587cff39a96bdbd6aa944d98cfb"
 dependencies = [
  "arrayvec",
  "bytes",
@@ -1412,7 +1164,7 @@ dependencies = [
  "reqwest",
  "serde",
  "serde_json",
- "syn 2.0.23",
+ "syn 2.0.32",
  "toml",
  "walkdir",
 ]
@@ -1429,7 +1181,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "serde_json",
- "syn 2.0.23",
+ "syn 2.0.32",
 ]
 
 [[package]]
@@ -1454,7 +1206,7 @@ dependencies = [
  "serde",
  "serde_json",
  "strum",
- "syn 2.0.23",
+ "syn 2.0.32",
  "tempfile",
  "thiserror",
  "tiny-keccak",
@@ -1709,9 +1461,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
 
 [[package]]
 name = "futures"
-version = "0.3.28"
+version = "0.3.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
+checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -1724,9 +1476,9 @@ dependencies = [
 
 [[package]]
 name = "futures-channel"
-version = "0.3.28"
+version = "0.3.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
+checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb"
 dependencies = [
  "futures-core",
  "futures-sink",
@@ -1734,15 +1486,15 @@ dependencies = [
 
 [[package]]
 name = "futures-core"
-version = "0.3.28"
+version = "0.3.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
+checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
 
 [[package]]
 name = "futures-executor"
-version = "0.3.28"
+version = "0.3.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
+checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
 dependencies = [
  "futures-core",
  "futures-task",
@@ -1751,9 +1503,9 @@ dependencies = [
 
 [[package]]
 name = "futures-io"
-version = "0.3.28"
+version = "0.3.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
+checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa"
 
 [[package]]
 name = "futures-locks"
@@ -1767,26 +1519,26 @@ dependencies = [
 
 [[package]]
 name = "futures-macro"
-version = "0.3.28"
+version = "0.3.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
+checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.23",
+ "syn 2.0.32",
 ]
 
 [[package]]
 name = "futures-sink"
-version = "0.3.28"
+version = "0.3.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
+checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817"
 
 [[package]]
 name = "futures-task"
-version = "0.3.28"
+version = "0.3.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
+checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2"
 
 [[package]]
 name = "futures-timer"
@@ -1800,9 +1552,9 @@ dependencies = [
 
 [[package]]
 name = "futures-util"
-version = "0.3.28"
+version = "0.3.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
+checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -1845,7 +1597,7 @@ dependencies = [
  "cfg-if",
  "js-sys",
  "libc",
- "wasi 0.11.0+wasi-snapshot-preview1",
+ "wasi",
  "wasm-bindgen",
 ]
 
@@ -2378,12 +2130,6 @@ version = "0.19.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d3c48237b9604c5a4702de6b824e02006c3214327564636aef27c1028a8fa0ed"
 
-[[package]]
-name = "language-tags"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
-
 [[package]]
 name = "lazy_static"
 version = "1.4.0"
@@ -2408,24 +2154,6 @@ version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0"
 
-[[package]]
-name = "local-channel"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c"
-dependencies = [
- "futures-core",
- "futures-sink",
- "futures-util",
- "local-waker",
-]
-
-[[package]]
-name = "local-waker"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1"
-
 [[package]]
 name = "lock_api"
 version = "0.4.10"
@@ -2494,8 +2222,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
 dependencies = [
  "libc",
- "log",
- "wasi 0.11.0+wasi-snapshot-preview1",
+ "wasi",
  "windows-sys",
 ]
 
@@ -2558,7 +2285,7 @@ dependencies = [
  "proc-macro-crate",
  "proc-macro2",
  "quote",
- "syn 2.0.23",
+ "syn 2.0.32",
 ]
 
 [[package]]
@@ -2673,12 +2400,6 @@ dependencies = [
  "subtle",
 ]
 
-[[package]]
-name = "paste"
-version = "1.0.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35"
-
 [[package]]
 name = "path-slash"
 version = "0.2.1"
@@ -2763,7 +2484,7 @@ dependencies = [
  "phf_shared 0.11.2",
  "proc-macro2",
  "quote",
- "syn 2.0.23",
+ "syn 2.0.32",
 ]
 
 [[package]]
@@ -2801,7 +2522,7 @@ checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.23",
+ "syn 2.0.32",
 ]
 
 [[package]]
@@ -2832,6 +2553,12 @@ version = "0.3.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
 
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
 [[package]]
 name = "ppv-lite86"
 version = "0.2.17"
@@ -2861,7 +2588,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9825a04601d60621feed79c4e6b56d65db77cdca55cef43b46b0de1096d1c282"
 dependencies = [
  "proc-macro2",
- "syn 2.0.23",
+ "syn 2.0.32",
 ]
 
 [[package]]
@@ -3408,9 +3135,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
 
 [[package]]
 name = "serde"
-version = "1.0.166"
+version = "1.0.192"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d01b7404f9d441d3ad40e6a636a7782c377d2abdbe4fa2440e2edcc2f4f10db8"
+checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
 dependencies = [
  "serde_derive",
 ]
@@ -3426,13 +3153,13 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.166"
+version = "1.0.192"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5dd83d6dde2b6b2d466e14d9d1acce8816dedee94f735eac6395808b3483c6d6"
+checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.23",
+ "syn 2.0.32",
 ]
 
 [[package]]
@@ -3480,7 +3207,7 @@ dependencies = [
  "serde",
  "serde_json",
  "serde_with_macros",
- "time 0.3.22",
+ "time",
 ]
 
 [[package]]
@@ -3492,7 +3219,7 @@ dependencies = [
  "darling",
  "proc-macro2",
  "quote",
- "syn 2.0.23",
+ "syn 2.0.32",
 ]
 
 [[package]]
@@ -3562,15 +3289,6 @@ dependencies = [
  "lazy_static",
 ]
 
-[[package]]
-name = "signal-hook-registry"
-version = "1.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
-dependencies = [
- "libc",
-]
-
 [[package]]
 name = "signature"
 version = "2.1.0"
@@ -3702,7 +3420,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "rustversion",
- "syn 2.0.23",
+ "syn 2.0.32",
 ]
 
 [[package]]
@@ -3744,9 +3462,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.23"
+version = "2.0.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737"
+checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3794,10 +3512,9 @@ dependencies = [
 name = "testnet-injector"
 version = "0.1.0"
 dependencies = [
- "actix-web",
- "chrono",
+ "anyhow",
  "clap",
- "dotenvy",
+ "csv",
  "ecomobicoin-jsonrpc",
  "ethereum-interfaces",
  "ethereum-types",
@@ -3805,13 +3522,14 @@ dependencies = [
  "ethnum",
  "eyre",
  "fastrlp",
+ "futures",
  "primitive-types",
  "serde",
  "serde-big-array",
  "serde_json",
  "serde_with",
+ "time",
  "tokio",
- "tokio_schedule",
  "tracing",
  "tracing-subscriber",
 ]
@@ -3833,7 +3551,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.23",
+ "syn 2.0.32",
 ]
 
 [[package]]
@@ -3848,22 +3566,13 @@ dependencies = [
 
 [[package]]
 name = "time"
-version = "0.1.45"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
-dependencies = [
- "libc",
- "wasi 0.10.0+wasi-snapshot-preview1",
- "winapi",
-]
-
-[[package]]
-name = "time"
-version = "0.3.22"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd"
+checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
 dependencies = [
+ "deranged",
  "itoa",
+ "powerfmt",
  "serde",
  "time-core",
  "time-macros",
@@ -3871,15 +3580,15 @@ dependencies = [
 
 [[package]]
 name = "time-core"
-version = "0.1.1"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
 
 [[package]]
 name = "time-macros"
-version = "0.2.9"
+version = "0.2.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b"
+checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
 dependencies = [
  "time-core",
 ]
@@ -3920,9 +3629,7 @@ dependencies = [
  "libc",
  "mio",
  "num_cpus",
- "parking_lot",
  "pin-project-lite",
- "signal-hook-registry",
  "socket2",
  "tokio-macros",
  "windows-sys",
@@ -3946,7 +3653,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.23",
+ "syn 2.0.32",
 ]
 
 [[package]]
@@ -4000,16 +3707,6 @@ dependencies = [
  "tracing",
 ]
 
-[[package]]
-name = "tokio_schedule"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b4a14ab1658c39d137ebcc5fbaab364c9922a6cc04ab48b364546c2e6022256"
-dependencies = [
- "chrono",
- "tokio",
-]
-
 [[package]]
 name = "toml"
 version = "0.7.5"
@@ -4142,7 +3839,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.23",
+ "syn 2.0.32",
 ]
 
 [[package]]
@@ -4332,12 +4029,6 @@ dependencies = [
  "try-lock",
 ]
 
-[[package]]
-name = "wasi"
-version = "0.10.0+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
-
 [[package]]
 name = "wasi"
 version = "0.11.0+wasi-snapshot-preview1"
@@ -4365,7 +4056,7 @@ dependencies = [
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.23",
+ "syn 2.0.32",
  "wasm-bindgen-shared",
 ]
 
@@ -4399,7 +4090,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.23",
+ "syn 2.0.32",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
@@ -4639,8 +4330,8 @@ dependencies = [
  "hmac",
  "pbkdf2 0.11.0",
  "sha1",
- "time 0.3.22",
- "zstd 0.11.2+zstd.1.5.2",
+ "time",
+ "zstd",
 ]
 
 [[package]]
@@ -4649,16 +4340,7 @@ version = "0.11.2+zstd.1.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
 dependencies = [
- "zstd-safe 5.0.2+zstd.1.5.2",
-]
-
-[[package]]
-name = "zstd"
-version = "0.12.3+zstd.1.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76eea132fb024e0e13fd9c2f5d5d595d8a967aa72382ac2f9d39fcc95afd0806"
-dependencies = [
- "zstd-safe 6.0.5+zstd.1.5.4",
+ "zstd-safe",
 ]
 
 [[package]]
@@ -4671,16 +4353,6 @@ dependencies = [
  "zstd-sys",
 ]
 
-[[package]]
-name = "zstd-safe"
-version = "6.0.5+zstd.1.5.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d56d9e60b4b1758206c238a10165fbcae3ca37b01744e394c463463f6529d23b"
-dependencies = [
- "libc",
- "zstd-sys",
-]
-
 [[package]]
 name = "zstd-sys"
 version = "2.0.8+zstd.1.5.5"
diff --git a/Cargo.toml b/Cargo.toml
index e691cdcbbc24d695bd86591dc4941171daf19e85..b0f8a7068a7377bdd0fbfd28545c8ecde56badac 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,7 +9,6 @@ license = "AGPL-3.0-only"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-actix-web = "4"
 ethereum-interfaces = { git = "https://github.com/ledgerwatch/interfaces", features = [
   "remotekv",
   "sentry",
@@ -34,13 +33,12 @@ primitive-types = { version = "0.12", default-features = false, features = [
   "rlp",
   "rustc-hex",
 ] }
-serde = "1"
+serde = { version = "1", features = ["derive"] }
 serde-big-array = "0.5.1"
 serde_json = "1"
 serde_with = "2"
 tracing = "0.1"
 tracing-subscriber = "0.3"
-dotenvy = "0.15"
 ethers = { version = "2.0", git = "https://gitlab.limos.fr/ecomobicoin/ethers-rs.git", features = [
   "ecomobicoin",
 ] }
@@ -52,6 +50,8 @@ ethers = { version = "2.0", git = "https://gitlab.limos.fr/ecomobicoin/ethers-rs
 tokio = { version = "1", features = ["macros"] }
 # Flexible concrete Error Reporting type built on std::error::Error with customizable Reports
 eyre = "0.6"
-tokio_schedule = "0.3"
-chrono = "0.4"
+time = { version = "0.3", features = ["serde", "serde-well-known"] }
 clap = { version = "4", features = ["derive"] }
+futures = "0.3.29"
+csv = "1.3.0"
+anyhow = "1.0.75"
diff --git a/README.md b/README.md
index 583524046b534c4a6738adebc708aa271d893509..4e1cc62ef7a11cdd4d0e773ac68152481619e1f9 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,101 @@
 # Testnet injector
 
-A simple HTTP API to inject transactions or behaviors in an EcoMobiCoin test network.
+A CLI tool to inject transactions or behaviors in an EcoMobiCoin blockchain.
 
-## Features
+## Components
 
 This project heavily use the forked and adapted [ethers-rs](https://gitlab.limos.fr/ecomobicoin/ethers-rs).
 
+### ETL
+
+-   Extract CSV file e.g. `dataset-extract.csv` to `MobilityRecord`
+-   Transform it with simulation params (relative time from earliest entry and behavior attributes)
+-   Load data in memory with thread scheduled
+
+### Sending Queue
+
+## Usage
+
+### Simulate tx & bx
+
+To simulate Behaviors from a CSV Mobility dataset, run `cargo run simulate-bx-from-csv < data/dataset-extract.csv`
+
+Options:
+
+-   dataset e.g. CSV file
+-   `total_simulation_duration` in seconds (or minutes ?)
+
+### Send
+
+Send tx or bx
+
+Options:
+
+-   `txs` Number of transactions to send
+-   `bxs` Number of behaviors to send
+
+In a near future:
+
+-   `time_period` in seconds
+
+### Mine
+
+To mine a block from env vars `MINER_SIGNING_KEY` and `RPC_URL`, use `cargo run mine`
+
+Options:
+
+-   None yet
+
+In a near futur:
+
+-   quantity
+-   timestamp
+-   input
+
 ## Env vars
 
-Copy the env example file to `.env` with the following command: `cp .env.example .env`
+You can export env vars in the cli like `export RPC_URL="http://ecomobicoin-2.cri.local.isima.fr:10002"; testnet-injector mine`
+
+Or you can use your user's cargo config file in (see https://doc.rust-lang.org/cargo/reference/config.html) or the project one `cargo/config.toml`.
+And set the following content to set env vars:
+
+```
+[env]
+MINER_SIGNING_KEY = ""
+MNEMONIC = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
+
+RPC_URL=http://127.0.0.1:8545
+CHAIN_ID = "636363"
+DATABASE_URL = "./injector.db"
+```
+
+You can also specify `--rpc-url` and `--chain-id` as args in command line that will overwrite env vars.
+
+## Ecomobicoin Simulation
+
+> WORK IN PROGRESS
+
+```mermaid
+flowchart LR
+    subgraph Configuration
+    A[CSV Dataset] --> B[Simulation config]
+    AA[Nodes & Miner config] --> B
+    AAA[Chainspec] --> B
+    end
+    subgraph S[Simulation]
+    subgraph D[Deployments]
+    B --> D1[Deploy nodes]
+    B --> D21[Deploy txbx CSV injector]
+    B --> D22[Deploy txbx injectors cli]
+    B --> D23[Deploy miners cli]
+    B --> D3[Deploy block explorer]
+    end
+    D --> E[Run]
+    end
+    subgraph R[Reporting]
+    E --> R1[Report]
+    end
+```
 
 ## Development
 
diff --git a/data/dataset-extract.csv b/data/dataset-extract.csv
new file mode 100644
index 0000000000000000000000000000000000000000..a74a11e9922efa679c4aa1967c2a153ea50b4235
--- /dev/null
+++ b/data/dataset-extract.csv
@@ -0,0 +1,7 @@
+id,user_id,network,service,label,created_at,from_coord,from_label,from_datetime,to_coord,to_label,to_datetime,from_latitude,from_longitude,to_latitude,to_longitude
+0,79,mulhouse,PUBLIC_TRANSPORT,"Abonnement mensuel 26-64 ans, 1 passager(s)",2023-05-31T22:09:51.226773Z,,,2023-06-01T00:09:50.000Z,,,,,,,
+1,901,mulhouse,PUBLIC_TRANSPORT,"Abonnement mensuel - de 26 ans, 1 passager(s)",2023-05-31T22:12:24.054056Z,,,2023-06-01T00:12:23.000Z,,,,,,,
+2,4037,mulhouse,PUBLIC_TRANSPORT,"Abonnement mensuel - de 26 ans, 1 passager(s)",2023-05-31T22:22:14.465196Z,,,2023-06-01T00:22:13.000Z,,,,,,,
+3,4638,mulhouse,PUBLIC_TRANSPORT,"Abonnement mensuel - de 26 ans, 1 passager(s)",2023-06-01T00:21:37.066224Z,,,2023-06-01T02:21:36.000Z,,,,,,,
+12,4272,mulhouse,PUBLIC_TRANSPORT,"Abonnement mensuel - de 26 ans, 1 passager(s)",2023-06-01T04:03:48.286701Z,,,2023-06-01T06:03:47.000Z,,,,,,,
+10653,79,mulhouse,PUBLIC_TRANSPORT,"Abonnement mensuel 26-64 ans, 1 passager(s)",2023-07-02T19:02:04.987621Z,,,2023-07-02T21:02:04.000Z,,,,,,,
\ No newline at end of file
diff --git a/scripts/t0-1mine-2sync.sh b/scripts/t0-1mine-2sync.sh
new file mode 100755
index 0000000000000000000000000000000000000000..f8c79834679038fac051c2a38b1f8517f47ef1cc
--- /dev/null
+++ b/scripts/t0-1mine-2sync.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+export MINER_SIGNING_KEY=4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318;
+
+# MINER_SIGNING_KEY & MNEMONIC env vars are set on workstation.
+export CHAIN_ID=6363;
+# 12 sec
+BLOCK_TIME=12;
+
+export RPC_URL=http://ecomobicoin-1.cri.local.isima.fr:8545;
+
+# Mine on eco1 with `--max-block 0`
+# 
+#  rm -rf /home/angraign/.local/share/truite/chaindata/
+# ./truite --chain-spec-file clermont-bootnode.ron --no-dns-discovery --cidr 192.168.0.0/16 --rpc-listen-address 0.0.0.0:8545 --max-block 0
+# export RPC_URL=http://ecomobicoin-1.cri.local.isima.fr:8545; export CHAIN_ID=6363; ./target/release/testnet-injector mine
+# ./truite --chain-spec-file clermont-bootnode.ron --no-dns-discovery --cidr 192.168.0.0/16 --rpc-listen-address 0.0.0.0:8545
+# 
+
+# Mine every 10 secs or number of blocks
+for ((i=1; i<=100; i++))
+do
+    echo "MINE $i/100";
+    ./target/release/testnet-injector mine
+    if [ $i -ne 100 ]; then
+        sleep $BLOCK_TIME
+    fi
+done
+
+# Check if eco2 sync properly
+
+
diff --git a/scripts/t1-1mine-with-simulation-2sync.sh b/scripts/t1-1mine-with-simulation-2sync.sh
new file mode 100755
index 0000000000000000000000000000000000000000..d536649ee610a7b211431a6d8af6ff47c1c10700
--- /dev/null
+++ b/scripts/t1-1mine-with-simulation-2sync.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+export MINER_SIGNING_KEY=4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318;
+export MNEMONIC='before exclude interest push zone jealous boat ice chimney juice young side diagram silent pipe';
+
+# MINER_SIGNING_KEY & MNEMONIC env vars are set on workstation.
+export CHAIN_ID=6363;
+#  30 mins
+TOTAL_SIMULATION_TIME=1800;
+# 12 sec
+BLOCK_TIME=12;
+
+export RPC_URL=http://ecomobicoin-1.cri.local.isima.fr:8545;
+
+# Mine on eco1 with `--max-block 0`
+# 
+#  rm -rf /home/angraign/.local/share/truite/chaindata/
+# ./truite --chain-spec-file clermont-bootnode.ron --no-dns-discovery --cidr 192.168.0.0/16 --rpc-listen-address 0.0.0.0:8545 --max-block 0
+# export RPC_URL=http://ecomobicoin-1.cri.local.isima.fr:8545; export CHAIN_ID=6363; ./target/release/testnet-injector mine
+# ./truite --chain-spec-file clermont-bootnode.ron --no-dns-discovery --cidr 192.168.0.0/16 --rpc-listen-address 0.0.0.0:8545
+# 
+
+# ./target/release/testnet-injector simulate-bx-from-csv ../movement-data/public-transport/MF_user_mobility_june-to-august-2023_mulhouse_dataset/anonymized_june-to-august-2023_mulhouse_dataset.csv $TOTAL_SIMULATION_TIME &
+./target/release/testnet-injector simulate-bx-from-csv ./data/dataset-extract.csv $TOTAL_SIMULATION_TIME &
+
+# Mine every 10 secs or number of blocks
+for ((i=1; i<=100; i++))
+do
+    echo "MINE $i/100";
+    ./target/release/testnet-injector mine
+    if [ $i -ne 100 ]; then
+        sleep $BLOCK_TIME
+    fi
+done
+
+
diff --git a/scripts/t2.sh b/scripts/t2.sh
new file mode 100755
index 0000000000000000000000000000000000000000..e62ef7ab487f4251083a4b521f23cbe2b452819d
--- /dev/null
+++ b/scripts/t2.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+export MINER_SIGNING_KEY=4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318;
+export MNEMONIC='before exclude interest push zone jealous boat ice chimney juice young side diagram silent pipe';
+
+# MINER_SIGNING_KEY & MNEMONIC env vars are set on workstation.
+export CHAIN_ID=6363;
+#  30 mins
+TOTAL_SIMULATION_TIME=180;
+# 12 sec
+BLOCK_TIME=12;
+
+# export RPC_URL=http://ecomobicoin-2.cri.local.isima.fr:10002;
+export RPC_URL=http://ecomobicoin-2.cri.local.isima.fr:8545;
+export RPC_URL_MINER=http://ecomobicoin-2.cri.local.isima.fr:8545;
+# export RPC_URL=http://127.0.0.1:8545;
+# export RPC_URL_MINER=http://127.0.0.1:8545;
+
+# Mine on eco1 with `--max-block 0`
+# export RPC_URL=http://ecomobicoin-1.cri.local.isima.fr:8545; export CHAIN_ID=6363; ./target/release/testnet-injector mine
+
+# ./target/release/testnet-injector simulate-bx-from-csv ../movement-data/public-transport/MF_user_mobility_june-to-august-2023_mulhouse_dataset/anonymized_june-to-august-2023_mulhouse_dataset.csv $TOTAL_SIMULATION_TIME &
+./target/release/testnet-injector simulate-bx-from-csv ./data/dataset-extract.csv $TOTAL_SIMULATION_TIME &
+
+# Mine every 10 secs or number of blocks
+for ((i=1; i<=100; i++))
+do
+    echo "MINE $i/100";
+    export RPC_URL=$RPC_URL_MINER; ./target/release/testnet-injector mine
+    if [ $i -ne 100 ]; then
+        sleep $BLOCK_TIME
+    fi
+done
+
+
diff --git a/src/helpers.rs b/src/helpers.rs
deleted file mode 100644
index e863b11c515d00af0a0bad6c5378f96047ebe2b9..0000000000000000000000000000000000000000
--- a/src/helpers.rs
+++ /dev/null
@@ -1,88 +0,0 @@
-pub mod txbx_helper {
-    use ecomobicoin_jsonrpc::types::{self, Bytes};
-    use ethereum_types::{Address, U64};
-    use ethnum::U256;
-    use std::time::SystemTime;
-
-    pub fn build_default_tx() -> ecomobicoin_jsonrpc::types::TransactionMessage {
-        build_tx(None, None, None, None, None, None, None)
-    }
-
-    pub fn build_tx(
-        chain_id: Option<U64>,
-        nonce: Option<U64>,
-        to: Option<Address>,
-        gas: Option<U64>,
-        gas_price: Option<U256>,
-        value: Option<U256>,
-        input: Option<Bytes>,
-    ) -> ecomobicoin_jsonrpc::types::TransactionMessage {
-        let chain_id = chain_id.unwrap_or_else(|| 636363.into());
-        let nonce = nonce.unwrap_or_else(|| 1.into());
-        // let to = to.unwrap_or_default();
-        let gas = gas.unwrap_or_default();
-        let gas_price = gas_price.unwrap_or_default();
-        let value = value.unwrap_or_default();
-        let input = input.unwrap_or_default();
-
-        types::TransactionMessage::EIP2930 {
-            chain_id,
-            nonce,
-            to,
-            gas,
-            gas_price,
-            value,
-            input,
-            access_list: [].to_vec(),
-        }
-    }
-
-    pub fn build_default_bx() -> ecomobicoin_jsonrpc::types::BehaviorMessage {
-        build_bx(None, None, None, None, None)
-    }
-
-    pub fn build_bx(
-        chain_id: Option<U64>,
-        to: Option<Address>,
-        timestamp: Option<U64>,
-        quantity: Option<U64>,
-        input: Option<Bytes>,
-    ) -> types::BehaviorMessage {
-        let chain_id = chain_id.unwrap_or_else(|| 636363.into());
-        let now = SystemTime::now()
-            .duration_since(SystemTime::UNIX_EPOCH)
-            .unwrap()
-            .as_secs();
-        let timestamp = timestamp.unwrap_or(now.into());
-        let quantity = quantity.unwrap_or(10_000.into());
-        let input = input.unwrap_or_default();
-
-        types::BehaviorMessage {
-            chain_id,
-            to,
-            timestamp,
-            quantity,
-            input,
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use std::default;
-
-    use super::*;
-    use crate::res::chainspec::MAINNET;
-
-    #[test]
-    fn build_default_tx_test() {
-        // ARRANGE
-        // ACT
-        let default_tx = build_default_tx();
-        // ASSERT
-        // default_tx.
-    }
-
-    #[test]
-    fn build_default_bx_test() {}
-}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b5614dd82335c52db29069c990b16b6c71b5a8b8
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1 @@
+pub mod utils;
diff --git a/src/main.rs b/src/main.rs
index dd8fc3229e1153df0da4efa72f77b653a3ef6e40..cff2cb7d83d5fa070958c9471311b281069cdff1 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,108 +1,71 @@
-use crate::helpers::txbx_helper::*;
-use actix_web::{
-    get, post,
-    web::{self, Data},
-    App, HttpRequest, HttpResponse, HttpServer, Responder,
+use ecomobicoin_jsonrpc::types::{Behavior, Block};
+use ethereum_types::U64;
+use ethers::{prelude::*, signers::coins_bip39::English};
+use futures::executor::block_on;
+use testnet_injector::utils::miner::get_miner_behavior;
+use std::{process, time::Duration};
+use std::time::{SystemTime, UNIX_EPOCH};
+use testnet_injector::{
+    utils::{mobility_data::*, txbx},
 };
-use dotenvy::dotenv;
-use ethers::{
-    prelude::*,
-    types::transaction::{
-        ecomobicoin_behavior::EcoMobiCoinBehaviorTransactionRequest, eip2718::TypedTransaction,
-    },
-    utils::keccak256,
-};
-use serde::Deserialize;
-use std::{
-    str::FromStr,
-    thread,
-    time::{Duration, SystemTime},
-};
-use tokio::spawn;
-use tokio_schedule::{every, Job};
-use tracing::{debug, info, Level};
-
-mod helpers;
-
-struct AppState {
-    // Mutex for thread safety ? https://actix.rs/docs/application#shared-mutable-state
-    signer: SignerMiddleware<Provider<Http>, LocalWallet>,
-    chain_id: u64,
-    rpc_url: String,
-}
-
-#[get("/health")]
-async fn healthcheck() -> impl Responder {
-    HttpResponse::Ok()
-}
-
-#[get("/")]
-async fn info(req: HttpRequest) -> impl Responder {
-    let data = req.app_data::<Data<AppState>>().unwrap();
-    HttpResponse::Ok().body(format!(
-        "Chain ID: {:?}, Signer's Address: {:?}, RPC: {:?}",
-        data.chain_id,
-        data.signer.address(),
-        data.rpc_url
-    ))
+use tracing::{debug, error, info, Level};
+
+use ::clap::{Args, Parser, Subcommand};
+
+#[derive(Parser)]
+#[command(author, version)]
+#[command(
+    about = "testnet-injector - a simple CLI to interact with a EcoMobiCoin testnet",
+    long_about = "The testnet-injector can perform all kinds of operations to enable the block construction of EcoMobiCoin blockchain with transactions (tx) and behaviors (bx)."
+)]
+struct Cli {
+    #[command(subcommand)]
+    command: Option<Commands>,
 }
 
-#[derive(Deserialize)]
-struct BlockFeed {
-    txs: usize,
-    bxs: usize,
+#[derive(Subcommand)]
+enum Commands {
+    /// Send txs and/or bxs using default content
+    Send(SendParams),
+    /// Simulate behaviors from dataset in csv and send them to node
+    SimulateBxFromCsv(SimulateBxFromCsv),
+    /// Mine a block
+    Mine(MineParams),
 }
 
-#[get("/blocks/feed")]
-async fn feed_block(req: HttpRequest, block_feed: web::Query<BlockFeed>) -> impl Responder {
-    let data = req.app_data::<Data<AppState>>().unwrap();
-
-    for _ in 0..block_feed.txs {
-        send_default_tx(data.signer.clone(), data.chain_id).await;
-    }
-
-    for _ in 0..block_feed.bxs {
-        send_default_bx(data.signer.clone(), data.chain_id).await;
-    }
-
-    HttpResponse::Ok().body(format!(
-        "Fed block with {} txs, {} bxs ...",
-        block_feed.txs, block_feed.bxs,
-    ))
+#[derive(Args)]
+struct SendParams {
+    /// Number of transactions to send
+    txs: u64,
+    /// Number of behaviors to send
+    bxs: u64,
+    // Node RPC URL
+    rpc_url: Option<String>,
+    // Chain ID
+    chain_id: Option<u64>
 }
 
-#[get("/bxs/send")]
-async fn send_bx(req: HttpRequest) -> impl Responder {
-    let data = req.app_data::<Data<AppState>>().unwrap();
-
-    let bx_hash = send_default_bx(data.signer.clone(), data.chain_id).await;
-
-    HttpResponse::Ok()
-        // .content_type("application/json")
-        .body(format!("BxHash: {:?}", bx_hash))
+#[derive(Args)]
+struct SimulateBxFromCsv {
+    /// Path to CSV file
+    file: String,
+    /// Total duration of simulation in seconds
+    total_simulation_duration: u64,
+    // Node RPC URL
+    rpc_url: Option<String>,
+    // Chain ID
+    chain_id: Option<u64>
 }
-#[get("/bxs/encode")]
-async fn encode_bx() -> impl Responder {
-    HttpResponse::Ok().body("bx")
-}
-#[get("/txs/send")]
-async fn send_tx(req: HttpRequest) -> impl Responder {
-    let data = req.app_data::<Data<AppState>>().unwrap();
-
-    let tx_hash = send_default_tx(data.signer.clone(), data.chain_id).await;
 
-    HttpResponse::Ok()
-        // .content_type("application/json")
-        .body(format!("TxHash: {:?}", tx_hash))
-}
-#[get("/txs/encode")]
-async fn encode_tx() -> impl Responder {
-    HttpResponse::Ok().body("tx encode")
+#[derive(Args)]
+struct MineParams {
+    // Node RPC URL
+    rpc_url: Option<String>,
+    // Chain ID
+    chain_id: Option<u64>
 }
 
-#[actix_web::main]
-async fn main() -> std::io::Result<()> {
-    let wait = false;
+async fn init() -> (u64, String) {
     // construct a subscriber that prints formatted traces to stdout
     let subscriber = tracing_subscriber::FmtSubscriber::builder()
         .with_max_level(Level::INFO)
@@ -111,7 +74,7 @@ async fn main() -> std::io::Result<()> {
     tracing::subscriber::set_global_default(subscriber)
         .expect("setting default tracing subscriber failed");
 
-    dotenv().expect(".env file not found");
+    // dotenv().expect(".env file not found");
     // for (key, value) in std::env::vars() {
     //     println!("{key}: {value}");
     // }
@@ -119,9 +82,6 @@ async fn main() -> std::io::Result<()> {
         std::env::var("RPC_URL").unwrap_or_else(|_| "http://127.0.0.1:8545".into());
     let provider = Provider::<Http>::try_from(rpc_url.clone()).unwrap();
 
-    let signing_key = std::env::var("SIGNING_KEY").unwrap();
-    let signer: LocalWallet = signing_key.parse::<LocalWallet>().unwrap();
-
     let chain_id: u64 = std::env::var("CHAIN_ID")
         .unwrap_or_else(|_| String::from("63"))
         .parse::<u64>()
@@ -133,220 +93,192 @@ async fn main() -> std::io::Result<()> {
             rpc_url
         )
     });
-    // connect the wallet to the provider
-    let client = SignerMiddleware::new(provider, signer.clone().with_chain_id(chain_id));
-    let addr = client.clone().address();
-    let nonce_manager = client.clone().nonce_manager(addr);
-    let curr_nonce = nonce_manager
-        .get_transaction_count(signer.address(), Some(BlockNumber::Pending.into()))
-        .await
-        .unwrap()
-        .as_u64();
-
-    info!(
-        "Signer: 0x{:02x}, nonce: {:?}",
-        signer.address(),
-        curr_nonce
-    );
-    // nonce_manager.clone();
-
-    // let shared_nonce_manager = Arc::new(Mutex::new(nonce_manager));
     info!(
         "Connected to chain id: {:?}, rpc: {:?}, latest block number: {:?}.",
         chain_id, rpc_url, block_number
     );
 
-    let auto_send_period: u64 = std::env::var("AUTO_SEND_PERIOD")
-        .unwrap_or_else(|_| "0".into())
-        .parse::<u64>()
-        .unwrap();
+    (chain_id, rpc_url)
+}
 
-    if auto_send_period > 0 {
-        info!(
-            "Auto sending bx and tx every {:?} seconds...",
-            auto_send_period
-        );
-
-        loop {
-            // TX
-            let tr = TransactionRequest::new()
-                .to(Address::zero())
-                .value(U256::from(210000))
-                .chain_id(chain_id);
-            let pending_tx = nonce_manager.send_transaction(tr, None).await.unwrap();
-            info!("Pending tx: {:?}", pending_tx.tx_hash());
-
-            // let tr = TransactionRequest::new()
-            //     .to(Address::zero())
-            //     .value(U256::from(210000))
-            //     .chain_id(chain_id);
-            // let pending_tx = nonce_manager.send_transaction(tr, None).await.unwrap();
-            // info!("Pending tx2: {:?}\n", pending_tx.tx_hash());
-
-            // BX
-            let tr = TransactionRequest::new()
-            // .to(signer.address())
-            .value(U256::from(210000))
-            .chain_id(chain_id)
-            .data(Bytes::from_str("0x015d8eb90000000000000000000000000000000000000000000000000000000000878c1c00000000000000000000000000000000000000000000000000000000644662bc0000000000000000000000000000000000000000000000000000001ee24fba17b7e19cc10812911dfa8a438e0a81a9933f843aa5b528899b8d9e221b649ae0df00000000000000000000000000000000000000000000000000000000000000060000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240").unwrap());
-
-            let now = SystemTime::now()
-                .duration_since(SystemTime::UNIX_EPOCH)
+#[tokio::main]
+async fn main() -> Result<(), anyhow::Error> {
+    let cli = Cli::parse();
+
+    let (mut chain_id, mut rpc_url) = init().await;
+
+    match &cli.command {
+        Some(Commands::Send(params)) => {
+            info!("Sending {:?} tx and {:?} bx ...", params.txs, params.bxs);
+            // Override env vars if specified in CLI
+            if params.rpc_url.clone().is_some(){
+                rpc_url = params.rpc_url.clone().unwrap();
+            }
+            if params.chain_id.clone().is_some(){
+                chain_id = params.chain_id.clone().unwrap();
+            }
+
+            let max = std::cmp::max(params.txs, params.bxs);
+
+            for i in 0..max {
+                let mnemonic = std::env::var("MNEMONIC").unwrap_or("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about".to_string());
+                let wallet = MnemonicBuilder::<English>::default()
+                .phrase(PathOrString::String(mnemonic.clone()))
+                .index(i as u32)
+                .unwrap()
+                .build()
                 .unwrap()
-                .as_secs();
-            let bx = EcoMobiCoinBehaviorTransactionRequest::new(tr, Some(U64::from(now)));
-            // Sending with client to keep nonce update only for tx
-            let pending_bx = client.send_transaction(bx, None).await.unwrap();
-            info!("Pending bx: {:?}", pending_bx.tx_hash());
-
-            info!("Sleeping for {:?} secs...", auto_send_period);
-            thread::sleep(Duration::from_millis(auto_send_period * 1000));
+                .with_chain_id(chain_id);
+                let provider = Provider::<Http>::try_from(rpc_url.clone()).unwrap();
+                let client = SignerMiddleware::new(provider.clone(), wallet.clone());
+
+
+                if i < params.bxs {
+                    let bx_hash = txbx::send_default_bx(
+                        client.clone(),
+                        chain_id,
+                    ).await;
+                    info!("Sent default bx {:?}", bx_hash);
+                }
+                if i < params.txs {
+                    let tx_hash = txbx::send_default_tx(
+                        client.clone(),
+                        chain_id,
+                    ).await;
+                    info!("Sent default tx {:?}", tx_hash);
+                }
+            }
         }
-        // let every_x_seconds = every(auto_send_period)
-        //     .seconds() // by default chrono::Local timezone
-        //     .perform(|| async {
-        //         let chain_id2: u64 = std::env::var("CHAIN_ID")
-        //             .unwrap_or_else(|_| String::from("63"))
-        //             .parse::<u64>()
-        //             .unwrap();
-        //         let rpc_url2: String =
-        //             std::env::var("RPC_URL").unwrap_or_else(|_| "http://127.0.0.1:8545".into());
-        //         let provider2 = Provider::<Http>::try_from(rpc_url2).unwrap();
-        //         let signing_key2 = std::env::var("SIGNING_KEY").unwrap();
-        //         let signer2: LocalWallet = signing_key2.parse::<LocalWallet>().unwrap();
-        //         let client2 = SignerMiddleware::new(provider2, signer2.with_chain_id(chain_id2));
-
-        //         // send_default_tx(client2.clone(), chain_id2).await;
-        //         // send_default_bx(client2, chain_id2).await;
-        //         let addr = client2.clone().address();
-
-        //         let nonce_manager = client2.clone().nonce_manager(addr);
-
-        //         // TX
-        //         let tr = TransactionRequest::new()
-        //             .to(Address::zero())
-        //             .value(U256::from(210000))
-        //             .chain_id(chain_id2);
-
-        //         let pending_tx =
-        //             nonce_manager.send_transaction(tr, None).await.unwrap();
-        //         info!("Pending tx: {:?}\n", pending_tx.tx_hash());
-
-        //         let tr = TransactionRequest::new()
-        //         .to(Address::zero())
-        //         .value(U256::from(210000))
-        //         .chain_id(chain_id2);
-        //         let pending_tx =
-        //             nonce_manager.send_transaction(tr, None).await.unwrap();
-        //         info!("Pending tx2: {:?}\n", pending_tx.tx_hash());
-
-        //         // BX
-        //         let tr = TransactionRequest::new()
-        //             // .to(signer.address())
-        //             .value(U256::from(210000))
-        //             .chain_id(chain_id2)
-        //             .data(Bytes::from_str("0x015d8eb90000000000000000000000000000000000000000000000000000000000878c1c00000000000000000000000000000000000000000000000000000000644662bc0000000000000000000000000000000000000000000000000000001ee24fba17b7e19cc10812911dfa8a438e0a81a9933f843aa5b528899b8d9e221b649ae0df00000000000000000000000000000000000000000000000000000000000000060000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240").unwrap());
-
-        //         let now = SystemTime::now()
-        //                 .duration_since(SystemTime::UNIX_EPOCH)
-        //                 .unwrap()
-        //                 .as_secs();
-        //         let bx = EcoMobiCoinBehaviorTransactionRequest::new(tr, Some(U64::from(now)));
-        //         let pending_bx =
-        //             nonce_manager.send_transaction(bx, None).await.unwrap();
-        //             info!("Pending bx: {:?}\n", pending_bx.tx_hash());
-        //     });
-        // spawn(every_x_seconds);
+        Some(Commands::SimulateBxFromCsv(params)) => {
+            info!("Simulating file {:?} for a total time of {:?} secs",params.file.clone(), params.total_simulation_duration.clone());
+            // Override env vars if specified in CLI
+            if params.rpc_url.clone().is_some(){
+                rpc_url = params.rpc_url.clone().unwrap();
+            }
+            if params.chain_id.clone().is_some(){
+                chain_id = params.chain_id.clone().unwrap();
+            }
+            // println!("Processing {:?}", params.file);
+            // csv::Reader::from_path(path)
+            let records = read_csv(csv::Reader::from_path(params.file.clone())?);
+            if let Err(err) = records {
+                error!("error running example: {}", err);
+                process::exit(1);
+            }
+            let records = records.unwrap();
+
+            let mobility_records = generate_behaviors_from_records(records.clone());
+
+            let dataset_max_relative_time = mobility_records
+                .iter()
+                .max_by(|x, y| x.relative_start_time.cmp(&y.relative_start_time))
+                .unwrap()
+                .relative_start_time;
+
+            let total_simulation_duration = if params.total_simulation_duration > 0 {
+                params.total_simulation_duration
+            } else {
+                dataset_max_relative_time as u64
+            };
+
+            info!(
+                "Simulation of {:?} records for {:?}secs (max time {:?})",
+                mobility_records.len(),
+                total_simulation_duration,
+                Duration::from_millis(dataset_max_relative_time as u64)
+            );
+
+            let mobility_records_times: Vec<Duration> = mobility_records.iter().map(|r| compute_stretched_relative_start_time(
+                r.relative_start_time as u64,
+                total_simulation_duration,
+                dataset_max_relative_time as u64,
+            )).collect();
+
+            // ----------------
+            // Sending queue
+            // ----------------
+            let mut handles = Vec::with_capacity(mobility_records.len());
+
+            for i in 0..mobility_records.len() {
+                let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
+                let simulation_behavior = mobility_records[i].clone();
+                let relative_start_duration = mobility_records_times[i].clone();
+                let rpc_url = rpc_url.clone();
+                let chain_id = chain_id.clone();
+                let mut time_since_beginning = now;
+
+                handles.push(tokio::spawn({
+                    async move {
+                            tokio::time::sleep(relative_start_duration).await;
+                            time_since_beginning += relative_start_duration.as_secs();
+
+                            let provider = Provider::<Http>::try_from(rpc_url.clone()).unwrap();
+
+                            debug!("Waited {:?} for behavior {:?}", relative_start_duration, simulation_behavior.id);
+                            // Wallet is derived from mnemomic with user_id to ensure each user have the same wallet for the simulation.
+                            let mnemonic = std::env::var("MNEMONIC").unwrap_or("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about".to_string());
+                            let wallet = MnemonicBuilder::<English>::default()
+                                .phrase(PathOrString::String(mnemonic.clone()))
+                                .index(simulation_behavior.mobility_record.user_id as u32)
+                                .unwrap()
+                                .build()
+                                .unwrap()
+                                .with_chain_id(chain_id);
+
+                            info!(
+                                "Sending bx {:?}, qty: {:?},user {:?} with wallet {:?} ",
+                                simulation_behavior.id,
+                                simulation_behavior.quantity,
+                                simulation_behavior.mobility_record.user_id,
+                                wallet.address()
+                            );
+                            let client = SignerMiddleware::new(provider.clone(), wallet.clone());
+
+                            let bx_hash = txbx::send_behavior_transaction(
+                                client.clone(),
+                                chain_id,
+                                time_since_beginning,
+                                simulation_behavior.quantity,
+                                Bytes::default(), // Bytes::from_static(&behavior.mobility_record),
+                            ).await;
+                            info!("Sent bx({:?}): {:?}",simulation_behavior.id,bx_hash);
+                    }
+                        }))
+            }
+
+            info!("Scheduling done, waiting for bx & tx threads...");
+
+            // Wait for all of them to complete.
+            for handle in handles {
+                block_on(handle).unwrap();
+            }
+        },
+        Some(Commands::Mine(params)) => {
+
+            // Override env vars if specified in CLI
+            if params.rpc_url.clone().is_some(){
+                rpc_url = params.rpc_url.clone().unwrap();
+            }
+            if params.chain_id.clone().is_some(){
+                chain_id = params.chain_id.clone().unwrap();
+            }
+
+            // Wallet is derived from mnemomic with user_id to ensure each user have the same wallet for the simulation.
+            let miner_signing_key = std::env::var("MINER_SIGNING_KEY").expect("Must specify a miner private key");
+            let wallet: LocalWallet = miner_signing_key.parse::<LocalWallet>().unwrap();
+            let miner_addr = wallet.address();
+            info!("Mining with 0x{:02x} ...",miner_addr);
+
+            let behavior = get_miner_behavior(wallet, chain_id, miner_addr);
+            // SEND IT
+            let provider = Provider::<Http>::try_from(rpc_url.clone()).unwrap();
+            debug!("Miner behavior: {:?}",serde_json::to_string(&behavior.clone()));
+            let block = provider.request::<[Behavior;1], Block>("emc_mine", [behavior]).await.unwrap();
+
+            debug!("Mining response: {:?}", block);
+            info!("Mined block {:?}", block.number.unwrap())
+        },
+        None => {}
     }
-
-    HttpServer::new(move || {
-        App::new()
-            .app_data(web::Data::new(AppState {
-                signer: client.clone(),
-                chain_id,
-                rpc_url: rpc_url.clone(),
-            }))
-            .service(info)
-            .service(healthcheck)
-            .service(feed_block)
-            .service(send_tx)
-            .service(send_bx)
-    })
-    .bind(("127.0.0.1", 8080))?
-    .run()
-    .await
-}
-
-async fn send_transaction(
-    signer: SignerMiddleware<Provider<Http>, LocalWallet>,
-    chain_id: u64,
-    to: Address,
-    value: U256,
-) -> H256 {
-    // craft the transaction
-    let tx = TransactionRequest::new()
-        .to(to)
-        .value(value)
-        .chain_id(chain_id);
-
-    let pending_tx: PendingTransaction<'_, Http> = signer.send_transaction(tx, None).await.unwrap();
-    info!("Pending tx: {:?}\n", pending_tx.tx_hash());
-    pending_tx.tx_hash()
-}
-async fn send_behavior_transaction(
-    signer: SignerMiddleware<Provider<Http>, LocalWallet>,
-    chain_id: u64,
-    timestamp: u64,
-    quantity: U256,
-    data: Bytes,
-) -> H256 {
-    let tr = TransactionRequest::new()
-        // .to(signer.address())
-        .value(quantity)
-        .chain_id(chain_id)
-        .data(data.0.clone());
-
-    let bx = EcoMobiCoinBehaviorTransactionRequest::new(tr, Some(U64::from(timestamp)));
-    // let typed_tx = transaction::eip2718::TypedTransaction::EcoMobiCoinBehavior(bx);
-    // let bx_signed = signer.sign_transaction(&typed_tx);
-    // send it!
-    let pending_bx: PendingTransaction<'_, Http> = signer.send_transaction(bx, None).await.unwrap();
-
-    let bx: EcoMobiCoinBehaviorTransactionRequest = EcoMobiCoinBehaviorTransactionRequest::new(
-        TransactionRequest::new()
-            // .to(signer.address())
-            .value(quantity)
-            .chain_id(chain_id)
-            .data(data.0.clone()),
-        Some(U64::from(timestamp)),
-    );
-    debug!("bx {:?}", bx);
-
-    let bx_sighash = TypedTransaction::EcoMobiCoinBehavior(bx.clone()).sighash();
-    debug!("bx_sighash {:02x}", bx_sighash);
-
-    let behavior_rlp = bx.rlp();
-    debug!("behavior_rlp {:02x}", behavior_rlp.clone());
-    let bx_hash: H256 = keccak256(behavior_rlp.as_ref()).into();
-    debug!("bx_hash {:02x}", bx_hash);
-
-    info!("Pending bx: {:?}\n", pending_bx.tx_hash());
-    pending_bx.tx_hash()
-}
-
-async fn send_default_tx(
-    signer: SignerMiddleware<Provider<Http>, LocalWallet>,
-    chain_id: u64,
-) -> H256 {
-    send_transaction(signer, chain_id, Address::zero(), U256::from(210000)).await
-}
-async fn send_default_bx(
-    signer: SignerMiddleware<Provider<Http>, LocalWallet>,
-    chain_id: u64,
-) -> H256 {
-    let now = SystemTime::now()
-        .duration_since(SystemTime::UNIX_EPOCH)
-        .unwrap()
-        .as_secs();
-    send_behavior_transaction(signer, chain_id, now,U256::from(210000), Bytes::from_str("0x015d8eb90000000000000000000000000000000000000000000000000000000000878c1c00000000000000000000000000000000000000000000000000000000644662bc0000000000000000000000000000000000000000000000000000001ee24fba17b7e19cc10812911dfa8a438e0a81a9933f843aa5b528899b8d9e221b649ae0df00000000000000000000000000000000000000000000000000000000000000060000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240").unwrap()).await
+    Ok(())
 }
diff --git a/src/utils/miner.rs b/src/utils/miner.rs
new file mode 100644
index 0000000000000000000000000000000000000000..82fae9982958ee0bf88926739df982e13a110f9e
--- /dev/null
+++ b/src/utils/miner.rs
@@ -0,0 +1,55 @@
+use std::time::SystemTime;
+
+use ecomobicoin_jsonrpc::types::{Behavior, BehaviorMessage, Bytes};
+use ethereum_interfaces::types::H256;
+use ethereum_types::{Address, U64};
+use ethers::{
+    signers::{LocalWallet, Signer},
+    types::{
+        transaction::ecomobicoin_behavior::EcoMobiCoinBehaviorTransactionRequest,
+        TransactionRequest,
+    },
+};
+use ethnum::U256;
+
+/// Get a RPC Behavior for miner with default values, ready to send.
+pub fn get_miner_behavior(wallet: LocalWallet, chain_id: u64, to: Address) -> Behavior {
+    let now = SystemTime::now()
+        .duration_since(SystemTime::UNIX_EPOCH)
+        .unwrap()
+        .as_secs();
+
+    let behavior_msg = BehaviorMessage{
+         chain_id: U64::from(chain_id),
+          to:Some(to.clone()),
+           timestamp: U64::from(now),
+           quantity: U256::from(210000_u64),
+            input: Bytes::from("0x015d8eb90000000000000000000000000000000000000000000000000000000000878c1c00000000000000000000000000000000000000000000000000000000644662bc0000000000000000000000000000000000000000000000000000001ee24fba17b7e19cc10812911dfa8a438e0a81a9933f843aa5b528899b8d9e221b649ae0df00000000000000000000000000000000000000000000000000000000000000060000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240") };
+
+    // let behavior = wallet.
+    let tr = TransactionRequest::new()
+        .to(to)
+        .value(primitive_types::U256::zero())
+        .chain_id(chain_id)
+        .data(behavior_msg.input.0.clone());
+
+    let bx = EcoMobiCoinBehaviorTransactionRequest::new(tr, Some(U64::from(now)));
+    // wallet.sig
+    let signature = wallet
+        .sign_transaction_sync(
+            &ethers::types::transaction::eip2718::TypedTransaction::EcoMobiCoinBehavior(bx),
+        )
+        .unwrap();
+
+    Behavior {
+        message: behavior_msg,
+        v: signature.v.into(),
+        r: H256::from(signature.r).into(),
+        s: H256::from(signature.s).into(),
+        from: wallet.address(),
+        hash: H256::default().into(),
+        behavior_index: None,
+        block_number: None,
+        block_hash: None,
+    }
+}
diff --git a/src/utils/mobility_data.rs b/src/utils/mobility_data.rs
new file mode 100644
index 0000000000000000000000000000000000000000..edc15bf75154d526e49eb7b07ba9601341554f8d
--- /dev/null
+++ b/src/utils/mobility_data.rs
@@ -0,0 +1,297 @@
+use std::{error::Error, ops::Sub, time::Duration};
+
+use csv::Reader;
+use ethers::{
+    prelude::rand::{thread_rng, Rng},
+    types::Bytes,
+};
+use primitive_types::U256;
+use serde::Deserialize;
+use time::OffsetDateTime;
+
+/// Mobility Record from Public Transportation
+/// ---
+/// Created from data in `data/dataset-extract.csv`
+///
+/// CSV header:
+/// id user_id network service label created_at from_coord from_label from_datetime from_latitude from_longitude to_coord to_label to_datetime to_latitude to_longitude
+#[derive(Debug, Deserialize, Clone)]
+pub struct MobilityRecord {
+    pub id: u64,
+    pub user_id: u64,
+    pub network: String,
+    pub service: String,
+    pub label: String,
+    // `YYYY-MM-DD HH:mm:ss.SSSSSS` rfc3339
+    #[serde(with = "time::serde::rfc3339")]
+    pub created_at: time::OffsetDateTime,
+    /// Departure coordinates in hex PostGIS EWKB (Extended Well-Known Binary)
+    pub from_coord: Option<String>,
+    pub from_label: Option<String>,
+    // either `YYYY-MM-DD HH:mm:ss` or `YYYY-MM-DD HH:mm:ss.SSS` rfc3339
+    #[serde(with = "time::serde::rfc3339::option")]
+    pub from_datetime: Option<time::OffsetDateTime>,
+    pub from_latitude: Option<f64>,
+    pub from_longitude: Option<f64>,
+    /// Arrival coordinates in hex PostGIS EWKB (Extended Well-Known Binary)
+    pub to_coord: Option<String>,
+    pub to_label: Option<String>,
+    // `YYYY-MM-DD HH:mm:ss` rfc3339
+    #[serde(with = "time::serde::rfc3339::option")]
+    pub to_datetime: Option<time::OffsetDateTime>,
+    pub to_latitude: Option<f64>,
+    pub to_longitude: Option<f64>,
+}
+
+impl MobilityRecord {
+    unsafe fn any_as_u8_slice<T: Sized>(p: &T) -> &[u8] {
+        ::core::slice::from_raw_parts((p as *const T) as *const u8, ::core::mem::size_of::<T>())
+    }
+    pub fn to_bytes(&self) -> Bytes {
+        let bytes: &[u8] = unsafe {
+            ::core::slice::from_raw_parts(
+                (self as *const MobilityRecord) as *const u8,
+                ::core::mem::size_of::<MobilityRecord>(),
+            )
+        };
+
+        Bytes::from_static(bytes)
+    }
+}
+
+pub fn read_csv<R: std::io::Read>(
+    mut reader: Reader<R>,
+) -> Result<Vec<MobilityRecord>, Box<dyn Error>> {
+    let mut records = vec![];
+    for result in reader.deserialize() {
+        let record: MobilityRecord = result?;
+        // println!("{:?}", record);
+        records.push(record)
+    }
+    Ok(records)
+}
+
+/// Mobility Behavior
+/// ---
+///
+/// Including a MobilityRecord from CSV extraction
+#[derive(Debug, Clone)]
+pub struct MobilityBehavior {
+    pub id: u64,
+    // pub dataset_id: u64,
+    // in milliseconds
+    pub relative_start_time: i128,
+    pub quantity: U256,
+    pub mobility_record: MobilityRecord,
+}
+
+pub fn find_earliest_mobility_record(records: &Vec<MobilityRecord>) -> OffsetDateTime {
+    records
+        .iter()
+        .min_by(|x, y| x.created_at.cmp(&y.created_at))
+        .unwrap()
+        .created_at
+}
+
+pub fn find_latest_mobility_record(records: &Vec<MobilityRecord>) -> OffsetDateTime {
+    records
+        .iter()
+        .max_by(|x, y| x.created_at.cmp(&y.created_at))
+        .unwrap()
+        .created_at
+}
+
+pub fn generate_behaviors_from_records(records: Vec<MobilityRecord>) -> Vec<MobilityBehavior> {
+    let earliest_datetime = find_earliest_mobility_record(&records);
+
+    // let dataset_id = thread_rng().gen::<u64>();
+
+    records
+        .iter()
+        .enumerate()
+        .map(|(i, r)| MobilityBehavior {
+            id: i as u64,
+            // dataset_id,
+            relative_start_time: r.created_at.sub(earliest_datetime).whole_milliseconds(),
+            quantity: quantify_mobility(r.service.clone()),
+            mobility_record: r.clone(),
+        })
+        .collect()
+}
+
+/// Strech behavior start time for simulation
+pub fn compute_stretched_relative_start_time(
+    relative_start_time: u64,
+    total_simulation_duration: u64,
+    dataset_max_relative_time: u64,
+) -> Duration {
+    Duration::from_millis(
+        (relative_start_time * total_simulation_duration * 1000) / dataset_max_relative_time,
+    )
+}
+
+/// Provide differents fake quantities depending on mobility e.g. BIKE that will be use for rewards calculation
+pub fn quantify_mobility(service: String) -> U256 {
+    if service == "BIKE_SHARING" {
+        U256::from(1000000000000000000_u128)
+    } else if service == "BIKE_RENTAL" {
+        U256::from(500000000000000000_u128)
+    } else if service == "PUBLIC_TRANSPORT" {
+        U256::from(10000000000000000_u128)
+    } else {
+        U256::from(1)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use time::OffsetDateTime;
+
+    use super::*;
+
+    #[test]
+    fn read_csv_should_work() {
+        // ARRANGE
+        let example_data = "\
+        id,user_id,network,service,label,created_at,from_coord,from_label,from_datetime,to_coord,to_label,to_datetime,from_latitude,from_longitude,to_latitude,to_longitude
+        10653,79,mulhouse,PUBLIC_TRANSPORT,Abo,2023-07-02T19:02:04.987621Z,DEADC0DE,TEST,2023-07-02T21:02:04.000Z,DEADC0DE,TEST,2023-07-02T21:02:04.000Z,0.0,0.0,0.0,0.0
+        ";
+        // let mut rdr = Reader::from_reader(example_data.as_bytes());
+        let mut rdr = Reader::from_path("./data/dataset-extract.csv").unwrap();
+        // ACT
+        let result = read_csv(rdr).unwrap();
+        // ASSERT
+        assert_eq!(6, result.len())
+    }
+
+    #[test]
+    fn to_bytes_should_work() {
+        // ARRANGE
+        let mobility_record = MobilityRecord {
+            id: 1337,
+            user_id: 1,
+            network: String::from("mulhouse"),
+            service: String::from("PUBLIC_TRANSPORT"),
+            label: String::from("BLABLABLA"),
+            created_at: OffsetDateTime::from_unix_timestamp(1699353308).unwrap(),
+            from_datetime: None,
+            from_coord: None,
+            from_label: None,
+            from_latitude: None,
+            from_longitude: None,
+            to_datetime: None,
+            to_coord: None,
+            to_label: None,
+            to_latitude: None,
+            to_longitude: None,
+        };
+
+        // ACT
+        let result = mobility_record.to_bytes();
+        // ASSERT
+
+        // FIXME Binary data change between tests o
+        assert!(!result.to_string().is_empty());
+        // assert_eq!("0x", result.to_string());
+    }
+
+    #[test]
+    fn find_earliest_mobility_record_should_work() {
+        // ARRANGE
+        let mut rdr = Reader::from_path("./data/dataset-extract.csv").unwrap();
+        let records = read_csv(rdr).unwrap();
+
+        // ACT
+        let earliest_datetime = find_earliest_mobility_record(&records);
+
+        // ASSERT
+        assert_eq!(
+            1685570991226773000,
+            earliest_datetime.unix_timestamp_nanos()
+        );
+    }
+
+    #[test]
+    fn find_latest_mobility_record_should_work() {
+        // ARRANGE
+        let mut rdr = Reader::from_path("./data/dataset-extract.csv").unwrap();
+        let records = read_csv(rdr).unwrap();
+
+        // ACT
+        let latest_datetime = find_latest_mobility_record(&records);
+
+        // ASSERT
+        assert_eq!(1688324524987621000, latest_datetime.unix_timestamp_nanos());
+    }
+
+    #[test]
+    fn create_behaviors_from_records_should_work() {
+        // ARRANGE
+        let mut rdr = Reader::from_path("./data/dataset-extract.csv").unwrap();
+        let records = read_csv(rdr).unwrap();
+
+        // ACT
+        let behaviors = generate_behaviors_from_records(records.clone());
+        // ASSERT
+        assert_eq!(records.len(), behaviors.len());
+    }
+
+    #[test]
+    fn compute_stretched_relative_start_time_should_work_0() {
+        // ARRANGE
+
+        // ACT
+        let result = compute_stretched_relative_start_time(0, 10, 1000);
+
+        // ASSERT
+        assert_eq!(Duration::from_secs(0), result);
+    }
+
+    #[test]
+    fn compute_stretched_relative_start_time_should_work() {
+        // ARRANGE
+        // 1000 ms
+        let relative_start_time = 1000;
+        // 10 secs
+        let total_simulation_duration = 10;
+        // 2 sec
+        let dataset_max_relative_time = 2000;
+
+        // ACT
+        let result = compute_stretched_relative_start_time(
+            relative_start_time,
+            total_simulation_duration,
+            dataset_max_relative_time,
+        );
+        // ASSERT
+        assert_eq!(Duration::from_secs(5), result);
+    }
+    #[test]
+    fn compute_stretched_relative_start_time_should_work_max() {
+        // ARRANGE
+        // 1000 ms
+        let relative_start_time = 2000;
+        // 10 secs
+        let total_simulation_duration = 10;
+        // 2 sec
+        let dataset_max_relative_time = 2000;
+
+        // ACT
+        let result = compute_stretched_relative_start_time(
+            relative_start_time,
+            total_simulation_duration,
+            dataset_max_relative_time,
+        );
+        // ASSERT
+        assert_eq!(Duration::from_secs(10), result);
+    }
+
+    #[test]
+    fn quantify_mobility_bike() {
+        // ARRANGE
+        // ACT
+        let result = quantify_mobility(String::from("BIKE_SHARING"));
+        // ASSERT
+        assert_eq!(U256::from(1000000000000000000_u128), result);
+    }
+}
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..7a89f5ada9ca2650f6cad2fde6d451c3f679c2f0
--- /dev/null
+++ b/src/utils/mod.rs
@@ -0,0 +1,3 @@
+pub mod miner;
+pub mod mobility_data;
+pub mod txbx;
diff --git a/src/utils/txbx.rs b/src/utils/txbx.rs
new file mode 100644
index 0000000000000000000000000000000000000000..fb674957cbd33ed43f6629781d353efe239dfee7
--- /dev/null
+++ b/src/utils/txbx.rs
@@ -0,0 +1,103 @@
+use ethereum_types::U256;
+use ethereum_types::{Address, U64};
+use ethers::{
+    prelude::*,
+    types::transaction::{
+        ecomobicoin_behavior::EcoMobiCoinBehaviorTransactionRequest, eip2718::TypedTransaction,
+    },
+    utils::keccak256,
+};
+use std::str::FromStr;
+use std::time::SystemTime;
+use tracing::debug;
+
+pub async fn send_transaction(
+    signer: SignerMiddleware<Provider<Http>, LocalWallet>,
+    chain_id: u64,
+    to: Address,
+    value: U256,
+) -> H256 {
+    // craft the transaction
+    let tx = TransactionRequest::new()
+        .to(to)
+        .value(value)
+        .chain_id(chain_id);
+
+    let pending_tx: PendingTransaction<'_, Http> = signer.send_transaction(tx, None).await.unwrap();
+    pending_tx.tx_hash()
+}
+pub async fn send_behavior_transaction(
+    signer: SignerMiddleware<Provider<Http>, LocalWallet>,
+    chain_id: u64,
+    timestamp: u64,
+    quantity: U256,
+    data: Bytes,
+) -> H256 {
+    let tr = TransactionRequest::new()
+        // .to(signer.address())
+        .value(quantity)
+        .chain_id(chain_id)
+        .data(data.0.clone());
+
+    let bx = EcoMobiCoinBehaviorTransactionRequest::new(tr, Some(U64::from(timestamp)));
+    // let typed_tx = transaction::eip2718::TypedTransaction::EcoMobiCoinBehavior(bx);
+    // let bx_signed = signer.sign_transaction(&typed_tx);
+    // send it!
+    let pending_bx: PendingTransaction<'_, Http> = signer.send_transaction(bx, None).await.unwrap();
+
+    let bx: EcoMobiCoinBehaviorTransactionRequest = EcoMobiCoinBehaviorTransactionRequest::new(
+        TransactionRequest::new()
+            // .to(signer.address())
+            .value(quantity)
+            .chain_id(chain_id)
+            .data(data.0.clone()),
+        Some(U64::from(timestamp)),
+    );
+    debug!("bx {:?}", bx);
+
+    let bx_sighash = TypedTransaction::EcoMobiCoinBehavior(bx.clone()).sighash();
+    debug!("bx_sighash {:02x}", bx_sighash);
+
+    let behavior_rlp = bx.rlp();
+    debug!("behavior_rlp {:02x}", behavior_rlp.clone());
+    let bx_hash: H256 = keccak256(behavior_rlp.as_ref()).into();
+    debug!("bx_hash {:02x}", bx_hash);
+
+    pending_bx.tx_hash()
+}
+
+pub async fn send_default_tx(
+    signer: SignerMiddleware<Provider<Http>, LocalWallet>,
+    chain_id: u64,
+) -> H256 {
+    send_transaction(signer, chain_id, Address::zero(), U256::from(210000)).await
+}
+pub async fn send_default_bx(
+    signer: SignerMiddleware<Provider<Http>, LocalWallet>,
+    chain_id: u64,
+) -> H256 {
+    let now = SystemTime::now()
+        .duration_since(SystemTime::UNIX_EPOCH)
+        .unwrap()
+        .as_secs();
+    send_behavior_transaction(signer, chain_id, now,U256::from(210000), Bytes::from_str("0x015d8eb90000000000000000000000000000000000000000000000000000000000878c1c00000000000000000000000000000000000000000000000000000000644662bc0000000000000000000000000000000000000000000000000000001ee24fba17b7e19cc10812911dfa8a438e0a81a9933f843aa5b528899b8d9e221b649ae0df00000000000000000000000000000000000000000000000000000000000000060000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240").unwrap()).await
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn build_default_tx_test() {
+        // ARRANGE
+        // ACT
+        // ASSERT
+    }
+
+    #[test]
+    fn build_default_bx_test() {
+        // ARRANGE
+        // ACT
+        // ASSERT
+    }
+}