diff --git a/.eslintignore b/.eslintignore
index 8604f057d756c38f85d0baf8995d7e350db6cbb2..a457fd77528e4296ad87ecc7e140a1d3d356f492 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -32,5 +32,9 @@ app/modules/prover/*.js
 app/modules/prover/lib/*.js
 app/modules/keypair/*.js
 app/modules/keypair/lib/*.js
+app/modules/bma/*.js
+app/modules/bma/lib/*.js
+app/modules/bma/lib/entity/*.js
+app/modules/bma/lib/controllers/*.js
 test/*.js
 test/**/*.js
\ No newline at end of file
diff --git a/app/lib/dal/fileDAL.ts b/app/lib/dal/fileDAL.ts
index b6d910be81a0c37911ae997725c3c66aceeef68b..ab9e576f5c07fd18f1aff0978b9995a3acf283dd 100644
--- a/app/lib/dal/fileDAL.ts
+++ b/app/lib/dal/fileDAL.ts
@@ -328,7 +328,7 @@ export class FileDAL {
     return this.txsDAL.removeTX(hash)
   }
 
-  getTransactionsPending(versionMin:number) {
+  getTransactionsPending(versionMin = 0) {
     return this.txsDAL.getAllPending(versionMin)
   }
 
@@ -457,7 +457,7 @@ export class FileDAL {
     return _(pending).sortBy((ms:any) => -ms.number)[0];
   }
 
-  async findNewcomers(blockMedianTime:number) {
+  async findNewcomers(blockMedianTime = 0) {
     const pending = await this.msDAL.getPendingIN()
     const mss = await Promise.all(pending.map(async (p:any) => {
       const reduced = await this.mindexDAL.getReducedMS(p.issuer)
diff --git a/app/lib/dto/BlockDTO.ts b/app/lib/dto/BlockDTO.ts
index aab7328c3c65fc687810b95d016698a296340972..b1e4b3ebdea804af938a6d95d513eacc74ebc045 100644
--- a/app/lib/dto/BlockDTO.ts
+++ b/app/lib/dto/BlockDTO.ts
@@ -107,8 +107,12 @@ export class BlockDTO {
     return found;
   }
 
+  getRawUnSigned() {
+    return this.getRawInnerPart() + this.getSignedPart()
+  }
+
   getRawSigned() {
-    return this.getRawInnerPart() + this.getSignedPart() + this.signature + "\n"
+    return this.getRawUnSigned() + this.signature + "\n"
   }
 
   getSignedPart() {
diff --git a/app/lib/dto/ConfDTO.ts b/app/lib/dto/ConfDTO.ts
index 0144f692bec403b9f8a98018af91815c18cb279e..f1049fdbf001b6c5464df2eda85ad0a5390c98e0 100644
--- a/app/lib/dto/ConfDTO.ts
+++ b/app/lib/dto/ConfDTO.ts
@@ -32,12 +32,25 @@ export interface CurrencyConfDTO {
 
 export interface KeypairConfDTO {
   pair: Keypair
-  oldPair: Keypair
+  oldPair: Keypair|null
   salt: string
   passwd: string
 }
 
-export class ConfDTO implements CurrencyConfDTO {
+export interface NetworkConfDTO {
+  remoteport: number
+  remotehost: string|null
+  remoteipv4: string|null
+  remoteipv6: string|null
+  port: number
+  ipv4: string
+  ipv6: string
+  dos:any
+  upnp:boolean
+  httplogs:boolean
+}
+
+export class ConfDTO implements CurrencyConfDTO, KeypairConfDTO, NetworkConfDTO {
 
   constructor(
     public loglevel: string,
@@ -79,19 +92,24 @@ export class ConfDTO implements CurrencyConfDTO {
     public sigWindow: number,
     public swichOnTimeAheadBy: number,
     public pair: Keypair,
+    public oldPair: Keypair|null,
+    public salt: string,
+    public passwd: string,
     public remoteport: number,
-    public remotehost: string,
-    public remoteipv4: string,
-    public remoteipv6: string,
+    public remotehost: string|null,
+    public remoteipv4: string|null,
+    public remoteipv6: string|null,
     public port: number,
     public ipv4: string,
     public ipv6: string,
+    public dos: any,
+    public upnp: boolean,
     public homename: string,
     public memory: boolean,
 ) {}
 
   static mock() {
-    return new ConfDTO("", "", [], [], 0, 0, 0.6, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false, 0, false, 0, 0, 0, 0, 0, { pub:'', sec:'' }, 0, "", "", "", 0, "", "", "", true)
+    return new ConfDTO("", "", [], [], 0, 0, 0.6, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false, 0, false, 0, 0, 0, 0, 0, { pub:'', sec:'' }, null, "", "", 0, "", "", "", 0, "", "", null, false, "", true)
   }
 
   static defaultConf() {
diff --git a/app/modules/bma/index.ts b/app/modules/bma/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ae281165ef48d4b4d4b6ab30350bd0f861c5c3c0
--- /dev/null
+++ b/app/modules/bma/index.ts
@@ -0,0 +1,568 @@
+"use strict";
+import {NetworkConfDTO} from "../../lib/dto/ConfDTO"
+import {Server} from "../../../server"
+import * as stream from "stream"
+import {BmaApi, Network} from "./lib/network"
+import {UpnpApi} from "./lib/upnp"
+import {BMAConstants} from "./lib/constants"
+import {BMALimitation} from "./lib/limiter"
+
+const Q = require('q');
+const os = require('os');
+const async = require('async');
+const _ = require('underscore');
+const upnp = require('./lib/upnp').Upnp
+const bma = require('./lib/bma').bma
+const dtos = require('./lib/dtos')
+const sanitize = require('./lib/sanitize');
+const http2raw = require('./lib/http2raw');
+const inquirer = require('inquirer');
+
+let networkWizardDone = false;
+
+export const BmaDependency = {
+  duniter: {
+
+    cliOptions: [
+      { value: '--upnp', desc: 'Use UPnP to open remote port.' },
+      { value: '--noupnp', desc: 'Do not use UPnP to open remote port.' },
+      { value: '-p, --port <port>', desc: 'Port to listen for requests', parser: (val:string) => parseInt(val) },
+      { value: '--ipv4 <address>', desc: 'IPv4 interface to listen for requests' },
+      { value: '--ipv6 <address>', desc: 'IPv6 interface to listen for requests' },
+      { value: '--remoteh <host>', desc: 'Remote interface others may use to contact this node' },
+      { value: '--remote4 <host>', desc: 'Remote interface for IPv4 access' },
+      { value: '--remote6 <host>', desc: 'Remote interface for IPv6 access' },
+      { value: '--remotep <port>', desc: 'Remote port others may use to contact this node' },
+    ],
+
+    wizard: {
+
+      'network': async (conf:NetworkConfDTO, program:any, logger:any) => {
+        await Q.nbind(networkConfiguration, null, conf, logger)()
+        networkWizardDone = true;
+      },
+
+      'network-reconfigure': async (conf:NetworkConfDTO, program:any, logger:any) => {
+        if (!networkWizardDone) {
+          // This step can only be launched lonely
+          await Q.nbind(networkReconfiguration, null)(conf, program.autoconf, logger, program.noupnp);
+        }
+      }
+    },
+
+    config: {
+
+      onLoading: async (conf:NetworkConfDTO, program:any, logger:any) => {
+
+        if (program.port !== undefined) conf.port = program.port;
+        if (program.ipv4 !== undefined) conf.ipv4 = program.ipv4;
+        if (program.ipv6 !== undefined) conf.ipv6 = program.ipv6;
+        if (program.remoteh !== undefined) conf.remotehost = program.remoteh;
+        if (program.remote4 !== undefined) conf.remoteipv4 = program.remote4;
+        if (program.remote6 !== undefined) conf.remoteipv6 = program.remote6;
+        if (program.remotep !== undefined) conf.remoteport = program.remotep;
+
+        if (!conf.ipv4) delete conf.ipv4;
+        if (!conf.ipv6) delete conf.ipv6;
+        if (!conf.remoteipv4) delete conf.remoteipv4;
+        if (!conf.remoteipv6) delete conf.remoteipv6;
+
+        // Default remoteipv6: same as local if defined
+        if (!conf.remoteipv6 && conf.ipv6) {
+          conf.remoteipv6 = conf.ipv6;
+        }
+        // Fix #807: default remoteipv4: same as local ipv4 if no removeipv4 is not defined AND no DNS nor IPv6
+        if (conf.ipv4 && !(conf.remoteipv4 || conf.remotehost || conf.remoteipv6)) {
+          conf.remoteipv4 = conf.ipv4;
+        }
+        if (!conf.remoteport && conf.port) {
+          conf.remoteport = conf.port;
+        }
+
+        // Network autoconf
+        const autoconfNet = program.autoconf
+          || !(conf.ipv4 || conf.ipv6)
+          || !(conf.remoteipv4 || conf.remoteipv6 || conf.remotehost)
+          || !(conf.port && conf.remoteport);
+        if (autoconfNet) {
+          await Q.nbind(networkReconfiguration, null)(conf, autoconfNet, logger, program.noupnp);
+        }
+
+        // Default value
+        if (conf.upnp === undefined || conf.upnp === null) {
+          conf.upnp = true; // Defaults to true
+        }
+        if (!conf.dos) {
+          conf.dos = { whitelist: ['127.0.0.1'] };
+          conf.dos.maxcount = 50;
+          conf.dos.burst = 20;
+          conf.dos.limit = conf.dos.burst * 2;
+          conf.dos.maxexpiry = 10;
+          conf.dos.checkinterval = 1;
+          conf.dos.trustProxy = true;
+          conf.dos.includeUserAgent = true;
+          conf.dos.errormessage = 'Error';
+          conf.dos.testmode = false;
+          conf.dos.silent = false;
+          conf.dos.silentStart = false;
+          conf.dos.responseStatus = 429;
+        }
+
+        // UPnP
+        if (program.noupnp === true) {
+          conf.upnp = false;
+        }
+        if (program.upnp === true) {
+          conf.upnp = true;
+        }
+
+        // Configuration errors
+        if(!conf.ipv4 && !conf.ipv6){
+          throw new Error("No interface to listen to.");
+        }
+        if(!conf.remoteipv4 && !conf.remoteipv6 && !conf.remotehost){
+          throw new Error('No interface for remote contact.');
+        }
+        if (!conf.remoteport) {
+          throw new Error('No port for remote contact.');
+        }
+      },
+
+      beforeSave: async (conf:NetworkConfDTO, program:any) => {
+        if (!conf.ipv4) delete conf.ipv4;
+        if (!conf.ipv6) delete conf.ipv6;
+        if (!conf.remoteipv4) delete conf.remoteipv4;
+        if (!conf.remoteipv6) delete conf.remoteipv6;
+        conf.dos.whitelist = _.uniq(conf.dos.whitelist);
+      }
+    },
+
+    service: {
+      input: (server:Server, conf:NetworkConfDTO, logger:any) => {
+        server.getMainEndpoint = () => Promise.resolve(getEndpoint(conf))
+        return new BMAPI(server, conf, logger)
+      }
+    },
+
+    methods: {
+      noLimit: () => BMALimitation.noLimit(),
+      bma, sanitize, dtos,
+      upnpConf: Network.upnpConf,
+      getRandomPort: Network.getRandomPort,
+      listInterfaces: Network.listInterfaces,
+      getEndpoint: getEndpoint,
+      getMainEndpoint: (conf:NetworkConfDTO) => Promise.resolve(getEndpoint(conf)),
+      getBestLocalIPv6: Network.getBestLocalIPv6,
+      getBestLocalIPv4: Network.getBestLocalIPv4,
+      createServersAndListen: Network.createServersAndListen,
+      http2raw
+    }
+  }
+}
+
+export class BMAPI extends stream.Transform {
+
+  // Public http interface
+  private bmapi:BmaApi
+  private upnpAPI:UpnpApi
+
+  constructor(
+    private server:Server,
+    private conf:NetworkConfDTO,
+    private logger:any) {
+    super({ objectMode: true })
+  }
+
+  startService = async () => {
+    this.bmapi = await bma(this.server, null, this.conf.httplogs, this.logger);
+    await this.bmapi.openConnections();
+
+    /***************
+     *    UPnP
+     **************/
+    if (this.upnpAPI) {
+      this.upnpAPI.stopRegular();
+    }
+    if (this.server.conf.upnp) {
+      try {
+        this.upnpAPI = await upnp(this.server.conf.port, this.server.conf.remoteport, this.logger);
+        this.upnpAPI.startRegular();
+        const gateway = await this.upnpAPI.findGateway();
+        if (gateway) {
+          if (this.bmapi.getDDOS().params.whitelist.indexOf(gateway) === -1) {
+            this.bmapi.getDDOS().params.whitelist.push(gateway);
+          }
+        }
+      } catch (e) {
+        this.logger.warn(e);
+      }
+    }
+  }
+
+  stopService = async () => {
+    if (this.bmapi) {
+      await this.bmapi.closeConnections();
+    }
+    if (this.upnpAPI) {
+      this.upnpAPI.stopRegular();
+    }
+  }
+}
+
+function getEndpoint(theConf:NetworkConfDTO) {
+  let endpoint = 'BASIC_MERKLED_API';
+  if (theConf.remotehost) {
+    endpoint += ' ' + theConf.remotehost;
+  }
+  if (theConf.remoteipv4) {
+    endpoint += ' ' + theConf.remoteipv4;
+  }
+  if (theConf.remoteipv6) {
+    endpoint += ' ' + theConf.remoteipv6;
+  }
+  if (theConf.remoteport) {
+    endpoint += ' ' + theConf.remoteport;
+  }
+  return endpoint;
+}
+
+function networkReconfiguration(conf:NetworkConfDTO, autoconf:boolean, logger:any, noupnp:boolean, done:any) {
+  async.waterfall([
+    upnpResolve.bind(null, noupnp, logger),
+    function(upnpSuccess:boolean, upnpConf:NetworkConfDTO, next:any) {
+
+      // Default values
+      conf.port = conf.port || BMAConstants.DEFAULT_PORT;
+      conf.remoteport = conf.remoteport || BMAConstants.DEFAULT_PORT;
+
+      const localOperations = getLocalNetworkOperations(conf, autoconf);
+      const remoteOpertions = getRemoteNetworkOperations(conf, upnpConf.remoteipv4);
+      const dnsOperations = getHostnameOperations(conf, logger, autoconf);
+      const useUPnPOperations = getUseUPnPOperations(conf, logger, autoconf);
+
+      if (upnpSuccess) {
+        _.extend(conf, upnpConf);
+        const local = [conf.ipv4, conf.port].join(':');
+        const remote = [conf.remoteipv4, conf.remoteport].join(':');
+        if (autoconf) {
+          conf.ipv6 = conf.remoteipv6 = Network.getBestLocalIPv6();
+          logger.info('IPv6: %s', conf.ipv6 || "");
+          logger.info('Local IPv4: %s', local);
+          logger.info('Remote IPv4: %s', remote);
+          // Use proposed local + remote with UPnP binding
+          return async.waterfall(useUPnPOperations
+            .concat(dnsOperations), next);
+        }
+        choose("UPnP is available: duniter will be bound: \n  from " + local + "\n  to " + remote + "\nKeep this configuration?", true,
+          function () {
+            // Yes: not network changes
+            conf.ipv6 = conf.remoteipv6 = Network.getBestLocalIPv6();
+            async.waterfall(useUPnPOperations
+              .concat(dnsOperations), next);
+          },
+          function () {
+            // No: want to change
+            async.waterfall(
+              localOperations
+                .concat(remoteOpertions)
+                .concat(useUPnPOperations)
+                .concat(dnsOperations), next);
+          });
+      } else {
+        conf.upnp = false;
+        if (autoconf) {
+          // Yes: local configuration = remote configuration
+          return async.waterfall(
+            localOperations
+              .concat(getHostnameOperations(conf, logger, autoconf))
+              .concat([function (confDone:any) {
+                conf.remoteipv4 = conf.ipv4;
+                conf.remoteipv6 = conf.ipv6;
+                conf.remoteport = conf.port;
+                logger.info('Local & Remote IPv4: %s', [conf.ipv4, conf.port].join(':'));
+                logger.info('Local & Remote IPv6: %s', [conf.ipv6, conf.port].join(':'));
+                confDone();
+              }]), next);
+        }
+        choose("UPnP is *not* available: is this a public server (like a VPS)?", true,
+          function () {
+            // Yes: local configuration = remote configuration
+            async.waterfall(
+              localOperations
+                .concat(getHostnameOperations(conf, logger))
+                .concat([function(confDone:any) {
+                  conf.remoteipv4 = conf.ipv4;
+                  conf.remoteipv6 = conf.ipv6;
+                  conf.remoteport = conf.port;
+                  confDone();
+                }]), next);
+          },
+          function () {
+            // No: must give all details
+            async.waterfall(
+              localOperations
+                .concat(remoteOpertions)
+                .concat(dnsOperations), next);
+          });
+      }
+    }
+  ], done);
+}
+
+
+async function upnpResolve(noupnp:boolean, logger:any, done:any) {
+  try {
+    let conf = await Network.upnpConf(noupnp, logger);
+    done(null, true, conf);
+  } catch (err) {
+    done(null, false, {});
+  }
+}
+
+function networkConfiguration(conf:NetworkConfDTO, logger:any, done:any) {
+  async.waterfall([
+    upnpResolve.bind(null, !conf.upnp, logger),
+    function(upnpSuccess:boolean, upnpConf:NetworkConfDTO, next:any) {
+
+      let operations = getLocalNetworkOperations(conf)
+        .concat(getRemoteNetworkOperations(conf, upnpConf.remoteipv4));
+
+      if (upnpSuccess) {
+        operations = operations.concat(getUseUPnPOperations(conf, logger));
+      }
+
+      async.waterfall(operations.concat(getHostnameOperations(conf, logger, false)), next);
+    }
+  ], done);
+}
+
+function getLocalNetworkOperations(conf:NetworkConfDTO, autoconf:boolean = false) {
+  return [
+    function (next:any){
+      const osInterfaces = Network.listInterfaces();
+      const interfaces = [{ name: "None", value: null }];
+      osInterfaces.forEach(function(netInterface:any){
+        const addresses = netInterface.addresses;
+        const filtered = _(addresses).where({family: 'IPv4'});
+        filtered.forEach(function(addr:any){
+          interfaces.push({
+            name: [netInterface.name, addr.address].join(' '),
+            value: addr.address
+          });
+        });
+      });
+      if (autoconf) {
+        conf.ipv4 = Network.getBestLocalIPv4();
+        return next();
+      }
+      inquirer.prompt([{
+        type: "list",
+        name: "ipv4",
+        message: "IPv4 interface",
+        default: conf.ipv4,
+        choices: interfaces
+      }]).then((answers:any) => {
+        conf.ipv4 = answers.ipv4;
+        next();
+      });
+    },
+    function (next:any){
+      const osInterfaces = Network.listInterfaces();
+      const interfaces:any = [{ name: "None", value: null }];
+      osInterfaces.forEach(function(netInterface:any){
+        const addresses = netInterface.addresses;
+        const filtered = _(addresses).where({ family: 'IPv6' });
+        filtered.forEach(function(addr:any){
+          let address = addr.address
+          if (addr.scopeid)
+            address += "%" + netInterface.name
+          let nameSuffix = "";
+          if (addr.scopeid == 0 && !addr.internal) {
+            nameSuffix = " (Global)";
+          }
+          interfaces.push({
+            name: [netInterface.name, address, nameSuffix].join(' '),
+            internal: addr.internal,
+            scopeid: addr.scopeid,
+            value: address
+          });
+        });
+      });
+      interfaces.sort((addr1:any, addr2:any) => {
+        if (addr1.value === null) return -1;
+        if (addr1.internal && !addr2.internal) return 1;
+        if (addr1.scopeid && !addr2.scopeid) return 1;
+        return 0;
+      });
+      if (autoconf || !conf.ipv6) {
+        conf.ipv6 = conf.remoteipv6 = Network.getBestLocalIPv6();
+      }
+      if (autoconf) {
+        return next();
+      }
+      inquirer.prompt([{
+        type: "list",
+        name: "ipv6",
+        message: "IPv6 interface",
+        default: conf.ipv6,
+        choices: interfaces
+      }]).then((answers:any) => {
+        conf.ipv6 = conf.remoteipv6 = answers.ipv6;
+        next();
+      });
+    },
+    autoconf ? (done:any) => {
+        conf.port = Network.getRandomPort(conf);
+        done();
+      } : async.apply(simpleInteger, "Port", "port", conf)
+  ];
+}
+
+function getRemoteNetworkOperations(conf:NetworkConfDTO, remoteipv4:string|null) {
+  return [
+    function (next:any){
+      if (!conf.ipv4) {
+        conf.remoteipv4 = null;
+        return next(null, {});
+      }
+      const choices:any = [{ name: "None", value: null }];
+      // Local interfaces
+      const osInterfaces = Network.listInterfaces();
+      osInterfaces.forEach(function(netInterface:any){
+        const addresses = netInterface.addresses;
+        const filtered = _(addresses).where({family: 'IPv4'});
+        filtered.forEach(function(addr:any){
+          choices.push({
+            name: [netInterface.name, addr.address].join(' '),
+            value: addr.address
+          });
+        });
+      });
+      if (conf.remoteipv4) {
+        choices.push({ name: conf.remoteipv4, value: conf.remoteipv4 });
+      }
+      if (remoteipv4 && remoteipv4 != conf.remoteipv4) {
+        choices.push({ name: remoteipv4, value: remoteipv4 });
+      }
+      choices.push({ name: "Enter new one", value: "new" });
+      inquirer.prompt([{
+        type: "list",
+        name: "remoteipv4",
+        message: "Remote IPv4",
+        default: conf.remoteipv4 || conf.ipv4 || null,
+        choices: choices,
+        validate: function (input:any) {
+          return !!(input && input.toString().match(BMAConstants.IPV4_REGEXP));
+        }
+      }]).then((answers:any) => {
+        if (answers.remoteipv4 == "new") {
+          inquirer.prompt([{
+            type: "input",
+            name: "remoteipv4",
+            message: "Remote IPv4",
+            default: conf.remoteipv4 || conf.ipv4,
+            validate: function (input:any) {
+              return !!(input && input.toString().match(BMAConstants.IPV4_REGEXP));
+            }
+          }]).then((answers:any) => next(null, answers));
+        } else {
+          next(null, answers);
+        }
+      });
+    },
+    async function (answers:any, next:any){
+      conf.remoteipv4 = answers.remoteipv4;
+      try {
+        if (conf.remoteipv4 || conf.remotehost) {
+          await new Promise((resolve, reject) => {
+            const getPort = async.apply(simpleInteger, "Remote port", "remoteport", conf);
+            getPort((err:any) => {
+              if (err) return reject(err);
+              resolve();
+            });
+          });
+        } else if (conf.remoteipv6) {
+          conf.remoteport = conf.port;
+        }
+        next();
+      } catch (e) {
+        next(e);
+      }
+    }
+  ];
+}
+
+function getHostnameOperations(conf:NetworkConfDTO, logger:any, autoconf = false) {
+  return [function(next:any) {
+    if (!conf.ipv4) {
+      conf.remotehost = null;
+      return next();
+    }
+    if (autoconf) {
+      logger.info('DNS: %s', conf.remotehost || 'No');
+      return next();
+    }
+    choose("Does this server has a DNS name?", !!conf.remotehost,
+      function() {
+        // Yes
+        simpleValue("DNS name:", "remotehost", "", conf, function(){ return true; }, next);
+      },
+      function() {
+        conf.remotehost = null;
+        next();
+      });
+  }];
+}
+
+function getUseUPnPOperations(conf:NetworkConfDTO, logger:any, autoconf:boolean = false) {
+  return [function(next:any) {
+    if (!conf.ipv4) {
+      conf.upnp = false;
+      return next();
+    }
+    if (autoconf) {
+      logger.info('UPnP: %s', 'Yes');
+      conf.upnp = true;
+      return next();
+    }
+    choose("UPnP is available: use automatic port mapping? (easier)", conf.upnp,
+      function() {
+        conf.upnp = true;
+        next();
+      },
+      function() {
+        conf.upnp = false;
+        next();
+      });
+  }];
+}
+
+function choose (question:string, defaultValue:any, ifOK:any, ifNotOK:any) {
+  inquirer.prompt([{
+    type: "confirm",
+    name: "q",
+    message: question,
+    default: defaultValue
+  }]).then((answer:any) => {
+    answer.q ? ifOK() : ifNotOK();
+  });
+}
+
+function simpleValue (question:string, property:any, defaultValue:any, conf:any, validation:any, done:any) {
+  inquirer.prompt([{
+    type: "input",
+    name: property,
+    message: question,
+    default: conf[property],
+    validate: validation
+  }]).then((answers:any) => {
+    conf[property] = answers[property];
+    done();
+  });
+}
+
+function simpleInteger (question:string, property:any, conf:any, done:any) {
+  simpleValue(question, property, conf[property], conf, function (input:any) {
+    return input && input.toString().match(/^[0-9]+$/) ? true : false;
+  }, done);
+}
diff --git a/app/modules/bma/lib/bma.ts b/app/modules/bma/lib/bma.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2fcbe2cb1fe3348892228577e557142325df1775
--- /dev/null
+++ b/app/modules/bma/lib/bma.ts
@@ -0,0 +1,152 @@
+"use strict";
+import {Server} from "../../../../server"
+import {Network, NetworkInterface} from "./network"
+import * as dtos from "./dtos"
+import {BMALimitation} from "./limiter"
+import {BlockchainBinding} from "./controllers/blockchain"
+import {NodeBinding} from "./controllers/node"
+import {NetworkBinding} from "./controllers/network"
+import {WOTBinding} from "./controllers/wot"
+import {TransactionBinding} from "./controllers/transactions"
+import {UDBinding} from "./controllers/uds"
+
+const co = require('co');
+const es = require('event-stream');
+const sanitize = require('./sanitize');
+const WebSocketServer = require('ws').Server;
+
+export const bma = function(server:Server, interfaces:NetworkInterface[], httpLogs:boolean, logger:any) {
+
+  if (!interfaces) {
+    interfaces = [];
+    if (server.conf) {
+      if (server.conf.ipv4) {
+        interfaces = [{
+          ip: server.conf.ipv4,
+          port: server.conf.port
+        }];
+      }
+      if (server.conf.ipv6) {
+        interfaces.push({
+          ip: server.conf.ipv6,
+          port: (server.conf.remoteport || server.conf.port) // We try to get the best one
+        });
+      }
+    }
+  }
+
+  return Network.createServersAndListen('Duniter server', server, interfaces, httpLogs, logger, null, (app:any, httpMethods:any) => {
+
+    const node         = new NodeBinding(server);
+    const blockchain   = new BlockchainBinding(server)
+    const net          = new NetworkBinding(server)
+    const wot          = new WOTBinding(server)
+    const transactions = new TransactionBinding(server)
+    const dividend     = new UDBinding(server)
+    httpMethods.httpGET(  '/',                                      node.summary,                         dtos.Summary,        BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/node/summary',                          node.summary,                         dtos.Summary,        BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/node/sandboxes',                        node.sandboxes,                       dtos.Sandboxes,      BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/blockchain/parameters',                 blockchain.parameters,                dtos.Parameters,     BMALimitation.limitAsHighUsage());
+    httpMethods.httpPOST( '/blockchain/membership',                 blockchain.parseMembership,           dtos.Membership,     BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/blockchain/memberships/:search',        blockchain.memberships,               dtos.Memberships,    BMALimitation.limitAsHighUsage());
+    httpMethods.httpPOST( '/blockchain/block',                      blockchain.parseBlock,                dtos.Block,          BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/blockchain/block/:number',              blockchain.promoted,                  dtos.Block,          BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/blockchain/blocks/:count/:from',        blockchain.blocks,                    dtos.Blocks,         BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/blockchain/current',                    blockchain.current,                   dtos.Block,          BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/blockchain/hardship/:search',           blockchain.hardship,                  dtos.Hardship,       BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/blockchain/difficulties',               blockchain.difficulties,              dtos.Difficulties,   BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/blockchain/with/newcomers',             blockchain.with.newcomers,            dtos.Stat,           BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/blockchain/with/certs',                 blockchain.with.certs,                dtos.Stat,           BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/blockchain/with/joiners',               blockchain.with.joiners,              dtos.Stat,           BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/blockchain/with/actives',               blockchain.with.actives,              dtos.Stat,           BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/blockchain/with/leavers',               blockchain.with.leavers,              dtos.Stat,           BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/blockchain/with/excluded',              blockchain.with.excluded,             dtos.Stat,           BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/blockchain/with/revoked',               blockchain.with.revoked,              dtos.Stat,           BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/blockchain/with/ud',                    blockchain.with.ud,                   dtos.Stat,           BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/blockchain/with/tx',                    blockchain.with.tx,                   dtos.Stat,           BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/blockchain/branches',                   blockchain.branches,                  dtos.Branches,       BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/network/peering',                       net.peer,                             dtos.Peer,           BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/network/peering/peers',                 net.peersGet,                         dtos.MerkleOfPeers,  BMALimitation.limitAsVeryHighUsage());
+    httpMethods.httpPOST( '/network/peering/peers',                 net.peersPost,                        dtos.Peer,           BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/network/peers',                         net.peers,                            dtos.Peers,          BMALimitation.limitAsHighUsage());
+    httpMethods.httpPOST( '/wot/add',                               wot.add,                              dtos.Identity,       BMALimitation.limitAsHighUsage());
+    httpMethods.httpPOST( '/wot/certify',                           wot.certify,                          dtos.Cert,           BMALimitation.limitAsHighUsage());
+    httpMethods.httpPOST( '/wot/revoke',                            wot.revoke,                           dtos.Result,         BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/wot/lookup/:search',                    wot.lookup,                           dtos.Lookup,         BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/wot/members',                           wot.members,                          dtos.Members,        BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/wot/pending',                           wot.pendingMemberships,               dtos.MembershipList, BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/wot/requirements/:search',              wot.requirements,                     dtos.Requirements,   BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/wot/requirements-of-pending/:minsig',   wot.requirementsOfPending,            dtos.Requirements,   BMALimitation.limitAsLowUsage());
+    httpMethods.httpGET(  '/wot/certifiers-of/:search',             wot.certifiersOf,                     dtos.Certifications, BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/wot/certified-by/:search',              wot.certifiedBy,                      dtos.Certifications, BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/wot/identity-of/:search',               wot.identityOf,                       dtos.SimpleIdentity, BMALimitation.limitAsHighUsage());
+    httpMethods.httpPOST( '/tx/process',                            transactions.parseTransaction,        dtos.Transaction,    BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/tx/hash/:hash',                         transactions.getByHash,               dtos.Transaction,    BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/tx/sources/:pubkey',                    transactions.getSources,              dtos.Sources,        BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/tx/history/:pubkey',                    transactions.getHistory,              dtos.TxHistory,      BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/tx/history/:pubkey/blocks/:from/:to',   transactions.getHistoryBetweenBlocks, dtos.TxHistory,      BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/tx/history/:pubkey/times/:from/:to',    transactions.getHistoryBetweenTimes,  dtos.TxHistory,      BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/tx/history/:pubkey/pending',            transactions.getPendingForPubkey,     dtos.TxHistory,      BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/tx/pending',                            transactions.getPending,              dtos.TxPending,      BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/ud/history/:pubkey',                    dividend.getHistory,                  dtos.UDHistory,      BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/ud/history/:pubkey/blocks/:from/:to',   dividend.getHistoryBetweenBlocks,     dtos.UDHistory,      BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/ud/history/:pubkey/times/:from/:to',    dividend.getHistoryBetweenTimes,      dtos.UDHistory,      BMALimitation.limitAsHighUsage());
+
+  }, (httpServer:any) => {
+
+    let currentBlock = {};
+    let wssBlock = new WebSocketServer({
+      server: httpServer,
+      path: '/ws/block'
+    });
+    let wssPeer = new WebSocketServer({
+      server: httpServer,
+      path: '/ws/peer'
+    });
+
+    wssBlock.on('error', function (error:any) {
+      logger && logger.error('Error on WS Server');
+      logger && logger.error(error);
+    });
+
+    wssBlock.on('connection', function connection(ws:any) {
+      co(function *() {
+        try {
+          currentBlock = yield server.dal.getCurrentBlockOrNull();
+          if (currentBlock) {
+            ws.send(JSON.stringify(sanitize(currentBlock, dtos.Block)));
+          }
+        } catch (e) {
+          logger.error(e);
+        }
+      });
+    });
+
+    wssBlock.broadcast = (data:any) => wssBlock.clients.forEach((client:any) => {
+      try {
+        client.send(data);
+      } catch (e) {
+        logger && logger.error('error on ws: %s', e);
+      }
+    });
+    wssPeer.broadcast = (data:any) => wssPeer.clients.forEach((client:any) => client.send(data));
+
+    // Forward blocks & peers
+    server
+      .pipe(es.mapSync(function(data:any) {
+        try {
+          // Broadcast block
+          if (data.joiners) {
+            currentBlock = data;
+            wssBlock.broadcast(JSON.stringify(sanitize(currentBlock, dtos.Block)));
+          }
+          // Broadcast peer
+          if (data.endpoints) {
+            wssPeer.broadcast(JSON.stringify(sanitize(data, dtos.Peer)));
+          }
+        } catch (e) {
+          logger && logger.error('error on ws mapSync:', e);
+        }
+      }));
+  });
+};
diff --git a/app/modules/bma/lib/constants.ts b/app/modules/bma/lib/constants.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5dd915869980a220e933533e3317ca14eb71d889
--- /dev/null
+++ b/app/modules/bma/lib/constants.ts
@@ -0,0 +1,47 @@
+export const BMAConstants = {
+  ENTITY_BLOCK: 'block',
+  ENTITY_IDENTITY: 'identity',
+  ENTITY_CERTIFICATION: 'certification',
+  ENTITY_MEMBERSHIP: 'membership',
+  ENTITY_REVOCATION: 'revocation',
+  ENTITY_TRANSACTION: 'transaction',
+  ENTITY_PEER: 'peer',
+  DEFAULT_PORT: 10901,
+  IPV4_REGEXP: /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/,
+  IPV6_REGEXP: /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b).){3}(b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b))|(([0-9A-Fa-f]{1,4}:){0,5}:((b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b).){3}(b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b))|(::([0-9A-Fa-f]{1,4}:){0,5}((b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b).){3}(b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/,
+  PORT_START: 15000,
+  UPNP_INTERVAL: 300,
+  UPNP_TTL: 600,
+  PUBLIC_KEY: /^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{43,44}$/,
+  SHA256_HASH: /^[A-F0-9]{64}$/,
+
+  ERRORS: {
+
+    // Technical errors
+    UNKNOWN:                              { httpCode: 500, uerr: { ucode: 1001, message: "An unknown error occured" }},
+    UNHANDLED:                            { httpCode: 500, uerr: { ucode: 1002, message: "An unhandled error occured" }},
+    HTTP_LIMITATION:                      { httpCode: 503, uerr: { ucode: 1006, message: "This URI has reached its maximum usage quota. Please retry later." }},
+    HTTP_PARAM_PUBKEY_REQUIRED:           { httpCode: 400, uerr: { ucode: 1101, message: "Parameter `pubkey` is required" }},
+    HTTP_PARAM_IDENTITY_REQUIRED:         { httpCode: 400, uerr: { ucode: 1102, message: "Parameter `identity` is required" }},
+    HTTP_PARAM_PEER_REQUIRED:             { httpCode: 400, uerr: { ucode: 1103, message: "Requires a peer" }},
+    HTTP_PARAM_BLOCK_REQUIRED:            { httpCode: 400, uerr: { ucode: 1104, message: "Requires a block" }},
+    HTTP_PARAM_MEMBERSHIP_REQUIRED:       { httpCode: 400, uerr: { ucode: 1105, message: "Requires a membership" }},
+    HTTP_PARAM_TX_REQUIRED:               { httpCode: 400, uerr: { ucode: 1106, message: "Requires a transaction" }},
+    HTTP_PARAM_SIG_REQUIRED:              { httpCode: 400, uerr: { ucode: 1107, message: "Parameter `sig` is required" }},
+    HTTP_PARAM_CERT_REQUIRED:             { httpCode: 400, uerr: { ucode: 1108, message: "Parameter `cert` is required" }},
+    HTTP_PARAM_REVOCATION_REQUIRED:       { httpCode: 400, uerr: { ucode: 1109, message: "Parameter `revocation` is required" }},
+    HTTP_PARAM_CONF_REQUIRED:             { httpCode: 400, uerr: { ucode: 1110, message: "Parameter `conf` is required" }},
+    HTTP_PARAM_CPU_REQUIRED:              { httpCode: 400, uerr: { ucode: 1111, message: "Parameter `cpu` is required" }},
+
+    // Business errors
+    NO_MATCHING_IDENTITY:                 { httpCode: 404, uerr: { ucode: 2001, message: "No matching identity" }},
+    SELF_PEER_NOT_FOUND:                  { httpCode: 404, uerr: { ucode: 2005, message: "Self peering was not found" }},
+    NOT_A_MEMBER:                         { httpCode: 400, uerr: { ucode: 2009, message: "Not a member" }},
+    NO_CURRENT_BLOCK:                     { httpCode: 404, uerr: { ucode: 2010, message: "No current block" }},
+    PEER_NOT_FOUND:                       { httpCode: 404, uerr: { ucode: 2012, message: "Peer not found" }},
+    NO_IDTY_MATCHING_PUB_OR_UID:          { httpCode: 404, uerr: { ucode: 2021, message: "No identity matching this pubkey or uid" }},
+    TX_NOT_FOUND:                         { httpCode: 400, uerr: { ucode: 2034, message: 'Transaction not found' }}
+
+    // New errors: range 3000-4000
+  }
+}
\ No newline at end of file
diff --git a/app/modules/bma/lib/controllers/AbstractController.ts b/app/modules/bma/lib/controllers/AbstractController.ts
new file mode 100644
index 0000000000000000000000000000000000000000..baf52825aa4075f5131288379c717da0d22ff3cf
--- /dev/null
+++ b/app/modules/bma/lib/controllers/AbstractController.ts
@@ -0,0 +1,46 @@
+import {Server} from "../../../../../server"
+
+const dos2unix = require('../dos2unix')
+
+export abstract class AbstractController {
+
+  constructor(protected server:Server) {
+  }
+
+  get conf() {
+    return this.server.conf
+  }
+
+  get logger() {
+    return this.server.logger
+  }
+
+  get BlockchainService() {
+    return this.server.BlockchainService
+  }
+
+  get IdentityService() {
+    return this.server.IdentityService
+  }
+
+  get PeeringService() {
+    return this.server.PeeringService
+  }
+
+  get MerkleService() {
+    return this.server.MerkleService
+  }
+
+  async pushEntity(req:any, rawer:(req:any)=>string, type:any) {
+    let rawDocument = rawer(req);
+    rawDocument = dos2unix(rawDocument);
+    const written = await this.server.writeRaw(rawDocument, type);
+    try {
+      return written.json();
+    } catch (e) {
+      this.logger.error('Written:', written);
+      this.logger.error(e);
+      throw e;
+    }
+  }
+}
\ No newline at end of file
diff --git a/app/modules/bma/lib/controllers/blockchain.ts b/app/modules/bma/lib/controllers/blockchain.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4195fabb454b4a98325e8e1ddcc4a9d20c4c4a5d
--- /dev/null
+++ b/app/modules/bma/lib/controllers/blockchain.ts
@@ -0,0 +1,141 @@
+"use strict";
+import {Server} from "../../../../../server"
+import {AbstractController} from "./AbstractController"
+import {ParametersService} from "../parameters"
+import {BMAConstants} from "../constants"
+
+const co               = require('co');
+const _                = require('underscore');
+const common           = require('duniter-common');
+const http2raw         = require('../http2raw');
+const toJson = require('../tojson');
+
+const Membership = common.document.Membership
+
+export class BlockchainBinding extends AbstractController {
+
+  with:any
+
+  constructor(server:Server) {
+    super(server)
+    this.with = {
+
+      newcomers: this.getStat('newcomers'),
+      certs:     this.getStat('certs'),
+      joiners:   this.getStat('joiners'),
+      actives:   this.getStat('actives'),
+      leavers:   this.getStat('leavers'),
+      revoked:   this.getStat('revoked'),
+      excluded:  this.getStat('excluded'),
+      ud:        this.getStat('ud'),
+      tx:        this.getStat('tx')
+    }
+  }
+
+  parseMembership = (req:any) => this.pushEntity(req, http2raw.membership, BMAConstants.ENTITY_MEMBERSHIP);
+
+  parseBlock = (req:any) => this.pushEntity(req, http2raw.block, BMAConstants.ENTITY_BLOCK);
+
+  parameters = () => this.server.dal.getParameters();
+
+  private getStat(statName:string) {
+    return async () => {
+      let stat = await this.server.dal.getStat(statName);
+      return { result: toJson.stat(stat) };
+    }
+  }
+
+  async promoted(req:any) {
+    const number = await ParametersService.getNumberP(req);
+    const promoted = await this.BlockchainService.promoted(number);
+    return toJson.block(promoted);
+  }
+
+  async blocks(req:any) {
+    const params = ParametersService.getCountAndFrom(req);
+    const count = parseInt(params.count);
+    const from = parseInt(params.from);
+    let blocks = await this.BlockchainService.blocksBetween(from, count);
+    blocks = blocks.map((b:any) => toJson.block(b));
+    return blocks;
+  }
+
+  async current() {
+    const current = await this.server.dal.getCurrentBlockOrNull();
+    if (!current) throw BMAConstants.ERRORS.NO_CURRENT_BLOCK;
+    return toJson.block(current);
+  }
+
+  async hardship(req:any) {
+    let nextBlockNumber = 0;
+    const search = await ParametersService.getSearchP(req);
+    const idty = await this.IdentityService.findMemberWithoutMemberships(search);
+    if (!idty) {
+      throw BMAConstants.ERRORS.NO_MATCHING_IDENTITY;
+    }
+    if (!idty.member) {
+      throw BMAConstants.ERRORS.NOT_A_MEMBER;
+    }
+    const current = await this.BlockchainService.current();
+    if (current) {
+      nextBlockNumber = current ? current.number + 1 : 0;
+    }
+    const difficulty = await this.server.getBcContext().getIssuerPersonalizedDifficulty(idty.pubkey);
+    return {
+      "block": nextBlockNumber,
+      "level": difficulty
+    };
+  }
+
+  async difficulties() {
+    const current = await this.server.dal.getCurrentBlockOrNull();
+    const number = (current && current.number) || 0;
+    const issuers = await this.server.dal.getUniqueIssuersBetween(number - 1 - current.issuersFrame, number - 1);
+    const difficulties = [];
+    for (const issuer of issuers) {
+      const member = await this.server.dal.getWrittenIdtyByPubkey(issuer);
+      const difficulty = await this.server.getBcContext().getIssuerPersonalizedDifficulty(member.pubkey);
+      difficulties.push({
+        uid: member.uid,
+        level: difficulty
+      });
+    }
+    return {
+      "block": number + 1,
+      "levels": _.sortBy(difficulties, (diff:any) => diff.level)
+    };
+  }
+
+  async memberships(req:any) {
+    const search = await ParametersService.getSearchP(req);
+    const idty:any = await this.IdentityService.findMember(search);
+    const json = {
+      pubkey: idty.pubkey,
+      uid: idty.uid,
+      sigDate: idty.buid,
+      memberships: []
+    };
+    json.memberships = idty.memberships.map((msObj:any) => {
+      const ms = Membership.fromJSON(msObj);
+      return {
+        version: ms.version,
+        currency: this.conf.currency,
+        membership: ms.membership,
+        blockNumber: parseInt(ms.blockNumber),
+        blockHash: ms.blockHash,
+        written: (!msObj.written_number && msObj.written_number !== 0) ? null : msObj.written_number
+      };
+    });
+    json.memberships = _.sortBy(json.memberships, 'blockNumber');
+    json.memberships.reverse();
+    return json;
+  }
+
+  async branches() {
+    const branches = await this.BlockchainService.branches();
+    const blocks = branches.map((b) => toJson.block(b));
+    return {
+      blocks: blocks
+    };
+  }
+}
diff --git a/app/modules/bma/lib/controllers/network.ts b/app/modules/bma/lib/controllers/network.ts
new file mode 100644
index 0000000000000000000000000000000000000000..604846d006ee943b17893d4002f0c1fef29c043c
--- /dev/null
+++ b/app/modules/bma/lib/controllers/network.ts
@@ -0,0 +1,57 @@
+import {AbstractController} from "./AbstractController"
+import {BMAConstants} from "../constants"
+
+const _                = require('underscore');
+const http2raw         = require('../http2raw');
+
+export class NetworkBinding extends AbstractController {
+
+  async peer() {
+    const p = await this.PeeringService.peer();
+    if (!p) {
+      throw BMAConstants.ERRORS.SELF_PEER_NOT_FOUND;
+    }
+    return p.json();
+  }
+
+  async peersGet(req:any) {
+    let merkle = await this.server.dal.merkleForPeers();
+    return await this.MerkleService(req, merkle, async (hashes:string[]) => {
+      try {
+        let peers = await this.server.dal.findPeersWhoseHashIsIn(hashes);
+        const map:any = {};
+        peers.forEach((peer:any) => {
+          map[peer.hash] = peer;
+        });
+        if (peers.length == 0) {
+          throw BMAConstants.ERRORS.PEER_NOT_FOUND;
+        }
+        return map;
+      } catch (e) {
+        throw e;
+      }
+    })
+  }
+
+  peersPost(req:any) {
+    return this.pushEntity(req, http2raw.peer, BMAConstants.ENTITY_PEER)
+  }
+
+  async peers() {
+    let peers = await this.server.dal.listAllPeers();
+    return {
+      peers: peers.map((p:any) => {
+        return _.pick(p,
+          'version',
+          'currency',
+          'status',
+          'first_down',
+          'last_try',
+          'pubkey',
+          'block',
+          'signature',
+          'endpoints');
+      })
+    };
+  }
+}
diff --git a/app/modules/bma/lib/controllers/node.ts b/app/modules/bma/lib/controllers/node.ts
new file mode 100644
index 0000000000000000000000000000000000000000..420008dfb0707116190da84f763183e138e960b1
--- /dev/null
+++ b/app/modules/bma/lib/controllers/node.ts
@@ -0,0 +1,30 @@
+"use strict";
+import {AbstractController} from "./AbstractController"
+
+export class NodeBinding extends AbstractController {
+
+  summary = () => {
+    return {
+      "duniter": {
+        "software": "duniter",
+        "version": this.server.version,
+        "forkWindowSize": this.server.conf.forksize
+      }
+    }
+  }
+
+  async sandboxes() {
+    return {
+      identities: await sandboxIt(this.server.dal.idtyDAL.sandbox),
+      memberships: await sandboxIt(this.server.dal.msDAL.sandbox),
+      transactions: await sandboxIt(this.server.dal.txsDAL.sandbox)
+    }
+  }
+}
+
+async function sandboxIt(sandbox:any) {
+  return {
+    size: sandbox.maxSize,
+    free: await sandbox.getSandboxRoom()
+  }
+}
diff --git a/app/modules/bma/lib/controllers/transactions.ts b/app/modules/bma/lib/controllers/transactions.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e09609e688676e187dca112cae7f550492d64b7d
--- /dev/null
+++ b/app/modules/bma/lib/controllers/transactions.ts
@@ -0,0 +1,112 @@
+import {AbstractController} from "./AbstractController"
+import {ParametersService} from "../parameters"
+import {Source} from "../entity/source"
+
+const _                = require('underscore');
+const common           = require('duniter-common');
+const http2raw         = require('../http2raw');
+
+const Transaction = common.document.Transaction
+
+export class TransactionBinding extends AbstractController {
+
+  parseTransaction(req:any) {
+    return this.pushEntity(req, http2raw.transaction, constants.ENTITY_TRANSACTION)
+  }
+
+  async getSources(req:any) {
+    const pubkey = await ParametersService.getPubkeyP(req);
+    const sources = await this.server.dal.getAvailableSourcesByPubkey(pubkey);
+    const result:any = {
+      "currency": this.conf.currency,
+      "pubkey": pubkey,
+      "sources": []
+    };
+    sources.forEach(function (src:any) {
+      result.sources.push(new Source(src).json());
+    });
+    return result;
+  }
+
+  async getByHash(req:any) {
+    const hash = ParametersService.getHash(req);
+    const tx = await this.server.dal.getTxByHash(hash);
+    if (!tx) {
+      throw constants.ERRORS.TX_NOT_FOUND;
+    }
+    if (tx.block_number) {
+      tx.written_block = tx.block_number
+    }
+    tx.inputs = tx.inputs.map((i:any) => i.raw || i)
+    tx.outputs = tx.outputs.map((o:any) => o.raw || o)
+    return tx;
+  }
+
+  async getHistory(req:any) {
+    const pubkey = await ParametersService.getPubkeyP(req);
+    return this.getFilteredHistory(pubkey, (results:any) => results);
+  }
+
+  async getHistoryBetweenBlocks(req:any) {
+    const pubkey = await ParametersService.getPubkeyP(req);
+    const from = await ParametersService.getFromP(req);
+    const to = await ParametersService.getToP(req);
+    return this.getFilteredHistory(pubkey, (res:any) => {
+      const histo = res.history;
+      histo.sent =     _.filter(histo.sent, function(tx:any){ return tx && tx.block_number >= from && tx.block_number <= to; });
+      histo.received = _.filter(histo.received, function(tx:any){ return tx && tx.block_number >= from && tx.block_number <= to; });
+      _.extend(histo, { sending: [], receiving: [] });
+      return res;
+    });
+  }
+
+  async getHistoryBetweenTimes(req:any) {
+    const pubkey = await ParametersService.getPubkeyP(req);
+    const from = await ParametersService.getFromP(req);
+    const to = await ParametersService.getToP(req);
+    return this.getFilteredHistory(pubkey, (res:any) => {
+      const histo = res.history;
+      histo.sent =     _.filter(histo.sent, function(tx:any){ return tx && tx.time >= from && tx.time <= to; });
+      histo.received = _.filter(histo.received, function(tx:any){ return tx && tx.time >= from && tx.time <= to; });
+      _.extend(histo, { sending: [], receiving: [] });
+      return res;
+    });
+  }
+
+  async getPendingForPubkey(req:any) {
+    const pubkey = await ParametersService.getPubkeyP(req);
+    return this.getFilteredHistory(pubkey, function(res:any) {
+      const histo = res.history;
+      _.extend(histo, { sent: [], received: [] });
+      return res;
+    });
+  }
+
+  async getPending() {
+    const pending = await this.server.dal.getTransactionsPending();
+    const res = {
+      "currency": this.conf.currency,
+      "pending": pending
+    };
+    pending.map(function(tx:any, index:number) {
+      pending[index] = _.omit(Transaction.fromJSON(tx).json(), 'currency', 'raw');
+    });
+    return res;
+  }
+
+  private async getFilteredHistory(pubkey:string, filter:any) {
+    let history:any = await this.server.dal.getTransactionsHistory(pubkey);
+    let result = {
+      "currency": this.conf.currency,
+      "pubkey": pubkey,
+      "history": history
+    };
+    _.keys(history).map((key:any) => {
+      history[key].map((tx:any, index:number) => {
+        history[key][index] = _.omit(Transaction.fromJSON(tx).json(), 'currency', 'raw');
+        _.extend(history[key][index], {block_number: tx && tx.block_number, time: tx && tx.time});
+      });
+    });
+    return filter(result);
+  }
+}
diff --git a/app/modules/bma/lib/controllers/uds.ts b/app/modules/bma/lib/controllers/uds.ts
new file mode 100644
index 0000000000000000000000000000000000000000..566f59fcd4c2e5a2fac6d59247913c1d7dbb81cd
--- /dev/null
+++ b/app/modules/bma/lib/controllers/uds.ts
@@ -0,0 +1,49 @@
+import {AbstractController} from "./AbstractController"
+import {ParametersService} from "../parameters"
+import {Source} from "../entity/source"
+
+const _ = require('underscore');
+
+export class UDBinding extends AbstractController {
+
+  async getHistory(req:any) {
+    const pubkey = await ParametersService.getPubkeyP(req);
+    return this.getUDSources(pubkey, (results:any) => results);
+  }
+
+  async getHistoryBetweenBlocks(req:any) {
+    const pubkey = await ParametersService.getPubkeyP(req);
+    const from = await ParametersService.getFromP(req);
+    const to = await ParametersService.getToP(req);
+    return this.getUDSources(pubkey, (results:any) => {
+      results.history.history = _.filter(results.history.history, function(ud:any){ return ud.block_number >= from && ud.block_number <= to; });
+      return results;
+    })
+  }
+
+  async getHistoryBetweenTimes(req:any) {
+    const pubkey = await ParametersService.getPubkeyP(req);
+    const from = await ParametersService.getFromP(req);
+    const to = await ParametersService.getToP(req);
+    return this.getUDSources(pubkey, (results:any) => {
+      results.history.history = _.filter(results.history.history, function(ud:any){ return ud.time >= from && ud.time <= to; });
+      return results;
+    });
+  }
+
+  private async getUDSources(pubkey:string, filter:any) {
+      const history:any = await this.server.dal.getUDHistory(pubkey);
+      const result = {
+        "currency": this.conf.currency,
+        "pubkey": pubkey,
+        "history": history
+      };
+      _.keys(history).map((key:any) => {
+        history[key].map((src:any, index:number) => {
+          history[key][index] = _.omit(new Source(src).UDjson(), 'currency', 'raw');
+          _.extend(history[key][index], { block_number: src && src.block_number, time: src && src.time });
+        });
+      });
+      return filter(result);
+  }
+}
diff --git a/app/modules/bma/lib/controllers/wot.ts b/app/modules/bma/lib/controllers/wot.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7f21cfe988c3815f19e4145f15fba9c9eea92ce3
--- /dev/null
+++ b/app/modules/bma/lib/controllers/wot.ts
@@ -0,0 +1,250 @@
+import {AbstractController} from "./AbstractController"
+import {BMAConstants} from "../constants"
+
+const _        = require('underscore');
+const common   = require('duniter-common');
+const http2raw = require('../http2raw');
+
+const Identity = common.document.Identity
+const ParametersService = require('../parameters').ParametersService
+
+export class WOTBinding extends AbstractController {
+
+  async lookup(req:any) {
+    // Get the search parameter from HTTP query
+    const search = await ParametersService.getSearchP(req);
+    // Make the research
+    const identities:any[] = await this.IdentityService.searchIdentities(search);
+    // Entitify each result
+    identities.forEach((idty, index) => identities[index] = Identity.fromJSON(idty));
+    // Prepare some data to avoid displaying expired certifications
+    for (const idty of identities) {
+      const certs = await this.server.dal.certsToTarget(idty.pubkey, idty.getTargetHash());
+      const validCerts = [];
+      for (const cert of certs) {
+        const member = await this.IdentityService.getWrittenByPubkey(cert.from);
+        if (member) {
+          cert.uids = [member.uid];
+          cert.isMember = member.member;
+          cert.wasMember = member.wasMember;
+        } else {
+          const potentials = await this.IdentityService.getPendingFromPubkey(cert.from);
+          cert.uids = _(potentials).pluck('uid');
+          cert.isMember = false;
+          cert.wasMember = false;
+        }
+        validCerts.push(cert);
+      }
+      idty.certs = validCerts;
+      const signed = await this.server.dal.certsFrom(idty.pubkey);
+      const validSigned = [];
+      for (let j = 0; j < signed.length; j++) {
+        const cert = _.clone(signed[j]);
+        cert.idty = await this.server.dal.getIdentityByHashOrNull(cert.target);
+        if (cert.idty) {
+          validSigned.push(cert);
+        } else {
+          this.logger.debug('A certification to an unknown identity was found (%s => %s)', cert.from, cert.to);
+        }
+      }
+      idty.signed = validSigned;
+    }
+    if (identities.length == 0) {
+      throw BMAConstants.ERRORS.NO_MATCHING_IDENTITY;
+    }
+    const resultsByPubkey:any = {};
+    identities.forEach((identity) => {
+      const jsoned = identity.json();
+      if (!resultsByPubkey[jsoned.pubkey]) {
+        // Create the first matching identity with this pubkey in the map
+        resultsByPubkey[jsoned.pubkey] = jsoned;
+      } else {
+        // Merge the identity with the existing(s)
+        const existing = resultsByPubkey[jsoned.pubkey];
+        // We add the UID of the identity to the list of already added UIDs
+        existing.uids = existing.uids.concat(jsoned.uids);
+        // We do not merge the `signed`: every identity with the same pubkey has the same `signed` because it the *pubkey* which signs, not the identity
+      }
+    });
+    return {
+      partial: false,
+      results: _.values(resultsByPubkey)
+    };
+  }
+
+  async members() {
+    const identities = await this.server.dal.getMembers();
+    const json:any = {
+      results: []
+    };
+    identities.forEach((identity:any) => json.results.push({ pubkey: identity.pubkey, uid: identity.uid }));
+    return json;
+  }
+
+  async certifiersOf(req:any) {
+    const search = await ParametersService.getSearchP(req);
+    const idty = await this.IdentityService.findMemberWithoutMemberships(search);
+    const certs = await this.server.dal.certsToTarget(idty.pubkey, idty.getTargetHash());
+    idty.certs = [];
+    for (const cert of certs) {
+      const certifier = await this.server.dal.getWrittenIdtyByPubkey(cert.from);
+      if (certifier) {
+        cert.uid = certifier.uid;
+        cert.isMember = certifier.member;
+        cert.sigDate = certifier.buid;
+        cert.wasMember = true; // As we checked if(certified)
+        if (!cert.cert_time) {
+          let certBlock = await this.server.dal.getBlock(cert.block_number);
+          cert.cert_time = {
+            block: certBlock.number,
+            medianTime: certBlock.medianTime
+          };
+        }
+        idty.certs.push(cert);
+      }
+    }
+    const json:any = {
+      pubkey: idty.pubkey,
+      uid: idty.uid,
+      sigDate: idty.buid,
+      isMember: idty.member,
+      certifications: []
+    };
+    idty.certs.forEach(function(cert){
+      json.certifications.push({
+        pubkey: cert.from,
+        uid: cert.uid,
+        isMember: cert.isMember,
+        wasMember: cert.wasMember,
+        cert_time: cert.cert_time,
+        sigDate: cert.sigDate,
+        written: cert.linked ? {
+          number: cert.written_block,
+          hash: cert.written_hash
+        } : null,
+        signature: cert.sig
+      });
+    });
+    return json;
+  }
+
+  async requirements(req:any) {
+    const search = await ParametersService.getSearchP(req);
+    const identities:any = await this.IdentityService.searchIdentities(search);
+    const all = await this.BlockchainService.requirementsOfIdentities(identities);
+    if (!all || !all.length) {
+      throw BMAConstants.ERRORS.NO_IDTY_MATCHING_PUB_OR_UID;
+    }
+    return {
+      identities: all
+    };
+  }
+
+  async requirementsOfPending(req:any) {
+    const minsig = ParametersService.getMinSig(req)
+    const identities = await this.server.dal.idtyDAL.query('SELECT i.*, count(c.sig) as nbSig FROM idty i, cert c WHERE c.target = i.hash group by i.hash having nbSig >= ?', minsig)
+    const all = await this.BlockchainService.requirementsOfIdentities(identities);
+    if (!all || !all.length) {
+      throw BMAConstants.ERRORS.NO_IDTY_MATCHING_PUB_OR_UID;
+    }
+    return {
+      identities: all
+    };
+  }
+
+  async certifiedBy(req:any) {
+    const search = await ParametersService.getSearchP(req);
+    const idty = await this.IdentityService.findMemberWithoutMemberships(search);
+    const certs = await this.server.dal.certsFrom(idty.pubkey);
+    idty.certs = [];
+    for (const cert of certs) {
+      const certified = await this.server.dal.getWrittenIdtyByPubkey(cert.to);
+      if (certified) {
+        cert.uid = certified.uid;
+        cert.isMember = certified.member;
+        cert.sigDate = certified.buid;
+        cert.wasMember = true; // As we checked if(certified)
+        if (!cert.cert_time) {
+          let certBlock = await this.server.dal.getBlock(cert.block_number);
+          cert.cert_time = {
+            block: certBlock.number,
+            medianTime: certBlock.medianTime
+          };
+        }
+        idty.certs.push(cert);
+      }
+    }
+    const json:any = {
+      pubkey: idty.pubkey,
+      uid: idty.uid,
+      sigDate: idty.buid,
+      isMember: idty.member,
+      certifications: []
+    };
+    idty.certs.forEach((cert) => json.certifications.push({
+        pubkey: cert.to,
+        uid: cert.uid,
+        isMember: cert.isMember,
+        wasMember: cert.wasMember,
+        cert_time: cert.cert_time,
+        sigDate: cert.sigDate,
+        written: cert.linked ? {
+          number: cert.written_block,
+          hash: cert.written_hash
+        } : null,
+        signature: cert.sig
+      })
+    );
+    return json;
+  }
+
+  async identityOf(req:any) {
+    let search = await ParametersService.getSearchP(req);
+    let idty = await this.IdentityService.findMemberWithoutMemberships(search);
+    if (!idty) {
+      throw 'Identity not found';
+    }
+    if (!idty.member) {
+      throw 'Not a member';
+    }
+    return {
+      pubkey: idty.pubkey,
+      uid: idty.uid,
+      sigDate: idty.buid
+    };
+  }
+
+  add(req:any) {
+    return this.pushEntity(req, http2raw.identity, BMAConstants.ENTITY_IDENTITY)
+  }
+
+  certify(req:any) {
+    return this.pushEntity(req, http2raw.certification, BMAConstants.ENTITY_CERTIFICATION)
+  }
+
+  revoke(req:any) {
+    return this.pushEntity(req, http2raw.revocation, BMAConstants.ENTITY_REVOCATION)
+  }
+
+  async pendingMemberships() {
+    const memberships = await this.server.dal.findNewcomers();
+    const json = {
+      memberships: []
+    };
+    json.memberships = memberships.map((ms:any) => {
+      return {
+        pubkey: ms.issuer,
+        uid: ms.userid,
+        version: ms.version,
+        currency: this.server.conf.currency,
+        membership: ms.membership,
+        blockNumber: parseInt(ms.blockNumber),
+        blockHash: ms.blockHash,
+        written: (!ms.written_number && ms.written_number !== 0) ? null : ms.written_number
+      };
+    });
+    json.memberships = _.sortBy(json.memberships, 'blockNumber');
+    json.memberships.reverse();
+    return json;
+  }
+}
diff --git a/app/modules/bma/lib/dos2unix.ts b/app/modules/bma/lib/dos2unix.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bbc290fc2f9c29643300f88cc1268e749c1d87cb
--- /dev/null
+++ b/app/modules/bma/lib/dos2unix.ts
@@ -0,0 +1,3 @@
+module.exports = function dos2unix(str:string) {
+  return str.replace(/\r\n/g, '\n')
+}
diff --git a/app/modules/bma/lib/dtos.ts b/app/modules/bma/lib/dtos.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0949bbb9f5b9a34aa9fac0deebd73a4c19239aec
--- /dev/null
+++ b/app/modules/bma/lib/dtos.ts
@@ -0,0 +1,469 @@
+
+export const Summary = {
+  duniter: {
+    "software": String,
+    "version": String,
+    "forkWindowSize": Number
+  }
+};
+
+export const Parameters = {
+  currency: String,
+  c: Number,
+  dt: Number,
+  ud0: Number,
+  sigPeriod: Number,
+  sigStock: Number,
+  sigWindow: Number,
+  sigValidity: Number,
+  sigQty: Number,
+  idtyWindow: Number,
+  msWindow: Number,
+  xpercent: Number,
+  msValidity: Number,
+  stepMax: Number,
+  medianTimeBlocks: Number,
+  avgGenTime: Number,
+  dtDiffEval: Number,
+  percentRot: Number,
+  udTime0: Number,
+  udReevalTime0: Number,
+  dtReeval: Number
+};
+
+export const Membership = {
+  "signature": String,
+  "membership": {
+    "version": Number,
+    "currency": String,
+    "issuer": String,
+    "membership": String,
+    "date": Number,
+    "sigDate": Number,
+    "raw": String
+  }
+};
+
+export const Memberships = {
+  "pubkey": String,
+  "uid": String,
+  "sigDate": String,
+  "memberships": [
+    {
+      "version": Number,
+      "currency": String,
+      "membership": String,
+      "blockNumber": Number,
+      "blockHash": String,
+      "written": Number
+    }
+  ]
+};
+
+export const MembershipList = {
+  "memberships": [
+    {
+      "pubkey": String,
+      "uid": String,
+      "version": Number,
+      "currency": String,
+      "membership": String,
+      "blockNumber": Number,
+      "blockHash": String,
+      "written": Number
+    }
+  ]
+};
+
+export const TransactionOfBlock = {
+  "version": Number,
+  "currency": String,
+  "comment": String,
+  "locktime": Number,
+  "signatures": [String],
+  "outputs": [String],
+  "inputs": [String],
+  "unlocks": [String],
+  "block_number": Number,
+  "blockstamp": String,
+  "blockstampTime": Number,
+  "time": Number,
+  "issuers": [String]
+};
+
+export const Block = {
+  "version": Number,
+  "currency": String,
+  "number": Number,
+  "issuer": String,
+  "issuersFrame": Number,
+  "issuersFrameVar": Number,
+  "issuersCount": Number,
+  "parameters": String,
+  "membersCount": Number,
+  "monetaryMass": Number,
+  "powMin": Number,
+  "time": Number,
+  "medianTime": Number,
+  "dividend": Number,
+  "unitbase": Number,
+  "hash": String,
+  "previousHash": String,
+  "previousIssuer": String,
+  "identities": [String],
+  "certifications": [String],
+  "joiners": [String],
+  "actives": [String],
+  "leavers": [String],
+  "revoked": [String],
+  "excluded": [String],
+  "transactions": [TransactionOfBlock],
+  "nonce": Number,
+  "inner_hash": String,
+  "signature": String,
+  "raw": String
+};
+
+export const Hardship = {
+  "block": Number,
+  "level": Number
+};
+
+export const Difficulty = {
+  "uid": String,
+  "level": Number
+};
+
+export const Difficulties = {
+  "block": Number,
+  "levels": [Difficulty]
+};
+
+export const Blocks = [Block];
+
+export const Stat = {
+  "result": {
+    "blocks": [Number]
+  }
+};
+
+export const Branches = {
+  "blocks": [Block]
+};
+
+export const Peer = {
+  "version": Number,
+  "currency": String,
+  "pubkey": String,
+  "block": String,
+  "endpoints": [String],
+  "signature": String,
+  "raw": String
+};
+
+export const DBPeer = {
+  "version": Number,
+  "currency": String,
+  "pubkey": String,
+  "block": String,
+  "status": String,
+  "first_down": Number,
+  "last_try": Number,
+  "endpoints": [String],
+  "signature": String,
+  "raw": String
+};
+
+export const Peers = {
+  "peers": [DBPeer]
+};
+
+export const MerkleOfPeers = {
+  "depth": Number,
+  "nodesCount": Number,
+  "leavesCount": Number,
+  "root": String,
+  "leaves": [String],
+  "leaf": {
+    "hash": String,
+    "value": DBPeer
+  }
+};
+
+export const Other = {
+  "pubkey": String,
+  "meta": {
+    "block_number": Number,
+    "block_hash": String
+  },
+  "uids": [String],
+  "isMember": Boolean,
+  "wasMember": Boolean,
+  "signature": String
+};
+
+export const UID = {
+  "uid": String,
+  "meta": {
+    "timestamp": String
+  },
+  "self": String,
+  "revocation_sig": String,
+  "revoked": Boolean,
+  "revoked_on": Number,
+  "others": [Other]
+};
+
+export const Signed = {
+  "uid": String,
+  "pubkey": String,
+  "meta": {
+    "timestamp": String
+  },
+  "cert_time": {
+    "block": Number,
+    "block_hash": String
+  },
+  "isMember": Boolean,
+  "wasMember": Boolean,
+  "signature": String
+};
+
+export const CertIdentity = {
+  "issuer": String,
+  "uid": String,
+  "timestamp": String,
+  "sig": String
+};
+
+export const Cert = {
+  "issuer": String,
+  "timestamp": String,
+  "sig": String,
+  "target": CertIdentity
+};
+
+export const Identity = {
+  "pubkey": String,
+  "uids": [UID],
+  "signed": [Signed]
+};
+
+export const Result = {
+  "result": Boolean
+};
+
+export const Lookup = {
+  "partial": Boolean,
+  "results": [Identity]
+};
+
+export const Members = {
+  "results": [{
+    pubkey: String,
+    uid: String
+  }]
+};
+
+export const RequirementsCert = {
+  from: String,
+  to: String,
+  expiresIn: Number,
+  sig: String
+};
+
+export const RequirementsPendingCert = {
+  from: String,
+  to: String,
+  blockstamp: String,
+  sig: String
+};
+
+export const RequirementsPendingMembership = {
+  type: String,
+  blockstamp: String,
+  sig: String
+};
+
+export const Requirements = {
+  "identities": [{
+    pubkey: String,
+    uid: String,
+    meta: {
+      timestamp: String
+    },
+    sig: String,
+    revocation_sig: String,
+    revoked: Boolean,
+    revoked_on: Number,
+    expired: Boolean,
+    outdistanced: Boolean,
+    isSentry: Boolean,
+    wasMember: Boolean,
+    certifications: [RequirementsCert],
+    pendingCerts: [RequirementsPendingCert],
+    pendingMemberships: [RequirementsPendingMembership],
+    membershipPendingExpiresIn: Number,
+    membershipExpiresIn: Number
+  }]
+};
+
+export const Certification = {
+  "pubkey": String,
+  "uid": String,
+  "isMember": Boolean,
+  "wasMember": Boolean,
+  "cert_time": {
+    "block": Number,
+    "medianTime": Number
+  },
+  "sigDate": String,
+  "written": {
+    "number": Number,
+    "hash": String
+  },
+  "signature": String
+};
+
+export const Certifications = {
+  "pubkey": String,
+  "uid": String,
+  "sigDate": String,
+  "isMember": Boolean,
+  "certifications": [Certification]
+};
+
+export const SimpleIdentity = {
+  "pubkey": String,
+  "uid": String,
+  "sigDate": String
+};
+
+export const Transaction = {
+  "version": Number,
+  "currency": String,
+  "issuers": [String],
+  "inputs": [String],
+  "unlocks": [String],
+  "outputs": [String],
+  "comment": String,
+  "locktime": Number,
+  "signatures": [String],
+  "raw": String,
+  "written_block": Number,
+  "hash": String
+};
+
+export const Source = {
+  "type": String,
+  "noffset": Number,
+  "identifier": String,
+  "amount": Number,
+  "base": Number,
+  "conditions": String
+};
+
+export const Sources = {
+  "currency": String,
+  "pubkey": String,
+  "sources": [Source]
+};
+
+export const TxOfHistory = {
+  "version": Number,
+  "issuers": [String],
+  "inputs": [String],
+  "unlocks": [String],
+  "outputs": [String],
+  "comment": String,
+  "locktime": Number,
+  "received": Number,
+  "signatures": [String],
+  "hash": String,
+  "block_number": Number,
+  "time": Number,
+  "blockstamp": String,
+  "blockstampTime": Number
+};
+
+export const TxHistory = {
+  "currency": String,
+  "pubkey": String,
+  "history": {
+    "sent": [TxOfHistory],
+    "received": [TxOfHistory],
+    "sending": [TxOfHistory],
+    "receiving": [TxOfHistory],
+    "pending": [TxOfHistory]
+  }
+};
+
+export const TxPending = {
+  "currency": String,
+  "pending": [Transaction]
+};
+
+export const UD = {
+  "block_number": Number,
+  "consumed": Boolean,
+  "time": Number,
+  "amount": Number,
+  "base": Number
+};
+
+export const UDHistory = {
+  "currency": String,
+  "pubkey": String,
+  "history": {
+    "history": [UD]
+  }
+};
+
+export const BooleanDTO = {
+  "success": Boolean
+};
+
+export const SummaryConf = {
+  "cpu": Number
+};
+
+export const AdminSummary = {
+  "version": String,
+  "host": String,
+  "current": Block,
+  "rootBlock": Block,
+  "pubkey": String,
+  "seckey": String,
+  "conf": SummaryConf,
+  "parameters": Parameters,
+  "lastUDBlock": Block
+};
+
+export const PoWSummary = {
+  "total": Number,
+  "mirror": Boolean,
+  "waiting": Boolean
+};
+
+export const PreviewPubkey = {
+  "pubkey": String
+};
+
+export const Sandbox = {
+  size: Number,
+  free: Number
+};
+
+export const IdentitySandbox = Sandbox;
+export const MembershipSandbox = Sandbox;
+export const TransactionSandbox = Sandbox;
+
+export const Sandboxes = {
+  identities: IdentitySandbox,
+  memberships: MembershipSandbox,
+  transactions: TransactionSandbox
+};
+
+export const LogLink = {
+  link: String
+};
diff --git a/app/modules/bma/lib/entity/source.ts b/app/modules/bma/lib/entity/source.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1bc134728948bf89cc51c245677cbdc5eb093176
--- /dev/null
+++ b/app/modules/bma/lib/entity/source.ts
@@ -0,0 +1,41 @@
+"use strict";
+const _ = require('underscore');
+
+export class Source {
+
+  [k:string]: any
+
+  constructor(json:any) {
+    _(json || {}).keys().forEach((key:string) => {
+      let value = json[key];
+      if (key == "number") {
+        value = parseInt(value);
+      }
+      else if (key == "consumed") {
+        value = !!value;
+      }
+      this[key] = value;
+    })
+  }
+
+  json() {
+    return {
+      "type": this.type,
+      "noffset": this.pos,
+      "identifier": this.identifier,
+      "amount": this.amount,
+      "conditions": this.conditions,
+      "base": this.base
+    };
+  };
+
+  UDjson() {
+    return {
+      "block_number": this.number,
+      "consumed": this.consumed,
+      "time": this.time,
+      "amount": this.amount,
+      "base": this.base
+    };
+  };
+}
diff --git a/app/modules/bma/lib/http2raw.ts b/app/modules/bma/lib/http2raw.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0fe2d48c5645850eec2fa01365ec76b860a06512
--- /dev/null
+++ b/app/modules/bma/lib/http2raw.ts
@@ -0,0 +1,36 @@
+import {BMAConstants} from "./constants"
+
+module.exports = {
+  identity:      requiresParameter('identity',    BMAConstants.ERRORS.HTTP_PARAM_IDENTITY_REQUIRED),
+  certification: requiresParameter('cert',        BMAConstants.ERRORS.HTTP_PARAM_CERT_REQUIRED),
+  revocation:    requiresParameter('revocation',  BMAConstants.ERRORS.HTTP_PARAM_REVOCATION_REQUIRED),
+  transaction:   requiresParameter('transaction', BMAConstants.ERRORS.HTTP_PARAM_TX_REQUIRED),
+  peer:          requiresParameter('peer',        BMAConstants.ERRORS.HTTP_PARAM_PEER_REQUIRED),
+  membership:    Http2RawMembership,
+  block:         requiresParameter('block',       BMAConstants.ERRORS.HTTP_PARAM_BLOCK_REQUIRED),
+  conf:          requiresParameter('conf',        BMAConstants.ERRORS.HTTP_PARAM_CONF_REQUIRED),
+  cpu:           requiresParameter('cpu',         BMAConstants.ERRORS.HTTP_PARAM_CPU_REQUIRED)
+};
+
+function requiresParameter(parameter:string, err:any) {
+  return (req:any) => {
+    if(!req.body || req.body[parameter] === undefined){
+      throw err;
+    }
+    return req.body[parameter];
+  };
+}
+
+function Http2RawMembership (req:any) {
+  if(!(req.body && req.body.membership)){
+    throw BMAConstants.ERRORS.HTTP_PARAM_MEMBERSHIP_REQUIRED;
+  }
+  let ms = req.body.membership;
+  if(req.body && req.body.signature){
+    ms = [ms, req.body.signature].join('');
+    if (!ms.match(/\n$/)) {
+      ms += '\n';
+    }
+  }
+  return ms;
+}
diff --git a/app/modules/bma/lib/limiter.ts b/app/modules/bma/lib/limiter.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7a0c70f9abf0c41a0097ae3910e4b909205733ae
--- /dev/null
+++ b/app/modules/bma/lib/limiter.ts
@@ -0,0 +1,129 @@
+"use strict";
+
+const A_MINUTE = 60 * 1000;
+const A_SECOND = 1000;
+
+export class Limiter {
+
+  private limitPerSecond:number
+  private limitPerMinute:number
+
+  // Stock of request times
+  private reqsSec:number[] = []
+  
+  // The length of reqs.
+  // It is better to have it instead of calling reqs.length
+  private reqsSecLen:number
+
+  // Minute specific
+  private reqsMin:number[] = []
+  private reqsMinLen:number
+
+  constructor(strategy: { limitPerSecond:number, limitPerMinute:number }) {
+    this.limitPerSecond = strategy.limitPerSecond
+    this.limitPerMinute = strategy.limitPerMinute
+  }
+
+  /**
+   * Tells wether the quota is reached at current time or not.
+   */
+  canAnswerNow() {
+    // Rapid decision first.
+    // Note: we suppose limitPerSecond < limitPerMinute
+    if (this.reqsSecLen < this.limitPerSecond && this.reqsMinLen < this.limitPerMinute) {
+      return true;
+    }
+    this.updateRequests();
+    return this.reqsSecLen < this.limitPerSecond && this.reqsMinLen < this.limitPerMinute;
+  }
+
+  /**
+   * Filter the current requests stock to remove the too old ones
+   */
+  updateRequests() {
+    // Clean current requests stock and make the test again
+    const now = Date.now();
+    let i = 0, reqs = this.reqsMin, len = this.reqsMinLen;
+    // Reinit specific indicators
+    this.reqsSec = [];
+    this.reqsMin = [];
+    while (i < len) {
+      const duration = now - reqs[i];
+      if (duration < A_SECOND) {
+        this.reqsSec.push(reqs[i]);
+      }
+      if (duration < A_MINUTE) {
+        this.reqsMin.push(reqs[i]);
+      }
+      i++;
+    }
+    this.reqsSecLen = this.reqsSec.length;
+    this.reqsMinLen = this.reqsMin.length;
+  }
+  
+  processRequest() {
+    const now = Date.now();
+    this.reqsSec.push(now);
+    this.reqsSecLen++;
+    this.reqsMin.push(now);
+    this.reqsMinLen++;
+  }
+}
+
+let LOW_USAGE_STRATEGY = {
+  limitPerSecond: 1,
+  limitPerMinute: 30
+}
+
+let HIGH_USAGE_STRATEGY = {
+  limitPerSecond: 10,
+  limitPerMinute: 300
+}
+
+let VERY_HIGH_USAGE_STRATEGY = {
+  limitPerSecond: 30,
+  limitPerMinute: 30 * 60 // Limit is only per secon
+}
+
+let TEST_STRATEGY = {
+  limitPerSecond: 5,
+  limitPerMinute: 6
+}
+
+let NO_LIMIT_STRATEGY = {
+  limitPerSecond: 1000000,
+  limitPerMinute: 1000000 * 60
+}
+
+let disableLimits = false;
+
+export const BMALimitation = {
+  
+  limitAsLowUsage() {
+    return disableLimits ? new Limiter(NO_LIMIT_STRATEGY) : new Limiter(LOW_USAGE_STRATEGY);
+  },
+
+  limitAsHighUsage() {
+    return disableLimits ? new Limiter(NO_LIMIT_STRATEGY) : new Limiter(HIGH_USAGE_STRATEGY);
+  },
+
+  limitAsVeryHighUsage() {
+    return disableLimits ? new Limiter(NO_LIMIT_STRATEGY) : new Limiter(VERY_HIGH_USAGE_STRATEGY);
+  },
+
+  limitAsUnlimited() {
+    return new Limiter(NO_LIMIT_STRATEGY);
+  },
+
+  limitAsTest() {
+    return disableLimits ? new Limiter(NO_LIMIT_STRATEGY) : new Limiter(TEST_STRATEGY);
+  },
+
+  noLimit() {
+    disableLimits = true;
+  },
+
+  withLimit() {
+    disableLimits = false;
+  }
+};
diff --git a/app/modules/bma/lib/network.ts b/app/modules/bma/lib/network.ts
new file mode 100644
index 0000000000000000000000000000000000000000..61e74ec38bdba9d8f32addcb403bd7d481591b4d
--- /dev/null
+++ b/app/modules/bma/lib/network.ts
@@ -0,0 +1,381 @@
+"use strict";
+import {NetworkConfDTO} from "../../../lib/dto/ConfDTO"
+import {Server} from "../../../../server"
+import {BMAConstants} from "./constants"
+import {BMALimitation} from "./limiter"
+
+const os = require('os');
+const Q = require('q');
+const _ = require('underscore');
+const ddos = require('ddos');
+const http = require('http');
+const express = require('express');
+const morgan = require('morgan');
+const errorhandler = require('errorhandler');
+const bodyParser = require('body-parser');
+const cors = require('cors');
+const fileUpload = require('express-fileupload');
+const sanitize = require('./sanitize');
+
+export interface NetworkInterface {
+  ip:string|null
+  port:number|null
+}
+
+export const Network = {
+
+  getBestLocalIPv4: getBestLocalIPv4,
+  getBestLocalIPv6: getBestLocalIPv6,
+
+  listInterfaces: listInterfaces,
+
+  upnpConf: (noupnp:boolean, logger:any) => upnpConf(noupnp, logger),
+
+  getRandomPort: getRandomPort,
+
+  createServersAndListen: async (name:string, server:Server, interfaces:NetworkInterface[], httpLogs:boolean, logger:any, staticPath:string|null, routingCallback:any, listenWebSocket:any, enableFileUpload:boolean = false) => {
+
+    const app = express();
+
+    // all environments
+    if (httpLogs) {
+      app.use(morgan('\x1b[90m:remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms\x1b[0m', {
+        stream: {
+          write: function(message:string){
+            message && logger && logger.trace(message.replace(/\n$/,''));
+          }
+        }
+      }));
+    }
+
+    // DDOS protection
+    const whitelist = interfaces.map(i => i.ip);
+    if (whitelist.indexOf('127.0.0.1') === -1) {
+      whitelist.push('127.0.0.1');
+    }
+    const ddosConf = server.conf.dos || { silentStart: true };
+    ddosConf.whitelist = _.uniq((ddosConf.whitelist || []).concat(whitelist));
+    const ddosInstance = new ddos(ddosConf);
+    app.use(ddosInstance.express);
+
+    // CORS for **any** HTTP request
+    app.use(cors());
+
+    if (enableFileUpload) {
+      // File upload for backup API
+      app.use(fileUpload());
+    }
+
+    app.use(bodyParser.urlencoded({
+      extended: true
+    }));
+    app.use(bodyParser.json({ limit: '10mb' }));
+
+    // development only
+    if (app.get('env') == 'development') {
+      app.use(errorhandler());
+    }
+
+    const handleRequest = (method:any, uri:string, promiseFunc:(...args:any[])=>Promise<any>, dtoContract:any, theLimiter:any) => {
+      const limiter = theLimiter || BMALimitation.limitAsUnlimited();
+      method(uri, async function(req:any, res:any) {
+        res.set('Access-Control-Allow-Origin', '*');
+        res.type('application/json');
+        try {
+          if (!limiter.canAnswerNow()) {
+            throw BMAConstants.ERRORS.HTTP_LIMITATION;
+          }
+          limiter.processRequest();
+          let result = await promiseFunc(req);
+          // Ensure of the answer format
+          result = sanitize(result, dtoContract);
+          // HTTP answer
+          res.status(200).send(JSON.stringify(result, null, "  "));
+        } catch (e) {
+          let error = getResultingError(e, logger);
+          // HTTP error
+          res.status(error.httpCode).send(JSON.stringify(error.uerr, null, "  "));
+        }
+      });
+    };
+
+    const handleFileRequest = (method:any, uri:string, promiseFunc:(...args:any[])=>Promise<any>, theLimiter:any) => {
+      const limiter = theLimiter || BMALimitation.limitAsUnlimited();
+      method(uri, async function(req:any, res:any) {
+        res.set('Access-Control-Allow-Origin', '*');
+        try {
+          if (!limiter.canAnswerNow()) {
+            throw BMAConstants.ERRORS.HTTP_LIMITATION;
+          }
+          limiter.processRequest();
+          let fileStream:any = await promiseFunc(req);
+          // HTTP answer
+          fileStream.pipe(res);
+        } catch (e) {
+          let error = getResultingError(e, logger);
+          // HTTP error
+          res.status(error.httpCode).send(JSON.stringify(error.uerr, null, "  "));
+          throw e
+        }
+      });
+    };
+
+    routingCallback(app, {
+      httpGET:     (uri:string, promiseFunc:(...args:any[])=>Promise<any>, dtoContract:any, limiter:any) => handleRequest(app.get.bind(app), uri, promiseFunc, dtoContract, limiter),
+      httpPOST:    (uri:string, promiseFunc:(...args:any[])=>Promise<any>, dtoContract:any, limiter:any) => handleRequest(app.post.bind(app), uri, promiseFunc, dtoContract, limiter),
+      httpGETFile: (uri:string, promiseFunc:(...args:any[])=>Promise<any>, dtoContract:any, limiter:any) => handleFileRequest(app.get.bind(app), uri, promiseFunc, limiter)
+    });
+
+    if (staticPath) {
+      app.use(express.static(staticPath));
+    }
+
+    const httpServers = interfaces.map(() => {
+      const httpServer = http.createServer(app);
+      const sockets:any = {};
+      let nextSocketId = 0;
+      httpServer.on('connection', (socket:any) => {
+        const socketId = nextSocketId++;
+        sockets[socketId] = socket;
+        //logger && logger.debug('socket %s opened', socketId);
+
+        socket.on('close', () => {
+          //logger && logger.debug('socket %s closed', socketId);
+          delete sockets[socketId];
+        });
+      });
+      httpServer.on('error', (err:any) => {
+        httpServer.errorPropagates(err);
+      });
+      listenWebSocket && listenWebSocket(httpServer);
+      return {
+        http: httpServer,
+        closeSockets: () => {
+          _.keys(sockets).map((socketId:number) => {
+            sockets[socketId].destroy();
+          });
+        }
+      };
+    });
+
+    if (httpServers.length == 0){
+      throw 'Duniter does not have any interface to listen to.';
+    }
+
+    // Return API
+    return new BmaApi(interfaces, ddosInstance, httpServers, logger)
+  }
+}
+
+export class BmaApi {
+
+  private listenings:boolean[]
+
+  constructor(
+    private interfaces:any,
+    private ddosInstance:any,
+    private httpServers:any,
+    private logger:any
+  ) {
+
+    // May be removed when using Node 5.x where httpServer.listening boolean exists
+    this.listenings = interfaces.map(() => false)
+  }
+
+  getDDOS() {
+    return this.ddosInstance
+  }
+
+  async closeConnections() {
+    for (let i = 0, len = this.httpServers.length; i < len; i++) {
+    const httpServer = this.httpServers[i].http;
+    const isListening = this.listenings[i];
+    if (isListening) {
+      this.listenings[i] = false;
+      this.logger && this.logger.info(name + ' stop listening');
+      await Q.Promise((resolve:any, reject:any) => {
+        httpServer.errorPropagates((err:any) => {
+          reject(err);
+        });
+        this.httpServers[i].closeSockets();
+        httpServer.close((err:any) => {
+          err && this.logger && this.logger.error(err.stack || err);
+          resolve();
+        });
+      });
+    }
+  }
+  return [];
+  }
+
+  async openConnections() {
+    for (let i = 0, len = this.httpServers.length; i < len; i++) {
+      const httpServer = this.httpServers[i].http;
+      const isListening = this.listenings[i];
+      if (!isListening) {
+        const netInterface = this.interfaces[i].ip;
+        const port = this.interfaces[i].port;
+        try {
+          await Q.Promise((resolve:any, reject:any) => {
+            // Weird the need of such a hack to catch an exception...
+            httpServer.errorPropagates = function(err:any) {
+              reject(err);
+            };
+            //httpServer.on('listening', resolve.bind(this, httpServer));
+            httpServer.listen(port, netInterface, (err:any) => {
+              if (err) return reject(err);
+              this.listenings[i] = true;
+              resolve(httpServer);
+            });
+          });
+          this.logger && this.logger.info(name + ' listening on http://' + (netInterface.match(/:/) ? '[' + netInterface + ']' : netInterface) + ':' + port);
+        } catch (e) {
+          this.logger && this.logger.warn('Could NOT listen to http://' + netInterface + ':' + port);
+          this.logger && this.logger.warn(e);
+        }
+      }
+    }
+    return [];
+  }
+}
+
+function getResultingError(e:any, logger:any) {
+  // Default is 500 unknown error
+  let error = BMAConstants.ERRORS.UNKNOWN;
+  if (e) {
+    // Print eventual stack trace
+    typeof e == 'string' && logger && logger.error(e);
+    e.stack && logger && logger.error(e.stack);
+    e.message && logger && logger.warn(e.message);
+    // BusinessException
+    if (e.uerr) {
+      error = e;
+    } else {
+      const cp = BMAConstants.ERRORS.UNHANDLED;
+      error = {
+        httpCode: cp.httpCode,
+        uerr: {
+          ucode: cp.uerr.ucode,
+          message: e.message || e || error.uerr.message
+        }
+      };
+    }
+  }
+  return error;
+}
+
+function getBestLocalIPv4() {
+  return getBestLocal('IPv4');
+}
+
+function getBestLocalIPv6() {
+  const osInterfaces = listInterfaces();
+  for (let netInterface of osInterfaces) {
+    const addresses = netInterface.addresses;
+    const filtered = _(addresses).where({family: 'IPv6', scopeid: 0, internal: false });
+    const filtered2 = _.filter(filtered, (address:any) => !address.address.match(/^fe80/) && !address.address.match(/^::1/));
+    if (filtered2[0]) {
+      return filtered2[0].address;
+    }
+  }
+  return null;
+}
+
+function getBestLocal(family:string) {
+  let netInterfaces = os.networkInterfaces();
+  let keys = _.keys(netInterfaces);
+  let res = [];
+  for (const name of keys) {
+    let addresses = netInterfaces[name];
+    for (const addr of addresses) {
+      if (!family || addr.family == family) {
+        res.push({
+          name: name,
+          value: addr.address
+        });
+      }
+    }
+  }
+  const interfacePriorityRegCatcher = [
+    /^tun\d/,
+    /^enp\ds\d/,
+    /^enp\ds\df\d/,
+    /^eth\d/,
+    /^Ethernet/,
+    /^wlp\ds\d/,
+    /^wlan\d/,
+    /^Wi-Fi/,
+    /^lo/,
+    /^Loopback/,
+    /^None/
+  ];
+  const best = _.sortBy(res, function(entry:any) {
+    for (let i = 0; i < interfacePriorityRegCatcher.length; i++) {
+      // `i` is the priority (0 is the better, 1 is the second, ...)
+      if (entry.name.match(interfacePriorityRegCatcher[i])) return i;
+    }
+    return interfacePriorityRegCatcher.length;
+  })[0];
+  return (best && best.value) || "";
+}
+
+function listInterfaces() {
+  const netInterfaces = os.networkInterfaces();
+  const keys = _.keys(netInterfaces);
+  const res = [];
+  for (const name of keys) {
+    res.push({
+      name: name,
+      addresses: netInterfaces[name]
+    });
+  }
+  return res;
+}
+
+async function upnpConf (noupnp:boolean, logger:any) {
+  const conf:NetworkConfDTO = {
+    port: 10901,
+    ipv4: '127.0.0.1',
+    ipv6: '::1',
+    dos: null,
+    upnp: false,
+    httplogs: false,
+    remoteport: 10901,
+    remotehost: null,
+    remoteipv4: null,
+    remoteipv6: null
+  }
+  const client = require('nnupnp').createClient();
+  // Look for 2 random ports
+  const privatePort = getRandomPort(conf);
+  const publicPort = privatePort;
+  logger && logger.info('Checking UPnP features...');
+  if (noupnp) {
+    throw Error('No UPnP');
+  }
+  const publicIP = await Q.nbind(client.externalIp, client)();
+  await Q.nbind(client.portMapping, client)({
+    public: publicPort,
+    private: privatePort,
+    ttl: 120
+  });
+  const privateIP = await Q.Promise((resolve:any, reject:any) => {
+    client.findGateway((err:any, res:any, localIP:any) => {
+      if (err) return reject(err);
+      resolve(localIP);
+    });
+  });
+  conf.remoteipv4 = publicIP.match(BMAConstants.IPV4_REGEXP) ? publicIP : null;
+  conf.remoteport = publicPort;
+  conf.port = privatePort;
+  conf.ipv4 = privateIP.match(BMAConstants.IPV4_REGEXP) ? privateIP : null;
+  return conf;
+}
+
+function getRandomPort(conf:NetworkConfDTO) {
+  if (conf && conf.remoteport) {
+    return conf.remoteport;
+  } else {
+    return ~~(Math.random() * (65536 - BMAConstants.PORT_START)) + BMAConstants.PORT_START;
+  }
+}
diff --git a/app/modules/bma/lib/parameters.ts b/app/modules/bma/lib/parameters.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3a27c9db386b50058aada7eaeb1dbeb1ff955f48
--- /dev/null
+++ b/app/modules/bma/lib/parameters.ts
@@ -0,0 +1,130 @@
+"use strict";
+import {BMAConstants} from "./constants"
+
+const Q = require('q');
+
+export class ParametersService {
+
+  static getSearch(req:any, callback:any) {
+    if(!req.params || !req.params.search){
+      callback("No search criteria given");
+      return;
+    }
+    callback(null, req.params.search);
+  }
+
+  static getSearchP(req:any) {
+    return Q.nbind(ParametersService.getSearch, this)(req)
+  }
+
+  static getCountAndFrom(req:any) {
+    if(!req.params.from){
+      throw "From is required";
+    }
+    if(!req.params.count){
+      throw "Count is required";
+    }
+    const matches = req.params.from.match(/^(\d+)$/);
+    if(!matches){
+      throw "From format is incorrect, must be a positive integer";
+    }
+    const matches2 = req.params.count.match(/^(\d+)$/);
+    if(!matches2){
+      throw "Count format is incorrect, must be a positive integer";
+    }
+    return {
+      count: matches2[1],
+      from: matches[1]
+    };
+  }
+
+  static getHash(req:any) {
+    if(!req.params.hash){
+      throw Error("`hash` is required");
+    }
+    const matches = req.params.hash.match(BMAConstants.SHA256_HASH);
+    if(!matches){
+      throw Error("`hash` format is incorrect, must be a SHA256 hash");
+    }
+    return req.params.hash;
+  };
+
+  static getMinSig(req:any){
+    if(!req.params.minsig){
+      return 4 // Default value
+    }
+    const matches = req.params.minsig.match(/\d+/)
+    if(!matches){
+      throw Error("`minsig` format is incorrect, must be an integer")
+    }
+    return parseInt(req.params.minsig)
+  }
+
+  static getPubkey = function (req:any, callback:any){
+    if(!req.params.pubkey){
+      callback('Parameter `pubkey` is required');
+      return;
+    }
+    const matches = req.params.pubkey.match(BMAConstants.PUBLIC_KEY);
+    if(!matches){
+      callback("Pubkey format is incorrect, must be a Base58 string");
+      return;
+    }
+    callback(null, matches[0]);
+  }
+
+  static getPubkeyP(req:any) {
+    return Q.nbind(ParametersService.getPubkey, this)(req)
+  }
+
+  static getFrom(req:any, callback:any){
+    if(!req.params.from){
+      callback('Parameter `from` is required');
+      return;
+    }
+    const matches = req.params.from.match(/^(\d+)$/);
+    if(!matches){
+      callback("From format is incorrect, must be a positive or zero integer");
+      return;
+    }
+    callback(null, matches[0]);
+  }
+
+  static getFromP(req:any) {
+    return Q.nbind(ParametersService.getFrom, this)(req)
+  }
+
+  static getTo(req:any, callback:any){
+    if(!req.params.to){
+      callback('Parameter `to` is required');
+      return;
+    }
+    const matches = req.params.to.match(/^(\d+)$/);
+    if(!matches){
+      callback("To format is incorrect, must be a positive or zero integer");
+      return;
+    }
+    callback(null, matches[0]);
+  }
+
+  static getToP(req:any) {
+    return Q.nbind(ParametersService.getTo, this)(req)
+  }
+
+  static getNumber(req:any, callback:any){
+    if(!req.params.number){
+      callback("Number is required");
+      return;
+    }
+    const matches = req.params.number.match(/^(\d+)$/);
+    if(!matches){
+      callback("Number format is incorrect, must be a positive integer");
+      return;
+    }
+    callback(null, parseInt(matches[1]));
+  }
+
+  static getNumberP(req:any) {
+    return Q.nbind(ParametersService.getNumber, this)(req)
+  }
+}
diff --git a/app/modules/bma/lib/sanitize.ts b/app/modules/bma/lib/sanitize.ts
new file mode 100644
index 0000000000000000000000000000000000000000..01d6cec2c3764c6e1e58d68af1ec2bf4466e9481
--- /dev/null
+++ b/app/modules/bma/lib/sanitize.ts
@@ -0,0 +1,118 @@
+"use strict";
+
+let _ = require('underscore');
+
+module.exports = function sanitize (json:any, contract:any) {
+
+  // Tries to sanitize only if contract is given
+  if (contract) {
+
+    if (Object.prototype.toString.call(contract) === "[object Array]") {
+      // Contract is an array
+
+      if (Object.prototype.toString.call(json) !== "[object Array]") {
+        json = [];
+      }
+
+      for (let i = 0, len = json.length; i < len; i++) {
+        json[i] = sanitize(json[i], contract[0]);
+      }
+    } else {
+      // Contract is an object or native type
+
+      // Return type is either a string, a number or an object
+      if (typeof json != typeof contract) {
+        try {
+          // Cast value
+          json = contract(json);
+        } catch (e) {
+          // Cannot be casted: create empty value
+          json = contract();
+        }
+      }
+
+      let contractFields = _(contract).keys();
+      let objectFields = _(json).keys();
+      let toDeleteFromObj = _.difference(objectFields, contractFields);
+
+      // Remove unwanted fields
+      for (let i = 0, len = toDeleteFromObj.length; i < len; i++) {
+        let field = toDeleteFromObj[i];
+        delete json[field];
+      }
+
+      // Format wanted fields
+      for (let i = 0, len = contractFields.length; i < len; i++) {
+        let prop = contractFields[i];
+        let propType = contract[prop];
+        let t = "";
+        if (propType.name) {
+          t = propType.name;
+        } else if (propType.length != undefined) {
+          t = 'Array';
+        } else {
+          t = 'Object';
+        }
+        // Test json member type
+        let tjson:any = typeof json[prop];
+        if (~['Array', 'Object'].indexOf(t)) {
+          if (tjson == 'object' && json[prop] !== null) {
+            tjson = json[prop].length == undefined ? 'Object' : 'Array';
+          }
+        }
+        // Check coherence & alter member if needed
+        if (!_(json[prop]).isNull() && t.toLowerCase() != tjson.toLowerCase()) {
+          try {
+            if (t == "String") {
+              let s = json[prop] == undefined ? '' : json[prop];
+              json[prop] = String(s).valueOf();
+            }
+            else if (t == "Number") {
+              let s = json[prop] == undefined ? '' : json[prop];
+              json[prop] = Number(s).valueOf();
+            }
+            else if (t == "Array") {
+              json[prop] = [];
+            }
+            else if (t == "Object") {
+              json[prop] = {};
+            }
+            else {
+              json[prop] = Boolean();
+            }
+          } catch (ex) {
+            if (t == "String") {
+              json[prop] = String();
+            }
+            else if (t == "Number") {
+              json[prop] = Number();
+            }
+            else if (t == "Array") {
+              json[prop] = [];
+            }
+            else if (t == "Object") {
+              json[prop] = {};
+            }
+            else {
+              json[prop] = Boolean();
+            }
+          }
+        }
+        // Arrays
+        if (t == 'Array') {
+          let subt = propType[0];
+          for (let j = 0, len2 = json[prop].length; j < len2; j++) {
+            if (!(subt == "String" || subt == "Number")) {
+              json[prop][j] = sanitize(json[prop][j], subt);
+            }
+          }
+        }
+        // Recursivity
+        if (t == 'Object' && json[prop] !== null) {
+          json[prop] = sanitize(json[prop], contract[prop]);
+        }
+      }
+    }
+  }
+  return json;
+};
diff --git a/app/modules/bma/lib/tojson.ts b/app/modules/bma/lib/tojson.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4d7e4268b0d40bba85099e17682473e158b5167f
--- /dev/null
+++ b/app/modules/bma/lib/tojson.ts
@@ -0,0 +1,52 @@
+"use strict";
+import {BlockDTO} from "../../../lib/dto/BlockDTO"
+
+const _ = require('underscore')
+
+export const stat = (stat:any) => {
+  return { "blocks": stat.blocks }
+}
+
+export const block = (block:any) => {
+  const json:any = {};
+  json.version = parseInt(block.version)
+  json.nonce = parseInt(block.nonce)
+  json.number = parseInt(block.number)
+  json.powMin = parseInt(block.powMin)
+  json.time = parseInt(block.time)
+  json.medianTime = parseInt(block.medianTime)
+  json.membersCount = parseInt(block.membersCount)
+  json.monetaryMass = parseInt(block.monetaryMass)
+  json.unitbase = parseInt(block.unitbase)
+  json.issuersCount = parseInt(block.issuersCount)
+  json.issuersFrame = parseInt(block.issuersFrame)
+  json.issuersFrameVar = parseInt(block.issuersFrameVar)
+  json.len = parseInt(block.len)
+  json.currency = block.currency || ""
+  json.issuer = block.issuer || ""
+  json.signature = block.signature || ""
+  json.hash = block.hash || ""
+  json.parameters = block.parameters || ""
+  json.previousHash = block.previousHash || null
+  json.previousIssuer = block.previousIssuer || null
+  json.inner_hash = block.inner_hash || null
+  json.dividend = parseInt(block.dividend) || null
+  json.identities = (block.identities || [])
+  json.joiners = (block.joiners || [])
+  json.actives = (block.actives || [])
+  json.leavers = (block.leavers || [])
+  json.revoked = (block.revoked || [])
+  json.excluded = (block.excluded || [])
+  json.certifications = (block.certifications || [])
+  json.transactions = [];
+  block.transactions.forEach((obj:any) => {
+    json.transactions.push(_(obj).omit('raw', 'certifiers', 'hash'));
+  });
+  json.transactions = block.transactions.map((tx:any) => {
+    tx.inputs = tx.inputs.map((i:any) => i.raw || i)
+    tx.outputs = tx.outputs.map((o:any) => o.raw || o)
+    return tx
+  })
+  json.raw = BlockDTO.fromJSONObject(block).getRawUnSigned()
+  return json;
+}
\ No newline at end of file
diff --git a/app/modules/bma/lib/upnp.ts b/app/modules/bma/lib/upnp.ts
new file mode 100644
index 0000000000000000000000000000000000000000..45b451ff05a81698a1d5052e551103e5db1ef043
--- /dev/null
+++ b/app/modules/bma/lib/upnp.ts
@@ -0,0 +1,86 @@
+import {BMAConstants} from "./constants"
+const upnp = require('nnupnp');
+const Q = require('q');
+
+export const Upnp = async function (localPort:number, remotePort:number, logger:any) {
+  "use strict";
+
+  logger.info('UPnP: configuring...');
+  const api = new UpnpApi(localPort, remotePort, logger)
+  try {
+    await api.openPort()
+  } catch (e) {
+    const client = upnp.createClient();
+    try {
+      await Q.nbind(client.externalIp, client)();
+    } catch (err) {
+      if (err && err.message == 'timeout') {
+        throw 'No UPnP gateway found: your node won\'t be reachable from the Internet. Use --noupnp option to avoid this message.'
+      }
+      throw err;
+    } finally {
+      client.close();
+    }
+  }
+  return api
+};
+
+export class UpnpApi {
+
+  private interval:NodeJS.Timer|null
+
+  constructor(
+    private localPort:number,
+    private remotePort:number,
+    private logger:any
+  ) {}
+
+  openPort() {
+    "use strict";
+    return Q.Promise((resolve:any, reject:any) => {
+      this.logger.trace('UPnP: mapping external port %s to local %s...', this.remotePort, this.localPort);
+      const client = upnp.createClient();
+      client.portMapping({
+        'public': this.remotePort,
+        'private': this.localPort,
+        'ttl': BMAConstants.UPNP_TTL
+      }, (err:any) => {
+        client.close();
+        if (err) {
+          this.logger.warn(err);
+          return reject(err);
+        }
+        resolve();
+      });
+    });
+  }
+
+  async findGateway() {
+    try {
+      const client = upnp.createClient();
+      const res = await Q.nbind(client.findGateway, client)();
+      const desc = res && res[0] && res[0].description;
+      if (desc) {
+        const match = desc.match(/(\d+.\d+.\d+.\d+):/);
+        if (match) {
+          return match[1];
+        }
+      }
+      return null;
+    } catch (e) {
+      return null;
+    }
+  }
+
+  startRegular() {
+    this.stopRegular();
+    // Update UPnP IGD every INTERVAL seconds
+    this.interval = setInterval(() => this.openPort(), 1000 * BMAConstants.UPNP_INTERVAL)
+  }
+
+  stopRegular() {
+    if (this.interval) {
+      clearInterval(this.interval)
+    }
+  }
+}
\ No newline at end of file
diff --git a/app/service/PeeringService.ts b/app/service/PeeringService.ts
index 32d5851f02f0f6d6b8b9aa7daae4929702891368..9355de98445f82b4370f4772111061bbce7fe40e 100644
--- a/app/service/PeeringService.ts
+++ b/app/service/PeeringService.ts
@@ -261,7 +261,7 @@ export class PeeringService {
     return endpoints.filter((ep) => {
       return !ep.match(constants.BMA_REGEXP) || (
           !(ep.includes(' ' + theConf.remoteport) && (
-          ep.includes(theConf.remotehost) || ep.includes(theConf.remoteipv6) || ep.includes(theConf.remoteipv4))));
+          ep.includes(theConf.remotehost || '') || ep.includes(theConf.remoteipv6 || '') || ep.includes(theConf.remoteipv4 || ''))));
     });
   }
 }
diff --git a/doc/API.md b/doc/API.md
new file mode 100644
index 0000000000000000000000000000000000000000..8a4b818bd0196282ca13c4c5957ba03401db7297
--- /dev/null
+++ b/doc/API.md
@@ -0,0 +1,1845 @@
+# Duniter HTTP API
+
+## Contents
+
+* [Contents](#contents)
+* [Overview](#overview)
+* [Merkle URLs](#merkle-urls)
+* [API](#api)
+  * [node/](#node)
+      * [summary](#nodesummary)
+      * [sandboxes](#sandboxes)
+  * [wot/](#wot)
+      * [add](#wotadd)
+      * [certify](#wotcertify)
+      * [revoke](#wotrevoke)
+      * [lookup/[search]](#wotlookupsearch)
+      * [requirements/[PUBKEY]](#wotrequirementspubkey)
+      * [certifiers-of/[search]](#wotcertifiers-ofsearch)
+      * [certified-by/[search]](#wotcertified-bysearch)
+  * [blockchain/](#blockchain)
+      * [parameters](#blockchainparameters)
+      * [membership](#blockchainmembership)
+      * [memberships/[search]](#blockchainmembershipssearch)
+      * [block](#blockchainblock)
+      * [block/[number]](#blockchainblocknumber)
+      * [blocks/[count]/[from]](#blockchainblockscountfrom)
+      * [current](#blockchaincurrent)
+      * [hardship/[PUBKEY]](#blockchainhardshippubkey)
+      * [difficulties](#blockchaindifficulties)
+      * [with/](#blockchainwith)
+          * [newcomers](#blockchainwithnewcomers)
+          * [certs](#blockchainwithcerts)
+          * [actives](#blockchainwithactives)
+          * [leavers](#blockchainwithleavers)
+          * [excluded](#blockchainwithexcluded)
+          * [ud](#blockchainwithud)
+          * [tx](#blockchainwithtx)
+      * [branches](#blockchainbranches)
+  * [network/](#network)
+      * [peers](#networkpeers)
+      * [peering](#networkpeering)
+      * [peering/peers (GET)](#networkpeeringpeers-get)
+      * [peering/peers (POST)](#networkpeeringpeers-post)
+  * [tx/](#tx)
+      * [process](#txprocess)
+      * [sources/[pubkey]](#txsourcespubkey)
+      * [history/[pubkey]](#txhistorypubkey)
+      * [history/[pubkey]/blocks/[from]/[to]](#txhistorypubkeyblocksfromto)
+      * [history/[pubkey]/times/[from]/[to]](#txhistorypubkeytimesfromto)
+  * [ud/](#ud)
+      * [history/[pubkey]](#udhistorypubkey)
+  * [ws/](#ws)
+      * [block](#wsblock)
+      * [peer](#wspeer)
+
+## Overview
+
+Data is made accessible through an HTTP API mainly inspired from [OpenUDC_exchange_formats draft](https://github.com/Open-UDC/open-udc/blob/master/docs/OpenUDC_exchange_formats.draft.txt), and has been adapted to fit Duniter specificities.
+
+    http[s]://Node[:port]/...
+    |-- node/
+    |   |-- summary
+    |   |-- sandboxes
+    |-- wot/
+    |   |-- add
+    |   |-- certify
+    |   |-- revoke
+    |   |-- requirements/[pubkey]
+    |   |-- certifiers-of/[uid|pubkey]
+    |   |-- certified-by/[uid|pubkey]
+    |   |-- members
+    |   `-- lookup
+    |-- blockchain/
+    |   |-- parameters
+    |   |-- membership
+    |   |-- with/
+    |       |-- newcomers
+    |       |-- certs
+    |       |-- joiners
+    |       |-- actives
+    |       |-- leavers
+    |       |-- excluded
+    |       |-- ud
+    |       `-- tx
+    |   |-- hardship
+    |   |   `-- [PUBKEY]
+    |   |-- block
+    |   |   `-- [NUMBER]
+    |   |-- difficulties
+    |   `-- current
+    |-- network/
+    |   |-- peers
+    |   `-- peering
+    |       `-- peers
+    |-- tx/
+    |   |-- process
+    |   |-- sources
+    |   `-- history
+    |-- ud/
+    |   `-- history
+    `-- ws/
+        |-- block
+        `-- peer
+
+## Merkle URLs
+
+Merkle URL is a special kind of URL applicable for resources:
+
+* `network/peering/peers (GET)`
+
+Such kind of URL returns Merkle tree hashes informations. In Duniter, Merkle trees are an easy way to detect unsynced data and where the differences come from. For example, `network/peering/peers` is a Merkle tree whose leaves are peers' key fingerprint sorted ascending way. Thus, if any new peer is added, a branch of the tree will see its hash modified and propagated to the root hash. Change is then easy to detect.
+
+For commodity issues, this URL uses query parameters to retrieve partial data of the tree, as most of the time all the data is not required. Duniter Merkle tree has a determined number of parent nodes (given a number of leaves), which allows to ask only for interval of them.
+
+Here is an example of members Merkle tree with 5 members (taken from [Tree Hash EXchange format (THEX)](http://web.archive.org/web/20080316033726/http://www.open-content.net/specs/draft-jchapweske-thex-02.html)):
+
+                       ROOT=H(H+E)
+                        /        \
+                       /          \
+                 H=H(F+G)          E
+                /       \           \
+               /         \           \
+        F=H(A+B)         G=H(C+D)     E
+        /     \           /     \      \
+       /       \         /       \      \
+      A         B       C         D      E
+
+
+    Note: H() is some hash function
+
+Where A,B,C,D,E are already hashed data.
+
+With such a tree structure, Duniter consider the tree has exactly 6 nodes: `[ROOT,H,E,F,G,E]`. Nodes are just an array, and for a Lambda Server LS1, it is easy to ask for the values of another server LS2 for level 1 (`H` and `E`, the second level): it requires nodes interval `[1;2]`.
+
+Hence it is quite easy for anyone who wants to check if a `Z` member joined the Duniter community as it would alter the `E` branch of the tree:
+
+                        ROOT'=H(H+E')
+                        /            \
+                       /              \
+                 H=H(F+G)              E'
+                /       \               \
+               /         \               \
+        F=H(A+B)          G=H(C+D)       E'=H(E+Z)
+        /     \           /     \         /     \
+       /       \         /       \       /       \
+      A         B       C         D     E         Z
+
+`ROOT` changed to `ROOT'`, `E` to `E'`, but `H` did not. The whole `E'` branch should be updated with the proper new data.
+
+For that purpose, Merkle URL defines different parameters and results:
+
+**Parameters**
+
+Parameter | Description
+--------- | -----------
+`leaves`  | Defines wether or not leaves hashes should be returned too. Defaults to `false`.
+`leaf`    | Hash of a leaf whose content should be returned. Ignore `leaves` parameter.
+
+**Returns**
+
+Merkle URL result with `leaves=false`.
+```json
+{
+  "depth": 3,
+  "nodesCount": 6,
+  "leavesCount": 5,
+  "root": "6513D6A1582DAE614D8A3B364BF3C64C513D236B"
+}
+```
+
+Merkle URL result with `leaves=true`.
+```json
+{
+  "depth": 3,
+  "nodesCount": 6,
+  "leavesCount": 5,
+  "root": "6513D6A1582DAE614D8A3B364BF3C64C513D236B",
+  "leaves": [
+    "32096C2E0EFF33D844EE6D675407ACE18289357D",
+    "50C9E8D5FC98727B4BBC93CF5D64A68DB647F04F",
+    "6DCD4CE23D88E2EE9568BA546C007C63D9131C1B",
+    "AE4F281DF5A5D0FF3CAD6371F76D5C29B6D953EC",
+    "E0184ADEDF913B076626646D3F52C3B49C39AD6D"
+  ]
+}
+```
+
+Merkle URL result with `leaf=AE4F281DF5A5D0FF3CAD6371F76D5C29B6D953EC`.
+```json
+{
+  "depth": 3,
+  "nodesCount": 6,
+  "leavesCount": 5,
+  "root": "6513D6A1582DAE614D8A3B364BF3C64C513D236B",
+  "leaf": {
+    "hash": "AE4F281DF5A5D0FF3CAD6371F76D5C29B6D953EC",
+    "value": // JSON value (object, string, int, ...)
+  }
+}
+```
+
+### Duniter Merkle trees leaves
+
+Each tree manages different data, and has a different goal. Hence, each tree has its own rules on how are generated and sorted tree leaves.
+Here is a summup of such rules:
+
+
+Merkle URL             | Leaf                      | Sort
+---------------------- | --------------------------| -------------
+`network/peers (GET)`    | Hash of the peers' pubkey | By hash string sort, ascending.
+
+#### Unicity
+
+It has to be noted that **possible conflict exists** for leaves, as every leaf is hash, but is rather unlikely.
+
+## API
+
+### node/*
+
+#### `node/summary`
+**Goal**
+
+GET technical informations about this peer.
+
+**Parameters**
+
+*None*.
+
+**Returns**
+
+Technical informations about the node.
+```json
+{
+  "duniter": {
+    "software": "duniter",
+    "version": "0.10.3",
+    "forkWindowSize": 10
+  }
+}
+```
+
+#### `node/sandboxes`
+**Goal**
+
+GET filling and capacity of indentities, membership and transactions sandboxes of the requested peer.
+
+**Parameters**
+
+*None*.
+
+**Returns**
+
+Technical informations about identities, membership and transactions sandboxes.
+```json
+{
+  "identities": {
+    "size": 5000,
+    "free": 4626
+  },
+  "memberships": {
+    "size": 5000,
+    "free": 4750
+  },
+  "transactions": {
+    "size": 200,
+    "free": 190
+  }
+}
+```
+
+### wot/*
+
+#### `wot/add`
+
+
+**Goal**
+
+POST [Identity](./Protocol.md#identity) data.
+
+**Parameters**
+
+Name  | Value | Method
+----  | ----- | ------
+`identity` | The raw identity. | POST
+
+**Returns**
+
+The available validated data for this public key.
+```json
+{
+  "pubkey": "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY",
+  "uids": [
+    {
+      "uid": "udid2;c;TOCQUEVILLE;FRANCOIS-XAVIER-ROBE;1989-07-14;e+48.84+002.30;0;",
+      "meta": {
+        "timestamp": "44-76522E321B3380B058DB6D9E66121705EEA63610869A7C5B3E701CF6AF2D55A8"
+      },
+      "self": "J3G9oM5AKYZNLAB5Wx499w61NuUoS57JVccTShUbGpCMjCqj9yXXqNq7dyZpDWA6BxipsiaMZhujMeBfCznzyci",
+      "others": [
+        {
+          "pubkey": "9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB",
+          "meta": {
+            "timestamp": "22-2E910FCCCEE008C4B978040CA68211C2395C84C3E6BFB432A267384ED8CD22E5"
+          },
+          "signature": "42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r"
+        }
+      ]
+    }
+  ]
+}
+```
+
+#### `wot/certify`
+
+
+**Goal**
+
+POST [Certification](./Protocol.md#certification) data.
+
+**Parameters**
+
+Name  | Value | Method
+----  | ----- | ------
+`cert` | The raw certification. | POST
+
+**Returns**
+
+The available validated data for this public key.
+```json
+{
+  "issuer": "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY",
+  "timestamp": "44-76522E321B3380B058DB6D9E66121705EEA63610869A7C5B3E701CF6AF2D55A8",
+  "sig": "42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r",
+  "target": {
+    "issuer": "CqwuWfMsPqtUkWdUK6FxV6hPWeHaUfEcz7dFZZJA49BS",
+    "uid": "johnsnow",
+    "timestamp": "44-76522E321B3380B058DB6D9E66121705EEA63610869A7C5B3E701CF6AF2D55A8",
+    "sig": "42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r",
+  }
+}
+```
+
+#### `wot/revoke`
+
+
+**Goal**
+
+Remove an identity from Identity pool.
+
+> N.B.: An identity **written in the blockchain cannot be removed**.
+
+**Parameters**
+
+Name  | Value | Method
+----  | ----- | ------
+`revocation` | The raw revocation. | POST
+
+**Returns**
+
+True if operation went well. An HTTP error otherwise with body as error message.
+```json
+{
+  "result": true
+}
+```
+
+#### `wot/lookup/[search]`
+
+
+**Goal**
+
+GET [Public key](./Protocol.md#publickey) data.
+
+**Parameters**
+
+Name  | Value | Method
+----  | ----- | ------
+`search` | A string of data to look for (public key, uid). | URL
+
+**Returns**
+
+A list of public key data matching search string (may not return all results, check `partial` flag which is set to `false` if all results are here, ` true` otherwise).
+```json
+{
+  "partial": false,
+  "results": [
+    {
+      "pubkey": "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY",
+      "uids": [
+        {
+          "uid": "udid2;c;TOCQUEVILLE;FRANCOIS-XAVIER-ROBE;1989-07-14;e+48.84+002.30;0;",
+          "meta": {
+            "timestamp": "56-97A56CCE04A1B7A03264ADE09545B262CBE65E62DDA481B7D7C89EB4669F5435"
+          },
+          "self": "J3G9oM5AKYZNLAB5Wx499w61NuUoS57JVccTShUbGpCMjCqj9yXXqNq7dyZpDWA6BxipsiaMZhujMeBfCznzyci",
+          "revocation_sig": "CTmlh3tO4B8f8IbL8iDy5ZEr3jZDcxkPmDmRPQY74C39MRLXi0CKUP+oFzTZPYmyUC7fZrUXrb3LwRKWw1jEBQ==",
+          "revoked": false,
+          "others": [
+            {
+              "pubkey": "9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB",
+              "meta": {
+                "timestamp": "32-DB30D958EE5CB75186972286ED3F4686B8A1C2CD"
+              },
+              "signature": "42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r"
+            }
+          ]
+        }
+      ],
+      "signed": [
+        {
+          "uid": "snow",
+          "pubkey": "2P7y2UDiCcvsgSSt8sgHF3BPKS4m9waqKw4yXHCuP6CN",
+          "meta": {
+            "timestamp": "33-AB30D958EE5CB75186972286ED3F4686B8A1C2CD"
+          },
+          "revocation_sig": "CK6UDDJM3d0weE1RVtzFJnw/+J507lPAtspleHc59T4+N1tzQj1RRGWrzPiTknCjnCO6SxBSJX0B+MIUWrpNAw==",
+          "revoked": false,
+          "signature": "Xbr7qhyGNCmLoVuuKnKIbrdmtCvb9VBIEY19izUNwA5nufsjNm8iEsBTwKWOo0lq5O1+AAPMnht8cm2JjMq8AQ=="
+        },
+        {
+          "uid": "snow",
+          "pubkey": "2P7y2UDiCcvsgSSt8sgHF3BPKS4m9waqKw4yXHCuP6CN",
+          "meta": {
+            "timestamp": "978-B38F54242807DFA1A12F17E012D355D8DB92CA6E947FC5D147F131B30C639163"
+          },
+          "revocation_sig": "a7SFapoVaXq27NU+wZj4afmxp0SbwLGqLJih8pfX6TRKPvNp/V93fbKixbqg10cwa1CadNenztxq3ZgOivqADw==",
+          "revoked": false,
+          "signature": "HU9VPwC4EqPJwATPuyUJM7HLjfig5Ke1CKonL9Q78n5/uNSL2hkgE9Pxsor8CCJfkwCxh66NjGyqnGYqZnQMAg=="
+        },
+        {
+          "uid": "snow",
+          "pubkey": "7xapQvvxQ6367bs8DsskEf3nvQAgJv97Yu11aPbkCLQj",
+          "meta": {
+            "timestamp": "76-0DC977717C49E69A78A67C6A1526EC17ED380BC68F0C38D290A954471A1349B7"
+          },
+          "revocation_sig": "h8D/dx/z5K2dx06ktp7fnmLRdxkdV5wRkJgnmEvKy2k55mM2RyREpHfD7t/1CC5Ew+UD0V9N27PfaoLxZc1KCQ==",
+          "revoked": true,
+          "signature": "6S3x3NwiHB2QqYEY79x4wCUYHcDctbazfxIyxejs38V1uRAl4DuC8R3HJUfD6wMSiWKPqbO+td+8ZMuIn0L8AA=="
+        },
+        {
+          "uid": "cat",
+          "pubkey": "CK2hBp25sYQhdBf9oFMGHyokkmYFfzSCmwio27jYWAN7",
+          "meta": {
+            "timestamp": "63-999677597FC04E6148860AE888A2E1942DF0E1E732C31500BA8EFF07F06FEC0C"
+          },
+          "revocation_sig": "bJyoM2Tz4hltVXkLvYHOOmLP4qqh2fx7aMLkS5q0cMoEg5AFER3iETj13uoFyhz8yiAKESyAZSDjjQwp8A1QDw==",
+          "revoked": false,
+          "signature": "AhgblSOdxUkLwpUN9Ec46St3JGaw2jPyDn/mLcR4j3EjKxUOwHBYqqkxcQdRz/6K4Qo/xMa941MgUp6NjNbKBA=="
+        }
+      ]
+    }
+  ]
+}
+```
+
+#### `wot/members`
+
+
+**Goal**
+
+GET the list of current Web of Trust members.
+
+**Parameters**
+
+*None*.
+
+**Returns**
+
+A list of public key + uid.
+```json
+{
+  "results": [
+    { "pubkey": "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY", "uid": "cat" },
+    { "pubkey": "9kNEiyseUNoPn3pmNUhWpvCCwPRgavsLu7YFKZuzzd1L", "uid": "tac" },
+    { "pubkey": "9HJ9VXa9wc6EKC6NkCi8b5TKWBot68VhYDg7kDk5T8Cz", "uid": "toc" }
+  ]
+}
+```
+
+#### `wot/requirements/[pubkey]`
+
+
+**Goal**
+
+GET requirements to be filled by pubkey to become a member.
+
+**Parameters**
+
+Name  | Value | Method
+----  | ----- | ------
+`pbkey` | Public key to check. | URL
+
+**Returns**
+
+A list of identities matching this pubkey and the requirements of each identities to become a member.
+
+> If the pubkey is matching a member, only one identity may be displayed: the one which is a member.
+
+```json
+{
+  "pubkey": "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY",
+  "identities": [{
+      "uid": "tobi",
+      "meta": {
+        "timestamp": "12-20504546F37853625C1E695B757D93CFCC6E494069D53F73748E428947933E45"
+      },
+      "outdistanced": true,
+      "certifications": 2,
+      "membershipMissing": true
+    }
+    ...
+  ]
+}
+```
+
+#### `wot/certifiers-of/[search]`
+
+
+**Goal**
+
+GET [Certification](./Protocol.md#certification) data over a member.
+
+**Parameters**
+
+Name  | Value | Method
+----  | ----- | ------
+`search` | Public key or uid of a *member* (or someone who *was a member*) we want see the certifications. | URL
+
+**Returns**
+
+A list of certifications issued to the member by other members (or who used to be members), with `written` data indicating wether the certification is written in the blockchain or not.
+
+Each certification also has:
+
+* a `isMember` field to indicate wether the issuer of the certification is still a member or not.
+* a `written` field to indicate the block where the certification was written (or null if not written yet).
+
+```json
+{
+  "pubkey": "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY",
+  "uid": "user identifier",
+  "isMember": true,
+  sigDate: 1421787461,
+  "certifications": [
+    {
+      "pubkey": "9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB",
+      "uid": "certifier uid",
+      "cert_time": {
+        "block": 88,
+        "medianTime": 1509991044
+      },
+      sigDate: 1421787461,
+      "written": {
+        "number": 872768,
+        "hash": "D30978C9D6C5A348A8188603F039423D90E50DC5"
+      },
+      "isMember": true,
+      "signature": "42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r"
+    },
+    ...
+  ]
+}
+```
+
+#### `wot/certified-by/[search]`
+
+
+**Goal**
+
+GET [Certification](./Protocol.md#certification) data over a member.
+
+**Parameters**
+
+Name  | Value | Method
+----  | ----- | ------
+`search` | Public key or uid of a *member* (or someone who *was a member*) we want see the certifications. | URL
+
+**Returns**
+
+A list of certifications issued by the member to other members (or who used to be members), with `written` data indicating wether the certification is written in the blockchain or not.
+
+Each certification also has:
+
+* a `isMember` field to indicate wether the issuer of the certification is still a member or not.
+* a `written` field to indicate the block where the certification was written (or null if not written yet).
+```json
+{
+  "pubkey": "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY",
+  "uid": "user identifier",
+  "isMember": true,
+  sigDate: 1421787461,
+  "certifications": [
+    {
+      "pubkey": "9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB",
+      "uid": "certifier uid",
+      "cert_time": {
+        "block": 88,
+        "medianTime": 1509991044
+      },
+      sigDate: 1421787461,
+      "written": {
+        "number": 872768,
+        "hash": "D30978C9D6C5A348A8188603F039423D90E50DC5"
+      },
+      "isMember": true,
+      "signature": "42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r"
+    },
+    ...
+  ]
+}
+```
+
+#### `wot/identity-of/[search]`
+
+
+**Goal**
+
+GET identity data written for a member.
+
+**Parameters**
+
+Name  | Value | Method
+----  | ----- | ------
+`search` | Public key or uid of a *member* we want see the attached identity. | URL
+
+**Returns**
+
+Identity data written in the blockchain.
+```json
+{
+  "pubkey": "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY",
+  "uid": "user identifier",
+  "sigDate": "21-EB18A8D89256EA80195990C91AD399B798F92EE8187F775DF7F4823C46E61F00"
+}
+```
+
+
+### blockchain/*
+
+#### `blockchain/parameters`
+
+**Goal**
+
+GET the blockchain parameters used by this node.
+
+**Parameters**
+
+*None*.
+
+**Returns**
+
+The synchronization parameters.
+```json
+{
+  currency: "beta_brouzouf",
+  c: 0.01,
+  dt: 302400,
+  ud0: 100,
+  sigPeriod: 7200,
+  sigStock: 45,
+  sigWindow: 604800,
+  sigValidity: 2629800,
+  sigQty: 3,
+  idtyWindow: 604800,
+  msWindow: 604800,
+  xpercent: 5,
+  msValidity: 2629800,
+  stepMax: 3,
+  medianTimeBlocks: 11,
+  avgGenTime: 600,
+  dtDiffEval: 10,
+  percentRot: 0.67
+}
+```
+
+Parameters meaning is described under [Protocol parameters](./Protocol.md#protocol-parameters).
+
+#### `blockchain/membership`
+
+
+**Goal**
+
+POST a [Membership](./Protocol.md#membership) document.
+
+**Parameters**
+
+Name | Value | Method
+---- | ----- | ------
+`membership` | The membership document (with signature). | POST
+
+**Returns**
+
+The posted membership request.
+```json
+{
+  "signature": "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==",
+  "membership": {
+    "version": "2",
+    "currency": "beta_brouzouf",
+    "issuer": "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY",
+    "membership": "IN",
+    "sigDate": 1390739944,
+    "uid": "superman63"
+  }
+}
+```
+
+#### `blockchain/memberships/[search]`
+
+
+**Goal**
+
+GET [Membership](./Protocol.md#membership) data written for a member.
+
+**Parameters**
+
+Name  | Value | Method
+----  | ----- | ------
+`search` | Public key or uid of a *member* we want see the memberships. | URL
+
+**Returns**
+
+A list of memberships issued by the *member* and written in the blockchain.
+```json
+{
+  "pubkey": "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY",
+  "uid": "user identifier",
+  "sigDate": 1390739944,
+  "memberships": [
+    {
+	  "version": "2",
+	  "currency": "beta_brouzouf",
+	  "membership": "IN",
+	  "blockNumber": 678,
+	  "blockHash": "000007936DF3CC32BFCC1023D1258EC9E485D474",
+	  "written_number": null
+	},
+    ...
+  ]
+}
+```
+
+#### `blockchain/block`
+
+**Goal**
+
+POST a new block to add to the blockchain.
+
+**Parameters**
+
+Name               | Value                          | Method
+------------------ | ------------------------------ | ------
+`block`             | The raw block to be added     | POST
+`signature`         | Signature of the raw block    | POST
+
+**Returns**
+
+The promoted block if successfully added to the blockchain (see [block/[number]](#blockchainblocknumber) return object).
+
+#### `blockchain/block/[NUMBER]`
+
+**Goal**
+
+GET the promoted block whose number `NUMBER`.
+
+**Parameters**
+
+Name               | Value                                                         | Method
+------------------ | ------------------------------------------------------------- | ------
+`NUMBER`           | The promoted block number (integer value) we want to see.  | URL
+
+**Returns**
+
+The promoted block if it exists (otherwise return HTTP 404).
+```json
+{
+  "version": 2,
+  "currency": "beta_brouzouf",
+  "nonce": 28,
+  "inner_hash": "FD09B0F7CEC5A575CA6E528DC4C854B612AE77B7283F48E0D28677F5C9C9D0DD",
+  "number": 1,
+  "time": 1408996317,
+  "medianTime": 1408992543,
+  "dividend": 254,
+  "monetaryMass": 18948,
+  "issuer": "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY",
+  "previousHash": "0009A7A62703F976F683BBA500FC0CB832B8220D",
+  "previousIssuer": "CYYjHsNyg3HMRMpTHqCJAN9McjH5BwFLmDKGV3PmCuKp",
+  "membersCount": 4,
+  "hash": "0000F40BDC0399F2E84000468628F50A122B5F16",
+  "identities": [
+    "9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB:2D96KZwNUvVtcapQPq2mm7J9isFcDCfykwJpVEZwBc7tCgL4qPyu17BT5ePozAE9HS6Yvj51f62Mp4n9d9dkzJoX:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:crowlin"
+  ],
+  "joiners": [
+    "9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB:2XiBDpuUdu6zCPWGzHXXy8c4ATSscfFQG9DjmqMZUxDZVt1Dp4m2N5oHYVUfoPdrU9SLk4qxi65RNrfCVnvQtQJk:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:crowlin"
+  ],
+  "leavers": [
+    "9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB:2XiBDpuUdu6zCPWGzHXXy8c4ATSscfFQG9DjmqMZUxDZVt1Dp4m2N5oHYVUfoPdrU9SLk4qxi65RNrfCVnvQtQJk:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:crowlin"
+  ],
+  "revoked": [
+    "9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB:2D96KZwNUvVtcapQPq2mm7J9isFcDCfykwJpVEZwBc7tCgL4qPyu17BT5ePozAE9HS6Yvj51f62Mp4n9d9dkzJoX"
+  ],
+  "excluded": [
+    "9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB"
+  ],
+  "certifications": [
+    "CYYjHsNyg3HMRMpTHqCJAN9McjH5BwFLmDKGV3PmCuKp:9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB:1505900000:2XiBDpuUdu6zCPWGzHXXy8c4ATSscfFQG9DjmqMZUxDZVt1Dp4m2N5oHYVUfoPdrU9SLk4qxi65RNrfCVnvQtQJk"
+  ],
+  "transactions": [
+    {
+      "signatures": [
+        "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw=="
+        "2D96KZwNUvVtcapQPq2mm7J9isFcDCfykwJpVEZwBc7tCgL4qPyu17BT5ePozAE9HS6Yvj51f62Mp4n9d9dkzJoX",
+        "2XiBDpuUdu6zCPWGzHXXy8c4ATSscfFQG9DjmqMZUxDZVt1Dp4m2N5oHYVUfoPdrU9SLk4qxi65RNrfCVnvQtQJk"
+      ],
+        "version": 2,
+        "currency": "beta_brouzouf",
+        "issuers": [
+          "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY",
+          "CYYjHsNyg3HMRMpTHqCJAN9McjH5BwFLmDKGV3PmCuKp",
+          "9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB"
+        ],
+        "inputs": [
+          "T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:0",
+          "T:3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435:0",
+          "D:4745EEBA84D4E3C2BDAE4768D4E0F5A671531EE1B0B9F5206744B4551C664FDF:243",
+          "T:3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435:1",
+          "T:67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B:0",
+          "D:521A760049DF4FAA602FEF86B7A8E306654502FA3A345F6169B8468B81E71AD3:187"
+       ],
+       "unlocks": [
+          "0:SIG(0)",
+          "1:SIG(2)",
+          "2:SIG(1)",
+          "3:SIG(1)",
+          "4:SIG(0)",
+          "5:SIG(0)"
+       ],
+      "outputs": [
+        "30:SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)",
+        "156:SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx)",
+        "49:SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i)"
+      ]
+    }
+  ],
+  "signature": "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==",
+}
+```
+
+#### `blockchain/blocks/[COUNT]/[FROM]`
+
+**Goal**
+
+GET the `[COUNT]` promoted blocks from `[FROM]` number, inclusive.
+
+**Parameters**
+
+Name               | Value                                                         | Method
+------------------ | ------------------------------------------------------------- | ------
+`COUNT`           | The number of blocks we want to see.  | URL
+`FROM`           | The starting block.  | URL
+
+**Returns**
+
+The promoted blocks if it exists block `[FROM]` (otherwise return HTTP 404). Result is an array whose values are the same structure as [/blockchain/block/[number]](#blockchainblocknumber).
+```json
+{
+  "blocks": [
+    { number: 2, ... },
+    { number: 3, ... }
+  ]
+}
+```
+
+#### `blockchain/current`
+
+Same as [block/[number]](#blockchainblocknumber), but return last accepted block.
+
+#### `blockchain/hardship/[PUBKEY]`
+
+**Goal**
+
+GET hardship level for given member's pubkey for writing next block.
+
+**Parameters**
+
+Name              | Value                     | Method
+----              | -----                     | ------
+`PUBKEY` | Member's pubkey.   | URL
+
+**Returns**
+
+The hardship value (`level`) + `block` number.
+```json
+{
+  "block": 598,
+  "level": 3
+}
+
+```
+
+#### `blockchain/difficulties`
+
+**Goal**
+
+GET hardship level for all member's uid for writing next block.
+
+**Parameters**
+
+None.
+
+**Returns**
+
+The respective difficulty of each member in the last `IssuersFrame` blocks for current block.
+```json
+{
+  "block": 598,
+  "levels": [{
+    "uid": "jack",
+    "level": 8
+  },{
+    "uid": "cat",
+    "level": 4
+  }]
+}
+
+```
+
+#### `blockchain/with/newcomers`
+**Goal**
+
+GET the block numbers containing newcomers (new identities).
+
+**Parameters**
+
+*None*.
+
+**Returns**
+
+Block numbers.
+```json
+{
+  "result": {
+    "blocks": [223,813]
+  }
+}
+```
+
+#### `blockchain/with/certs`
+**Goal**
+
+GET the block numbers containing certifications.
+
+**Parameters**
+
+*None*.
+
+**Returns**
+
+Block numbers.
+```json
+{
+  "result": {
+    "blocks": [223,813]
+  }
+}
+```
+
+#### `blockchain/with/joiners`
+**Goal**
+
+GET the block numbers containing joiners (newcomers or people coming back after exclusion).
+
+**Parameters**
+
+*None*.
+
+**Returns**
+
+Block numbers.
+```json
+{
+  "result": {
+    "blocks": [223,813]
+  }
+}
+```
+
+#### `blockchain/with/actives`
+**Goal**
+
+GET the block numbers containing actives (members updating their membership).
+
+**Parameters**
+
+*None*.
+
+**Returns**
+
+Block numbers.
+```json
+{
+  "result": {
+    "blocks": [223,813]
+  }
+}
+```
+
+#### `blockchain/with/leavers`
+**Goal**
+
+GET the block numbers containing leavers (members leaving definitely the currency).
+
+**Parameters**
+
+*None*.
+
+**Returns**
+
+Block numbers.
+```json
+{
+  "result": {
+    "blocks": [223,813]
+  }
+}
+```
+
+#### `blockchain/with/revoked`
+**Goal**
+
+GET the block numbers containing revoked members.
+
+**Parameters**
+
+*None*.
+
+**Returns**
+
+Block numbers.
+```json
+{
+  "result": {
+    "blocks": [223,813]
+  }
+}
+```
+
+#### `blockchain/with/excluded`
+**Goal**
+
+GET the block numbers containing excluded members.
+
+**Parameters**
+
+*None*.
+
+**Returns**
+
+Block numbers.
+```json
+{
+  "result": {
+    "blocks": [223,813]
+  }
+}
+```
+
+#### `blockchain/with/ud`
+**Goal**
+
+GET the block numbers containing Universal Dividend.
+
+**Parameters**
+
+*None*.
+
+**Returns**
+
+Block numbers.
+```json
+{
+  "result": {
+    "blocks": [223,813]
+  }
+}
+```
+
+#### `blockchain/with/tx`
+**Goal**
+
+GET the block numbers containing transactions.
+
+**Parameters**
+
+*None*.
+
+**Returns**
+
+Block numbers.
+```json
+{
+  "result": {
+    "blocks": [223,813]
+  }
+}
+```
+
+#### `blockchain/branches`
+
+**Goal**
+
+GET current branches of the node.
+
+**Parameters**
+
+*None*.
+
+**Returns**
+
+Top block of each branch, i.e. the last received block of each branch. An array of 4 blocks would mean the node has 4 branches,
+3 would mean 3 branches, and so on.
+
+```json
+{
+  "blocks": [
+    { number: 2, ... },
+    { number: 3, ... }
+  ]
+}
+```
+
+### network/*
+
+This URL is used for Duniter Gossip protocol (exchanging UCG messages).
+
+#### `network/peers`
+**Goal**
+
+GET the exhaustive list of peers known by the node.
+
+**Parameters**
+
+*None*.
+
+**Returns**
+
+List of peering entries.
+```json
+{
+  "peers": [
+    {
+      "version": "2",
+      "currency": "meta_brouzouf",
+      "status": "UP",
+      "first_down": null,
+      "last_try": null,
+      "pubkey": "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk",
+      "block": "45180-00000E577DD4B308B98D0ED3E43926CE4D22E9A8",
+      "signature": "GKTrlUc4um9lQuj9UI8fyA/n/JKieYqBYcl9keIWfAVOnvHamLHaqGzijsdX1kNt64cadcle/zkd7xOgMTdQAQ==",
+      "endpoints": [
+        "BASIC_MERKLED_API metab.ucoin.io 88.174.120.187 9201"
+      ]
+    },
+    {
+      "version": "2",
+      "currency": "meta_brouzouf",
+      "status": "UP",
+      "first_down": null,
+      "last_try": null,
+      "pubkey": "2aeLmae5d466y8D42wLK5MknwUBCR6MWWeixRzdTQ4Hu",
+      "block": "45182-0000064EEF412C1CDD1B370CC45A3BC3B9743464",
+      "signature": "kbdTay1OirDqG/E3jyCaDlL7HVVHb9/BXvNHAg+xO9sSA+NgmBo/4mEqL9b7hH0UnbXHss6TfuvxAHZLmBqsCw==",
+      "endpoints": [
+        "BASIC_MERKLED_API twiced.fr 88.174.120.187 9223"
+      ]
+    },
+    ...
+  ]
+}
+```
+
+#### `network/peering`
+**Goal**
+
+GET the peering informations of this node.
+
+**Parameters**
+
+*None*.
+
+**Returns**
+
+Peering entry of the node.
+```json
+{
+  "version": "2",
+  "currency": "beta_brouzouf",
+  "pubkey": "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY",
+  "endpoints": [
+    "BASIC_MERKLED_API some.dns.name 88.77.66.55 2001:0db8:0000:85a3:0000:0000:ac1f 9001",
+    "BASIC_MERKLED_API some.dns.name 88.77.66.55 2001:0db8:0000:85a3:0000:0000:ac1f 9002",
+    "OTHER_PROTOCOL 88.77.66.55 9001",
+  ],
+  "signature": "42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r"
+}
+```
+
+#### `network/peering/peers (GET)`
+**Goal**
+
+Merkle URL refering to peering entries of every node inside the currency network.
+
+**Parameters**
+
+*None*.
+
+**Returns**
+
+Merkle URL result.
+```json
+{
+  "depth": 3,
+  "nodesCount": 6,
+  "leavesCount": 5,
+  "root": "114B6E61CB5BB93D862CA3C1DFA8B99E313E66E9"
+}
+```
+
+Merkle URL leaf: peering entry
+```json
+{
+  "hash": "2E69197FAB029D8669EF85E82457A1587CA0ED9C",
+  "value": {
+    "version": "2",
+    "currency": "beta_brouzouf",
+    "pubkey": "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY",
+    "endpoints": [
+      "BASIC_MERKLED_API some.dns.name 88.77.66.55 2001:0db8:0000:85a3:0000:0000:ac1f 9001",
+      "BASIC_MERKLED_API some.dns.name 88.77.66.55 2001:0db8:0000:85a3:0000:0000:ac1f 9002",
+      "OTHER_PROTOCOL 88.77.66.55 9001",
+    ],
+    "signature": "42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r"
+  }
+}
+```
+
+#### `network/peering/peers (POST)`
+**Goal**
+
+POST a peering entry document.
+
+**Parameters**
+
+Name        | Value                               | Method
+----------- | ----------------------------------- | ------
+`peer`      | The peering entry document.         | POST
+
+**Returns**
+
+The posted entry.
+```json
+{
+  "version": "2",
+  "currency": "beta_brouzouf",
+  "pubkey": "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY",
+  "endpoints": [
+    "BASIC_MERKLED_API some.dns.name 88.77.66.55 2001:0db8:0000:85a3:0000:0000:ac1f 9001",
+    "BASIC_MERKLED_API some.dns.name 88.77.66.55 2001:0db8:0000:85a3:0000:0000:ac1f 9002",
+    "OTHER_PROTOCOL 88.77.66.55 9001",
+  ],
+  "signature": "42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r"
+}
+```
+
+### tx/*
+
+#### `tx/process`
+**Goal**
+
+POST a transaction.
+
+**Parameters**
+
+Name              | Value                                                         | Method
+----------------- | ------------------------------------------------------------- | ------
+`transaction`     | The raw transaction.                                          | POST
+
+**Returns**
+
+The recorded transaction.
+```json
+{
+  "raw": "Version: 2\r\n...\r\n",
+  "transaction":
+  {
+      "signatures": [
+        "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw=="
+        "2D96KZwNUvVtcapQPq2mm7J9isFcDCfykwJpVEZwBc7tCgL4qPyu17BT5ePozAE9HS6Yvj51f62Mp4n9d9dkzJoX",
+        "2XiBDpuUdu6zCPWGzHXXy8c4ATSscfFQG9DjmqMZUxDZVt1Dp4m2N5oHYVUfoPdrU9SLk4qxi65RNrfCVnvQtQJk"
+      ],
+        "version": 2,
+        "currency": "beta_brouzouf",
+        "issuers": [
+          "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY",
+          "CYYjHsNyg3HMRMpTHqCJAN9McjH5BwFLmDKGV3PmCuKp",
+          "9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB"
+        ],
+        "inputs": [
+          "T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:0",
+          "T:3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435:0",
+          "D:4745EEBA84D4E3C2BDAE4768D4E0F5A671531EE1B0B9F5206744B4551C664FDF:243",
+          "T:3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435:1",
+          "T:67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B:0",
+          "D:521A760049DF4FAA602FEF86B7A8E306654502FA3A345F6169B8468B81E71AD3:187"
+       ],
+       "unlocks": [
+          "0:SIG(0)",
+          "1:SIG(2)",
+          "2:SIG(1)",
+          "3:SIG(1)",
+          "4:SIG(0)",
+          "5:SIG(0)"
+       ],
+      "outputs": [
+        "30:SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)",
+        "156:SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx)",
+        "49:SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i)"
+      ]
+  }
+}
+```
+
+
+#### `tx/sources/[pubkey]`
+
+**Goal**
+
+GET a list of available sources.
+
+**Parameters**
+
+Name              | Value                       | Method
+----              | -----                       | ------
+`pubkey`           | Owner of the coins' pubkey. | URL
+
+**Returns**
+
+A list of available sources for the given `pubkey`.
+```json
+{
+  "currency": "beta_brouzouf",
+  "pubkey": "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY",
+  "sources": [
+    {
+      "type: "D",
+      "noffset": 5,
+      "identifier": "6C20752F6AD06AEA8D0BB46BB8C4F68641A34C79",
+      "amount": 100
+    },
+    {
+      "type: "D",
+      "noffset": 18,
+      "identifier": "DB7D88E795E42CF8CFBFAAFC77379E97847F9B42",
+      "amount": 110
+    },
+    {
+      "type: "T",
+      "noffset": 55,
+      "identifier": "E614E814179F313B1113475E6319EF4A3D470AD0",
+      "amount": 30
+    }
+  ]
+}
+```
+
+
+#### `tx/history/[pubkey]`
+
+**Goal**
+
+Get the wallet transaction history
+
+**parameters**
+
+Name              | Value							| Method
+----              | -----							| ------
+`pubkey`          | Wallet public key.				| URL
+
+**Returns**
+
+The full transaction history for the given `pubkey`
+```json
+{
+  "currency": "meta_brouzouf",
+  "pubkey": "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk",
+  "history": {
+    "sent": [
+      {
+        "version": 2,
+        "received": null,
+        "issuers": [
+          "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk"
+        ],
+        "inputs": [
+          "D:000A8362AE0C1B8045569CE07735DE4C18E81586:125"
+        ],
+        "outputs": [
+          "5:SIG(8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU)",
+          "95:SIG(HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk)"
+        ],
+        "comment": "Essai",
+        "signatures": [
+          "8zzWSU+GNSNURnH1NKPT/TBoxngEc/0wcpPSbs7FqknGxud+94knvT+dpe99k6NwyB5RXvOVnKAr4p9/KEluCw=="
+        ],
+        "hash": "FC7BAC2D94AC9C16AFC5C0150C2C9E7FBB2E2A09",
+        "block_number": 173,
+        "time": 1421932545
+      }
+    ],
+    "received": [
+      {
+        "version": 2,
+        "received": null,
+        "issuers": [
+          "8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU"
+        ],
+        "inputs": [
+          "D:000A8362AE0C1B8045569CE07735DE4C18E81586:125"
+        ],
+        "outputs": [
+          "7:SIG(HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk)",
+          "93:SIG(8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU)"
+        ],
+        "comment": "",
+        "signatures": [
+          "1Mn8q3K7N+R4GZEpAUm+XSyty1Uu+BuOy5t7BIRqgZcKqiaxfhAUfDBOcuk2i4TJy1oA5Rntby8hDN+cUCpvDg=="
+        ],
+        "hash": "5FB3CB80A982E2BDFBB3EA94673A74763F58CB2A",
+        "block_number": 207,
+        "time": 1421955525
+      },
+      {
+        "version": 2,
+        "received": null,
+        "issuers": [
+          "J78bPUvLjxmjaEkdjxWLeENQtcfXm7iobqB49uT1Bgp3"
+        ],
+        "inputs": [
+          "T:6A50FF82410387B239489CE38B34E0FDDE1697FE:0"
+        ],
+        "outputs": [
+          "42:SIG(HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk)",
+          "9958:SIG(J78bPUvLjxmjaEkdjxWLeENQtcfXm7iobqB49uT1Bgp3)"
+        ],
+        "comment": "",
+        "signatures": [
+          "XhBcCPizPiWdKeXWg1DX/FTQst6DppEjsYEtoAZNA0P11reXtgc9IduiIxNWzNjt/KvTw8APkSI8/Uf31QQVDA=="
+        ],
+        "hash": "ADE7D1C4002D6BC10013C34CE22733A55173BAD4",
+        "block_number": 15778,
+        "time": 1432314584
+      }
+    ],
+    "sending": [
+	  {
+        "version": 2,
+        "received": 1459691641,
+        "issuers": [
+          "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk"
+        ],
+        "inputs": [
+          "0:D:8196:000022AD426FE727C707D847EC2168A64C577706:5872"
+        ],
+        "outputs": [
+          "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:5871"
+          "2sq8bBDQGK74f1eD3mAPQVgHCmFdijZr9nbv16FwbokX:1",
+        ],
+        "comment": "some comment",
+        "signatures": [
+          "kLOAAy7/UldQk7zz4I7Jhv9ICuGYRx7upl8wH8RYL43MMF6+7MbPh3QRN1qNFGpAfa3XMWIQmbUWtjZKP6OfDA=="
+        ],
+        "hash": "BA41013F2CD38EDFFA9D38A275F8532DD906A2DE"
+      }
+    ],
+    "receiving": [
+	 {
+        "version": 2,
+        "received": 1459691641,
+        "issuers": [
+          "2sq8bBDQGK74f1eD3mAPQVgHCmFdijZr9nbv16FwbokX"
+        ],
+        "inputs": [
+          "0:D:8196:000022AD426FE727C707D847EC2168A64C577706:4334"
+        ],
+        "outputs": [
+          "2sq8bBDQGK74f1eD3mAPQVgHCmFdijZr9nbv16FwbokX:1",
+          "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:4333"
+        ],
+        "comment": "some comment",
+        "signatures": [
+          "DRiZinUEKrrLiJNogtydzwEbmETrvWiLNYXCiJsRekxTLyU5g4LjnwiLp/XlvmIekjJK5n/gullLWrHUBvFSAw==
+        ],
+        "hash": "A0A511131CD0E837204A9441B3354918AC4CE671"
+      }
+	]
+  }
+}
+```
+
+#### `tx/history/[PUBKEY]/blocks/[from]/[to]`
+
+**Goal**
+
+Get the wallet transaction history
+
+**parameters**
+
+Name				| Value							| Method
+----				| -----							| ------
+`pubkey`			| Wallet public key.			| URL
+`from`				| The starting block.			| URL
+`to`				| the ending block.				| URL
+
+**Returns**
+
+The transaction history for the given `pubkey` and between the given `from` and `to` blocks. 
+```json
+{
+  "currency": "meta_brouzouf",
+  "pubkey": "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk",
+  "history": {
+    "sent": [
+      {
+        "version": 2,
+        "issuers": [
+          "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk"
+        ],
+        "inputs": [
+          "0:D:125:000A8362AE0C1B8045569CE07735DE4C18E81586:100"
+        ],
+        "outputs": [
+          "8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU:5",
+          "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:95"
+        ],
+        "comment": "Essai",
+        "signatures": [
+          "8zzWSU+GNSNURnH1NKPT/TBoxngEc/0wcpPSbs7FqknGxud+94knvT+dpe99k6NwyB5RXvOVnKAr4p9/KEluCw=="
+        ],
+        "hash": "FC7BAC2D94AC9C16AFC5C0150C2C9E7FBB2E2A09",
+        "block_number": 173,
+        "time": 1421932545
+      }
+    ],
+    "received": [
+      {
+        "version": 2,
+        "issuers": [
+          "8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU"
+        ],
+        "inputs": [
+          "0:D:125:000A8362AE0C1B8045569CE07735DE4C18E81586:100"
+        ],
+        "outputs": [
+          "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:7",
+          "8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU:93"
+        ],
+        "comment": "",
+        "signatures": [
+          "1Mn8q3K7N+R4GZEpAUm+XSyty1Uu+BuOy5t7BIRqgZcKqiaxfhAUfDBOcuk2i4TJy1oA5Rntby8hDN+cUCpvDg=="
+        ],
+        "hash": "5FB3CB80A982E2BDFBB3EA94673A74763F58CB2A",
+        "block_number": 207,
+        "time": 1421955525
+      },
+      {
+        "version": 2,
+        "issuers": [
+          "J78bPUvLjxmjaEkdjxWLeENQtcfXm7iobqB49uT1Bgp3"
+        ],
+        "inputs": [
+          "0:T:15128:6A50FF82410387B239489CE38B34E0FDDE1697FE:10000"
+        ],
+        "outputs": [
+          "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:42",
+          "J78bPUvLjxmjaEkdjxWLeENQtcfXm7iobqB49uT1Bgp3:9958"
+        ],
+        "comment": "",
+        "signatures": [
+          "XhBcCPizPiWdKeXWg1DX/FTQst6DppEjsYEtoAZNA0P11reXtgc9IduiIxNWzNjt/KvTw8APkSI8/Uf31QQVDA=="
+        ],
+        "hash": "ADE7D1C4002D6BC10013C34CE22733A55173BAD4",
+        "block_number": 15778,
+        "time": 1432314584
+      }
+    ],
+    "sending": [
+	  {
+        "version": 2,
+        "issuers": [
+          "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk"
+        ],
+        "inputs": [
+          "0:D:8196:000022AD426FE727C707D847EC2168A64C577706:5872"
+        ],
+        "outputs": [
+          "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:5871"
+          "2sq8bBDQGK74f1eD3mAPQVgHCmFdijZr9nbv16FwbokX:1",
+        ],
+        "comment": "some comment",
+        "signatures": [
+          "kLOAAy7/UldQk7zz4I7Jhv9ICuGYRx7upl8wH8RYL43MMF6+7MbPh3QRN1qNFGpAfa3XMWIQmbUWtjZKP6OfDA=="
+        ],
+        "hash": "BA41013F2CD38EDFFA9D38A275F8532DD906A2DE"
+      }
+    ],
+    "receiving": [
+	 {
+        "version": 2,
+        "issuers": [
+          "2sq8bBDQGK74f1eD3mAPQVgHCmFdijZr9nbv16FwbokX"
+        ],
+        "inputs": [
+          "0:D:8196:000022AD426FE727C707D847EC2168A64C577706:4334"
+        ],
+        "outputs": [
+          "2sq8bBDQGK74f1eD3mAPQVgHCmFdijZr9nbv16FwbokX:1",
+          "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:4333"
+        ],
+        "comment": "some comment",
+        "signatures": [
+          "DRiZinUEKrrLiJNogtydzwEbmETrvWiLNYXCiJsRekxTLyU5g4LjnwiLp/XlvmIekjJK5n/gullLWrHUBvFSAw==
+        ],
+        "hash": "A0A511131CD0E837204A9441B3354918AC4CE671"
+      }
+	]
+  }
+}
+```
+
+#### `tx/history/[pubkey]/times/[from]/[to]`
+
+**Goal**
+
+Get the wallet transaction history
+
+**parameters**
+
+Name              | Value							| Method
+----              | -----							| ------
+`pubkey`          | Wallet public key.				| URL
+`from` | The starting timestamp limit. (optionnal) | URL
+`to`        | The ending timestamp. (optionnal)	| URL
+
+**Returns**
+
+The transaction history for the given `pubkey` and between the given `from` and `to` dates. 
+```json
+{
+  "currency": "meta_brouzouf",
+  "pubkey": "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk",
+  "history": {
+    "sent": [
+      {
+        "version": 2,
+        "issuers": [
+          "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk"
+        ],
+        "inputs": [
+          "0:D:125:000A8362AE0C1B8045569CE07735DE4C18E81586:100"
+        ],
+        "outputs": [
+          "8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU:5",
+          "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:95"
+        ],
+        "comment": "Essai",
+        "signatures": [
+          "8zzWSU+GNSNURnH1NKPT/TBoxngEc/0wcpPSbs7FqknGxud+94knvT+dpe99k6NwyB5RXvOVnKAr4p9/KEluCw=="
+        ],
+        "hash": "FC7BAC2D94AC9C16AFC5C0150C2C9E7FBB2E2A09",
+        "block_number": 173,
+        "time": 1421932545
+      }
+    ],
+    "received": [
+      {
+        "version": 2,
+        "issuers": [
+          "8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU"
+        ],
+        "inputs": [
+          "0:D:125:000A8362AE0C1B8045569CE07735DE4C18E81586:100"
+        ],
+        "outputs": [
+          "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:7",
+          "8Fi1VSTbjkXguwThF4v2ZxC5whK7pwG2vcGTkPUPjPGU:93"
+        ],
+        "comment": "",
+        "signatures": [
+          "1Mn8q3K7N+R4GZEpAUm+XSyty1Uu+BuOy5t7BIRqgZcKqiaxfhAUfDBOcuk2i4TJy1oA5Rntby8hDN+cUCpvDg=="
+        ],
+        "hash": "5FB3CB80A982E2BDFBB3EA94673A74763F58CB2A",
+        "block_number": 207,
+        "time": 1421955525
+      },
+      {
+        "version": 2,
+        "issuers": [
+          "J78bPUvLjxmjaEkdjxWLeENQtcfXm7iobqB49uT1Bgp3"
+        ],
+        "inputs": [
+          "0:T:15128:6A50FF82410387B239489CE38B34E0FDDE1697FE:10000"
+        ],
+        "outputs": [
+          "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:42",
+          "J78bPUvLjxmjaEkdjxWLeENQtcfXm7iobqB49uT1Bgp3:9958"
+        ],
+        "comment": "",
+        "signatures": [
+          "XhBcCPizPiWdKeXWg1DX/FTQst6DppEjsYEtoAZNA0P11reXtgc9IduiIxNWzNjt/KvTw8APkSI8/Uf31QQVDA=="
+        ],
+        "hash": "ADE7D1C4002D6BC10013C34CE22733A55173BAD4",
+        "block_number": 15778,
+        "time": 1432314584
+      }
+    ],
+    "sending": [
+	  {
+        "version": 2,
+        "issuers": [
+          "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk"
+        ],
+        "inputs": [
+          "0:D:8196:000022AD426FE727C707D847EC2168A64C577706:5872"
+        ],
+        "outputs": [
+          "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:5871"
+          "2sq8bBDQGK74f1eD3mAPQVgHCmFdijZr9nbv16FwbokX:1",
+        ],
+        "comment": "some comment",
+        "signatures": [
+          "kLOAAy7/UldQk7zz4I7Jhv9ICuGYRx7upl8wH8RYL43MMF6+7MbPh3QRN1qNFGpAfa3XMWIQmbUWtjZKP6OfDA=="
+        ],
+        "hash": "BA41013F2CD38EDFFA9D38A275F8532DD906A2DE"
+      }
+    ],
+    "receiving": [
+	 {
+        "version": 2,
+        "issuers": [
+          "2sq8bBDQGK74f1eD3mAPQVgHCmFdijZr9nbv16FwbokX"
+        ],
+        "inputs": [
+          "0:D:8196:000022AD426FE727C707D847EC2168A64C577706:4334"
+        ],
+        "outputs": [
+          "2sq8bBDQGK74f1eD3mAPQVgHCmFdijZr9nbv16FwbokX:1",
+          "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk:4333"
+        ],
+        "comment": "some comment",
+        "signatures": [
+          "DRiZinUEKrrLiJNogtydzwEbmETrvWiLNYXCiJsRekxTLyU5g4LjnwiLp/XlvmIekjJK5n/gullLWrHUBvFSAw==
+        ],
+        "hash": "A0A511131CD0E837204A9441B3354918AC4CE671"
+      }
+	]
+  }
+}
+```
+### ud/*
+
+#### `ud/history/[pubkey]`
+
+**Goal**
+
+Get the wallet universal dividend history
+
+**parameters**
+
+Name              | Value							| Method
+----              | -----							| ------
+`pubkey`          | Wallet public key.				| URL
+
+**Returns**
+
+The universal dividend history for the given `pubkey`. 
+```json
+{
+  "currency": "meta_brouzouf",
+  "pubkey": "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk",
+  "history": {
+    "history": [
+      {
+        "block_number": 125,
+        "consumed": true,
+        "time": 1421927007,
+        "amount": 100
+      },
+      {
+        "block_number": 410,
+        "consumed": false,
+        "time": 1422012828,
+        "amount": 100
+      },
+      {
+        "block_number": 585,
+        "consumed": true,
+        "time": 1422098800,
+        "amount": 100
+      }
+    ]
+  }
+}
+```
+
+
+### ws/*
+
+#### `ws/block`
+
+**Goal**
+
+A websocket entry point for receiving blocks.
+
+**Parameters**
+
+*None*.
+
+**Returns**
+
+Websocket connection.
+
+#### `ws/peer`
+
+**Goal**
+
+A websocket entry point for receiving peers.
+
+**Parameters**
+
+*None*.
+
+**Returns**
+
+Websocket connection.
diff --git a/test/fast/modules/bma/ddos-test.js b/test/fast/modules/bma/ddos-test.js
new file mode 100644
index 0000000000000000000000000000000000000000..c4127c62d6754fb0fc8f1a64260d235590a68dc9
--- /dev/null
+++ b/test/fast/modules/bma/ddos-test.js
@@ -0,0 +1,39 @@
+"use strict";
+// const should = require('should');
+// const co = require('co');
+// const limiter = require('../../app/lib/system/limiter');
+// const toolbox = require('../integration/tools/toolbox');
+// const user    = require('../integration/tools/user');
+// const bma     = require('../lib/bma');
+//
+// limiter.noLimit();
+//
+// const s1 = toolbox.server({
+//   pair: {
+//     pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd',
+//     sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'
+//   }
+// });
+//
+// describe('DDOS', () => {
+//
+//   before(() => co(function*() {
+//     limiter.noLimit();
+//     yield s1.initWithDAL().then(bma).then((bmapi) => {
+//       s1.bma = bmapi;
+//       bmapi.openConnections();
+//     });
+//   }));
+//
+//   it('should not be able to send more than 4 reqs/s', () => co(function*() {
+//     try {
+//       s1.bma.getDDOS().params.limit = 3;
+//       s1.bma.getDDOS().params.burst = 3;
+//       s1.bma.getDDOS().params.whitelist = [];
+//       yield Array.from({ length: 4 }).map(() => s1.get('/blockchain/current'));
+//       throw 'Wrong error thrown';
+//     } catch (e) {
+//       e.should.have.property('statusCode').equal(429);
+//     }
+//   }));
+// });
diff --git a/test/fast/modules/bma/limiter-test.js b/test/fast/modules/bma/limiter-test.js
new file mode 100644
index 0000000000000000000000000000000000000000..0ba0c0254a0b8c6597f4628b01d09e3245a52e65
--- /dev/null
+++ b/test/fast/modules/bma/limiter-test.js
@@ -0,0 +1,60 @@
+"use strict";
+// const should = require('should');
+// const co = require('co');
+// const limiter = require('../lib/limiter');
+// const toolbox = require('../integration/tools/toolbox');
+// const user    = require('../integration/tools/user');
+// const bma     = require('duniter-bma').duniter.methods.bma;
+//
+// limiter.noLimit();
+//
+// const s1 = toolbox.server({
+//   pair: {
+//     pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd',
+//     sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'
+//   }
+// });
+//
+// const cat = user('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 });
+//
+// let theLimiter;
+//
+// describe('Limiter', () => {
+//
+//   before(() => {
+//     limiter.withLimit();
+//     theLimiter = limiter.limitAsTest();
+//   });
+//
+//   it('should not be able to send more than 10 reqs/s', () => {
+//     theLimiter.canAnswerNow().should.equal(true);
+//     for (let i = 1; i <= 4; i++) {
+//         theLimiter.processRequest();
+//     }
+//     theLimiter.canAnswerNow().should.equal(true);
+//     theLimiter.processRequest(); // 5 in 1sec
+//     theLimiter.canAnswerNow().should.equal(false);
+//   });
+//
+//   it('should be able to send 1 more request (by minute constraint)', () => co(function*(){
+//     yield new Promise((resolve) => setTimeout(resolve, 1000));
+//     theLimiter.canAnswerNow().should.equal(true);
+//     theLimiter.processRequest(); // 1 in 1sec, 6 in 1min
+//     theLimiter.canAnswerNow().should.equal(false);
+//   }));
+//
+//   it('should work with BMA API', () => co(function*(){
+//     yield s1.initWithDAL().then(bma).then((bmapi) => bmapi.openConnections());
+//     yield cat.createIdentity();
+//     try {
+//       for (let i = 0; i < 11; i++) {
+//         yield s1.get('/wot/lookup/cat');
+//       }
+//       throw 'Should have thrown a limiter error';
+//     } catch (e) {
+//       e.should.have.property('error').property('ucode').equal(1006);
+//     }
+//   }));
+//
+//   after(() => limiter.noLimit());
+// });
diff --git a/test/fast/modules/bma/module-test.js b/test/fast/modules/bma/module-test.js
new file mode 100644
index 0000000000000000000000000000000000000000..9d9bb5063276b80aff738f16be61b54b32004dc4
--- /dev/null
+++ b/test/fast/modules/bma/module-test.js
@@ -0,0 +1,140 @@
+"use strict";
+const assert = require('assert');
+const should = require('should');
+const co  = require('co');
+const duniterBMA = require('../../../../app/modules/bma/index').BmaDependency
+const duniterKeypair = require('../../../../app/modules/keypair').KeypairDependency
+const network = require('../../../../app/modules/bma/lib/network').Network
+const duniter = require('../../../../index')
+const logger = require('../../../../app/lib/logger').NewLogger()
+const rp = require('request-promise');
+
+// Do not pollute the tests with logs
+logger.mute();
+
+const stack = duniter.statics.minimalStack();
+stack.registerDependency(duniterKeypair, 'duniter-keypair');
+stack.registerDependency(duniterBMA,     'duniter-bma');
+
+describe('Module usage', () => {
+
+  it('/node/summary should answer', () => co(function*() {
+    stack.registerDependency({
+      duniter: {
+        cli: [{
+          name: 'test1',
+          desc: 'Unit Test execution',
+          onDatabaseExecute: (server, conf, program, params, startServices) => co(function*() {
+            yield startServices();
+          })
+        }]
+      }
+    }, 'duniter-automated-test');
+    yield stack.executeStack(['node', 'index.js', 'test1',
+      '--memory',
+      '--ipv4', '127.0.0.1',
+      '--port', '10400'
+    ]);
+    const json = yield rp.get({
+      url: 'http://127.0.0.1:10400/node/summary',
+      json: true,
+      timeout: 1000
+    });
+    should.exist(json);
+    json.should.have.property('duniter').property('software').equal('duniter');
+  }));
+
+  it('remoteipv4 should NOT be filled if remote Host is declared', () => co(function*() {
+    stack.registerDependency({
+      duniter: {
+        cli: [{
+          name: 'test2',
+          desc: 'Unit Test execution',
+          onConfiguredExecute: (server, conf, program, params, startServices) => co(function*() {
+            conf.should.not.have.property('remoteipv4');
+            conf.should.have.property('remoteipv6').equal(undefined);
+            conf.should.have.property('remotehost').equal('localhost');
+          })
+        }]
+      }
+    }, 'duniter-automated-test');
+    yield stack.executeStack(['node', 'index.js', 'test2',
+      '--memory',
+      '--ipv4',    '127.0.0.1',
+      '--remoteh', 'localhost',
+      '--port',    '10400'
+    ]);
+  }));
+
+  it('remoteipv4 should NOT be filled if remote IPv6 is declared', () => co(function*() {
+    stack.registerDependency({
+      duniter: {
+        cli: [{
+          name: 'test3',
+          desc: 'Unit Test execution',
+          onConfiguredExecute: (server, conf, program, params, startServices) => co(function*() {
+            conf.should.not.have.property('remoteipv4');
+            conf.should.not.have.property('remotehost');
+            conf.should.have.property('remoteipv6').equal('::1');
+          })
+        }]
+      }
+    }, 'duniter-automated-test');
+    yield stack.executeStack(['node', 'index.js', 'test3',
+      '--memory',
+      '--ipv4',    '127.0.0.1',
+      '--ipv6', '::1',
+      '--port', '10400'
+    ]);
+  }));
+
+  it('remoteipv4 should be NOT be auto-filled if manual remoteipv4 is declared', () => co(function*() {
+    stack.registerDependency({
+      duniter: {
+        cli: [{
+          name: 'test4',
+          desc: 'Unit Test execution',
+          onConfiguredExecute: (server, conf, program, params, startServices) => co(function*() {
+            conf.should.not.have.property('remotehost');
+            conf.should.have.property('remoteipv6').equal(undefined);
+            conf.should.have.property('remoteipv4').equal('192.168.0.1');
+          })
+        }]
+      }
+    }, 'duniter-automated-test');
+    yield stack.executeStack(['node', 'index.js', 'test4',
+      '--memory',
+      '--remote4', '192.168.0.1',
+      '--ipv4', '127.0.0.1',
+      '--port', '10400'
+    ]);
+  }));
+
+  it('remoteipv4 should be filled if no remote is declared, but local IPv4 is', () => co(function*() {
+    stack.registerDependency({
+      duniter: {
+        cli: [{
+          name: 'test5',
+          desc: 'Unit Test execution',
+          onConfiguredExecute: (server, conf, program, params, startServices) => co(function*() {
+            conf.should.not.have.property('remotehost');
+            conf.should.have.property('remoteipv6').equal(undefined);
+            conf.should.have.property('remoteipv4').equal('127.0.0.1');
+          })
+        }]
+      }
+    }, 'duniter-automated-test');
+    yield stack.executeStack(['node', 'index.js', 'test5',
+      '--memory',
+      '--ipv4', '127.0.0.1',
+      '--port', '10400'
+    ]);
+  }));
+
+  it('default IPv6 should not be a local one', () => co(function*() {
+    const ipv6 = network.getBestLocalIPv6();
+    if (ipv6) {
+      ipv6.should.not.match(/fe80/);
+    }
+  }));
+});