gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-depolymerization] branch master updated (f1cd4f8 -> 2ad5eb9)


From: gnunet
Subject: [taler-depolymerization] branch master updated (f1cd4f8 -> 2ad5eb9)
Date: Wed, 02 Feb 2022 19:51:27 +0100

This is an automated email from the git hooks/post-receive script.

antoine pushed a change to branch master
in repository depolymerization.

    from f1cd4f8  presentation: progress
     new ed31faa  presentation: progress
     new c0c672b  eth_wire: initialize
     new d76ac36  eth_wire: rpc pub/sub
     new 48b3266  Preparation for eth-wire
     new 2ad5eb9  eth-wire: wire draft

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 Cargo.lock                                         | 146 +++++++-
 Cargo.toml                                         |   1 +
 btc-wire/Cargo.toml                                |   6 +-
 btc-wire/README.md                                 |   4 +-
 btc-wire/src/bin/{test.rs => btc-test.rs}          |   0
 btc-wire/src/bin/btc-wire-cli.rs                   |  10 +-
 btc-wire/src/bin/btc-wire-utils.rs                 |  19 +-
 btc-wire/src/info.rs                               |   4 +
 btc-wire/src/loops/analysis.rs                     |  10 +-
 btc-wire/src/loops/watcher.rs                      |   2 +-
 btc-wire/src/loops/worker.rs                       |  16 +-
 btc-wire/src/main.rs                               |  31 +-
 btc-wire/src/taler_util.rs                         |   9 +-
 db/btc.sql                                         |   4 +-
 db/{btc.sql => eth.sql}                            |   4 +-
 docs/presentation.tex                              | 169 +++++++--
 eth-wire/Cargo.toml                                |  24 ++
 eth-wire/src/bin/eth-test.rs                       | 140 +++++++
 eth-wire/src/bin/eth-wire-cli.rs                   |  99 +++++
 eth-wire/src/bin/eth-wire-utils.rs                 | 163 +++++++++
 eth-wire/src/lib.rs                                | 123 +++++++
 eth-wire/src/main.rs                               | 275 ++++++++++++++
 eth-wire/src/metadata.rs                           | 177 +++++++++
 eth-wire/src/rpc.rs                                | 404 +++++++++++++++++++++
 {btc-wire => eth-wire}/src/status.rs               |   0
 eth-wire/src/taler_util.rs                         |  46 +++
 makefile                                           |   6 +-
 script/prepare.sh                                  |  13 +-
 taler-common/Cargo.toml                            |   8 +-
 taler-common/src/config.rs                         | 125 ++++---
 test/btc/analysis.sh                               |   5 +-
 test/btc/bumpfee.sh                                |   4 +-
 test/btc/config.sh                                 |   1 +
 test/btc/conflict.sh                               |   7 +-
 test/btc/hell.sh                                   |  11 +-
 test/btc/lifetime.sh                               |   6 +-
 test/btc/maxfee.sh                                 |   3 +-
 test/btc/reconnect.sh                              |   5 +-
 test/btc/reorg.sh                                  |   3 +-
 test/btc/stress.sh                                 |   3 +-
 test/btc/wire.sh                                   |   5 +-
 test/common.sh                                     | 143 ++++++--
 test/conf/{taler_test.conf => taler_btc.conf}      |   3 +-
 test/conf/{taler_bump.conf => taler_btc_bump.conf} |   0
 ...taler_lifetime.conf => taler_btc_lifetime.conf} |   0
 test/conf/taler_eth.conf                           |  10 +
 test/eth/test.sh                                   |  19 +
 test/{btc => eth}/wire.sh                          |  55 +--
 test/gateway/api.sh                                |   1 +
 uri-pack/Cargo.toml                                |   2 +-
 wire-gateway/Cargo.toml                            |   6 +-
 wire-gateway/src/main.rs                           |  34 +-
 52 files changed, 2118 insertions(+), 246 deletions(-)
 rename btc-wire/src/bin/{test.rs => btc-test.rs} (100%)
 copy db/{btc.sql => eth.sql} (90%)
 create mode 100644 eth-wire/Cargo.toml
 create mode 100644 eth-wire/src/bin/eth-test.rs
 create mode 100644 eth-wire/src/bin/eth-wire-cli.rs
 create mode 100644 eth-wire/src/bin/eth-wire-utils.rs
 create mode 100644 eth-wire/src/lib.rs
 create mode 100644 eth-wire/src/main.rs
 create mode 100644 eth-wire/src/metadata.rs
 create mode 100644 eth-wire/src/rpc.rs
 copy {btc-wire => eth-wire}/src/status.rs (100%)
 create mode 100644 eth-wire/src/taler_util.rs
 rename test/conf/{taler_test.conf => taler_btc.conf} (88%)
 rename test/conf/{taler_bump.conf => taler_btc_bump.conf} (100%)
 rename test/conf/{taler_lifetime.conf => taler_btc_lifetime.conf} (100%)
 create mode 100644 test/conf/taler_eth.conf
 create mode 100644 test/eth/test.sh
 copy test/{btc => eth}/wire.sh (50%)

diff --git a/Cargo.lock b/Cargo.lock
index 4d27de6..b4e5c13 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -315,6 +315,12 @@ dependencies = [
  "lazy_static",
 ]
 
+[[package]]
+name = "crunchy"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
+
 [[package]]
 name = "crypto-common"
 version = "0.1.1"
@@ -451,6 +457,46 @@ dependencies = [
  "regex",
 ]
 
+[[package]]
+name = "eth-wire"
+version = "0.1.0"
+dependencies = [
+ "argh",
+ "ethereum-types",
+ "hex",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "taler-common",
+ "thiserror",
+ "uri-pack",
+]
+
+[[package]]
+name = "ethbloom"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "bfb684ac8fa8f6c5759f788862bb22ec6fe3cb392f6bfd08e3c64b603661e3f8"
+dependencies = [
+ "crunchy",
+ "fixed-hash",
+ "impl-serde",
+ "tiny-keccak",
+]
+
+[[package]]
+name = "ethereum-types"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "05136f7057fe789f06e6d41d07b34e6f70d8c86e5693b60f97aaa6553553bdaf"
+dependencies = [
+ "ethbloom",
+ "fixed-hash",
+ "impl-serde",
+ "primitive-types",
+ "uint",
+]
+
 [[package]]
 name = "fallible-iterator"
 version = "0.2.0"
@@ -466,11 +512,22 @@ dependencies = [
  "instant",
 ]
 
+[[package]]
+name = "fixed-hash"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c"
+dependencies = [
+ "byteorder",
+ "rustc-hex",
+ "static_assertions",
+]
+
 [[package]]
 name = "flexi_logger"
-version = "0.22.2"
+version = "0.22.3"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "0b51b4517f4422bfa0515dafcc10b4cc4cd3953d69a19608fd74afb3b19e227c"
+checksum = "969940c39bc718475391e53a3a59b0157e64929c80cf83ad5dde5f770ecdc423"
 dependencies = [
  "chrono",
  "glob",
@@ -748,6 +805,15 @@ dependencies = [
  "unicode-normalization",
 ]
 
+[[package]]
+name = "impl-serde"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "instant"
 version = "0.1.12"
@@ -795,9 +861,9 @@ checksum = 
"e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
 name = "libc"
-version = "0.2.114"
+version = "0.2.116"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "b0005d08a8f7b65fb8073cb697aa0b12b631ed251ce73d862ce50eeb52ce3b50"
+checksum = "565dbd88872dbe4cc8a46e527f26483c1d1f7afa6b884a3bd6cd893d4f98da74"
 
 [[package]]
 name = "listenfd"
@@ -812,9 +878,9 @@ dependencies = [
 
 [[package]]
 name = "lock_api"
-version = "0.4.5"
+version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
+checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b"
 dependencies = [
  "scopeguard",
 ]
@@ -929,9 +995,9 @@ dependencies = [
 
 [[package]]
 name = "num_threads"
-version = "0.1.2"
+version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "71a1eb3a36534514077c1e079ada2fb170ef30c47d203aa6916138cf882ecd52"
+checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15"
 dependencies = [
  "libc",
 ]
@@ -1110,6 +1176,17 @@ version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
 
+[[package]]
+name = "primitive-types"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "05e4722c697a58a99d5d06a08c30821d7c082a4632198de1eaa5a6c22ef42373"
+dependencies = [
+ "fixed-hash",
+ "impl-serde",
+ "uint",
+]
+
 [[package]]
 name = "proc-macro2"
 version = "1.0.36"
@@ -1257,6 +1334,12 @@ dependencies = [
  "ordered-multimap",
 ]
 
+[[package]]
+name = "rustc-hex"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
+
 [[package]]
 name = "rustc_version"
 version = "0.4.0"
@@ -1320,9 +1403,9 @@ checksum = 
"568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012"
 
 [[package]]
 name = "serde"
-version = "1.0.135"
+version = "1.0.136"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "2cf9235533494ea2ddcdb794665461814781c53f19d87b76e571a1c35acbad2b"
+checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
 dependencies = [
  "serde_derive",
 ]
@@ -1339,9 +1422,9 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.135"
+version = "1.0.136"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "8dcde03d87d4c973c04be249e7d8f0b35db1c848c487bd43032808e59dd8328d"
+checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1436,14 +1519,20 @@ checksum = 
"f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
 
 [[package]]
 name = "socket2"
-version = "0.4.3"
+version = "0.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "0f82496b90c36d70af5fcd482edaa2e0bd16fade569de1330405fecbbdac736b"
+checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
 dependencies = [
  "libc",
  "winapi",
 ]
 
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
 [[package]]
 name = "stringprep"
 version = "0.1.2"
@@ -1526,9 +1615,9 @@ dependencies = [
 
 [[package]]
 name = "time"
-version = "0.3.6"
+version = "0.3.7"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "c8d54b9298e05179c335de2b9645d061255bcd5155f843b3e328d2cfe0a5b413"
+checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d"
 dependencies = [
  "itoa 1.0.1",
  "libc",
@@ -1542,6 +1631,15 @@ version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6"
 
+[[package]]
+name = "tiny-keccak"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
+dependencies = [
+ "crunchy",
+]
+
 [[package]]
 name = "tinytemplate"
 version = "1.2.1"
@@ -1569,9 +1667,9 @@ checksum = 
"cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
 
 [[package]]
 name = "tokio"
-version = "1.15.0"
+version = "1.16.1"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838"
+checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a"
 dependencies = [
  "bytes",
  "libc",
@@ -1669,6 +1767,18 @@ version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
 
+[[package]]
+name = "uint"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "1b1b413ebfe8c2c74a69ff124699dd156a7fa41cb1d09ba6df94aa2f2b0a4a3a"
+dependencies = [
+ "byteorder",
+ "crunchy",
+ "hex",
+ "static_assertions",
+]
+
 [[package]]
 name = "unicode-bidi"
 version = "0.3.7"
diff --git a/Cargo.toml b/Cargo.toml
index de5fdf8..4cf8132 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,6 +2,7 @@
 members = [
     "wire-gateway",
     "btc-wire",
+    "eth-wire",
     "uri-pack",
     "taler-common",
 ]
diff --git a/btc-wire/Cargo.toml b/btc-wire/Cargo.toml
index 8a5abcb..3714840 100644
--- a/btc-wire/Cargo.toml
+++ b/btc-wire/Cargo.toml
@@ -19,8 +19,8 @@ argh = "0.1.7"
 # Bech32 encoding and decoding
 bech32 = "0.8.1"
 # Serialization library
-serde = { version = "1.0.133", features = ["derive"] }
-serde_json = "1.0.75"
+serde = { version = "1.0.136", features = ["derive"] }
+serde_json = "1.0.78"
 serde_repr = "0.1.7"
 # Error macros
 thiserror = "1.0.30"
@@ -28,7 +28,7 @@ thiserror = "1.0.30"
 uri-pack = { path = "../uri-pack" }
 base64 = "0.13.0"
 # Taler libs
-taler-common = {path = "../taler-common"}
+taler-common = { path = "../taler-common" }
 # Ini parser
 rust-ini = "0.17.0"
 
diff --git a/btc-wire/README.md b/btc-wire/README.md
index 815d258..ee6e472 100644
--- a/btc-wire/README.md
+++ b/btc-wire/README.md
@@ -18,8 +18,8 @@ The configuration is based on 
[taler.conf](https://docs.taler.net/manpages/taler
 
 ``` ini
 # taler.conf - btc_wire config
-[depolymerizer-___]
-BTC_DATA_DIR =
+[depolymerizer-bitcoin]
+DATA_DIR     =
 CONFIRMATION = 6
 BOUNCE_FEE   = 1000
 BUMP_DELAY   =
diff --git a/btc-wire/src/bin/test.rs b/btc-wire/src/bin/btc-test.rs
similarity index 100%
rename from btc-wire/src/bin/test.rs
rename to btc-wire/src/bin/btc-test.rs
diff --git a/btc-wire/src/bin/btc-wire-cli.rs b/btc-wire/src/bin/btc-wire-cli.rs
index 992e18c..8bba7e7 100644
--- a/btc-wire/src/bin/btc-wire-cli.rs
+++ b/btc-wire/src/bin/btc-wire-cli.rs
@@ -18,12 +18,16 @@ use btc_wire::{
     rpc::{BtcRpc, Error, ErrorCode},
     rpc_utils::default_data_dir,
 };
-use taler_common::postgres::{NoTls, Client};
+use taler_common::{
+    config::{Config, CoreConfig},
+    postgres::{Client, NoTls},
+};
 
 fn main() {
     let args: Vec<_> = std::env::args().collect();
     // Parse taler config
-    let config = taler_common::config::InitConfig::load_from_file(&args[2]);
+    let config = CoreConfig::load_from_file(&args[2]);
+    assert_eq!(config.currency, "BTC");
     // Connect to database
     let mut db = Client::connect(&config.db_url, NoTls).expect("Failed to 
connect to database");
 
@@ -44,7 +48,7 @@ fn main() {
         "initwallet" => {
             // Parse bitcoin config
             let btc_conf =
-                
BitcoinConfig::load(config.btc_data_dir.unwrap_or_else(default_data_dir))
+                
BitcoinConfig::load(config.data_dir.unwrap_or_else(default_data_dir))
                     .expect("Failed to load bitcoin configuration");
             // Connect to bitcoin node
             let mut rpc =
diff --git a/btc-wire/src/bin/btc-wire-utils.rs 
b/btc-wire/src/bin/btc-wire-utils.rs
index c65d462..e5ecf71 100644
--- a/btc-wire/src/bin/btc-wire-utils.rs
+++ b/btc-wire/src/bin/btc-wire-utils.rs
@@ -21,7 +21,11 @@ use btc_wire::{
     rpc::{BtcRpc, Category, Error, ErrorCode},
     rpc_utils::default_data_dir,
 };
-use taler_common::{config::Config, rand_slice, postgres::{NoTls, Client}};
+use taler_common::{
+    config::{Config, CoreConfig},
+    postgres::{Client, NoTls},
+    rand_slice,
+};
 
 #[derive(argh::FromArgs)]
 /// Bitcoin wire test client
@@ -46,10 +50,6 @@ enum Cmd {
 #[argh(subcommand, name = "transfer")]
 /// Wait or mine the next block
 struct TransferCmd {
-    #[argh(option, short = 'k')]
-    /// reserve public key
-    key: Option<String>,
-
     #[argh(option, short = 'f', default = "String::from(\"client\")")]
     /// sender wallet
     from: String,
@@ -137,12 +137,7 @@ impl App {
 fn main() {
     let args: Args = argh::from_env();
     match args.cmd {
-        Cmd::Transfer(TransferCmd {
-            key: _key,
-            from,
-            to,
-            amount,
-        }) => {
+        Cmd::Transfer(TransferCmd { from, to, amount }) => {
             let mut app = App::start(args.datadir);
             let (mut client, _) = app.auto_wallet(&from);
             let (_, to) = app.auto_wallet(&to);
@@ -166,7 +161,7 @@ fn main() {
             }
         }
         Cmd::ClearDB(ClearCmd { config }) => {
-            let config = Config::load_from_file(&config);
+            let config = CoreConfig::load_from_file(&config);
             let mut db = Client::connect(&config.db_url, NoTls).unwrap();
             db.execute("DROP TABLE IF EXISTS state, tx_in, tx_out, bounce", 
&[])
                 .unwrap();
diff --git a/btc-wire/src/info.rs b/btc-wire/src/info.rs
index 23467ea..05d3159 100644
--- a/btc-wire/src/info.rs
+++ b/btc-wire/src/info.rs
@@ -28,6 +28,10 @@ pub enum DecodeErr {
     UnexpectedEOF,
 }
 
+// TODO rename Info to OutMetadata to match eth_wire
+// TODO use a common url format for both
+// TODO generic metadata struct ?
+
 /// Encoded metadata for outgoing transaction
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub enum Info {
diff --git a/btc-wire/src/loops/analysis.rs b/btc-wire/src/loops/analysis.rs
index 07d5f1c..dd0a562 100644
--- a/btc-wire/src/loops/analysis.rs
+++ b/btc-wire/src/loops/analysis.rs
@@ -17,7 +17,7 @@ use std::sync::atomic::Ordering;
 
 use btc_wire::rpc::ChainTipsStatus;
 use taler_common::{
-    config::Config,
+    config::BtcConfig,
     log::log::{error, warn},
     postgres::fallible_iterator::FallibleIterator,
 };
@@ -33,11 +33,11 @@ use super::LoopResult;
 pub fn analysis(
     mut rpc: AutoReconnectRPC,
     mut db: AutoReconnectSql,
-    config: &Config,
+    config: &BtcConfig,
     state: &WireState,
 ) {
     // The biggest fork ever seen
-    let mut max_conf = 0;
+    let mut max_seen = 0;
     loop {
         let rpc = rpc.client();
         let db = db.client();
@@ -55,8 +55,8 @@ pub fn analysis(
                     .max()
                     .unwrap_or(0) as u16;
                 // The first time we see a fork that big
-                if max_fork > max_conf {
-                    max_conf = max_fork;
+                if max_fork > max_seen {
+                    max_seen = max_fork;
                     let current_conf = 
state.confirmation.load(Ordering::SeqCst);
                     // If new fork is bigger than the current confirmation
                     if max_fork > current_conf {
diff --git a/btc-wire/src/loops/watcher.rs b/btc-wire/src/loops/watcher.rs
index f7f5543..6d74fa1 100644
--- a/btc-wire/src/loops/watcher.rs
+++ b/btc-wire/src/loops/watcher.rs
@@ -25,8 +25,8 @@ pub fn watcher(mut rpc: AutoReconnectRPC, mut db: 
AutoReconnectSql) {
         let rpc = rpc.client();
         let db = db.client();
         let result: LoopResult<()> = (|| loop {
-            rpc.wait_for_new_block(0)?;
             db.execute("NOTIFY new_block", &[])?;
+            rpc.wait_for_new_block(0)?;
         })();
         if let Err(e) = result {
             error!("watcher: {}", e);
diff --git a/btc-wire/src/loops/worker.rs b/btc-wire/src/loops/worker.rs
index aa45664..e2a32d8 100644
--- a/btc-wire/src/loops/worker.rs
+++ b/btc-wire/src/loops/worker.rs
@@ -29,7 +29,7 @@ use btc_wire::{
 use postgres::{fallible_iterator::FallibleIterator, Client};
 use taler_common::{
     api_common::base32,
-    config::Config,
+    config::BtcConfig,
     log::log::{error, info, warn},
     postgres,
     sql::{sql_array, sql_url},
@@ -51,14 +51,13 @@ use super::{LoopError, LoopResult};
 pub fn worker(
     mut rpc: AutoReconnectRPC,
     mut db: AutoReconnectSql,
-    config: &Config,
+    config: &BtcConfig,
     state: &WireState,
 ) {
     let mut lifetime = config.wire_lifetime;
     let mut status = true;
 
-    // Alway start with a sync work
-    let mut skip_notification = true;
+    let mut skip_notification = false;
     loop {
         // Check lifetime
         if let Some(nb) = lifetime.as_mut() {
@@ -131,6 +130,7 @@ pub fn worker(
             skip_notification = !matches!(
                 e,
                 LoopError::RPC(rpc::Error::RPC { .. } | rpc::Error::Bitcoin(_))
+                    | LoopError::Concurrency
             );
         } else {
             skip_notification = false;
@@ -138,7 +138,7 @@ pub fn worker(
     }
 }
 
-/// Send a transaction on the blockchain, return true if more transactions 
with the same status remains
+/// Send atransaction on the blockchain, return true if more transactions with 
the same status remains
 fn send(db: &mut Client, rpc: &mut BtcRpc, status: TxStatus) -> 
LoopResult<bool> {
     assert!(status == TxStatus::Delayed || status == TxStatus::Requested);
     // We rely on the advisory lock to ensure we are the only one sending 
transactions
@@ -240,7 +240,7 @@ fn last_hash(db: &mut Client) -> Result<Option<BlockHash>, 
postgres::Error> {
 fn sync_chain(
     rpc: &mut BtcRpc,
     db: &mut Client,
-    config: &Config,
+    config: &BtcConfig,
     state: &WireState,
     status: &mut bool,
 ) -> LoopResult<bool> {
@@ -398,7 +398,7 @@ fn sync_chain_outgoing(
     confirmations: i32,
     rpc: &mut BtcRpc,
     db: &mut Client,
-    config: &Config,
+    config: &BtcConfig,
 ) -> LoopResult<()> {
     match rpc
         .get_tx_op_return(id)
@@ -429,7 +429,7 @@ fn sync_chain_outgoing_send(
     rpc: &mut BtcRpc,
     db: &mut Client,
     confirmations: i32,
-    config: &Config,
+    config: &BtcConfig,
 ) -> LoopResult<()> {
     let credit_addr = full.details[0].address.as_ref().unwrap();
     let amount = btc_to_taler(&full.amount);
diff --git a/btc-wire/src/main.rs b/btc-wire/src/main.rs
index ab9cb6c..672295a 100644
--- a/btc-wire/src/main.rs
+++ b/btc-wire/src/main.rs
@@ -21,7 +21,10 @@ use btc_wire::{
 };
 use reconnect::{AutoReconnectRPC, AutoReconnectSql};
 use std::{sync::atomic::AtomicU16, thread::JoinHandle};
-use taler_common::{config::Config, log::log::info};
+use taler_common::{
+    config::{load_btc_config, BtcConfig},
+    log::log::info,
+};
 
 use crate::loops::{analysis::analysis, watcher::watcher, worker::worker};
 
@@ -29,9 +32,9 @@ mod fail_point;
 mod info;
 mod loops;
 mod reconnect;
+mod sql;
 mod status;
 mod taler_util;
-mod sql;
 
 pub struct WireState {
     confirmation: AtomicU16,
@@ -40,15 +43,14 @@ pub struct WireState {
 fn main() {
     taler_common::log::init();
 
-    let config = taler_common::config::Config::load_from_file(
-        std::env::args_os().nth(1).expect("Missing conf path arg"),
-    );
+    let config = load_btc_config(std::env::args_os().nth(1).expect("Missing 
conf path arg"));
     let data_dir = config
-        .btc_data_dir
+        .core
+        .data_dir
         .as_ref()
         .cloned()
         .unwrap_or_else(default_data_dir);
-    let config: &'static Config = Box::leak(Box::new(config));
+    let config: &'static BtcConfig = Box::leak(Box::new(config));
     let btc_config = BitcoinConfig::load(&data_dir).unwrap();
 
     #[cfg(feature = "fail")]
@@ -76,12 +78,10 @@ fn main() {
     let rpc_analysis = AutoReconnectRPC::new(btc_config.clone(), 
WIRE_WALLET_NAME);
     let rpc_worker = AutoReconnectRPC::new(btc_config, WIRE_WALLET_NAME);
 
-    let db_watcher = AutoReconnectSql::new(&config.db_url);
-    let db_analysis = AutoReconnectSql::new(&config.db_url);
-    let db_worker = AutoReconnectSql::new(&config.db_url);
-    named_spawn("watcher", move || {
-        watcher(rpc_watcher, db_watcher)
-    });
+    let db_watcher = AutoReconnectSql::new(&config.core.db_url);
+    let db_analysis = AutoReconnectSql::new(&config.core.db_url);
+    let db_worker = AutoReconnectSql::new(&config.core.db_url);
+    named_spawn("watcher", move || watcher(rpc_watcher, db_watcher));
     named_spawn("analysis", move || {
         analysis(rpc_analysis, db_analysis, config, state)
     });
@@ -94,5 +94,8 @@ where
     F: Send + 'static,
     T: Send + 'static,
 {
-    std::thread::Builder::new().name(name.into()).spawn(f).unwrap()
+    std::thread::Builder::new()
+        .name(name.into())
+        .spawn(f)
+        .unwrap()
 }
diff --git a/btc-wire/src/taler_util.rs b/btc-wire/src/taler_util.rs
index aabe70d..133e4a3 100644
--- a/btc-wire/src/taler_util.rs
+++ b/btc-wire/src/taler_util.rs
@@ -28,10 +28,13 @@ pub fn btc_payto_url(addr: &Address) -> Url {
 /// Extract a btc address from a payto uri
 pub fn btc_payto_addr(url: &Url) -> Result<Address, String> {
     if url.domain() != Some("bitcoin") {
-        return Err("".to_string());
+        return Err(format!(
+            "Expected domain 'bitcoin' got '{}'",
+            url.domain().unwrap_or_default()
+        ));
     }
     let str = url.path().trim_start_matches('/');
-    return Address::from_str(str).map_err(|_| "".to_string());
+    return Address::from_str(str).map_err(|e| e.to_string());
 }
 
 /// Transform a btc amount into a taler amount
@@ -44,7 +47,7 @@ pub fn btc_to_taler(amount: &SignedAmount) -> Amount {
 /// Transform a taler amount into a btc amount
 pub fn taler_to_btc(amount: &Amount) -> Result<BtcAmount, String> {
     if amount.currency != "BTC" {
-        return Err("Wrong currency".to_string());
+        return Err(format!("expected ETH for {}", amount.currency));
     }
 
     let sat = amount.value * 100_000_000 + amount.fraction as u64;
diff --git a/db/btc.sql b/db/btc.sql
index 0fbcc26..ad28991 100644
--- a/db/btc.sql
+++ b/db/btc.sql
@@ -23,9 +23,9 @@ CREATE TABLE tx_out (
     debit_acc TEXT NOT NULL,
     credit_acc TEXT NOT NULL,
     exchange_url TEXT NOT NULL,
+    request_uid BYTEA UNIQUE,
     status SMALLINT NOT NULL DEFAULT 0,
-    txid BYTEA UNIQUE,
-    request_uid BYTEA UNIQUE
+    txid BYTEA UNIQUE
 );
 
 -- Bounced transaction
diff --git a/db/btc.sql b/db/eth.sql
similarity index 90%
copy from db/btc.sql
copy to db/eth.sql
index 0fbcc26..ad28991 100644
--- a/db/btc.sql
+++ b/db/eth.sql
@@ -23,9 +23,9 @@ CREATE TABLE tx_out (
     debit_acc TEXT NOT NULL,
     credit_acc TEXT NOT NULL,
     exchange_url TEXT NOT NULL,
+    request_uid BYTEA UNIQUE,
     status SMALLINT NOT NULL DEFAULT 0,
-    txid BYTEA UNIQUE,
-    request_uid BYTEA UNIQUE
+    txid BYTEA UNIQUE
 );
 
 -- Bounced transaction
diff --git a/docs/presentation.tex b/docs/presentation.tex
index 677b7de..3eea3d0 100644
--- a/docs/presentation.tex
+++ b/docs/presentation.tex
@@ -54,9 +54,9 @@
             \node[rect, below left=1.5cm and 0.7cm of 1](2) {Customer};
             \node[rect, below right=1.5cm and 0.7cm of 1](3) {Merchant};
 
-            \draw[sym] (1) -- node [midway, left] {\tiny Withdraw coins} (2);
-            \draw[sym] (2) -- node [midway, below] {\tiny Spend coins} (3);
-            \draw[sym] (3) -- node [midway, right] {\tiny Deposit coins} (1);
+            \draw[sym] (1) -- node [midway, above, sloped] {\tiny Withdraw 
coins} (2);
+            \draw[sym] (2) -- node [midway, above, sloped] {\tiny Spend coins} 
(3);
+            \draw[sym] (3) -- node [midway, above, sloped] {\tiny Deposit 
coins} (1);
 
             \node[left=2cm of 1](E1){};
             \node[right=2cm of 1](E2){};
@@ -128,8 +128,11 @@
 \section{Depolymerization}
 
 \begin{frame}{Depolymerization}{Metadata}
-    - Metadata
-    - Auditabilité
+    \begin{itemize}
+        \item Metadata are stored alongside depolymerizer on-chain transactions
+        \item The whole transaction history can be retrieved from the 
blockchain
+        \item Easily auditable
+    \end{itemize}
 \end{frame}
 
 \begin{frame}{Depolymerization}{Architecture}
@@ -150,7 +153,9 @@
             \draw[sym] (4) -- node [midway,left ] {\tiny RPC} (5);
         \end{tikzpicture}
     \end{center}
-    Two processes sharing a common database for state and communication. 
wire\_gateway is currency agnostic and there is a specific wire binary for 
every supported currency.
+    Two processes sharing a common database for state and communication.
+    wire\_gateway is currency agnostic and there is a specific wire binary for
+    every supported currency.
 \end{frame}
 
 \section{btc\_wire}
@@ -191,11 +196,29 @@
     Three concurrent loops
 \end{frame}
 
+\begin{frame}{btc\_wire}{Metadata}
+    \begin{block}{Deposit}
+        \begin{itemize}
+            \item Transaction from common wallet software
+            \item Only 32B
+            \item \textbf{Fake Segwit Addresses}
+        \end{itemize}
+    \end{block}
+    \begin{block}{Deposit}
+        \begin{itemize}
+            \item Transaction from code
+            \item Only 32B + URI
+            \item \textbf{OP\_RETURN}
+        \end{itemize}
+    \end{block}
+\end{frame}
+
+
 \begin{frame}{btc\_wire}{Security features}
     \begin{itemize}
         \item Reorg resilient
-        \item Resolve stuck transaction
         \item Adaptive confirmation
+        \item Resolve stuck transaction
     \end{itemize}
 \end{frame}
 
@@ -203,27 +226,46 @@
     \begin{center}
         \begin{tikzpicture}[
                 block/.style={rectangle,draw=black,fill=black!10,minimum 
size=7mm},
+                conf/.style={draw=black!60!green,fill=black!60!green!10},
+                err/.style={draw=black!60!red,fill=black!60!red!10},
             ]
             % Common
-            \node [block](1){1};
-            \node [block, right=5mm of 1](2){2};
-            \node [block, right=5mm of 2](3){3};
+            \only<1>{
+                \node [block](1){};
+                \node [block,right=5mm of 1](2){$D_0$};
+                \node [block,right=5mm of 2](3){};
+            }
+            \only<2->{
+                \node [block,conf](1){};
+                \node [block,conf,right=5mm of 1](2){$D_0$};
+                \node [block,conf,right=5mm of 2](3){};
+            }
             \draw[->] (1) -- (2);
             \draw[->] (2) -- (3);
 
             % Current
-            \node [block, right=5mm of 3](4){4};
-            \node [block, right=5mm of 4](5){5};
-            \node [block, right=5mm of 5](6){6};
+            \only<1-2>{
+                \node [block,right=5mm of 3](4){};
+            }
+            \only<3->{
+                \node [block,conf,right=5mm of 3](4){\only<4>{$D_3$}};
+            }
+            \node [block,right=5mm of 4](5){};
+            \node [block,right=5mm of 5](6){$D_1$};
             \draw[->] (3) -- (4);
             \draw[->] (4) -- (5);
             \draw[->] (5) -- (6);
 
             % Fork
-            \node [block, above=7mm of 4](4p){4'};
-            \node [block, right=5mm of 4p](5p){5'};
-            \node [block, right=5mm of 5p](6p){6'};
-            \node [block, right=5mm of 6p](7p){7};
+            \only<-3>{
+                \node [block,above=7mm of 4](4p){};
+            }
+            \only<4>{
+                \node [block,err,above=7mm of 4](4p){$D_3'$};
+            }
+            \node [block,right=5mm of 4p](5p){$D_2$};
+            \node [block,right=5mm of 5p](6p){};
+            \node [block,right=5mm of 6p](7p){};
             \draw[->] (3.east) -- (4p.west);
             \draw[->] (4p) -- (5p);
             \draw[->] (5p) -- (6p);
@@ -234,9 +276,80 @@
             \node [right=17mm of 6]{\emph{active}};
         \end{tikzpicture}
     \end{center}
-    A fork is when concurrent blockchain state coexists. Node will follow the 
longest chain replacing recent blocks if necessary. This is a blockchain 
reorganisation.
+    \only<1>{A fork is when concurrent blockchain state coexists. Node will 
follow the
+        longest chain replacing recent blocks if necessary. This is a 
blockchain
+        reorganisation. In Taler deposit transaction are expected to be
+        consistent, if a deposit transaction disappear from the blockchain
+        btc\_wire is comprised.}
+    \only<2>{As small forks are common, we apply a confirmation delay to handle
+        the most common disturbances and attack.}
+    \only<3>{If a reorganisation bigger than the confirmation delay happen,
+        but no deposit transaction are removed btc\_wire is safe.}
+    \only<4>{If a confirmed deposit transaction has been removed, it is
+        possible for a powerful attacker to have created a conflicting 
transaction. btc\_wire
+        stop functioning until lost deposit transactions reappeared.}
+\end{frame}
+
+\begin{frame}{btc\_wire}{Adaptive confirmation}
+    \begin{center}
+        \begin{tikzpicture}[
+                block/.style={rectangle,draw=black,fill=black!10,minimum 
size=7mm},
+                conf/.style={draw=black!60!green,fill=black!60!green!10},
+                conft/.style={text=black!60!green},
+                confl/.style={draw=black!60!green},
+            ]
+            % Common
+            \node(0){};
+            \node[block,conf,right=5mm of 0](1){};
+            \node[block,conf,right=5mm of 1](2){};
+            \draw[->] (0) -- (1);
+            \draw[->] (1) -- (2);
+
+            % Current
+
+            \node[block,conf,right=5mm of 2](3){};
+            \node[block,right=5mm of 3](4){};
+            \node[block,right=5mm of 4](5){};
+            \node[block,right=5mm of 5](6){};
+            \draw[->] (2) -- (3);
+            \draw[->] (3) -- (4);
+            \draw[->] (4) -- (5);
+            \draw[->] (5) -- (6);
+
+            % Fork
+            \node[block,above=7mm of 3](3p){};
+            \node[block,right=5mm of 3p](4p){};
+            \node[block,right=5mm of 4p](5p){};
+            \node[block,right=5mm of 5p](6p){};
+            \node[block,right=5mm of 6p](7p){};
+            \draw[->] (2.east) -- (3p.west);
+            \draw[->] (3p) -- (4p);
+            \draw[->] (4p) -- (5p);
+            \draw[->] (5p) -- (6p);
+            \draw[->] (6p) -- (7p);
+
+            % Indication
+            \node[right=5mm of 7p]{\emph{fork}};
+            \node[right=17mm of 6]{\emph{active}};
+
+            % Confirmation
+            \path (0) -- (1) node[conft,midway, below=6mm] (M) {Max};
+            \path (2) -- (3) node[conft,midway, below=6mm] (N) {New};
+            \path (3) -- (4) node[conft,midway, below=6mm] (I) {Initial};
+            \node[above=25mm of M] (Mp) {};
+            \node[above=25mm of N] (Np) {};
+            \node[above=25mm of I] (Ip) {};
+            \draw[confl,thick,dotted](M) -- (Mp);
+            \draw[confl](N) -- (Np);
+            \draw[confl,thick,dotted](I) -- (Ip);
+        \end{tikzpicture}
+    \end{center}
+    If we experience a reorganisation once, its plausible for another one of 
the
+    same size to happen again. We update the confirmation time, learning from
+    previous disturbances.
 \end{frame}
 
+
 \begin{frame}{btc\_wire}{Handle stuck transactions}
     \only<1>{We want transactions to be confirmed in a bounded time period}
     \begin{center}
@@ -249,22 +362,22 @@
             \node (I) {\includegraphics[width=\textwidth]{fee.png}};
             \only<2->{
                 \node [below left=-2.5mm and -1.5cm of I] (Tx) {\small Tx};
-                \node [dot, above=8.4mm of Tx](D) {};
-                \draw [dotted] (Tx) -- (D);
+                \node [dot,above=8.4mm of Tx](D) {};
+                \draw [dotted,thick] (Tx) -- (D);
             };
             \only<2>{
                 \node [left=-4.5cm of Tx] (C) {\small conf};
-                \node [dot, above=8.4mm of C](D1) {};
-                \draw [dotted] (C) -- (D1);
+                \node [dot,above=8.4mm of C](D1) {};
+                \draw [dotted,thick] (C) -- (D1);
             };
             \only<3>{
                 \node [left=-17mm of Tx] (Tx1) {\small Tx'};
-                \node [dot, above=15.5mm of Tx1](D1) {};
-                \draw [dotted] (Tx1) -- (D1);
+                \node [dot,above=15.5mm of Tx1](D1) {};
+                \draw [dotted,thick] (Tx1) -- (D1);
 
                 \node [left=-14.3mm of Tx1] (C) {\small conf};
-                \node [dot, above=15.5mm of C](D2) {};
-                \draw [dotted] (C) -- (D2);
+                \node [dot,above=15.5mm of C](D2) {};
+                \draw [dotted,thick] (C) -- (D2);
             };
         \end{tikzpicture}
     \end{center}
@@ -276,10 +389,6 @@
 \end{frame}
 
 
-\begin{frame}{btc\_wire}{Adaptive confirmation}
-\end{frame}
-
-
 
 \begin{frame}{Related work}
     \begin{block}{Centralization - Coinbase off-chain sending}
diff --git a/eth-wire/Cargo.toml b/eth-wire/Cargo.toml
new file mode 100644
index 0000000..8688146
--- /dev/null
+++ b/eth-wire/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "eth-wire"
+version = "0.1.0"
+edition = "2021"
+license = "AGPL-3.0-or-later"
+
+[dependencies]
+# Cli args 
+argh = "0.1.7"
+# Serialization library
+serde = { version = "1.0.136", features = ["derive"] }
+serde_json = "1.0.78"
+serde_repr = "0.1.7"
+hex = "0.4.3"
+# Ethereum serializable types
+ethereum-types = { version = "0.12.1", default-features = false, features = [
+    "serialize",
+] }
+# Error macros
+thiserror = "1.0.30"
+# Optimized uri binary format
+uri-pack = { path = "../uri-pack" }
+# Taler libs
+taler-common = { path = "../taler-common" }
diff --git a/eth-wire/src/bin/eth-test.rs b/eth-wire/src/bin/eth-test.rs
new file mode 100644
index 0000000..c979257
--- /dev/null
+++ b/eth-wire/src/bin/eth-test.rs
@@ -0,0 +1,140 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2022 Taler Systems SA
+
+  TALER 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, or (at your option) any later version.
+
+  TALER 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
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+use std::{panic::AssertUnwindSafe, str::FromStr};
+
+use eth_wire::{
+    metadata::{InMetadata, OutMetadata},
+    rpc::{self, Rpc},
+};
+use ethereum_types::{H256, U256};
+use taler_common::{config::load_eth_config, rand_slice, url::Url};
+
+pub fn main() {
+    let path = std::env::args().nth(1).unwrap();
+    let config = load_eth_config(path);
+    let mut rpc = 
Rpc::new(config.core.data_dir.unwrap().join("geth.ipc")).unwrap();
+
+    let accounts = rpc.list_accounts().unwrap();
+    for account in &accounts {
+        rpc.unlock_account(&account, "password").unwrap();
+    }
+    let wire = accounts[0];
+    let client = accounts[1];
+    let reserve = accounts[2];
+
+    let test_value = U256::from(15000u32);
+    let bounce_value = U256::from(10000u32);
+    let test_url = Url::from_str("http://test.com";).unwrap();
+
+    let mut runner = TestRunner::new();
+
+    runner.test("Deposit", || {
+        let rng = rand_slice();
+        let hash = rpc.deposit(client, wire, test_value, rng).unwrap();
+        assert!(tx_pending(&mut rpc, &hash).unwrap());
+        mine_pending(&mut rpc).unwrap();
+        assert!(!tx_pending(&mut rpc, &hash).unwrap());
+        let tx = rpc.get_transaction(&hash).unwrap().unwrap();
+        let metadata = InMetadata::decode(&tx.input).unwrap();
+        assert!(matches!(metadata, InMetadata::Deposit { reserve_pub } if 
reserve_pub == rng));
+        assert_eq!(tx.value, test_value);
+    });
+
+    runner.test("Withdraw", || {
+        let rng = rand_slice();
+        let hash = rpc
+            .withdraw(wire, client, test_value, rng, test_url.clone())
+            .unwrap();
+        assert!(tx_pending(&mut rpc, &hash).unwrap());
+        mine_pending(&mut rpc).unwrap();
+        assert!(!tx_pending(&mut rpc, &hash).unwrap());
+        let tx = rpc.get_transaction(&hash).unwrap().unwrap();
+        let metadata = OutMetadata::decode(&tx.input).unwrap();
+        assert!(
+            matches!(metadata, OutMetadata::Withdraw { wtid, url } if wtid == 
rng && url == url)
+        );
+        assert_eq!(tx.value, test_value);
+    });
+
+    runner.test("Bounce", || {
+        let rng = rand_slice();
+        let deposit = rpc.deposit(client, wire, test_value, rng).unwrap();
+        let hash = rpc.bounce(deposit, bounce_value).unwrap();
+        assert!(tx_pending(&mut rpc, &hash).unwrap());
+        mine_pending(&mut rpc).unwrap();
+        assert!(!tx_pending(&mut rpc, &hash).unwrap());
+        let tx = rpc.get_transaction(&hash).unwrap().unwrap();
+        let metadata = OutMetadata::decode(&tx.input).unwrap();
+        assert!(matches!(metadata, OutMetadata::Bounce { bounced } if bounced 
== deposit));
+        assert_eq!(tx.value, test_value - bounce_value);
+    });
+
+    runner.conclude();
+}
+
+/// Check a specific transaction is pending
+fn tx_pending(rpc: &mut Rpc, id: &H256) -> rpc::Result<bool> {
+    Ok(rpc.pending_transactions()?.iter().any(|t| t.hash == *id))
+}
+
+/// Mine pending transactions
+fn mine_pending(rpc: &mut Rpc) -> rpc::Result<()> {
+    let mut notifier = rpc.subscribe_new_head()?;
+    rpc.miner_start()?;
+    while !rpc.pending_transactions()?.is_empty() {
+        notifier.next()?;
+    }
+    rpc.miner_stop()?;
+    Ok(())
+}
+
+/// Run test track success and errors
+struct TestRunner {
+    nb_ok: usize,
+    nb_err: usize,
+}
+
+impl TestRunner {
+    fn new() -> Self {
+        Self {
+            nb_err: 0,
+            nb_ok: 0,
+        }
+    }
+
+    fn test(&mut self, name: &str, test: impl FnOnce()) {
+        println!("{}", name);
+
+        let result = std::panic::catch_unwind(AssertUnwindSafe(test));
+        if result.is_ok() {
+            println!("OK");
+            self.nb_ok += 1;
+        } else {
+            println!("ERR");
+            self.nb_err += 1;
+        }
+    }
+
+    /// Wait for tests completion and print results
+    fn conclude(self) {
+        println!(
+            "Result for {} tests: {} ok and {} err",
+            self.nb_ok + self.nb_err,
+            self.nb_ok,
+            self.nb_err
+        );
+    }
+}
diff --git a/eth-wire/src/bin/eth-wire-cli.rs b/eth-wire/src/bin/eth-wire-cli.rs
new file mode 100644
index 0000000..02d7b0f
--- /dev/null
+++ b/eth-wire/src/bin/eth-wire-cli.rs
@@ -0,0 +1,99 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2022 Taler Systems SA
+
+  TALER 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, or (at your option) any later version.
+
+  TALER 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
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+use eth_wire::{rpc::Rpc, BlockState};
+use ethereum_types::H160;
+use taler_common::{
+    config::{Config, CoreConfig},
+    postgres::{Client, NoTls},
+};
+
+fn main() {
+    let args: Vec<_> = std::env::args().collect();
+    let config = CoreConfig::load_from_file(&args[2]);
+    assert_eq!(config.currency, "ETH");
+    let mut db = Client::connect(&config.db_url, NoTls).expect("Failed to 
connect to database");
+
+    // TODO user defined password
+
+    match args[1].as_str() {
+        "initdb" => {
+            // Load schema
+            db.batch_execute(include_str!("../../../db/eth.sql"))
+                .expect("Failed to load database schema");
+            // Init status to true
+            db
+              .execute(
+                  "INSERT INTO state (name, value) VALUES ('status', $1) ON 
CONFLICT (name) DO NOTHING",
+                  &[&[1u8].as_ref()],
+              )
+              .expect("Failed to initialise database state");
+            println!("Database initialised");
+        }
+        "initwallet" => {
+            // Connect to ethereum node
+            let mut rpc = Rpc::new(config.data_dir.unwrap().join("geth.ipc"))
+                .expect("Failed to connect to ethereum RPC server");
+
+            // Skip previous blocks
+            let block = rpc.current_block().expect("Failed to get current 
block");
+            let state = BlockState {
+                hash: block.hash.unwrap(),
+                number: block.number.unwrap(),
+            };
+            let nb_row = db
+              .execute(
+                  "INSERT INTO state (name, value) VALUES ('last_block', $1) 
ON CONFLICT (name) DO NOTHING",
+                  &[&state.to_bytes().as_ref()],
+              )
+              .expect("Failed to update database state");
+            if nb_row > 0 {
+                println!("Skipped {} previous block", state.number);
+            }
+
+            let prev_addr = db
+                .query_opt("SELECT value FROM state WHERE name = 'addr'", &[])
+                .expect("Failed to query database state");
+            let (addr, created) = if let Some(row) = prev_addr {
+                (H160::from_slice(row.get(0)), false)
+            } else {
+                // Or generate a new one
+                let new = rpc
+                    .new_account("password")
+                    .expect("Failed creating account");
+                db.execute(
+                    "INSERT INTO state (name, value) VALUES ('addr', $1)",
+                    &[&new.as_bytes()],
+                )
+                .expect("Failed to update database state");
+                (new, true)
+            };
+
+            if created {
+                println!("Created new wallet");
+            } else {
+                println!("Found already existing wallet")
+            };
+
+            let addr = hex::encode(addr.as_bytes());
+            println!("Address is {}", &addr);
+            println!("Add the following line into taler.conf:");
+            println!("[depolymerizer-ethereum]");
+            println!("PAYTO = payto://ethereum/{}", addr);
+        }
+        cmd => panic!("Unknown command {}", cmd),
+    }
+}
diff --git a/eth-wire/src/bin/eth-wire-utils.rs 
b/eth-wire/src/bin/eth-wire-utils.rs
new file mode 100644
index 0000000..f47a681
--- /dev/null
+++ b/eth-wire/src/bin/eth-wire-utils.rs
@@ -0,0 +1,163 @@
+use std::{path::PathBuf, str::FromStr};
+
+use eth_wire::{
+    rpc::{hex::Hex, Rpc, TransactionRequest},
+    taler_util::taler_to_eth,
+};
+use ethereum_types::H160;
+use taler_common::{api_common::Amount, rand_slice};
+
+/*
+  This file is part of TALER
+  Copyright (C) 2022 Taler Systems SA
+
+  TALER 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, or (at your option) any later version.
+
+  TALER 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
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+#[derive(argh::FromArgs)]
+/// Euthereum wire test client
+struct Args {
+    #[argh(option, short = 'd')]
+    /// specify data directory
+    datadir: Option<PathBuf>,
+    #[argh(subcommand)]
+    cmd: Cmd,
+}
+
+#[derive(argh::FromArgs)]
+#[argh(subcommand)]
+enum Cmd {
+    Send(SendCmd),
+    Deposit(DepositCmd),
+    Mine(MineCmd),
+    ClearDB(ClearCmd),
+    Balance(BalanceCmd),
+}
+
+#[derive(argh::FromArgs)]
+#[argh(subcommand, name = "send")]
+/// Send a transaction
+struct SendCmd {
+    #[argh(positional)]
+    /// sender wallet
+    from: String,
+
+    #[argh(positional)]
+    /// receiver wallet
+    to: String,
+
+    #[argh(positional)]
+    /// amount to send in eth
+    amount: f64,
+}
+
+#[derive(argh::FromArgs)]
+#[argh(subcommand, name = "deposit")]
+/// Perform a deposit transaction
+struct DepositCmd {
+    #[argh(positional)]
+    /// sender wallet
+    from: String,
+
+    #[argh(positional)]
+    /// receiver wallet
+    to: String,
+
+    #[argh(positional)]
+    /// amount to send in eth
+    amount: f64,
+}
+
+#[derive(argh::FromArgs)]
+#[argh(subcommand, name = "mine")]
+/// Wait or mine the next block
+struct MineCmd {
+    #[argh(positional)]
+    /// receiver wallet
+    to: String,
+    #[argh(positional, default = "0")]
+    /// amount to mine in eth
+    amount: u64,
+}
+
+#[derive(argh::FromArgs)]
+#[argh(subcommand, name = "cleardb")]
+/// Clear database
+struct ClearCmd {
+    #[argh(positional)]
+    /// taler config
+    config: String,
+}
+
+#[derive(argh::FromArgs)]
+#[argh(subcommand, name = "balance")]
+/// Get eth balance
+struct BalanceCmd {
+    #[argh(positional)]
+    /// account address
+    addr: String,
+}
+
+fn main() {
+    let args: Args = argh::from_env();
+    match args.cmd {
+        Cmd::Deposit(DepositCmd { from, to, amount }) => {
+            let mut rpc = 
Rpc::new(args.datadir.unwrap().join("geth.ipc")).unwrap();
+            let from = H160::from_str(&from).unwrap();
+            let to = H160::from_str(&to).unwrap();
+            let amount = Amount::from_str(&format!("ETH:{}", amount)).unwrap();
+            let value = taler_to_eth(&amount).unwrap();
+            rpc.unlock_account(&from, "password").ok();
+            rpc.deposit(from, to, value, rand_slice()).unwrap();
+        }
+        Cmd::Send(SendCmd { from, to, amount }) => {
+            let mut rpc = 
Rpc::new(args.datadir.unwrap().join("geth.ipc")).unwrap();
+            let from = H160::from_str(&from).unwrap();
+            let to = H160::from_str(&to).unwrap();
+            let amount = Amount::from_str(&format!("ETH:{}", amount)).unwrap();
+            let value = taler_to_eth(&amount).unwrap();
+            rpc.unlock_account(&from, "password").ok();
+            rpc.send_transaction(&TransactionRequest {
+                from,
+                to,
+                value,
+                gas: None,
+                gas_price: None,
+                data: Hex(vec![]),
+            })
+            .unwrap();
+        }
+        Cmd::Mine(MineCmd { to, mut amount }) => {
+            let mut rpc = 
Rpc::new(args.datadir.unwrap().join("geth.ipc")).unwrap();
+            let to = H160::from_str(&to).unwrap();
+            rpc.unlock_account(&to, "password").ok();
+            let mut notifier = rpc.subscribe_new_head().unwrap();
+
+            rpc.miner_start().unwrap();
+            while !rpc.pending_transactions().unwrap().is_empty() {
+                notifier.next().unwrap();
+                amount = amount.saturating_sub(1);
+            }
+            for _ in 0..amount {
+                notifier.next().unwrap();
+            }
+            rpc.miner_stop().unwrap();
+        }
+        Cmd::Balance(BalanceCmd { addr }) => {
+            let mut rpc = 
Rpc::new(args.datadir.unwrap().join("geth.ipc")).unwrap();
+            let addr = H160::from_str(&addr).unwrap();
+            let balance = rpc.balance(&addr).unwrap();
+            println!("{}", (balance / 10_000_000_000u64).as_u64());
+        }
+        Cmd::ClearDB(_) => todo!(),
+    }
+}
diff --git a/eth-wire/src/lib.rs b/eth-wire/src/lib.rs
new file mode 100644
index 0000000..e8a76b4
--- /dev/null
+++ b/eth-wire/src/lib.rs
@@ -0,0 +1,123 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2022 Taler Systems SA
+
+  TALER 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, or (at your option) any later version.
+
+  TALER 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
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+use ethereum_types::{Address, H256, U256, U64};
+use metadata::{InMetadata, OutMetadata};
+use rpc::hex::Hex;
+use taler_common::url::Url;
+
+pub mod metadata;
+pub mod rpc;
+pub mod taler_util;
+
+impl rpc::Rpc {
+    pub fn deposit(
+        &mut self,
+        from: Address,
+        to: Address,
+        value: U256,
+        reserve_pub: [u8; 32],
+    ) -> rpc::Result<H256> {
+        let metadata = InMetadata::Deposit { reserve_pub };
+        self.send_transaction(&rpc::TransactionRequest {
+            from,
+            to,
+            value,
+            gas: None,
+            gas_price: None,
+            data: Hex(metadata.encode()),
+        })
+    }
+
+    pub fn withdraw(
+        &mut self,
+        from: Address,
+        to: Address,
+        value: U256,
+        wtid: [u8; 32],
+        url: Url,
+    ) -> rpc::Result<H256> {
+        let metadata = OutMetadata::Withdraw { wtid, url };
+        self.send_transaction(&rpc::TransactionRequest {
+            from,
+            to,
+            value,
+            gas: None,
+            gas_price: None,
+            data: Hex(metadata.encode()),
+        })
+    }
+
+    pub fn bounce(&mut self, hash: H256, bounce_fee: U256) -> 
rpc::Result<H256> {
+        let tx = self
+            .get_transaction(&hash)?
+            .expect("Cannot bounce a non existent transaction");
+        let bounce_value = tx.value.saturating_sub(bounce_fee);
+        let metadata = OutMetadata::Bounce { bounced: hash };
+        // TODO do not bounce empty amount
+        self.send_transaction(&rpc::TransactionRequest {
+            from: tx.to.expect("Cannot bounce contract transaction"),
+            to: tx.from.expect("Cannot bounce coinbase transaction"),
+            value: bounce_value,
+            gas: None,
+            gas_price: None,
+            data: Hex(metadata.encode()),
+        })
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct BlockState {
+    pub hash: H256,
+    pub number: U64,
+}
+
+impl BlockState {
+    pub fn to_bytes(&self) -> [u8; 40] {
+        let mut bytes = [0; 40];
+        bytes[..32].copy_from_slice(self.hash.as_bytes());
+        self.number.to_little_endian(&mut bytes[32..]);
+        bytes
+    }
+
+    pub fn from_bytes(bytes: &[u8; 40]) -> Self {
+        Self {
+            hash: H256::from_slice(&bytes[..32]),
+            number: U64::from_little_endian(&bytes[32..]),
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use ethereum_types::{H256, U64};
+    use taler_common::{rand::random, rand_slice};
+
+    use crate::BlockState;
+
+    #[test]
+    fn to_from_bytes_block_state() {
+        for _ in 0..4 {
+            let state = BlockState {
+                hash: H256::from_slice(&rand_slice::<32>()),
+                number: U64::from(random::<u64>()),
+            };
+            let encoded = state.to_bytes();
+            let decoded = BlockState::from_bytes(&encoded);
+            assert_eq!(state, decoded);
+        }
+    }
+}
diff --git a/eth-wire/src/main.rs b/eth-wire/src/main.rs
new file mode 100644
index 0000000..7005713
--- /dev/null
+++ b/eth-wire/src/main.rs
@@ -0,0 +1,275 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2022 Taler Systems SA
+
+  TALER 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, or (at your option) any later version.
+
+  TALER 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
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+use std::sync::atomic::AtomicU16;
+
+use eth_wire::{
+    rpc::{self, Rpc},
+    taler_util::eth_payto_addr,
+};
+use ethereum_types::H160;
+use loops::{watcher::watcher, worker::worker};
+use taler_common::{
+    config::{load_eth_config, EthConfig},
+    postgres::{self, Client, NoTls},
+};
+
+mod status;
+
+pub struct WireState {
+    confirmation: AtomicU16,
+    address: H160,
+    config: EthConfig,
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum LoopError {
+    #[error(transparent)]
+    RPC(#[from] rpc::Error),
+    #[error(transparent)]
+    DB(#[from] postgres::Error),
+    #[error("Another btc_wire process is running concurrently")]
+    Concurrency,
+    // #[error(transparent)]
+    // Injected(#[from] Injected),
+}
+
+pub type LoopResult<T> = Result<T, LoopError>;
+
+mod sql {
+    use eth_wire::taler_util::{eth_payto_addr, taler_to_eth};
+    use ethereum_types::{H160, U256};
+    use taler_common::{
+        postgres::Row,
+        sql::{sql_amount, sql_url},
+    };
+
+    pub fn sql_eth_amount(row: &Row, idx: usize) -> U256 {
+        let amount = sql_amount(row, idx);
+        taler_to_eth(&amount).unwrap_or_else(|_| {
+            panic!(
+                "Database invariant: expected an ethereum amount got {}",
+                amount
+            )
+        })
+    }
+
+    pub fn sql_addr(row: &Row, idx: usize) -> H160 {
+        let url = sql_url(row, idx);
+        eth_payto_addr(&url).unwrap_or_else(|_| {
+            panic!(
+                "Database invariant: expected an ethereum payto url got {}",
+                url
+            )
+        })
+    }
+}
+
+mod loops {
+    pub mod watcher {
+        use eth_wire::rpc::Rpc;
+        use taler_common::postgres::Client;
+
+        pub fn watcher(mut rpc: Rpc, mut db: Client) {
+            let mut notifier = rpc.subscribe_new_head().unwrap();
+            loop {
+                db.execute("NOTIFY new_block", &[]).unwrap();
+                notifier.next().unwrap();
+            }
+        }
+    }
+
+    pub mod worker {
+        use std::time::SystemTime;
+
+        use eth_wire::{
+            metadata::InMetadata,
+            rpc::Rpc,
+            taler_util::{eth_payto_url, eth_to_taler},
+            BlockState,
+        };
+        use taler_common::{
+            api_common::base32,
+            log::log::{error, info},
+            postgres::{fallible_iterator::FallibleIterator, Client},
+            sql::{sql_array, sql_url},
+        };
+
+        use crate::{
+            sql::{sql_addr, sql_eth_amount},
+            status::TxStatus,
+            LoopResult, WireState,
+        };
+
+        pub fn worker(mut rpc: Rpc, mut db: Client, state: &WireState) {
+            let mut skip_notification = false;
+            loop {
+                let result: LoopResult<()> = (|| {
+                    // Listen to all channels
+                    db.batch_execute("LISTEN new_block; LISTEN new_tx")?;
+                    // Wait for the next notification
+                    {
+                        let mut ntf = db.notifications();
+                        if !skip_notification && ntf.is_empty() {
+                            // Block until next notification
+                            ntf.blocking_iter().next()?;
+                        }
+                        // Conflate all notifications
+                        let mut iter = ntf.iter();
+                        while iter.next()?.is_some() {}
+                    }
+
+                    sync_chain(&mut rpc, &mut db, state)?;
+
+                    while send(&mut db, &mut rpc, state)? {}
+                    Ok(())
+                })();
+
+                if let Err(e) = result {
+                    error!("worker: {}", e);
+                    skip_notification = false;
+                } else {
+                    skip_notification = false;
+                }
+            }
+        }
+
+        fn sync_chain(rpc: &mut Rpc, db: &mut Client, state: &WireState) -> 
LoopResult<bool> {
+            let row = db.query_one("SELECT value FROM state WHERE 
name='last_block'", &[])?;
+            let slice: &[u8] = row.get(0);
+            let block = BlockState::from_bytes(slice.try_into().unwrap());
+
+            let mut txs = Vec::new();
+
+            txs.extend(rpc.pending_transactions()?.into_iter().map(|t| (t, 
0)));
+
+            let mut cursor = rpc.current_block()?;
+            let mut confirmation = 1;
+
+            // TODO check hash to detect reorg
+
+            while cursor.number.expect("Mined block") != block.number {
+                txs.extend(cursor.transactions.drain(..).map(|t| (t, 
confirmation)));
+                cursor = rpc.block(cursor.number.unwrap() - 1u64)?.unwrap();
+                confirmation += 1;
+            }
+
+            for (tx, _confirmation) in txs {
+                if tx.to == Some(state.address) {
+                    let metadata = InMetadata::decode(&tx.input).unwrap();
+                    match metadata {
+                        InMetadata::Deposit { reserve_pub } => {
+                            let date = SystemTime::now();
+                            let amount = eth_to_taler(&tx.value);
+                            let credit_addr = tx.from.expect("Not coinbase");
+                            let nb = db.execute("INSERT INTO tx_in (_date, 
amount, reserve_pub, debit_acc, credit_acc) VALUES ($1, $2, $3, $4, $5) ON 
CONFLICT (reserve_pub) DO NOTHING ", &[
+                        &date, &amount.to_string(), &reserve_pub.as_ref(), 
&eth_payto_url(&credit_addr).as_ref(), &state.config.payto.as_ref()
+                    ])?;
+                            if nb > 0 {
+                                info!(
+                                    "<< {} {} in {} from {}",
+                                    amount,
+                                    base32(&reserve_pub),
+                                    hex::encode(tx.hash),
+                                    hex::encode(credit_addr),
+                                );
+                            }
+                        }
+                    }
+                }
+            }
+
+            Ok(true)
+        }
+
+        /// Send a transaction on the blockchain, return true if more 
transactions with the same status remains
+        fn send(db: &mut Client, rpc: &mut Rpc, state: &WireState) -> 
LoopResult<bool> {
+            // We rely on the advisory lock to ensure we are the only one 
sending transactions
+            let row = db.query_opt(
+        "SELECT id, amount, wtid, credit_acc, exchange_url FROM tx_out WHERE 
status=$1 ORDER BY _date LIMIT 1",
+        &[&(TxStatus::Requested as i16)],
+    )?;
+            if let Some(row) = &row {
+                let id: i32 = row.get(0);
+                let amount = sql_eth_amount(row, 1);
+                let wtid: [u8; 32] = sql_array(row, 2);
+                let addr = sql_addr(row, 3);
+                let url = sql_url(row, 4);
+                match rpc.withdraw(state.address, addr, amount, wtid, url) {
+                    Ok(tx_id) => {
+                        db.execute(
+                            "UPDATE tx_out SET status=$1, txid=$2 WHERE id=$3",
+                            &[&(TxStatus::Sent as i16), &tx_id.as_ref(), &id],
+                        )?;
+                        let amount = eth_to_taler(&amount);
+                        info!(">> {} {} in {} to {}", amount, base32(&wtid), 
tx_id, addr);
+                    }
+                    Err(e) => {
+                        db.execute(
+                            "UPDATE tx_out SET status=$1 WHERE id=$2",
+                            &[&(TxStatus::Delayed as i16), &id],
+                        )?;
+                        Err(e)?;
+                    }
+                }
+            }
+            Ok(row.is_some())
+        }
+    }
+}
+
+fn main() {
+    taler_common::log::init();
+
+    let path = std::env::args().nth(1).unwrap();
+    let config = load_eth_config(path);
+
+    let state: &'static WireState = Box::leak(Box::new(WireState {
+        confirmation: AtomicU16::new(config.confirmation),
+        address: eth_payto_addr(&config.payto).unwrap(),
+        config,
+    }));
+
+    let mut rpc_worker = Rpc::new(
+        state
+            .config
+            .core
+            .data_dir
+            .as_ref()
+            .unwrap()
+            .join("geth.ipc"),
+    )
+    .unwrap();
+
+    rpc_worker.unlock_account(&state.address, "password").unwrap();
+
+    let rpc_watcher = Rpc::new(
+        state
+            .config
+            .core
+            .data_dir
+            .as_ref()
+            .unwrap()
+            .join("geth.ipc"),
+    )
+    .unwrap();
+    let db_watcher = Client::connect(&state.config.core.db_url, 
NoTls).unwrap();
+    let db_worker = Client::connect(&state.config.core.db_url, NoTls).unwrap();
+
+    std::thread::spawn(move || watcher(rpc_watcher, db_watcher));
+
+    worker(rpc_worker, db_worker, state);
+}
diff --git a/eth-wire/src/metadata.rs b/eth-wire/src/metadata.rs
new file mode 100644
index 0000000..50f9b05
--- /dev/null
+++ b/eth-wire/src/metadata.rs
@@ -0,0 +1,177 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2022 Taler Systems SA
+
+  TALER 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, or (at your option) any later version.
+
+  TALER 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
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+use taler_common::url::Url;
+use ethereum_types::H256;
+
+#[derive(Debug, Clone, Copy, thiserror::Error)]
+pub enum DecodeErr {
+    #[error("Unknown first byte: {0}")]
+    UnknownFirstByte(u8),
+    #[error(transparent)]
+    UriPack(#[from] uri_pack::DecodeErr),
+    #[error("Unexpected end of file")]
+    UnexpectedEOF,
+}
+
+/// Encoded metadata for outgoing transaction
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum OutMetadata {
+    Withdraw { wtid: [u8; 32], url: Url },
+    Bounce { bounced: H256 },
+}
+
+// We leave a potential special meaning for u8::MAX
+const BOUNCE_BYTE: u8 = u8::MAX - 1;
+
+impl OutMetadata {
+    pub fn encode(&self) -> Vec<u8> {
+        let mut buffer = Vec::new();
+        match self {
+            OutMetadata::Withdraw { wtid, url } => {
+                buffer.push(if url.scheme() == "http" { 1 } else { 0 });
+                buffer.extend_from_slice(wtid);
+                let parts = format!("{}{}", url.domain().unwrap_or(""), 
url.path());
+                let packed = uri_pack::pack_uri(&parts).unwrap();
+                buffer.extend_from_slice(&packed);
+            }
+            OutMetadata::Bounce { bounced } => {
+                buffer.push(BOUNCE_BYTE);
+                buffer.extend_from_slice(bounced.as_bytes());
+            }
+        }
+        return buffer;
+    }
+
+    pub fn decode(bytes: &[u8]) -> Result<Self, DecodeErr> {
+        if bytes.is_empty() {
+            return Err(DecodeErr::UnexpectedEOF);
+        }
+        match bytes[0] {
+            0..=1 => {
+                if bytes.len() < 33 {
+                    return Err(DecodeErr::UnexpectedEOF);
+                }
+                let packed = format!(
+                    "http{}://{}",
+                    if bytes[0] == 0 { "s" } else { "" },
+                    uri_pack::unpack_uri(&bytes[33..])?,
+                );
+                let url = Url::parse(&packed).unwrap();
+                Ok(OutMetadata::Withdraw {
+                    wtid: bytes[1..33].try_into().unwrap(),
+                    url,
+                })
+            }
+            BOUNCE_BYTE => {
+                if bytes.len() < 33 {
+                    return Err(DecodeErr::UnexpectedEOF);
+                }
+                Ok(OutMetadata::Bounce {
+                    bounced: H256::from_slice(&bytes[1..33]),
+                })
+            }
+            unknown => Err(DecodeErr::UnknownFirstByte(unknown)),
+        }
+    }
+}
+
+/// Encoded metadata for incoming transaction
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum InMetadata {
+    Deposit { reserve_pub: [u8; 32] },
+}
+
+impl InMetadata {
+    pub fn encode(&self) -> Vec<u8> {
+        let mut buffer = Vec::new();
+        match self {
+            InMetadata::Deposit { reserve_pub } => {
+                buffer.push(0);
+                buffer.extend_from_slice(reserve_pub);
+            }
+        }
+        return buffer;
+    }
+
+    pub fn decode(bytes: &[u8]) -> Result<Self, DecodeErr> {
+        if bytes.is_empty() {
+            return Err(DecodeErr::UnexpectedEOF);
+        }
+        match bytes[0] {
+            0 => {
+                if bytes.len() < 33 {
+                    return Err(DecodeErr::UnexpectedEOF);
+                }
+                Ok(InMetadata::Deposit {
+                    reserve_pub: bytes[1..33].try_into().unwrap(),
+                })
+            }
+            unknown => Err(DecodeErr::UnknownFirstByte(unknown)),
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use taler_common::{rand_slice, url::Url};
+    use ethereum_types::H256;
+
+    use crate::metadata::{InMetadata, OutMetadata};
+
+    #[test]
+    fn decode_encode_deposit() {
+        for _ in 0..4 {
+            let metadata = InMetadata::Deposit {
+                reserve_pub: rand_slice(),
+            };
+            let encoded = metadata.encode();
+            let decoded = InMetadata::decode(&encoded).unwrap();
+            assert_eq!(decoded, metadata);
+        }
+    }
+
+    #[test]
+    fn decode_encode_withdraw() {
+        let urls = [
+            "https://git.taler.net/";,
+            "https://git.taler.net/depolymerization.git/";,
+            "http://git.taler.net/";,
+            "http://git.taler.net/depolymerization.git/";,
+        ];
+        for url in urls {
+            let wtid = rand_slice();
+            let url = Url::parse(url).unwrap();
+            let metadata = OutMetadata::Withdraw { wtid, url };
+            let encoded = metadata.encode();
+            let decoded = OutMetadata::decode(&encoded).unwrap();
+            assert_eq!(decoded, metadata);
+        }
+    }
+
+    #[test]
+    fn decode_encode_bounce() {
+        for _ in 0..4 {
+            let id: [u8; 32] = rand_slice();
+            let metadata = OutMetadata::Bounce {
+                bounced: H256::from_slice(&id),
+            };
+            let encoded = metadata.encode();
+            let decoded = OutMetadata::decode(&encoded).unwrap();
+            assert_eq!(decoded, metadata);
+        }
+    }
+}
diff --git a/eth-wire/src/rpc.rs b/eth-wire/src/rpc.rs
new file mode 100644
index 0000000..f5c7fc6
--- /dev/null
+++ b/eth-wire/src/rpc.rs
@@ -0,0 +1,404 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2022 Taler Systems SA
+
+  TALER 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, or (at your option) any later version.
+
+  TALER 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
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+//! This is a very simple RPC client designed only for a specific geth version
+//! and to use on an secure unix domain socket to a trusted node
+//!
+//! We only parse the thing we actually use, this reduce memory usage and
+//! make our code more compatible with future deprecation
+
+use ethereum_types::{Address, H256, U256, U64};
+use serde::de::DeserializeOwned;
+use serde_json::error::Category;
+use std::{
+    fmt::Debug,
+    io::{self, BufWriter, ErrorKind, Read, Write},
+    marker::PhantomData,
+    os::unix::net::UnixStream,
+    path::{Path, PathBuf},
+};
+
+use self::hex::Hex;
+
+#[derive(Debug, serde::Serialize)]
+struct RpcRequest<'a, T: serde::Serialize> {
+    method: &'a str,
+    id: u64,
+    params: &'a T,
+}
+
+#[derive(Debug, serde::Deserialize)]
+struct RpcResponse<T> {
+    result: Option<T>,
+    error: Option<RpcErr>,
+    id: u64,
+}
+
+#[derive(Debug, serde::Deserialize)]
+struct RpcErr {
+    code: i64,
+    message: String,
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    #[error("{0:?}")]
+    Transport(#[from] std::io::Error),
+    #[error("{code:?} - {msg}")]
+    RPC { code: i64, msg: String },
+    #[error("JSON: {0}")]
+    Json(#[from] serde_json::Error),
+    #[error("No result or error")]
+    Null,
+}
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+const EMPTY: [(); 0] = [];
+
+pub trait RpcTrait {}
+
+/// Bitcoin RPC connection
+pub struct Rpc {
+    path: PathBuf,
+    id: u64,
+    conn: BufWriter<UnixStream>,
+    read_buf: Vec<u8>,
+    cursor: usize,
+}
+
+impl Rpc {
+    pub fn new(path: impl AsRef<Path>) -> io::Result<Self> {
+        let conn = UnixStream::connect(&path)?;
+
+        Ok(Self {
+            path: path.as_ref().to_path_buf(),
+            id: 0,
+            conn: BufWriter::new(conn),
+            read_buf: vec![0u8; 8 * 1024],
+            cursor: 0,
+        })
+    }
+
+    fn send(&mut self, method: &str, params: &impl serde::Serialize) -> 
Result<()> {
+        // TODO rethink timeout
+        let request = RpcRequest {
+            method,
+            id: self.id,
+            params,
+        };
+
+        // Send request
+        serde_json::to_writer(&mut self.conn, &request)?;
+        self.conn.flush()?;
+        Ok(())
+    }
+
+    fn receive<T>(&mut self) -> Result<T>
+    where
+        T: serde::de::DeserializeOwned + Debug,
+    {
+        loop {
+            if self.cursor == self.read_buf.len() {
+                self.read_buf.resize(self.cursor * 2, 0);
+            }
+            match self.conn.get_mut().read(&mut self.read_buf[self.cursor..]) {
+                Ok(nb) => {
+                    self.cursor += nb;
+                    let mut de: serde_json::StreamDeserializer<_, T> =
+                        
serde_json::Deserializer::from_slice(&self.read_buf[..self.cursor])
+                            .into_iter();
+
+                    if let Some(result) = de.next() {
+                        match result {
+                            Ok(response) => {
+                                let read = de.byte_offset();
+                                self.read_buf.copy_within(read..self.cursor, 
0);
+                                self.cursor -= read;
+                                return Ok(response);
+                            }
+                            Err(err) if err.classify() == Category::Eof => {
+                                if nb == 0 {
+                                    return Err(std::io::Error::new(
+                                        ErrorKind::UnexpectedEof,
+                                        "Stream EOF",
+                                    ))?;
+                                }
+                            }
+                            Err(e) => Err(e)?,
+                        }
+                    }
+                }
+                Err(e) if e.kind() == ErrorKind::Interrupted => {}
+                Err(e) => Err(e)?,
+            }
+        }
+    }
+
+    fn call<T>(&mut self, method: &str, params: &impl serde::Serialize) -> 
Result<T>
+    where
+        T: serde::de::DeserializeOwned + Debug,
+    {
+        self.send(method, params)?;
+        let response: RpcResponse<T> = self.receive()?;
+
+        assert_eq!(self.id, response.id);
+        self.id += 1;
+        return if let Some(ok) = response.result {
+            Ok(ok)
+        } else {
+            Err(match response.error {
+                Some(err) => Error::RPC {
+                    code: err.code,
+                    msg: err.message,
+                },
+                None => Error::Null,
+            })
+        };
+    }
+
+    pub fn list_accounts(&mut self) -> Result<Vec<Address>> {
+        self.call("personal_listAccounts", &EMPTY)
+    }
+
+    pub fn new_account(&mut self, passwd: &str) -> Result<Address> {
+        self.call("personal_newAccount", &[passwd])
+    }
+
+    pub fn import_account(&mut self, hex: &str, passwd: &str) -> Result<bool> {
+        self.call("personal_importRawKey", &(hex, passwd))
+    }
+
+    pub fn unlock_account(&mut self, account: &Address, passwd: &str) -> 
Result<bool> {
+        self.call("personal_unlockAccount", &(account, passwd, 0))
+    }
+
+    pub fn get_transaction(&mut self, hash: &H256) -> 
Result<Option<Transaction>> {
+        self.call("eth_getTransactionByHash", &[hash])
+    }
+
+    pub fn send_transaction(&mut self, params: &TransactionRequest) -> 
Result<H256> {
+        self.call("eth_sendTransaction", &[params])
+    }
+
+    pub fn block(&mut self, nb: U64) -> Result<Option<Block>> {
+        self.call("eth_getBlockByNumber", &(nb, &true))
+    }
+
+    pub fn pending_transactions(&mut self) -> Result<Vec<Transaction>> {
+        self.call("eth_pendingTransactions", &EMPTY)
+    }
+
+    pub fn miner_start(&mut self) -> Result<()> {
+        match self.call("miner_start", &[1]) {
+            Err(Error::Null) => Ok(()),
+            i => i,
+        }
+    }
+
+    pub fn miner_stop(&mut self) -> Result<()> {
+        match self.call("miner_stop", &EMPTY) {
+            Err(Error::Null) => Ok(()),
+            i => i,
+        }
+    }
+
+    pub fn subscribe_new_head(&mut self) -> Result<RpcStream<BlockHead>> {
+        let mut rpc = Self::new(&self.path)?;
+        let id: String = rpc.call("eth_subscribe", &["newHeads"])?;
+        Ok(RpcStream::new(rpc, id))
+    }
+
+    pub fn current_block(&mut self) -> Result<Block> {
+        let number: U64 = self.call("eth_blockNumber", &EMPTY)?;
+        Ok(self.block(number)?.expect("Current block must exist"))
+    }
+
+    pub fn balance(&mut self, addr: &Address) -> Result<U256> {
+        self.call("eth_getBalance", &(addr, "latest"))
+    }
+}
+
+pub struct RpcStream<T: Debug + DeserializeOwned> {
+    rpc: Rpc,
+    id: String,
+    phantom: PhantomData<T>,
+}
+
+impl<T: Debug + DeserializeOwned> RpcStream<T> {
+    fn new(rpc: Rpc, id: String) -> Self {
+        Self {
+            rpc,
+            id,
+            phantom: PhantomData,
+        }
+    }
+
+    pub fn next(&mut self) -> Result<T> {
+        let notification: Wrapper<T> = self.rpc.receive()?;
+        let notification = notification.params;
+        assert_eq!(self.id, notification.subscription);
+        Ok(notification.result)
+    }
+}
+
+#[derive(Debug, serde::Deserialize)]
+pub struct Notification<T> {
+    subscription: String,
+    result: T,
+}
+
+#[derive(Debug, serde::Deserialize)]
+struct Wrapper<T> {
+    params: Notification<T>,
+}
+
+#[derive(Debug, serde::Deserialize)]
+#[serde(untagged)]
+pub enum NotifEnd<T> {
+    Notification(Notification<T>),
+    End(bool),
+}
+
+#[derive(Debug, serde::Deserialize)]
+pub struct Block {
+    /// Hash of the block
+    pub hash: Option<H256>,
+    /// Block number (None if pending)
+    pub number: Option<U64>,
+    /// Hash of the parent
+    #[serde(rename = "parentHash")]
+    pub parent_hash: H256,
+    /// Transactions
+    pub transactions: Vec<Transaction>,
+}
+
+#[derive(Debug, serde::Deserialize)]
+pub struct BlockHead {}
+
+/// Description of a Transaction, pending or in the chain.
+#[derive(Debug, serde::Deserialize)]
+pub struct Transaction {
+    /// Hash
+    pub hash: H256,
+    /// Sender address (None when coinbase)
+    pub from: Option<Address>,
+    /// Recipient address (None when contract creation)
+    pub to: Option<Address>,
+    /// Transferred value
+    pub value: U256,
+    /// Input data
+    pub input: Hex,
+}
+
+/// Send Transaction Parameters
+#[derive(Debug, serde::Serialize)]
+pub struct TransactionRequest {
+    /// Sender address
+    pub from: Address,
+    /// Recipient address
+    pub to: Address,
+    /// Transferred value
+    pub value: U256,
+    /// Supplied gas (None for sensible default)
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub gas: Option<U256>,
+    /// Gas price (None for sensible default)
+    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(rename = "gasPrice")]
+    pub gas_price: Option<U256>,
+    /// Transaction data
+    pub data: Hex,
+}
+
+pub mod hex {
+    use std::{
+        fmt,
+        ops::{Deref, DerefMut},
+    };
+
+    use serde::{
+        de::{Error, Unexpected, Visitor},
+        Deserialize, Deserializer, Serialize, Serializer,
+    };
+
+    /// Raw bytes wrapper
+    #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
+    pub struct Hex(pub Vec<u8>);
+
+    impl Deref for Hex {
+        type Target = Vec<u8>;
+
+        fn deref(&self) -> &Self::Target {
+            &self.0
+        }
+    }
+
+    impl DerefMut for Hex {
+        fn deref_mut(&mut self) -> &mut Self::Target {
+            &mut self.0
+        }
+    }
+
+    impl Serialize for Hex {
+        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+        where
+            S: Serializer,
+        {
+            let mut serialized = "0x".to_owned();
+            serialized.push_str(&hex::encode(&self.0));
+            serializer.serialize_str(serialized.as_ref())
+        }
+    }
+
+    impl<'a> Deserialize<'a> for Hex {
+        fn deserialize<D>(deserializer: D) -> Result<Hex, D::Error>
+        where
+            D: Deserializer<'a>,
+        {
+            deserializer.deserialize_identifier(BytesVisitor)
+        }
+    }
+
+    struct BytesVisitor;
+
+    impl<'a> Visitor<'a> for BytesVisitor {
+        type Value = Hex;
+
+        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+            write!(formatter, "a 0x-prefixed hex-encoded vector of bytes")
+        }
+
+        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
+        where
+            E: Error,
+        {
+            if value.len() >= 2 && &value[0..2] == "0x" {
+                let bytes = hex::decode(&value[2..])
+                    .map_err(|e| Error::custom(format!("Invalid hex: {}", 
e)))?;
+                Ok(Hex(bytes))
+            } else {
+                Err(Error::invalid_value(Unexpected::Str(value), &"0x prefix"))
+            }
+        }
+
+        fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
+        where
+            E: Error,
+        {
+            self.visit_str(value.as_ref())
+        }
+    }
+}
diff --git a/btc-wire/src/status.rs b/eth-wire/src/status.rs
similarity index 100%
copy from btc-wire/src/status.rs
copy to eth-wire/src/status.rs
diff --git a/eth-wire/src/taler_util.rs b/eth-wire/src/taler_util.rs
new file mode 100644
index 0000000..40c8fff
--- /dev/null
+++ b/eth-wire/src/taler_util.rs
@@ -0,0 +1,46 @@
+use std::str::FromStr;
+
+use ethereum_types::{Address, U256};
+use taler_common::{api_common::Amount, url::Url};
+
+const WEI: u64 = 1_000_000_000_000_000_000;
+const TRUNC: u64 = 10_000_000_000;
+
+/// Generate a payto uri from an eth address
+pub fn eth_payto_url(addr: &Address) -> Url {
+    Url::from_str(&format!(
+        "payto://ethereum/{}",
+        hex::encode(addr.as_bytes())
+    ))
+    .unwrap()
+}
+
+/// Extract an eth address from a payto uri
+pub fn eth_payto_addr(url: &Url) -> Result<Address, String> {
+    if url.domain() != Some("ethereum") {
+        return Err(format!(
+            "Expected domain 'ethereum' got '{}'",
+            url.domain().unwrap_or_default()
+        ));
+    }
+    let str = url.path().trim_start_matches('/');
+    return Address::from_str(str).map_err(|e| e.to_string());
+}
+
+/// Transform a eth amount into a taler amount
+pub fn eth_to_taler(amount: &U256) -> Amount {
+    return Amount::new(
+        "ETH",
+        (amount / WEI).as_u64(),
+        ((amount % WEI) / TRUNC).as_u32(),
+    );
+}
+
+/// Transform a eth amount into a btc amount
+pub fn taler_to_eth(amount: &Amount) -> Result<U256, String> {
+    if amount.currency != "ETH" {
+        return Err(format!("expected ETH for {}", amount.currency));
+    }
+
+    Ok(U256::from(amount.value) * WEI + U256::from(amount.fraction) * TRUNC)
+}
diff --git a/makefile b/makefile
index deb9745..e11a023 100644
--- a/makefile
+++ b/makefile
@@ -1,5 +1,6 @@
 install:
        cargo install --path btc-wire --bin btc-wire-cli --bin btc-wire-utils 
--bin btc-wire
+       cargo install --path eth-wire --bin eth-wire-cli --bin eth-wire-utils 
--bin eth-wire
        cargo install --path wire-gateway
 
 test_gateway:
@@ -18,4 +19,7 @@ test_btc:
        test/btc/maxfee.sh
        test/btc/config.sh
 
-test: install test_gateway test_btc
\ No newline at end of file
+test_eth:
+       test/eth/wire.sh
+
+test: install test_gateway test_btc test_eth
\ No newline at end of file
diff --git a/script/prepare.sh b/script/prepare.sh
index 1257edf..c2ac225 100644
--- a/script/prepare.sh
+++ b/script/prepare.sh
@@ -32,6 +32,17 @@ mkdir -pv ~/bitcoin
 tar xvzf btc.tar.gz
 mv -v bitcoin-22.0/* ~/bitcoin
 
+
+echo "Ⅳ - Install Go Ethereum (Geth) v1.10.15 "
+cd $DIR
+curl -L 
https://gethstore.blob.core.windows.net/builds/geth-alltools-linux-amd64-1.10.15-8be800ff.tar.gz
 -o geth.tar.gz
+rm -rfv ~/geth
+mkdir -pv ~/geth
+tar xvzf geth.tar.gz
+mv -v geth-alltools-linux-amd64-1.10.15-8be800ff/* ~/geth
+
 echo "Ⅲ - Config"
+
+echo "Add ~/postgresql/bin to your path"
 echo "Add ~/bitcoin/bin to your path"
-echo "Add ~/postgresql/bin to your path"
\ No newline at end of file
+echo "Add ~/geth to your path"
\ No newline at end of file
diff --git a/taler-common/Cargo.toml b/taler-common/Cargo.toml
index 816524b..a18afea 100644
--- a/taler-common/Cargo.toml
+++ b/taler-common/Cargo.toml
@@ -8,11 +8,11 @@ license = "AGPL-3.0-or-later"
 
 [dependencies]
 # Serialization framework
-serde = { version = "1.0.133", features = ["derive"] }
+serde = { version = "1.0.136", features = ["derive"] }
 # Serialization helper
 serde_with = "1.11.0"
 # JSON serialization
-serde_json = "1.0.75"
+serde_json = "1.0.78"
 # Url format
 url = { version = "2.2.2", features = ["serde"] }
 # Crockford’s base32
@@ -23,11 +23,11 @@ thiserror = "1.0.30"
 rust-ini = "0.17.0"
 # Logging
 log = "0.4.14"
-flexi_logger = { version = "0.22.2", default-features = false, features = [
+flexi_logger = { version = "0.22.3", default-features = false, features = [
     "use_chrono_for_offset", # Temporary hack for multithreaded code 
https://rustsec.org/advisories/RUSTSEC-2020-0159
 ] }
 # Local timz
-time = { version = "0.3.5", features = ["formatting", "macros"] }
+time = { version = "0.3.7", features = ["formatting", "macros"] }
 # Postgres client
 postgres = "0.19.2"
 # Secure random
diff --git a/taler-common/src/config.rs b/taler-common/src/config.rs
index c0a85aa..5f5d008 100644
--- a/taler-common/src/config.rs
+++ b/taler-common/src/config.rs
@@ -20,69 +20,115 @@ use std::{
 };
 use url::Url;
 
-pub struct InitConfig {
+pub trait Config: Sized {
+    /// Load from a file
+    fn load_from_file(config_file: impl AsRef<Path>) -> Self {
+        let conf = ini::Ini::load_from_file(config_file).expect("Failed to 
open the config file");
+        let taler = section(&conf, "taler");
+        let currency = require(&taler, "CURRENCY", string);
+        let section_name = match currency.as_str() {
+            "BTC" => "depolymerizer-bitcoin",
+            "ETH" => "depolymerizer-ethereum",
+            currency => unimplemented!("Unsupported currency {}", currency),
+        };
+
+        let dep = section(&conf, section_name);
+        return Self::load_from_ini(&conf, &currency, dep);
+    }
+    /// Load from loaded ini file
+    fn load_from_ini(ini: &Ini, currency: &str, dep: &Properties) -> Self;
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct CoreConfig {
     pub db_url: String,
-    pub btc_data_dir: Option<PathBuf>,
+    pub data_dir: Option<PathBuf>,
+    pub currency: String,
 }
 
-impl InitConfig {
-    /// Load from a file
-    pub fn load_from_file(config_file: impl AsRef<Path>) -> Self {
-        let conf = ini::Ini::load_from_file(config_file).unwrap();
-        assert(section(&conf, "taler"), "CURRENCY", "BTC");
-        let self_conf = section(&conf, "depolymerizer-bitcoin");
+impl Config for CoreConfig {
+    fn load_from_ini(_: &Ini, currency: &str, dep: &Properties) -> Self {
         Self {
-            db_url: require(self_conf, "DB_URL", string),
-            btc_data_dir: path(self_conf, "BTC_DATA_DIR"),
+            db_url: require(dep, "DB_URL", string),
+            data_dir: path(dep, "DATA_DIR"),
+            currency: currency.to_string(),
         }
     }
 }
 
-/// Taler config with depolymerizer config
 #[derive(Debug, Clone, PartialEq, Eq)]
-pub struct Config {
-    pub base_url: Url,
-    pub db_url: String,
+pub struct GatewayConfig {
+    pub http_lifetime: Option<u32>,
     pub port: u16,
     pub unix_path: Option<PathBuf>,
-    pub btc_data_dir: Option<PathBuf>,
     pub payto: Url,
+    pub core: CoreConfig,
+}
+
+impl Config for GatewayConfig {
+    fn load_from_ini(ini: &Ini, currency: &str, dep: &Properties) -> Self {
+        Self {
+            port: nb(dep, "PORT").unwrap_or(8080),
+            unix_path: path(dep, "UNIXPATH"),
+            payto: require(dep, "PAYTO", url),
+            http_lifetime: nb(dep, "HTTP_LIFETIME")
+                .and_then(|nb| (nb != 0).then(|| Some(nb)))
+                .unwrap_or(None),
+            core: CoreConfig::load_from_ini(ini, currency, dep),
+        }
+    }
+}
+
+// TODO currency name as const generic
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct WireConfig<const DEFAULT_FEE: u64, const DEFAULT_CONFIRMATION: u16> 
{
+    pub base_url: Url,
     pub confirmation: u16,
     pub bounce_fee: u64,
     pub wire_lifetime: Option<u32>,
-    pub http_lifetime: Option<u32>,
     pub bump_delay: Option<u32>,
+    pub payto: Url,
+    pub core: CoreConfig,
 }
 
-impl Config {
-    /// Load from a file
-    pub fn load_from_file(config_file: impl AsRef<Path>) -> Self {
-        let conf = ini::Ini::load_from_file(config_file).unwrap();
-        assert(section(&conf, "taler"), "CURRENCY", "BTC");
-        let ex_conf = section(&conf, "exchange");
-        let self_conf = section(&conf, "depolymerizer-bitcoin");
+impl<const DEFAULT_FEE: u64, const DEFAULT_CONFIRMATION: u16> Config
+    for WireConfig<DEFAULT_FEE, DEFAULT_CONFIRMATION>
+{
+    fn load_from_ini(ini: &Ini, currency: &str, dep: &Properties) -> Self {
+        let ex = section(ini, "exchange");
         Self {
-            base_url: require(ex_conf, "BASE_URL", url),
-            db_url: require(self_conf, "DB_URL", string),
-            port: nb(self_conf, "PORT").unwrap_or(8080),
-            unix_path: path(self_conf, "UNIXPATH"),
-            btc_data_dir: path(self_conf, "BTC_DATA_DIR"),
-            payto: require(self_conf, "PAYTO", url),
-            confirmation: nb(self_conf, "CONFIRMATION").unwrap_or(6),
-            bounce_fee: nb(self_conf, "BOUNCE_FEE").unwrap_or(1000),
-            wire_lifetime: nb(self_conf, "WIRE_LIFETIME")
-                .and_then(|nb| (nb != 0).then(|| Some(nb)))
-                .unwrap_or(None),
-            http_lifetime: nb(self_conf, "HTTP_LIFETIME")
+            base_url: require(ex, "BASE_URL", url),
+            payto: require(dep, "PAYTO", url),
+            confirmation: nb(dep, 
"CONFIRMATION").unwrap_or(DEFAULT_CONFIRMATION),
+            bounce_fee: nb(dep, "BOUNCE_FEE").unwrap_or(DEFAULT_FEE),
+            wire_lifetime: nb(dep, "WIRE_LIFETIME")
                 .and_then(|nb| (nb != 0).then(|| Some(nb)))
                 .unwrap_or(None),
-            bump_delay: nb(self_conf, "BUMP_DELAY")
+            bump_delay: nb(dep, "BUMP_DELAY")
                 .and_then(|nb| (nb != 0).then(|| Some(nb)))
                 .unwrap_or(None),
+            core: CoreConfig::load_from_ini(ini, currency, dep),
         }
     }
 }
 
+pub type BtcConfig = WireConfig<1000, 6>;
+
+pub fn load_btc_config(path: impl AsRef<Path>) -> BtcConfig {
+    let config = WireConfig::load_from_file(path);
+    assert_eq!(config.core.currency, "BTC");
+    return config;
+}
+
+pub type EthConfig = WireConfig<1000000, 24>;
+
+pub fn load_eth_config(path: impl AsRef<Path>) -> EthConfig {
+    let config = WireConfig::load_from_file(path);
+    assert_eq!(config.core.currency, "ETH");
+    return config;
+}
+
 /* ----- Helper functions ----- */
 
 fn section<'a>(ini: &'a Ini, name: &str) -> &'a Properties {
@@ -90,13 +136,6 @@ fn section<'a>(ini: &'a Ini, name: &str) -> &'a Properties {
         .unwrap_or_else(|| panic!("missing config section {}", name))
 }
 
-fn assert(properties: &Properties, name: &str, expected: &str) {
-    let value = require(properties, name, string);
-    if value != expected {
-        panic!("config {} expected '{}' got '{}'", name, expected, value);
-    }
-}
-
 fn require<T>(
     properties: &Properties,
     name: &str,
diff --git a/test/btc/analysis.sh b/test/btc/analysis.sh
index 5677c2d..c667c84 100644
--- a/test/btc/analysis.sh
+++ b/test/btc/analysis.sh
@@ -6,6 +6,7 @@ set -eu
 
 source "${BASH_SOURCE%/*}/../common.sh"
 SCHEMA=btc.sql
+CONFIG=taler_btc.conf
 
 echo  "----- Setup -----"
 echo "Load config file"
@@ -28,7 +29,7 @@ echo "Loose second bitcoin node"
 btc2_deco
 
 echo -n "Making wire transfer to exchange:"
-btc-wire-utils -d $BTC_DIR transfer 0.042 > /dev/null
+btc-wire-utils -d $WIRE_DIR transfer 0.042 > /dev/null
 next_btc # Trigger btc_wire
 check_balance 9.95799209 0.04200000
 echo " OK"
@@ -50,7 +51,7 @@ echo "Loose second bitcoin node"
 btc2_deco
 
 echo -n "Making wire transfer to exchange:"
-btc-wire-utils -d $BTC_DIR transfer 0.064 > /dev/null
+btc-wire-utils -d $WIRE_DIR transfer 0.064 > /dev/null
 next_btc 5 # More block needed to confirm
 check_balance 9.89398418 0.10600000
 echo " OK"
diff --git a/test/btc/bumpfee.sh b/test/btc/bumpfee.sh
index 6dcdd9e..e1b7c51 100644
--- a/test/btc/bumpfee.sh
+++ b/test/btc/bumpfee.sh
@@ -6,7 +6,7 @@ set -eu
 
 source "${BASH_SOURCE%/*}/../common.sh"
 SCHEMA=btc.sql
-CONFIG=taler_bump.conf
+CONFIG=taler_btc_bump.conf
 
 echo  "----- Setup -----"
 echo "Load config file"
@@ -28,7 +28,7 @@ SEQ="seq 10 30"
 
 echo -n "Making wire transfer to exchange:"
 for n in `$SEQ`; do
-    btc-wire-utils -d $BTC_DIR transfer 0.$n > /dev/null
+    btc-wire-utils -d $WIRE_DIR transfer 0.$n > /dev/null
     mine_btc # Mine transactions
 done
 next_btc # Trigger btc_wire
diff --git a/test/btc/config.sh b/test/btc/config.sh
index 4125159..9e5767c 100644
--- a/test/btc/config.sh
+++ b/test/btc/config.sh
@@ -5,6 +5,7 @@
 set -eu
 
 SCHEMA=btc.sql
+CONFIG=taler_btc.conf
 
 function test() {
     echo "----- Config $1 -----"
diff --git a/test/btc/conflict.sh b/test/btc/conflict.sh
index f4710cd..832dfea 100644
--- a/test/btc/conflict.sh
+++ b/test/btc/conflict.sh
@@ -6,6 +6,7 @@ set -eu
 
 source "${BASH_SOURCE%/*}/../common.sh"
 SCHEMA=btc.sql
+CONFIG=taler_btc.conf
 
 echo  "----- Setup -----"
 echo "Load config file"
@@ -25,7 +26,7 @@ echo ""
 echo "----- Conflict send -----"
 
 echo -n "Making wire transfer to exchange:"
-btc-wire-utils -d $BTC_DIR transfer 0.042 > /dev/null
+btc-wire-utils -d $WIRE_DIR transfer 0.042 > /dev/null
 next_btc
 check_balance 9.95799209 0.04200000
 echo " OK"
@@ -41,7 +42,7 @@ echo " OK"
 
 echo -n "Abandon pending transaction:"
 restart_btc -minrelaytxfee=0.0001
-btc-wire-utils -d $BTC_DIR abandon
+btc-wire-utils -d $WIRE_DIR abandon
 check_balance 9.95799209 0.04200000
 echo " OK"
 
@@ -92,7 +93,7 @@ echo " OK"
 
 echo -n "Abandon pending transaction:"
 restart_btc -minrelaytxfee=0.0001
-btc-wire-utils -d $BTC_DIR abandon
+btc-wire-utils -d $WIRE_DIR abandon
 check_balance 9.95999859 0.04000000
 echo " OK"
 
diff --git a/test/btc/hell.sh b/test/btc/hell.sh
index f8862d2..13ada76 100644
--- a/test/btc/hell.sh
+++ b/test/btc/hell.sh
@@ -5,6 +5,7 @@
 set -eu
 
 source "${BASH_SOURCE%/*}/../common.sh"
+CONFIG=taler_btc.conf
 SCHEMA=btc.sql
 
 echo  "----- Setup -----"
@@ -28,7 +29,7 @@ echo "Loose second bitcoin node"
 btc2_deco
 
 echo -n "Gen incoming transactions:"
-btc-wire-utils -d $BTC_DIR transfer 0.0042 > /dev/null
+btc-wire-utils -d $WIRE_DIR transfer 0.0042 > /dev/null
 next_btc # Trigger btc_wire
 check_balance 9.99579209 0.00420000
 echo " OK"
@@ -42,8 +43,8 @@ echo " OK"
 
 echo -n "Generate conflict:"
 restart_btc -minrelaytxfee=0.0001
-btc-wire-utils -d $BTC_DIR abandon client
-btc-wire-utils -d $BTC_DIR transfer 0.0054 > /dev/null
+btc-wire-utils -d $WIRE_DIR abandon client
+btc-wire-utils -d $WIRE_DIR transfer 0.0054 > /dev/null
 next_btc
 check_balance 9.99457382 0.00540000
 echo " OK"
@@ -95,8 +96,8 @@ echo " OK"
 
 echo -n "Generate conflict:"
 restart_btc -minrelaytxfee=0.0001
-btc-wire-utils -d $BTC_DIR abandon client
-btc-wire-utils -d $BTC_DIR transfer 0.054 > /dev/null
+btc-wire-utils -d $WIRE_DIR abandon client
+btc-wire-utils -d $WIRE_DIR transfer 0.054 > /dev/null
 next_btc
 check_balance 9.94597382 0.05400000
 echo " OK"
diff --git a/test/btc/lifetime.sh b/test/btc/lifetime.sh
index 35f6ccd..a0260b5 100644
--- a/test/btc/lifetime.sh
+++ b/test/btc/lifetime.sh
@@ -2,12 +2,12 @@
 
 ## Check btc-wire and wire-gateway correctly stop when a lifetime limit is 
configured
 
-CONFIG=taler_lifetime.conf
-
 set -eu
 
 source "${BASH_SOURCE%/*}/../common.sh"
 SCHEMA=btc.sql
+CONFIG=taler_btc_lifetime.conf
+
 
 echo  "----- Setup -----"
 echo "Load config file"
@@ -33,7 +33,7 @@ echo " OK"
 
 echo -n "Do some work:"
 for n in `$SEQ`; do
-    btc-wire-utils -d $BTC_DIR transfer 0.000$n > /dev/null
+    btc-wire-utils -d $WIRE_DIR transfer 0.000$n > /dev/null
     mine_btc # Mine transactions
 done
 next_btc # Trigger btc_wire
diff --git a/test/btc/maxfee.sh b/test/btc/maxfee.sh
index 4bb40c9..f9aabea 100644
--- a/test/btc/maxfee.sh
+++ b/test/btc/maxfee.sh
@@ -6,6 +6,7 @@ set -eu
 
 source "${BASH_SOURCE%/*}/../common.sh"
 SCHEMA=btc.sql
+CONFIG=taler_btc.conf
 
 echo  "----- Setup -----"
 echo "Load config file"
@@ -26,7 +27,7 @@ SEQ="seq 10 30"
 
 echo -n "Making wire transfer to exchange:"
 for n in `$SEQ`; do
-    btc-wire-utils -d $BTC_DIR transfer 0.$n > /dev/null
+    btc-wire-utils -d $WIRE_DIR transfer 0.$n > /dev/null
     mine_btc # Mine transactions
 done
 next_btc # Trigger btc_wire
diff --git a/test/btc/reconnect.sh b/test/btc/reconnect.sh
index b234a0b..fb65882 100644
--- a/test/btc/reconnect.sh
+++ b/test/btc/reconnect.sh
@@ -6,6 +6,7 @@ set -eu
 
 source "${BASH_SOURCE%/*}/../common.sh"
 SCHEMA=btc.sql
+CONFIG=taler_btc.conf
 
 echo  "----- Setup -----"
 echo "Load config file"
@@ -22,7 +23,7 @@ echo ""
 
 echo "----- With DB -----"
 echo "Making wire transfer to exchange:"
-btc-wire-utils -d $BTC_DIR transfer 0.000042 > /dev/null
+btc-wire-utils -d $WIRE_DIR transfer 0.000042 > /dev/null
 next_btc
 check_balance 9.99995009 0.00004200
 echo -n "Requesting exchange incoming transaction list:"
@@ -35,7 +36,7 @@ pg_ctl stop -D $DB_DIR > /dev/null
 echo "Making incomplete wire transfer to exchange"
 $BTC_CLI -rpcwallet=client sendtoaddress $WIRE 0.00042 &> /dev/null
 echo -n "Making wire transfer to exchange:"
-btc-wire-utils -d $BTC_DIR transfer 0.00004 > /dev/null
+btc-wire-utils -d $WIRE_DIR transfer 0.00004 > /dev/null
 next_btc
 check_balance 9.99948077 0.00050200
 echo " OK"
diff --git a/test/btc/reorg.sh b/test/btc/reorg.sh
index a0de12d..c047290 100644
--- a/test/btc/reorg.sh
+++ b/test/btc/reorg.sh
@@ -6,6 +6,7 @@ set -eu
 
 source "${BASH_SOURCE%/*}/../common.sh"
 SCHEMA=btc.sql
+CONFIG=taler_btc.conf
 
 echo  "----- Setup -----"
 echo "Load config file"
@@ -31,7 +32,7 @@ btc2_deco
 
 echo -n "Gen incoming transactions:"
 for n in `$SEQ`; do
-    btc-wire-utils -d $BTC_DIR transfer 0.000$n > /dev/null
+    btc-wire-utils -d $WIRE_DIR transfer 0.000$n > /dev/null
     mine_btc # Mine transactions
 done
 next_btc # Trigger btc_wire
diff --git a/test/btc/stress.sh b/test/btc/stress.sh
index b750c9f..83a34a8 100644
--- a/test/btc/stress.sh
+++ b/test/btc/stress.sh
@@ -6,6 +6,7 @@ set -eu
 
 source "${BASH_SOURCE%/*}/../common.sh"
 SCHEMA=btc.sql
+CONFIG=taler_btc.conf
 
 echo  "----- Setup stressed -----"
 echo "Load config file"
@@ -26,7 +27,7 @@ echo "----- Handle incoming -----"
 
 echo -n "Making wire transfer to exchange:"
 for n in `$SEQ`; do
-    btc-wire-utils -d $BTC_DIR transfer 0.000$n > /dev/null
+    btc-wire-utils -d $WIRE_DIR transfer 0.000$n > /dev/null
     mine_btc # Mine transactions
 done
 next_btc # Confirm all transactions
diff --git a/test/btc/wire.sh b/test/btc/wire.sh
index a4e3843..cac806f 100644
--- a/test/btc/wire.sh
+++ b/test/btc/wire.sh
@@ -1,11 +1,12 @@
 #!/bin/bash
 
-## Test btc_wire correctly receive and sens transactions on the blockchain
+## Test btc_wire correctly receive and send transactions on the blockchain
 
 set -eu
 
 source "${BASH_SOURCE%/*}/../common.sh"
 SCHEMA=btc.sql
+CONFIG=taler_btc.conf
 
 echo  "----- Setup -----"
 echo "Load config file"
@@ -26,7 +27,7 @@ echo  "----- Receive -----"
 
 echo -n "Making wire transfer to exchange:"
 for n in `$SEQ`; do
-    btc-wire-utils -d $BTC_DIR transfer 0.000$n > /dev/null
+    btc-wire-utils -d $WIRE_DIR transfer 0.000$n > /dev/null
     mine_btc # Mine transactions
 done
 next_btc # Trigger btc_wire
diff --git a/test/common.sh b/test/common.sh
index ac3bda8..5a0331a 100644
--- a/test/common.sh
+++ b/test/common.sh
@@ -19,11 +19,11 @@ trap cleanup EXIT
 
 # Init temporary dirs
 DIR=$(mktemp -d)
-BTC_DIR=$DIR/bitcoin
-BTC_DIR2=$DIR/bitcoin2
+WIRE_DIR=$DIR/wire
+WIRE_DIR2=$DIR/wire2
 DB_DIR=$DIR/db
 CONF=$DIR/taler.conf
-for dir in $BTC_DIR $BTC_DIR2 $DB_DIR log; do
+for dir in $WIRE_DIR $WIRE_DIR2 $DB_DIR log; do
     mkdir -p $dir
 done
 
@@ -33,15 +33,24 @@ for log in log/*; do
 done
 
 # Setup command helpers
-BTC_CLI="bitcoin-cli -datadir=$BTC_DIR"
-BTC_CLI2="bitcoin-cli -datadir=$BTC_DIR2"
+BTC_CLI="bitcoin-cli -datadir=$WIRE_DIR"
+BTC_CLI2="bitcoin-cli -datadir=$WIRE_DIR2"
+ETH_CLI="geth -datadir=$WIRE_DIR"
+ETH_CLI2="geth -datadir=$WIRE_DIR2"
 
 # Load test.conf as bash variables
 function load_config() {
-    cp ${BASH_SOURCE%/*}/conf/${CONFIG:-taler_test.conf} $CONF
-    echo -e "\nBTC_DATA_DIR = ${BTC_DIR}" >> $CONF
+    cp ${BASH_SOURCE%/*}/conf/$CONFIG $CONF
+    echo -e "\nDATA_DIR = ${WIRE_DIR}" >> $CONF
     source <(grep = $CONF | sed 's/ *= */=/' | sed 's/=\(.*\)/="\1"/g1')
     BANK_ENDPOINT=http://127.0.0.1:$PORT/
+    if [ "$CURRENCY" == "BTC" ]; then
+        WIRE_CLI=btc-wire-cli
+        WIRE_UTILS=btc-wire-utils
+    else
+        WIRE_CLI=eth-wire-cli
+        WIRE_UTILS=eth-wire-utils
+    fi
 }
 
 # Check process is running
@@ -70,27 +79,27 @@ function setup_db() {
     echo "port=5454" >> $DB_DIR/postgresql.conf
     pg_ctl start -D $DB_DIR >> log/postgres.log
     echo "CREATE ROLE postgres LOGIN SUPERUSER PASSWORD 'password'" | psql -p 
5454 postgres > /dev/null
-    btc-wire-cli initdb $CONF > /dev/null
+    $WIRE_CLI initdb $CONF > /dev/null
 }
 
 # Erase database
 function reset_db() {
-    btc-wire-utils cleardb $CONF
-    btc-wire-cli initdb $CONF
+    $WIRE_UTILS cleardb $CONF
+    $WIRE_CLI initdb $CONF
 }
 
 # ----- Bitcoin node ----- #
 
 # Start a bitcoind regtest node, generate money, wallet and addresses
 function init_btc() {
-    cp ${BASH_SOURCE%/*}/conf/${BTC_CONFIG:-bitcoin.conf} $BTC_DIR/bitcoin.conf
-    bitcoind -datadir=$BTC_DIR $* &>> log/btc.log &
+    cp ${BASH_SOURCE%/*}/conf/${BTC_CONFIG:-bitcoin.conf} 
$WIRE_DIR/bitcoin.conf
+    bitcoind -datadir=$WIRE_DIR $* &>> log/btc.log &
     BTC_PID="$!"
     # Wait for RPC server to be online
     $BTC_CLI -rpcwait getnetworkinfo > /dev/null
     # Create wire wallet
     btc-wire-cli initwallet $CONF > /dev/null
-    # Load wallets
+    # Create other wallets
     for wallet in client reserve; do
         $BTC_CLI createwallet $wallet > /dev/null
     done
@@ -106,8 +115,8 @@ function init_btc() {
 
 # Start a second bitcoind regtest node connected to the first one
 function init_btc2() {
-    cp ${BASH_SOURCE%/*}/conf/bitcoin2.conf $BTC_DIR2/bitcoin.conf
-    bitcoind -datadir=$BTC_DIR2 $* &>> log/btc2.log &
+    cp ${BASH_SOURCE%/*}/conf/bitcoin2.conf $WIRE_DIR2/bitcoin.conf
+    bitcoind -datadir=$WIRE_DIR2 $* &>> log/btc2.log &
     $BTC_CLI2 -rpcwait getnetworkinfo > /dev/null    
     $BTC_CLI addnode 127.0.0.1:8346 onetry
 }
@@ -127,7 +136,7 @@ function btc2_fork() {
 # Restart a bitcoind regest server in a previously created temporary directory 
and load wallets
 function resume_btc() {
     # Restart node
-    bitcoind -datadir=$BTC_DIR $* &>> log/btc.log &
+    bitcoind -datadir=$WIRE_DIR $* &>> log/btc.log &
     BTC_PID="$!" 
     # Load wallets
     for wallet in wire client reserve; do
@@ -147,7 +156,7 @@ function restart_btc() {
     resume_btc $*
 }
 
-# Mine blocks
+# Mine bitcoin blocks
 function mine_btc() {
     $BTC_CLI generatetoaddress "${1:-1}" $RESERVE > /dev/null
 }
@@ -184,18 +193,100 @@ function check_balance() {
 
 # ----- btc-wire ----- #
 
-# Start btc_wire
+# Start btc-wire
 function btc_wire() {    
-    cargo build --bin btc-wire --release &> /dev/null
-    target/release/btc-wire $CONF &>> log/btc_wire.log &
+    cargo build --bin btc-wire --release &> log/cargo.log
+    target/release/btc-wire $CONF &> log/btc_wire.log &
     WIRE_PID="$!"
 }
 
 # Start multiple btc_wire with random failures in parallel
 function stress_btc_wire() {
-   cargo build --bin btc-wire --release --features fail &> /dev/null
-   target/release/btc-wire $CONF &>> log/btc_wire.log & 
-   target/release/btc-wire $CONF &>> log/btc_wire1.log & 
+   cargo build --bin btc-wire --release --features fail &> log/cargo.log
+   target/release/btc-wire $CONF &> log/btc_wire.log & 
+   target/release/btc-wire $CONF &> log/btc_wire1.log & 
+}
+
+# ----- Ethereum node ----- #
+
+# Start a geth dev node, generate money, wallet and addresses
+function init_eth() {
+    # Create wallets
+    for pswd in "reserve" "client"; do
+        $ETH_CLI account new --password <(echo "password") &> /dev/null
+    done
+    # Retrieve addresses
+    local ADDR=`$ETH_CLI account list 2> /dev/null | grep -oP '(?<={).*?(?=})'`
+    RESERVE=`sed -n '1p' <(echo "$ADDR")`
+    CLIENT=`sed -n '2p' <(echo "$ADDR")`
+    # Generate genesis 
+    echo "{
+  \"config\": {
+    \"chainId\": 42,
+    \"homesteadBlock\": 0,
+    \"eip150Block\": 0,
+    \"eip155Block\": 0,
+    \"eip158Block\": 0,
+    \"byzantiumBlock\": 0,
+    \"constantinopleBlock\": 0,
+    \"petersburgBlock\": 0,
+    \"istanbulBlock\": 0,
+    \"ethash\": {}
+  },
+  \"difficulty\": \"1\",
+  \"gasLimit\": \"0\",
+  \"baseFeePerGas\": null,
+  \"alloc\": {
+    \"$CLIENT\": { \"balance\": \"10000000000000000000\" }
+  }
+}" > $DIR/genesis.json
+    # Initialize blockchain
+    $ETH_CLI init $DIR/genesis.json &> log/eth.log
+    # Start node
+    $ETH_CLI --miner.gasprice 0 $* &> log/eth.log &
+    sleep 1
+    # Create wire address
+    WIRE=`eth-wire-cli initwallet $CONF | grep -oP '(?<=is ).*'`
+    echo -e "PAYTO = payto://ethereum/$WIRE" >> $CONF
+}
+
+# Check client and wire balance
+function check_balance_eth() {
+    local CLIENT_BALANCE=`eth-wire-utils -d $WIRE_DIR balance $CLIENT`
+    local WIRE_BALANCE=`eth-wire-utils -d $WIRE_DIR balance $WIRE`
+    local CLIENT="${1:-*}"
+    if [ "$1" == "*" ]; then
+        local CLIENT="$CLIENT_BALANCE"
+    fi
+    if [ "$CLIENT_BALANCE" != "$CLIENT" ] || [ "$WIRE_BALANCE" != 
"${2:-$WIRE_BALANCE}" ]; then
+        echo "expected: client $CLIENT wire ${2:-$WIRE_BALANCE}    got: client 
$CLIENT_BALANCE wire $WIRE_BALANCE"
+        exit 1
+    fi
+}
+
+
+# ----- eth-wire ----- #
+
+# Start eth-wire
+function eth_wire() {
+    cargo build --bin eth-wire --release &> log/cargo.log
+    target/release/eth-wire $CONF &> log/eth_wire.log &
+    WIRE_PID="$!"
+}
+
+# Mine ethereum blocks
+function mine_eth() {
+    eth-wire-utils -d $WIRE_DIR mine $RESERVE ${1:-}
+}
+
+# Mine previous transactions
+function next_eth() {
+    # Mine enough block to confirm previous transactions
+    mine_eth ${1:-$CONFIRMATION}
+    # Wait for eth-wire to catch up
+    sleep 0.2
+    # Mine one more block to trigger eth-wire
+    mine_eth
 }
 
 # ----- Gateway ------ #
@@ -234,12 +325,12 @@ function check_delta() {
     ALL=`curl -s ${BANK_ENDPOINT}history/$1`
     PRE=${3:-0.0000}
     for n in `$2`; do
-        if ! `echo $ALL | grep BTC:$PRE$n > /dev/null`; then
-            echo -n " missing tx with amount: BTC:$PRE$n"
+        if ! `echo $ALL | grep $CURRENCY:$PRE$n > /dev/null`; then
+            echo -n " missing tx with amount: $CURRENCY:$PRE$n"
             return 1
         fi
     done
-    NB=`echo $ALL | grep -o BTC:$PRE | wc -l`
+    NB=`echo $ALL | grep -o $CURRENCY:$PRE | wc -l`
     EXPECTED=`$2 | wc -w`
     if [ "$EXPECTED" != "$NB" ]; then
         echo -n " expected: $EXPECTED txs found $NB"
diff --git a/test/conf/taler_test.conf b/test/conf/taler_btc.conf
similarity index 88%
rename from test/conf/taler_test.conf
rename to test/conf/taler_btc.conf
index eb3904a..85ba7f2 100644
--- a/test/conf/taler_test.conf
+++ b/test/conf/taler_btc.conf
@@ -8,5 +8,4 @@ BASE_URL     = http://test.com
 DB_URL       = 
postgres://localhost:5454/postgres?user=postgres&password=password
 PORT         = 8060
 PAYTO        = payto://bitcoin/bcrt1qgkgxkjj27g3f7s87mcvjjsghay7gh34cx39prj
-CONFIRMATION = 3
-BOUNCE_FEE   = 1000
\ No newline at end of file
+CONFIRMATION = 3
\ No newline at end of file
diff --git a/test/conf/taler_bump.conf b/test/conf/taler_btc_bump.conf
similarity index 100%
rename from test/conf/taler_bump.conf
rename to test/conf/taler_btc_bump.conf
diff --git a/test/conf/taler_lifetime.conf b/test/conf/taler_btc_lifetime.conf
similarity index 100%
rename from test/conf/taler_lifetime.conf
rename to test/conf/taler_btc_lifetime.conf
diff --git a/test/conf/taler_eth.conf b/test/conf/taler_eth.conf
new file mode 100644
index 0000000..c292beb
--- /dev/null
+++ b/test/conf/taler_eth.conf
@@ -0,0 +1,10 @@
+[taler]
+CURRENCY     = ETH
+
+[exchange]
+BASE_URL     = http://test.com
+
+[depolymerizer-ethereum]
+DB_URL       = 
postgres://localhost:5454/postgres?user=postgres&password=password
+PORT         = 8060
+CONFIRMATION = 3
\ No newline at end of file
diff --git a/test/eth/test.sh b/test/eth/test.sh
new file mode 100644
index 0000000..2eac457
--- /dev/null
+++ b/test/eth/test.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+set -eu
+
+source "${BASH_SOURCE%/*}/../common.sh"
+SCHEMA=eth.sql
+CONFIG=taler_eth.conf
+
+echo  "----- Setup -----"
+echo "Load config file"
+load_config
+echo "Start database"
+setup_db
+echo "Start ethereum node"
+init_eth
+echo ""
+
+echo "----- Test -----"
+cargo run --bin eth-test --release -- $CONF
\ No newline at end of file
diff --git a/test/btc/wire.sh b/test/eth/wire.sh
similarity index 50%
copy from test/btc/wire.sh
copy to test/eth/wire.sh
index a4e3843..e277b59 100644
--- a/test/btc/wire.sh
+++ b/test/eth/wire.sh
@@ -1,36 +1,37 @@
 #!/bin/bash
 
-## Test btc_wire correctly receive and sens transactions on the blockchain
+## Test eth-wire correctly receive and send transactions on the blockchain
 
 set -eu
 
 source "${BASH_SOURCE%/*}/../common.sh"
-SCHEMA=btc.sql
+SCHEMA=eth.sql
+CONFIG=taler_eth.conf
 
 echo  "----- Setup -----"
 echo "Load config file"
 load_config
 echo "Start database"
 setup_db
-echo "Start bitcoin node"
-init_btc
-echo "Start btc-wire"
-btc_wire
+echo "Start ethereum node"
+init_eth
+echo "Start eth-wire"
+eth_wire
 echo "Start gateway"
 gateway
 echo ""
 
-SEQ="seq 10 99"
+SEQ="seq 10 29"
+RUST_BACKTRACE=1
 
 echo  "----- Receive -----"
 
 echo -n "Making wire transfer to exchange:"
 for n in `$SEQ`; do
-    btc-wire-utils -d $BTC_DIR transfer 0.000$n > /dev/null
-    mine_btc # Mine transactions
+    eth-wire-utils -d $WIRE_DIR deposit $CLIENT $WIRE 0.000$n
 done
-next_btc # Trigger btc_wire
-check_balance 9.95023810 0.04905000
+next_eth # Trigger eth-wire
+check_balance_eth 999610000 390000
 echo " OK"
 
 echo -n "Requesting exchange incoming transaction list:"
@@ -43,30 +44,30 @@ echo -n "Making wire transfer from exchange:"
 for n in `$SEQ`; do
     taler-exchange-wire-gateway-client \
         -b $BANK_ENDPOINT \
-        -C payto://bitcoin/$CLIENT \
-        -a BTC:0.0000$n > /dev/null
+        -C payto://ethereum/$CLIENT \
+        -a ETH:0.0000$n > /dev/null
 done
-sleep 6
-mine_btc # Mine transactions
-check_balance 9.95514310 ""
+sleep 1
+mine_eth # Mine transactions
+check_balance_eth 999649000 351000
 echo " OK"
 
 echo -n "Requesting exchange's outgoing transaction list:"
 check_delta "outgoing?delta=-100" "$SEQ"
 echo " OK"
 
-echo  "----- Bounce -----"
+#echo  "----- Bounce -----"
 
-clear_wallet
+#clear_wallet
 
-echo -n "Bounce:"
-for n in `seq 10 40`; do
-    $BTC_CLI -rpcwallet=client sendtoaddress $WIRE 0.000$n > /dev/null
-    mine_btc
-done
-next_btc
-sleep 3
-check_balance "*" 0.00031000
-echo " OK"
+#echo -n "Bounce:"
+#for n in `seq 10 40`; do
+#    eth-wire-utils -d $WIRE_DIR send $CLIENT $WIRE 0.000$n
+#    mine_btc
+#done
+#next_btc
+#sleep 3
+#check_balance_eth "*" 0.00031000
+#echo " OK"
 
 echo "All tests passed!"
\ No newline at end of file
diff --git a/test/gateway/api.sh b/test/gateway/api.sh
index 6e6a142..97ab224 100644
--- a/test/gateway/api.sh
+++ b/test/gateway/api.sh
@@ -19,6 +19,7 @@ trap cleanup EXIT
 source "${BASH_SOURCE%/*}/../common.sh"
 ADDRESS=mpTJZxWPerz1Gife6mQSdHT8mMuJK6FP85
 SCHEMA=btc.sq
+CONFIG=taler_btc.conf
 
 echo  "----- Setup -----"
 echo "Load config file"
diff --git a/uri-pack/Cargo.toml b/uri-pack/Cargo.toml
index b281c59..28b46db 100644
--- a/uri-pack/Cargo.toml
+++ b/uri-pack/Cargo.toml
@@ -19,7 +19,7 @@ url = "2.2.2"
 # statistics-driven micro-benchmarks
 criterion = "0.3.5"
 # Fast insecure random
-fastrand = "1.6.0"
+fastrand = "1.7.0"
 # Fuzzing test
 quickcheck = "1.0.3"
 quickcheck_macros = "1.0.0"
diff --git a/wire-gateway/Cargo.toml b/wire-gateway/Cargo.toml
index 44b2bda..ca4140a 100644
--- a/wire-gateway/Cargo.toml
+++ b/wire-gateway/Cargo.toml
@@ -14,13 +14,13 @@ hyper = { version = "0.14.16", features = ["http1", 
"server", "runtime"] }
 # Hyper compat lib for unix domain socket
 hyperlocal = "0.8.0"
 # Async runtime
-tokio = { version = "1.15.0", features = ["net", "macros", "rt-multi-thread"] }
+tokio = { version = "1.16.1", features = ["net", "macros", "rt-multi-thread"] }
 # Serialization framework
-serde = { version = "1.0.133", features = ["derive"] }
+serde = { version = "1.0.136", features = ["derive"] }
 # Serialization helper
 serde_with = "1.11.0"
 # JSON serialization
-serde_json = "1.0.75"
+serde_json = "1.0.78"
 # Url query serialization
 serde_urlencoded = "0.7.1"
 # Error macros
diff --git a/wire-gateway/src/main.rs b/wire-gateway/src/main.rs
index 505602b..db25fa5 100644
--- a/wire-gateway/src/main.rs
+++ b/wire-gateway/src/main.rs
@@ -34,10 +34,12 @@ use taler_common::{
         HistoryParams, IncomingBankTransaction, IncomingHistory, 
OutgoingBankTransaction,
         OutgoingHistory, TransferRequest, TransferResponse,
     },
+    config::{Config, GatewayConfig},
     error_codes::ErrorCode,
     log::log::{error, info, log, Level},
+    postgres::{fallible_iterator::FallibleIterator, Client},
     sql::{sql_amount, sql_array, sql_safe_u64, sql_url},
-    url::Url, postgres::{Client, fallible_iterator::FallibleIterator},
+    url::Url,
 };
 use tokio::sync::Notify;
 use tokio_postgres::{config::Host, NoTls};
@@ -47,7 +49,8 @@ mod json;
 
 struct ServerState {
     pool: Pool,
-    config: taler_common::config::Config,
+    config: GatewayConfig,
+    domain: &'static str,
     notify: Notify,
     lifetime: Option<AtomicU32>,
     status: AtomicBool,
@@ -85,15 +88,14 @@ impl ServerState {
 async fn main() {
     taler_common::log::init();
 
-    let conf = taler_common::config::Config::load_from_file(
-        std::env::args_os().nth(1).expect("Missing conf path arg"),
-    );
+    let conf =
+        
GatewayConfig::load_from_file(std::env::args_os().nth(1).expect("Missing conf 
path arg"));
 
     #[cfg(feature = "test")]
     taler_common::log::log::warn!("Running with test admin endpoint unsuitable 
for production");
 
     // Parse postgres url
-    let config = tokio_postgres::Config::from_str(&conf.db_url).unwrap();
+    let config = tokio_postgres::Config::from_str(&conf.core.db_url).unwrap();
     // TODO find a way to clean this ugly mess
     let mut cfg = deadpool_postgres::Config::new();
     cfg.user = config.get_user().map(|it| it.to_string());
@@ -120,6 +122,11 @@ async fn main() {
     let pool = cfg.create_pool(Some(Runtime::Tokio1), NoTls).unwrap();
     let state = ServerState {
         pool,
+        domain: match conf.core.currency.as_str() {
+            "BTC" => "bitcoin",
+            "ETH" => "ethereum",
+            currency => unimplemented!("Unsupported currency {}", currency),
+        },
         config: conf.clone(),
         notify: Notify::new(),
         lifetime: conf.http_lifetime.map(AtomicU32::new),
@@ -199,14 +206,15 @@ async fn main() {
 }
 
 /// Check if an url is a valid bitcoin payto url
-fn check_pay_to(url: &Url) -> bool {
-    return url.domain() == Some("bitcoin")
+fn check_pay_to(url: &Url, domain: &str) -> bool {
+    // TODO currency agnostic
+    return url.domain() == Some(domain)
         && url.scheme() == "payto"
         && url.username() == ""
         && url.password().is_none()
         && url.query().is_none()
-        && url.fragment().is_none()
-        && 
bitcoin::Address::from_str(url.path().trim_start_matches('/')).is_ok();
+        && url.fragment().is_none();
+    //&& 
bitcoin::Address::from_str(url.path().trim_start_matches('/')).is_ok();
 }
 
 /// Assert request method match expected
@@ -270,13 +278,13 @@ async fn router(
                 StatusCode::BAD_REQUEST,
                 ErrorCode::GENERIC_PARAMETER_MALFORMED,
             )?;
-            if !check_pay_to(&request.credit_account) {
+            if !check_pay_to(&request.credit_account, state.domain) {
                 return Err(ServerError::code(
                     StatusCode::BAD_REQUEST,
                     ErrorCode::GENERIC_PAYTO_URI_MALFORMED,
                 ));
             }
-            if request.amount.currency != "BTC" {
+            if request.amount.currency != state.config.core.currency {
                 return Err(ServerError::code(
                     StatusCode::BAD_REQUEST,
                     ErrorCode::GENERIC_PARAMETER_MALFORMED,
@@ -449,7 +457,7 @@ async fn router(
 /// Listen to backend status change
 fn status_watcher(state: &'static ServerState) {
     fn inner(state: &'static ServerState) -> Result<(), Box<dyn 
std::error::Error>> {
-        let mut db = Client::connect(&state.config.db_url, NoTls)?;
+        let mut db = Client::connect(&state.config.core.db_url, NoTls)?;
         // Register as listener
         db.batch_execute("LISTEN status")?;
         loop {

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]